콘텐츠로 이동

2024 03 14

2024-03-14

React-Router Dom

참고: https://www.npmjs.com/package/react-router-dom 참고: https://reactrouter.com/en/main 참고: https://velog.io/@kandy1002/React-Router-Dom-%EA%B0%9C%EB%85%90%EC%9E%A1%EA%B8%B0 참고: https://shape-coding.tistory.com/entry/React-React-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85%ED%95%98%EA%B8%B0-React-Router-%EC%84%A4%EC%B9%98

  • React-Router를 사용하여 라우팅을 하는 이유
    • 라우팅은 네트워크에서 경로를 선택하는 프로세스
    • SPA에서 모든 페이지 라우팅 없이 렌더링 하면...
      1. 즐겨찾기 등록 불가
      2. 뒤로 가기 불가
      3. 새로고침 불가
      4. SEO 불가
  • Router 종류
    1. HashRouter
      • 주소에 해시(#)가 붙음
      • 검색 엔진이 읽지 못함
      • 새로고침 시 오류가 발생하지 않음
      • history API 사용 X. 동적 페이지에 불리
    2. BrowserRouter
      • history API 사용
      • 별도 서버 설정 없으면 404
      • 큰 프로젝트에 적합
      • 서버가 있고, SEO 필요하다면 BrowserRouter를 쓰자
  • 자주쓰는 기능 살펴보기
    • [BrowserRouter]
      • history API를 통해 history 객체 생성
      • stack 자료 구조로, url 기록 차곡차곡
      • 라우팅 진행할 컴포넌트 상위에 BrowserRouter 컴포넌트 생성하고 감싸줄 것
    • [Route]
      • 현 브라우저의 Location(window.href.location)에 따른 다른 element 렌더링
      • Route.element: 조건이 맞을 때 렌더링할 element를 <Element /> 형식으로 전달
      • Route.path: 현재 path값이 url과 일치하는지 확인. 해당 url에 매칭된 element 렌더링
    • [Routes]
      • 모든 Route의 상위 경로. location 변경 시 하위에 있는 모든 Route를 조회해 맞는 Route 찾아줌
    • [Link]
      • <Link to='/about>About</Link>
      • A 태그와 같은 역할
      • 다만 state 넘겨주는 것도 가능
    • [useNavigate]
      • 특정 액션을 취할때 url 이동하도록 할 수 있음
      • 여기도 state 넘겨주기 가능
    • [Outlet]
      • 자식 라우터 컴포넌트를 여기에 넣어줄 수 있음
    • [useParams]
      • url 파라미터 조회 시 사용
    • [useSearchParams]
      • url 쿼리스트링 추출시 사용
      • setSearchParams: 함수의 인자에 객체/문자열 넣어주면 url 쿼리스트링 변경 가능
      • searchParams.get(key): key에 대응되는 value 가져옴
      • searchParams.getAll(key): key에 해당하는 모든 value 가져옴
      • searchParams.set(key, value): 전달한 key값을 value로 설정
      • searchParams.append(key, value): 기존 값 변경/삭제하지 않고 추가

React-Router v6 최신 기능 정리

참고: https://velog.io/@tjdgus0528/React-Router-v6-%EC%A0%95%EB%A6%AC

  • 가장 일치하는 route로 매칭
    <Route path="posts/:postId" element={<Team />} />
    <Route path="posts/new" element={<NewTeamForm />} />
    
  • Nested Routes

    • layout 코드 안에서 고민이 줄어듦
    • route들은 다른 route안에 nest 될 수 있다
    • 자식 route의 path는 부모의 path 뒤에 붙어 사용된다
    • 예시: /post, /post/:postId, /post/notice
      function App() {
        return (
          <Routes>
            <Route path="posts" element={<Posts />}>
              <Route path=":postId" element={<Post />} />
              <Route path="notice" element={<Notice />} />
            </Route>
          </Routes>
        );
      }
      
    • 결과적으로 /post/notice는 다음과 같이 그려진다
      <App>
        <Posts>
          <Notice />
        </Posts>
      </App>
      
    • 부모의 route에서는 꼭 <Outlet>을 넣어줘 매칭되는 자식 route를 그려줄 수 있는 공간을 마련해주자
      import { Routes, Route, Outlet } from "react-router-dom";
      
      function Posts() {
        return (
          <div>
            <h1>Posts</h1>
            <Outlet />
          </div>
        );
      } 
      
  • Index Routes
    • Route에 들어가는 index 값은 default child routes
    • 부모에 여러개의 자식 route가 있는데, 이 중 부모경로 + '/'로 만 입력하면 흘러들어갈 곳!

React-Router 예시

  • 구조를 보자 ("react-router-dom": "^6.11.1")
  • index.tsx
    import React from 'react';
    import {BrowserRouter} from 'react-router-dom';
    import App from './commons/App';
    
    const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
    root.render(
        <BrowserRouter>
            <App />
        </BrowserRouter>
    )
    
  • App.tsx
    import React from 'react';
    import TwmRoot from './TwmRoot';
    
    function App() {
        return <TwmRoot />;
    }
    
    export default App;
    
  • Root.tsx
    import {Navigate, Route, Routes, useRoutes} from 'react-router-dom';
    
    const TwmRoot = () => {
        return useRoutes([
            {
                path: '/',
                element: isMobile() ? <TwmServiceLayout /> : <MobileInstallPage />,
                children: [
                    {index: true, element: <TwmHome />},
                    {path: ':id', element: <TwmDetail />},
                    {path: '/other', element: <TwmOther />},
                    {path: '/mypage', element: <TwmMyPage />}
                ]
            }
        ])
    
        const TwmMyPage = () => {
            return (
                <Routes>
                    <Route path='/' element=<TwmMyPageMain /> />
                    <Route path='/info' element=<TwmMyPageInfo /> />
                </Routes>
            ); 
        } 
    }
    
    export default TwmRoot;
    
  • TwmServiceLayout.tsx - 여기서 Outlet이 child route의 element를 렌더링해준다
    import {Outlet} from 'react-router-dom';
    
    export const TwmServiceLayout = () => {
        const [isLoading, setIsLoading] = useState(false);
    
        return isLoading ? (
            <div>
                <Header />
                <Outlet />
                <Footer />
            </div>
        ) : (
            <></>
        );
    }
    

React useCallback()

참고: https://www.daleseo.com/react-hooks-use-callback/

  • 함수 메모이제이션
    • useMemo()와 더불어서 성능 최적화에 사용되는 React hook
    • 첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 해줌
      • const memoizedCallback = useCallback(함수, 배열)
    • useCallback()을 사용함으로써, 해당 컴포넌트가 렌더링되더라도 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환
      • 두 번째 인자로 넘어온 의존 배열이 변경될 때만 첫 번째 인자로 넘어온 함수를 호출!
        const add = useCallback(() => x + y, [x, y]) // x, y가 바뀌어야 그제서야 add를 계산하는 함수의 값이 바뀐다
        
  • When To Use
    • 자바스크립트 함수의 동등성 : 특정 함수를 넘길때, callback으로 선언한 add를 넘겨보면 동등성이 확보된다.
    • dependency array에 함수를 넘길 때

      • 정신나간 예시같지만... 이 경우, 함수의 참조값은 컴포넌트 렌더링 될 때마다 바뀜
        import React, {useState, useEffect} from 'react';
        
        function Profile({userId}) {
            const [user, setUser] = useState(null);
        
            const fetchUser = () => {
                fetch(`https://api.com/user/${userId}`)
                        .then(response => response.json())
                        .then(({user}) => user);
            }
        
            useEffect(() => {
                fetchUser().then(user => setUser(user))
            }, [fetchUser])
        }
        
      • useCallback 정의하면 dep array에 함수를 넘겨도 안전한 참조를 진행할 수 있음
        import React, {useState, useEffect, useCallback} from 'react';
        
        function Profile({userId}) {
            const [user, setUser] = useState(null);
        
            const fetchUser = useCallback(
                () => fetch(`https://api.com/user/${userId}`)
                        .then(response => response.json())
                        .then(({user}) => user),
                [userId]
            );
        
            useEffect(() => {
                fetchUser().then(user => setUser(user))
            }, [fetchUser])
        }
        

React 커스텀 훅 만들기

참고: https://react.vlpt.us/basic/21-custom-hook.html

  • 반복되는 로직을 없애고 추상화 하기 위함!
  • 보통 use뭐시깽이 형태로 만듦
  • 그 안에서 useState, useEffect, useReducer, useCallback 등의 다양한 Hook을 만들어 원하는 기능 구현하여, 컴포넌트에서 사용하고 싶은 값을 반환해주자

Intersection Observer

참고: https://moon-ga.github.io/javascript/intersectionobserver/ 참고: https://moon-ga.github.io/react/infinite-scroll-with-intersectionobserver/

  • 개요
    • 특정한 요소를 target으로 설정하여 observer가 그 target이 상위 요소 혹은 뷰포트와 교차가 발생하는지를 비동기적으로 observe 하는 API
    • target 요소가 root요소와 교차가 일어나는지를 판단하여 콜백함수 실행 가능
  • 사용하기
    1. 관찰자가 관찰할 target 생성
    2. target을 관찰할 새로운 IntersectionObserver 객체 생성
  • React 예시로 알아보기

    1. target 생성: 교차 상태를 판별할 대상인 target. 요소 지정으로 교차상태 판별 더 쉽게

      const [target, setTarget] = useState(null);
      const targetStyle = {width: "100%", height: "200%"};
      
      return (
          <div>
            <div ref={setTarget} style={targetStyle}>
              Target Area
            </div>
          </div>
      );
      

    2. observer 생성

      useEffect(() => {
          let observer;
          if (target) {
              observer = new IntersectionObserver();
              observer.observe(target);
          }
      }, [target])
      

    3. 콜백함수 생성

      const onIntersect = async ([entry], observer) => {
          if (entry.isIntersecting) {
              observer.unobserve(entry.target);
              await fetchData;
              observer.observe(entry.target);
          }
      }
      

LocalStorage vs SessionStorage

참고: https://bo5mi.tistory.com/213

  • 공통점
    • 둘 모두 웹 브라우저에 데이터를 저장하는 객체
  • LocalStorage
    • key-value 쌍으로 구성
    • 사용자 세션 데이터 유지 가능
    • 탭을 여러개 열어도 공유됨
    • 브라우저를 닫았다가 다시 열어도 지속됨
    • 명시적 삭제하기 전까지 지속됨 => 소멸 타이밍이 없음!!
  • SessionStorage
    • 세션 끝나면 다 날라감 (탭 닫히면 다 날라감)
    • 새로고침해도 유지됨
    • 대신 세션이 종료되면/탭이 닫히면 바로 소멸!

FTS query exceeds result cache limit

참고: https://grip.news/archives/1538

  • MySQL에서 Full-Text Search를 수행할 때, 결과 캐시의 제한을 초과했다는 것을 알려줌
  • 검색 결과가 너무 많아서 결과 캐시에 저장할 수 없을 정도로 메모리가 부족한 상황
  • ‘innodb_ft_result_cache_limit’
    • Full Text Search에 Thread 당 InnoDB FTS결과에 대한 캐시의 제한 정하기
      • InnoDB에서 FTS 결과가 메모리에서 처리됨