#9 React Router

sham·2021년 8월 24일
0

인프런에 올라간 제로초님의 강의를 보고 정리한 내용입니다.
https://www.inflearn.com/course/web-game-react


코드

Game.jsx

import React from 'react';
import { BrowserRouter, HashRouter, Link, Route, Switch } from 'react-router-dom';
import GameMacher from './gameMacher'
const Games = () => {
	return (
		<BrowserRouter>
		<div>
			<Link to="/game/Data1?key1=10&key2=20">DATA1</Link>
			&nbsp;
			<Link to="/game/Data2?key1=100&key2=200">DATA2</Link>
			&nbsp;
			<Link to="/game/RSP?key1=1000&key2=2000">RSP</Link>
			&nbsp;
			<Link to="/game/Baseball?key1=10000&key2=20000">Baseball</Link>
			&nbsp;
		</div>

		<div>
			<Switch>
			<Route exact path="/" component={GameMacher}/>
			<Route exact path="/game/:name" component={GameMacher}/>
			</Switch>
		</div>
		</BrowserRouter>
		);
};

export default GameMacher;

GameMacher.jsx

import React, {Component} from 'react'
import {withRouter} from 'react-router-dom';
import Data1 from './Data1';
import Data2 from './Data2';
import RSP from './../RSP/RSPclass';
import NumberBaseball from './../NumberBaseball/Baseball'

class GameMacher extends Component {

	render() {
		const {name} = this.props.match.params;
		let urlParams = (new URLSearchParams(this.props.location.search.slice(1)));
		console.log(this.props);
		if (name === "Data1") {
			return (
				<Data1/>
		)}
		else if (name === "Data2") {
			return (
				<Data2/>
			)}
			else if (name === "RSP") {
				return (
					<RSP/>
				)}
				else if (name === "Baseball") {
					return (
						<NumberBaseball/>
					)}
					else {
						return (
							<div>일치하는 부분이 없습니다.</div>
						)
					}
	}
}

export default GameMacher;

#9-1 React Router 도입하기

라우터라는 개념을 써서 여러 개의 페이지를 구현하는 것 처럼 꾸밀 수 있다.

npm i react-router : 기본적인 뼈대
npm i react-router-dom : 웹에서 쓰는 라이브러리
npm i react-router-native : 네이티브에서 쓰는 라이브러리

react-router, react-router-dom를 사용한다.

라우터

import React from 'react';
import { BrowserRouter, HashRouter } from 'react-router-dom';
import Lotto from '../Lotto/LottoClass';
import RSP from '../RSP/RSPclass';
import NumberBaseball from '../NumberBaseball/Baseball'
const Games = () => {
	return (
		<BrowserRouter>
		<div>
			<Route path="" component={Lotto}/>
			<Route path="" component={RSP}/>
			<Route path="" component={NumberBaseball}/>
		</div>
		</BrowserRouter>
		);
};

export default Games;

BrowserRouter, HashRouter가 주로 쓰인다.

전체 태그를 BrowserRouter나 HashRouter로 감싸주는 것으로 사용한다.

라우팅 태그를 만들고 속성으로 path에 나뉘어질 가상의 url 주소를, component에는 컴포넌트를 입력한다.


#9-2 Link와 브라우저라우터(BrowserRouter)

import React from 'react';
import { BrowserRouter, HashRouter, Link,  Route } from 'react-router-dom';
import Data1 from './Data1';
import Data2 from './Data2';
import RSP from './../RSP/RSPclass';
const Games = () => {
	return (
		<BrowserRouter>
		<div>
			<Link to="/Data1">DATA1</Link>
			&nbsp;
			<Link to="/Data2">DATA2</Link>
			&nbsp;
			<Link to="/RSP">RSP</Link>

		</div>
		<div>
			<Route path="/Data1" component={Data1}/>
			<Route path="/Data2" component={Data2}/>
			<Route path="/RSP" component={RSP}/>

		</div>
		</BrowserRouter>
		);
};

export default Games;

리액트 라우터는 눈속임이다. 페이지가 넘어가는 것처럼 보여도 가상으로 만들어낸 페이지이기 때문에 html의 a태그가 아닌 Link 컴포넌트를 써야 한다.

Link부분은 네비게이션 처럼 컴포넌트가 바뀌어도 그 자리에 고정되어 변하지 않는다.

라우팅을 도와주는 컴포넌트.

to 속성에 이동할 url를 작성한다.

브라우저 상에는 a 태그로 나타난다.

