🎮 웹 게임을 만들며 배우는 React
2주차: 끝말잇기
Hooks를 사용하는 함수형 컴포넌트는 클래스형 컴포넌트보다 깔끔하다. React 또한 클래스형보다 Hooks를 사용하는 것을 권장하고 있다.
// setState 사용
// useState 내부에는 초깃값 입력
const [first, setFirst] = React.useState();
const [second, setSecond] = React.useState();
const [value, setValue] = React.useState();
이때 useState는 꼭 컴포넌트 내에 있어야 한다.
이전에 작성하였던 구구단 컴포넌트를 함수형으로 변경해 보자.
const [first, setFirst] = React.useState(Math.ceil(Math.random() * 9));
const [second, setSecond] = React.useState(Math.ceil(Math.random() * 9));
const [value, setValue] = React.useState('');
const [result, setResult] = React.useState('');
const InputRef = React.useRef(null);
const onChangeInput = (e) => {
setValue(e.target.value);
}
const onSubmitForm = (e) => {
e.preventDefault();
if(parseInt(value) === first * second) {
setResult('정답 ' + value);
setFirst(Math.ceil(Math.random() * 9));
setSecond(Math.ceil(Math.random() * 9));
setValue('');
InputRef.current.focus();
}
else {
setResult('땡');
setValue('');
InputRef.current.focus();
}
}
return (
<div>
<div>{first} 곱하기 {second} 는? </div>
<form onSubmit={onSubmitForm}>
<input ref={InputRef} onChange={onChangeInput} value={value} />
<button>입력</button>
</form>
<div id="result">{result}</div>
</div>
);
};
클래스형 컴포넌트에 비하면 코드 줄 수가 짧다.
하지만 state가 변경될 때마다 함수가 통째로 다시 실행되므로 속도가 느릴 수 있다.
웹팩을 사용하면 여러 개의 자바스크립트 파일을 모아서 하나의 JS 파일로 만들어준다.
$ npm i react react-dom
$ npm i -D webpack webpack-cli
웹팩 설정을 끝낸 후, 끝말잇기 실습을 위해 우선 클래스형 컴포넌트를 만든다.
WordRelay.jsx
const React = require('react');
const { Component } = React;
// 파일에서 필요로 하는 패키지 및 라이브러리
class WordRelay extends React.Component {
state = {
text: 'hello, webpack',
};
render() {
return (
<div>
<h1> {this.state.text} </h1>
</div>
)
}
}
module.exports = WordRelay;
// 현재 컴포넌트를 바깥에서도 사용할 수 있도록
웹팩은 webpack.config.js로 돌아간다!
webpack.config.js
const path = require('path');
module.exports = {
name: 'wordrelay-setting',
mode: 'development', // 실서비스: production
devtool: 'eval',
resolve: {
extensions: ['.js', '.jsx'], // 웹팩이 알아서 확장자를 찾아준다!
},
// 중요
entry: {
app: ['./client.jsx', './WordRelay.jsx'],
}, // 입력
output: {
path: path.join(__dirname, 'dist'), // 현재 폴더의 'dist'라는 폴더
filename: 'app.js',
}, // 출력
}
이때 터미널에 webpack 명령어를 입력하면
이와 같은 오류가 발생한다.
이에 대한 해결 방법은 1) 명령어로 등록하거나 2) 스크립트에 작성하는 것 3) npx webpack 를 사용하는 것이다. 나는 3번 방법을 이용하였다.
이 에러를 해결하기 위하여 @babel/core(babel 최신 문법으로 바꾸어주는), @babel/preset-env(개발 환경에 맞도록 바꾸어주는), @babel/preset-react(JSX로 바꾸어주는), babel-loader(babel과 webpack을 연결해주는) 패키지를 설치한다.
webpack의 모듈화가 완료되면 dist/app.js에 파일들이 추가된다.
plugin들의 모음이 사진의 preset이다. 만약 각각의 plugin에 설정을 추가하고 싶다면,
이와 같이 작업해주면 좋다.
WordRelay.jsx
const React = require('react');
const { Component } = React;
// 파일에서 필요로 하는 패키지 및 라이브러리
class WordRelay extends React.Component {
state = {
text: '김밥',
value: '',
result: '',
};
onSubmitForm = () => {
e.prevetDefault();
if (this.state.word[this.state.word.length - 1] === this.state.value[0]) {
this.setState ({
result: '딩동댕',
word: this.state.value,
value: '',
})
this.input.focus();
}
else {
this.setState ({
result: '땡',
value: '',
})
this.input.focus();
}
}
onChangeInput = () => {
this.setState({ value: e.target.value });
}
input;
onRefInput = (c) => {
this.input = c;
}
render() {
return (
<>
<div>{this.state.word}</div>
<form onSubmit={this.onSubmitForm}>
<input ref={this.onRefInput} value={this.state.value}
onChange={this.onChangeInput} />
<button>입력</button>
</form>
<div>{this.state.result}</div>
</>
)
}
}
module.exports = WordRelay;
// 현재 컴포넌트를 바깥에서도 사용할 수 있도록
이제 핫 리로딩을 하는 방법을 알아보자.
(이쯤에서 강의자 분께서 갑자기 미래에서 오셨다. 웃느라 죽는 줄 알았다.)
핫 리로딩을 위한 2개의 패키지와 개발용 서버 패키지를 설치하자.
$ npm i react-refresh @pmmmwh/react-refresh-webpack-plugin -D
$ npm i -D webpack-dev-server
webpack.config.js
const path = require('path');
const RefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = {
name: 'wordrelay-setting',
mode: 'development', // 실서비스: production
devtool: 'eval',
resolve: {
extensions: ['.js', '.jsx'], // 웹팩이 알아서 확장자를 찾아준다!
},
// 중요
entry: {
app: ['./client.jsx', './WordRelay.jsx'],
}, // 입력
module: {
rules: [{
test: /\.jsx?/, // js , jsx 파일에 rule 적용
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: {
browsers: ['> 1% in KR'], // browserlist
},
debug: true,
}],
['@babel/preset-react'],
],
plugins: [
'@babel/plugin-proposal-class-properties',
'react-refresh/babel',
],
},
}],
},
plugins: [
new RefreshWebpackPlugin(),
],
output: {
path: path.join(__dirname, 'dist'), // 현재 폴더의 'dist'라는 폴더
filename: 'app.js',
publicPath: '/dist/',
}, // 출력
devServer: { // 변경점을 감지함
devMiddleware: {
publicPath: '/dist',
},
static: { directory: path.resolve(__dirname) },
hot: true,
},
};
사진으로만 확인하기는 어렵지만... 아무튼 localhost:8080에서 핫 리로딩이 잘 이루어진다!
이제 클래스형 컴포넌트를 hooks 방식으로 바꾸어 보자.
WordRelay.js
const { useState, useRef } = require('react');
const React = require('react');
const { Component } = React;
// 파일에서 필요로 하는 패키지 및 라이브러리
const WordRelay = () => {
const [word, setWord] = useState('김밥');
const [value, setValue] = useState('');
const [result, setResult] = useState('');
const inputRef = useRef(null);
const onSubmitForm = (e) => {
e.preventDefault();
if (word[word.length - 1] === value[0]) {
setResult('딩동댕');
setValue('');
setWord(value);
inputRef.current.focus();
}
else {
setResult('땡');
setValue('');
inputRef.current.focus();
}
}
const onChangeInput = (e) => {
setValue(e.target.value);
}
return (
<>
<div>{word}</div>
<form onSubmit={onSubmitForm}>
<input ref={inputRef} value={value}
onChange={onChangeInput} />
<button>입력</button>
</form>
<div>{result}</div>
</>
)
}
module.exports = WordRelay;
// 현재 컴포넌트를 바깥에서도 사용할 수 있도록
ERROR in ./client.jsx 5:16-38
Module not found: Error: Can't resolve './WordRelay' in 'D:\공부\React\react-game\wordchain'
resolve './WordRelay' in 'D:\공부\React\react-game\wordchain'
using description file: D:\공부\React\react-game\wordchain\package.json (relative path: .)
Field 'browser' doesn't contain a valid alias configuration
using description file: D:\공부\React\react-game\wordchain\package.json (relative path: ./WordRelay)
no extension
Field 'browser' doesn't contain a valid alias configuration
D:\공부\React\react-game\wordchain\WordRelay doesn't exist
.js
Field 'browser' doesn't contain a valid alias configuration
D:\공부\React\react-game\wordchain\WordRelay.js doesn't exist
jsx
Field 'browser' doesn't contain a valid alias configuration
D:\공부\React\react-game\wordchain\WordRelayjsx doesn't exist
as directory
D:\공부\React\react-game\wordchain\WordRelay doesn't exist
중간에 npx webpack을 했을 때, 이와 같은 오류가 발생하였다.
범인은 이곳...
Uncaught ReferenceError: e is not defined
Uncaught Error: A cross-origin error was thrown. React doesn't have access to the actual error object in development. See https://reactjs.org/link/crossorigin-error for more information.
끝말잇기 클래스 컴포넌트를 만들고 input에 텍스트를 입력하자 이와 같은 오류가 발생하였다.
이 부분에 e가 빠져 있어서 생긴 오류였다! 😟
[webpack-cli] Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.he API schema.
- options has an unknown property 'publicPath'. These properties are valid: , http2?, https?, ipc?, liveReload?, magicHtml?, o
object { allowedHosts?, bonjour?, client?, compress?, devMiddleware?, headers?, historyApiFallback?, host?, hot?etupMiddlewares?, static?, watchFiles?, webSocketS, http2?, https?, ipc?, liveReload?, magicHtml?, onAfterSetupMiddleware?, onBeforeSetupMiddleware?, onListening?, open?, port?, proxy?, server?, setupExitSignals?, setupMiddlewares?, static?, watchFiles?, webSocketServer? }
핫 리로딩 도중 이 오류가 발생했다. 찾아보니 publicPath 속성은 devMiddleware 내부에서 사용해야 한다고 한다.
devServer: { // 변경점을 감지함
devMiddleware: {
publicPath: '/dist/',
},
hot: true,
},
그런데 강의를 듣다 보니 강의자 분께서 이 부분에 대해 설명을 해 주셨다!! 머쓱.
핫 리로딩을 진행하는데, Cannot get / 페이지가 뜨고 404 오류가 발생하였다. 😂
서버를 끄고
$ npm i
를 한 뒤, 다시 서버를 재시작하니 해결되었다.