인프런에 올라간 제로초님의 강의를 보고 정리한 내용입니다.
https://www.inflearn.com/course/web-game-react
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>
<Link to="/game/Data2?key1=100&key2=200">DATA2</Link>
<Link to="/game/RSP?key1=1000&key2=2000">RSP</Link>
<Link to="/game/Baseball?key1=10000&key2=20000">Baseball</Link>
</div>
<div>
<Switch>
<Route exact path="/" component={GameMacher}/>
<Route exact path="/game/:name" component={GameMacher}/>
</Switch>
</div>
</BrowserRouter>
);
};
export default GameMacher;
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;
라우터라는 개념을 써서 여러 개의 페이지를 구현하는 것 처럼 꾸밀 수 있다.
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에는 컴포넌트를 입력한다.
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>
<Link to="/Data2">DATA2</Link>
<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 에러를 막아준다.
<HashRouter>
<div>
<Link to="/game/Data1">DATA1</Link>
<Link to="/game/Data2">DATA2</Link>
<Link to="/game/RSP">RSP</Link>
<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의 경우 서버쪽에 페이지가 존재한다는 것을 세팅해놨다는 전제하에 페이지를 찾을 수 있다.
검색 엔진이 아닌 관리자 페이지에서 유용.
const Games = () => {
return (
<HashRouter>
<div>
<Link to="/game/Data1">DATA1</Link>
<Link to="/game/Data2">DATA2</Link>
<Link to="/game/RSP">RSP</Link>
<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에 알맞는 모든 화면을 구현해주면 되는 것.
history
, location
, match
가 들어가는데 Route
컴포넌트가 연결된 GameMacher 컴포넌트에 넣어주는 것.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
를 쓸 수 있게 해준다.
//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에 따라 분기처리를 해주는 방식.
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 같은 함수들로 내역을 제어할 수 있다.
isExact: true
params: {name: "Data1"}
path: "/game/:name"
url: "/game/Data1"
[[Prototype]]: Object
params에 어떤 값이 동적으로 들어갔는지를 저장한다.
params를 가지고 분기 처리를 할 수 있다.
hash: ""
pathname: "/game/Data1"
search: ""
state: undefined
[[Prototype]]: Object
주소(url)에 대한 정보만을 갖고 있다.
함수형 컴포넌트에서도 props 자리에 history, location, match가 들어 있다.
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>
<Link to="/game/Data2?key1=100&key2=200">DATA2</Link>
<Link to="/game/RSP?key1=1000&key2=2000">RSP</Link>
<Link to="/game/Baseball?key1=10000&key2=20000">Baseball</Link>
</div>
<div>
<Route path="/game/:name" component={GameMacher}/>
</div>
</HashRouter>
);
};
export default Games;
주소(url)의 뒤에 ? 붙이고 key1=value1&key2=key=value2
처럼 데이터를 전송할 수 있다.
주소로 데이터를 전달하는 가장 쉬운 방법, 서버도 인식한다.
게시판에서 페이지를 이동하면 url도 페이지에 맞게 변하는 것도 쿼리스트링으로 정보를 전달하는 것이다.
다만 리액트 라우터에서는 쿼리스트링을 해석할 수 없어서 URLSearchParams을 같이 사용해야만 한다.
HashRouter의 경우에도 데이터를 전달하기는 하지만 서버는 모르고 브라우저만 알고 있는 정보라서 활용할 곳이 없다.
// 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
라는 객체를 활용해서 쿼리스트링으로 보낸 데이터를 이용할 수 있다.
게시판에서 목록(카테고리)은 그대로고 본문만 변하는 페이지같은 경우가 동적 라우팅 매칭이 적용되어 있는 예이다.
컴포넌트를 전환해가며 바뀐 척만 할 뿐, 서버 역시 페이지가 여러 개인 사실을 모르기 때문에 따로 알려줘야만 검색 엔진이 긁어갈 수 있다.
<Route path="/game/:name" component={() => <GameMacher props="123">}/>
화살표 함수로 Route에서도 props를 넘겨줄 수 있다.
<Route path="/game/:name" render={(props) => <GameMacher props={...props}/>}/>
props를 넘기는 목적이라면 부모의 props를 받아서 넘겨주는 것도 가능하다.
<Switch>
<Route path="/game/:name" component={GameMacher}/>
<Route path="/game/Baseball" component={GameMacher}/>
</Switch>
동적인 주소와 절대값인 주소를 적어놓으면 중복되었을 때 전부 출력이 되버린다.
라우터들을 switch 태그로 감싸주면 첫번째로 일치하는 라우터만 렌더링하게 할 수 있다.
<div>
<Switch>
<Route exact path="/" component={GameMacher}/>
<Route exact path="/game/:name" component={GameMacher}/>
</Switch>
</div>
주소가 일치하기만 하면 뒤에 주소가 일치하지 않는 상위의 주소도 렌더링이 된다.
switch를 감싸도 상위의 주소 하나만 출력하는 등 해결되지 않는다.
Route 태그에 exact를 넣으면 주소가 정확히 일치할 때만 렌더링을 하게 된다.