const path = require('path');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = {
  name: 'tictactoe-dev',
  mode: 'development',
  devtool: 'inline-source-map',
  resolve: {
    extensions: ['.js', '.jsx'],
  },
  entry: {
    app: './client',
  },
  module: {
    rules: [{
      test: /\.jsx?$/,
      loader: 'babel-loader',
      options: {
        presets: [
          ['@babel/preset-env', {
            targets: {browsers: ['last 2 chrome versions']},
            debug: true,
          }],
          '@babel/preset-react',
        ],
        plugins: [
          "react-refresh/babel",
          "@babel/plugin-proposal-class-properties"
      ]
      },
      exclude: path.join(__dirname, 'node_modules'),
    }],
  },
  plugins: [
    new ReactRefreshWebpackPlugin(),
  ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
    publicPath: '/dist',
  },
  devServer: {
    publicPath: '/dist',
    hot: true,
    historyApiFallback : true,
  }
};

리액트 Hooks를 import하면 에러 발생, 클래스 컴포넌트를 가져와야함.

컴포넌트를 라우팅한 상태에서 새로고침하면 NOT FOUND 에러가 발생한다. 그도 그럴 것이 이 주소는 클라이언트만 아는 가상의 주소이기 때문.

historyApiFallback : NOT FOUND 에러를 막아준다.


#9-3 HashRouter, params, withRouter

HashRouter

	<HashRouter>
		<div>
			<Link to="/game/Data1">DATA1</Link>
			&nbsp;
			<Link to="/game/Data2">DATA2</Link>
			&nbsp;
			<Link to="/game/RSP">RSP</Link>
			&nbsp;
			<Link to="/game/matcher">GameMacher</Link>
		</div>
		<div>
			<Route path="/Data1" component={Data1}/>
			<Route path="/Data2" component={Data2}/>
			<Route path="/RSP" component={RSP}/>
		</div>
		</HashRouter>

BrowserRouter와의 차이점이라고는 url 중간에 #가 들어있다는 것 뿐.

새로고침을 해도 NOT FOUND 에러가 발생하지 않는다.

검색 엔진에 있어 불이익이 있기 때문에 실무에서는 잘 쓰이지 않는다.

BrowserRouter의 경우 서버쪽에 페이지가 존재한다는 것을 세팅해놨다는 전제하에 페이지를 찾을 수 있다.

검색 엔진이 아닌 관리자 페이지에서 유용.

동적 라우팅 매칭(params)

const Games = () => {
	return (
		<HashRouter>
		<div>
			<Link to="/game/Data1">DATA1</Link>
			&nbsp;
			<Link to="/game/Data2">DATA2</Link>
			&nbsp;
			<Link to="/game/RSP">RSP</Link>
			&nbsp;
			<Link to="/game/matcher">GameMacher</Link>
		</div>
		
		<div>
			//<Route path="/Data1" component={Data1}/>
			//<Route path="/Data2" component={Data2}/>
			//<Route path="/RSP" component={RSP}/>
			<Route path="/game/:name" component={GameMacher}/>
		</div>
		</HashRouter>
		);
};

라우터가 많아질 경우 폴더처럼 하나의 라우터에 넣어버릴 수 있다.

path="/game/:name" 부분에서 :이 앞에 붙은 부분을 params이라고 부르는데, 동적으로 바뀐다.

  • game 주소가 params 앞에 있다면(game/params)
  • game 이 앞에 붙은 url은 무조건 해당 라우터와 연결된 컴포넌트로 이동한다.

불필요한 라우트를 없애버리고, GameMacher 컴포넌트에서 url에 알맞는 모든 화면을 구현해주면 되는 것.

  • GameMacher에 props로 history, location, match가 들어가는데 Route 컴포넌트가 연결된 GameMacher 컴포넌트에 넣어주는 것.

withRouter

import React, {Component} from 'react'
import {withRouter} from 'react-router-dom';
class GameMacher extends Component {
	render() {
		console.log(this.props); 
		return (
			<div>게임매처</div>
		)
	}
}

export default withRouter(GameMacher);

Route 컴포넌트로 연결 안된 컴포넌트에서 props로 history, location, match 를 쓸 수 있게 해준다.


#9-4 history, match, location

//GameMatcher.jsx

import React, {Component} from 'react'
import {withRouter} from 'react-router-dom';
import Data1 from './Data1';
import Data2 from './Data2';
import RSP from './../RSP/RSPclass';
import NumberBaseball from './../NumberBaseball/Baseball'

class GameMacher extends Component {
	render() {
		const {name} = this.props.match.params;
		if (name === "Data1") {
			return (
				<Data1/>
		)}
		else if (name === "Data2") {
			return (
				<Data2/>
			)}
			else if (name === "RSP") {
				return (
					<RSP/>
				)}
				else if (name === "Baseball") {
					return (
						<NumberBaseball/>
					)}
					else {
						return (
							<div>일치하는 부분이 없습니다.</div>
					)
			}
	}
}

export default withRouter(GameMacher);

기존의 부모 컴포넌트에서 모든 자식 컴포넌트를 라우팅하는 방식과 달리, 하나의 라우터에 모든 컴포넌트를 몰아넣고 params에 따라 분기처리를 해주는 방식.

