react-router-dom v6 업데이트 후 달라진 점 (ft. Prompt 창 띄우는 법)

    반응형

    불과 몇 달전만 해도 문제없이 잘 쓰고 있었던 react-router-dom

    리액트 공부를 다시 시작하면서 router 파트를 복습삼아 진행했는데

    분명 그대로! 평소에 썼던거처럼 작성했는데 에러가 뜨는거다!

    보니까 그 사이에 v6으로 업데이트 되었던 것...

    생각보다 너무나도 많은게 달라져서 기록해보려고 합니다

     

     


     

     

    1. Switch -> Routes 네이밍 변경

    <Route /> 여러개를 감싸는 부모 컴포넌트 네이밍이 변경됨

    Switch란?
    여러 Route를 감싸서 그 중 규칙이 일치하는 라우트 단 하나만을 렌더링 시켜줌
    (아무것도 일치하지 않으면 Not Found 페이지를 구현할 수 있었다)
    // v5
    <Switch>
    	<Route />
    	<Route />
    </Switch>
    
    
    // v6
    <Routes>
    	<Route />
    	<Route />
    </Routes>

     

     

     

    2. exact 옵션 삭제

    기존에 루트 ('/') 페이지 같은 경우 모든 페이지들이 다 불러오는 현상이 있었는데

    v6에서부터는 이 옵션이 삭제되고 알아서 정확히 매칭되는 컴포넌트를 보여줌

    만약 하위경로에 여러 라우팅을 매칭시키고 싶다면 URL 뒤에 *을 사용하여 일치시킬 수 있다

    <Route path="main/*" />

     

     

     

    3. <Route />에서 컴포넌트 렌더링

    컴포넌트 렌더링을 하기 위해서 사용했던 component , render(JSX 자체를 렌더링 하는 것) 속성 네이밍이 element 바뀜

    // v5
    <Route path='/user' component={UserInfo} />
    <Route path='/user' render={routeProps => (
    	<UserInfo routeProps={routeProps} isLogin={true} />
    )} />
    
    
    // v6
    <Route path="/user" element={<UserInfo />} />
    <Route path="/user" element={<UserInfo isLogin={true} />} />

     

     

     

     

    4. URL Params 읽는법 (match 객체)

    기존에는 파라미터를 받기 위해서 match객체를 사용했는데 v6에서는 useParams를 사용해야함

    // v5
    const Profile = ({ match }) => {
    	// v5에선 파라미터를 받아올 땐 match 안에 들어있는 params 값을 참조했었습니다
    	const { username } = match.params;
    }
    
    <Route path="/profiles/:username" component={Profile} />
    
    
    
    // v6
    import { useParams } from 'react-router-dom';
    
    const Profile = () => {
    	const { username } = useParams();
    }
    
    <Route path="/profiles/:username" element={<Profile />} />

     

     

     

     

    5. Query 읽는법 (location 객체)

    v5 에서는 라우트 컴포넌트에게 전달되는 location 객체에 있는 search 값에서 읽어올 수 있었다

    그리고 문자열을 객체 형태로 변환하기 위해서 qs라는 라이브러리를 사용해야만 했다

    // v5
    import qs from 'qs'
    
    const About = ({ location }) => {
    	const query = qs.parse(location.search, {
    		ignoreQueryPrefix: true   //쿼리 접두사 무시
    	}
    
    	const detail = query.detail === 'true'; // 쿼리의 파싱 결과값은 문자열
    
    	return (
    		<div>
    			{detail && <p>해당 경로로 들어오면 보이는 텍스트입니다</p>}
    		</div>
    	)
    }

    v6 부터는 useLocation을 써야한다

    // v6
    import { useLocation } from 'react-reuter-dom';
    
    const About = () => {
    	const { search } = useLocation();
    
    	//현재 지금 경로가(search) '?detail=true' 인지 확인
    	const detail = search === '?detail=true';
    
    	return (
    		<div>
    			{detail && <p>해당 경로로 들어오면 보이는 텍스트입니다</p>}
    		</div>
    	)	
    }

     

     

     

    6. 서브라우트 (중첩 라우팅)

    여러가지 방법이 있는데 그 중 하나만 설명

    // v5
    // Profiles 컴포넌트에 있는 Route 
    return (
      <div>
        <Route
          path="/profiles"
          exact
          render={() => <div>유저를 선택해주세요.</div>}
        />
        <Route path="/profiles/:username" component={Profile} />
      </div>
    );
    
    // App.js
    const App = () => {
      return (
        <div>
          <Route path="/" exact={true} component={Home} />
          <Route path="/about" component={About} />
          <Route path="/profiles" component={Profiles} />
        </div>
      );
    };

    App.js에서는 <Profiles /> 컴포넌트를 렌더링하고, Profiles 주소로 들어오면 render안에 작성한 <div> 태그를 렌더링, 그리고 <Profiles /> 컴포넌트를 렌더링 해줬다

    // v6
    // Profiles 컴포넌트에 있는 Route 
    return (
      <Routes>
    			<Route path="/*" element={<div>유저를 선택해주세요.</div>} />
          <Route path=":username" element={<Profile />} />
      </Routes>
    );
    
    // App.js
    const App = () => {
      return (
        <Routes>
          <Route path="/" exact={true} component={Home} />
          <Route path="/about" component={About} />
          <Route path="/profiles/*" element={<Profiles />} />
        </Routes>
      );
    };
    • render는 element로 바뀌었고. 화살표 함수를 사용할 필요가 없어졌다
    • 하위 페이지가 있다면 부모 Route에 '/*' 을 추가해줘야한다 (exact가 대체된 것)
    • path에 부모 경로까지 적을 필요 없이 파라미터만 적어준다 (:username)
    • 무조건!! <Routes>로 싹 다 감싸줘야한다

     

     

     

    7. history, useHistory 대신 useNavigate로 교체

    v5에서 사용하던 history객체는 라우트로 사용된 컴포넌트에게 match, location 과 함께 전달되는 props중 하나였다.

    그러나 v6부터 useHistory는 아예 사라졌고, history도 기존에 선언하는 방식처럼 선언하면 안된다

    기존 history의 모든 기능은 useNavigate로 통합되었다

    // v5
    function HistorySample({ history }) {
    	// 뒤로가기
    	const goBack = () => {
    		history.goBack();
    	};
    
    	// 홈으로 이동
    	const goHome = () => {
    		history.push('/');
    	}
    
    	return ( ... );
    }
    
    
    
    // v6
    // history를 쓰고싶은 경우
    import { createBrowserHistory} from 'react-router-dom';
    const history = createBrowserHistory();
    
    // useNavigate 사용하는법
    import { useNavigate } from 'react-router-dom';
    
    function NavigateSample() {
    	const navigate = useNavigate();
    
    	// 뒤로가기
    	// 인덱스로 처리, 두번 뒤로 가고싶으면 -2
    	const goBack = () => {
    		navigate(-1);
    	}
    
    	// 홈으로 가기
    	const goHome = () => {
    		navigate('/');
    	}
    
    	return ( ... );
    }

    페이지 이탈 시 Prompt 창을 띄우는 방법

    기존에는 history.block 으로 간단하게 해결할 수 있었는데,

    현재는Prompt가 사라졌기에 직접 로직을 구현해야한다

    // v5 
    function HistorySample({ history }) {
    	useEffect(() => {
    		const unblock = history.block('이 페이지를 나가시겠습니까?');
    		 return () => {
    			unblock();
    		};
    	}, [history]);
    
    	return ( ... );
    }

    history를 navigate으로 바꿔서 작성해봤지만 아무 일도 일어나지 않았다

    아래와 같이 로직을 구현해줘야한다

    // v6 페이지 이탈 시 경고창 띄우는 로직
    // Blocker.js
    import { useContext, useEffect, useCallback } from 'react';
    import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
    
    export function useBlocker(blocker, when = true) {
    	const { navigator } = useContext(NavigationContext);
    
    	useEffect(() => {
    		if(!when) return;
    		
    		const unblock = navigator.block((tx) => {
    			const autoUnblockingTx = {
    				...tx,
    				retry() {
    					unblock();
    					tx.retry();
    				},
    			};
    			blocker(autoUnblockingTx);
    		});
    		return unblovk;
    	}, [navigator, blocker, when]);
    }
    
    
    export function usePrompt(message, when = true) {
    	const blocker = useCallbak((tx) => {
    		//   eslint-disable-next-line no-alert
    		if(window.confirm(message)) tx.retry();
    	}, [message]);
    	
    	useBlocker(blocker, when);
    }
    
    
    // 사용할 컴포넌트
    import { usePrompt } from '../Blocker';
    
    function MyComponent () {
    	usePrompt('현재 페이지를 벗어나시겠습니까?', true);
    
    	return ( ... );
    }

    https://gist.github.com/rmorse/426ffcc579922a82749934826fa9f743#file-usage-useprompt-react-router-dom-js

     

    Adds back in `useBlocker` and `usePrompt` to `react-router-dom` version 6.0.2 (they removed after the 6.0.0 beta, temporarily)

    Adds back in `useBlocker` and `usePrompt` to `react-router-dom` version 6.0.2 (they removed after the 6.0.0 beta, temporarily) - react-router-dom-v.6.02.prompt.blocker.js

    gist.github.com

     

     

     

     

    8. withRouter, useRouteMatch, match 사라짐

    match객체가 사라지고, 이를 대신 해줬던거 같은 useRouteMatch가 있었으나 이것 또한 이번에 사라졌다

    그리고 withRouter도 사라짐에 따라 이를 대체해서 사용하는 예시가 나왔다

    withRouter HoC란?
    라우트 컴포넌트가 아닌곳(일반 컴포넌트)에서 match, location, history를 사용해야할 때 쓰였다
    // v5
    import { withRouter } from 'react-router-dom';
    
    const WithRouterSample = ({ location, match, history }) => {
      return (
        <div>
          <h4>location</h4>
          <textarea value={JSON.stringify(location, null, 2)} readOnly />
          <h4>match</h4>
          <textarea value={JSON.stringify(match, null, 2)} readOnly />
          <button onClick={() => history.push('/')}>홈으로</button>
        </div>
      );
    };
    
    export default withRouter(WithRouterSample);
    
    
    
    // v6
    import { useParams, useLocation, useNavigate } from 'react-router-dom';
    
    const WithRouterSample = () => {
      const params = useParams();
      const location = useLocation();
      const navigate = useNavigate();
    
      return (
        <>
          <h4>Location</h4>
          <textarea value={JSON.stringify(location, null, 2)} readOnly />
    
          <h4>Params</h4>
          <textarea value={JSON.stringify(params)} readOnly />
    
          <button onClick={() => navigate('/')}>홈으로</button>
        </>
      );
    };
    
    export default WithRouterSample;

    https://reactrouter.com/docs/en/v6/faq#what-happened-to-withrouter-i-need-it

     

    React Router | FAQs

    Declarative routing for React apps at any scale

    reactrouter.com

     

     

     

     

    9. 존재하지 않는 페이지

    Switch가 Routes로 바뀌면서 작성법이 바뀌었다

    // v5
    <Switch>
    	<Route render={({ location}) => (<h1>존재하지 않는 페이지입니다.</h1>)} />
    </Switch>
    
    
    // v6
    <Routes>
    	<Route path="/*" element={<h1>존재하지 않는 페이지입니다.</h1>} />
    </Routes>

     

     

     

     

    10. <NavLink> 에서 activeStyle, activeClassName이 삭제

    <NavLink>란 만약 현재 경로와 Link에서 사용하는 경로가 일치하는 경우 특정 스타일 혹은 클래스를 적용할 수 있는 컴포넌트

    근데 특정 스타일, 클래스를 적용해주던 activeStyle, activeClassName 속성이 사라졌다

    // v5
    <NavLink to="/profiles/kyungA" activeStyle={{ background: 'black', color: 'white' }}>
    	경아
    </NavLink>
    <NavLink to="/profiles/gildong" activeStyle={{ background: 'black', color: 'white' }}>
    	홍길동
    </NavLink>
    
    
    // v6
    <NavLink 
    	to="/profiles/kyungA" 
    	style={({ isActive }) => ({ color: isActive ? 'black' : 'white' })}
    >
     경아
    </NavLink>
    <NavLink 
    	to="/profiles/gildong"
    	className={({ isActive }) => 'nav-link' + (isActive ? ' activated' : '')}
    >
    	홍길동
    </NavLink>

     

    반응형

    댓글