history

action: "POP"
block: ƒ block(prompt)
createHref: ƒ createHref(location)
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
length: 50
listen: ƒ listen(listener)
location: {pathname: "/game/Data1", search: "", hash: "", state: undefined}
push: ƒ push(path, state)
replace: ƒ replace(path, state)
[[Prototype]]: Object

페이지를 넘나든 내역을 저장하고 있다.

goback, goForward 같은 함수들로 내역을 제어할 수 있다.

match

isExact: true
params: {name: "Data1"}
path: "/game/:name"
url: "/game/Data1"
[[Prototype]]: Object

params에 어떤 값이 동적으로 들어갔는지를 저장한다.

params를 가지고 분기 처리를 할 수 있다.

location

hash: ""
pathname: "/game/Data1"
search: ""
state: undefined
[[Prototype]]: Object

주소(url)에 대한 정보만을 갖고 있다.

함수형 컴포넌트에서도 props 자리에 history, location, match가 들어 있다.


#9-5 쿼리스트링과 URLSearchParams

쿼리스트링

import React from 'react';
import { BrowserRouter, HashRouter, Link,  Route } from 'react-router-dom';
import GameMacher from './gameMacher'
const Games = () => {
	return (
		<HashRouter>
		<div>
			<Link to="/game/Data1?key1=10&key2=20">DATA1</Link>
			&nbsp;
			<Link to="/game/Data2?key1=100&key2=200">DATA2</Link>
			&nbsp;
			<Link to="/game/RSP?key1=1000&key2=2000">RSP</Link>
			&nbsp;
			<Link to="/game/Baseball?key1=10000&key2=20000">Baseball</Link>
			&nbsp;
		</div>

		<div>
			<Route path="/game/:name" component={GameMacher}/>
		</div>
		</HashRouter>
		);
};

export default Games;

주소(url)의 뒤에 ? 붙이고 key1=value1&key2=key=value2 처럼 데이터를 전송할 수 있다.

주소로 데이터를 전달하는 가장 쉬운 방법, 서버도 인식한다.

게시판에서 페이지를 이동하면 url도 페이지에 맞게 변하는 것도 쿼리스트링으로 정보를 전달하는 것이다.

다만 리액트 라우터에서는 쿼리스트링을 해석할 수 없어서 URLSearchParams을 같이 사용해야만 한다.

HashRouter의 경우에도 데이터를 전달하기는 하지만 서버는 모르고 브라우저만 알고 있는 정보라서 활용할 곳이 없다.

URLSearchParams

// url : http://localhost:8080/Data1#/game/Baseball?key1=10&key2=20
let urlParams = (new URLSearchParams(this.props.location.search.slice(1)));
		console.log(urlParams.get('key1'));

해당 link로 이동한 후 this.props.location.search에서 쿼리스트링에 대한 정보가 들어있다.

URLSearchParams 라는 객체를 활용해서 쿼리스트링으로 보낸 데이터를 이용할 수 있다.

  • 파싱을 따로 해주어야만 원하는 값을 얻을 수 있다.

게시판에서 목록(카테고리)은 그대로고 본문만 변하는 페이지같은 경우가 동적 라우팅 매칭이 적용되어 있는 예이다.

컴포넌트를 전환해가며 바뀐 척만 할 뿐, 서버 역시 페이지가 여러 개인 사실을 모르기 때문에 따로 알려줘야만 검색 엔진이 긁어갈 수 있다.


#9-6 render props, switch, exact

<Route path="/game/:name" component={() => <GameMacher props="123">}/>

화살표 함수로 Route에서도 props를 넘겨줄 수 있다.

render props

<Route path="/game/:name" render={(props) => <GameMacher props={...props}/>}/>

props를 넘기는 목적이라면 부모의 props를 받아서 넘겨주는 것도 가능하다.

switch

<Switch>
			<Route path="/game/:name" component={GameMacher}/>
			<Route path="/game/Baseball" component={GameMacher}/>
		</Switch>

동적인 주소와 절대값인 주소를 적어놓으면 중복되었을 때 전부 출력이 되버린다.

라우터들을 switch 태그로 감싸주면 첫번째로 일치하는 라우터만 렌더링하게 할 수 있다.

exact

	<div>
		<Switch>
			<Route exact path="/" component={GameMacher}/>
			<Route exact path="/game/:name" component={GameMacher}/>
		</Switch>	
	</div>

주소가 일치하기만 하면 뒤에 주소가 일치하지 않는 상위의 주소도 렌더링이 된다.

switch를 감싸도 상위의 주소 하나만 출력하는 등 해결되지 않는다.

Route 태그에 exact를 넣으면 주소가 정확히 일치할 때만 렌더링을 하게 된다.

profile
씨앗 개발자

0개의 댓글

관련 채용 정보