함수 component 에서도 state와 ref를 쓸 수 있는 것이 React hooks 입니다.
❶ 에서 했던 class component로 구성된 구구단 게임을 hooks 로 바꾸어 보겠습니다.
> class GuGuDan extends React.Component {} // React Class > const GuGuDan = () => {} // React Hooks 함수형이라고 생각하면 됩니다.
<html>
<head>
<meta charset="UTF-8" />
<title> 구구단 </title>
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const GuGuDan = () => { //함수 component다. setstate랑 ref를 쓰지 않을땐 함수 componet를 사용.
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);// hooks에서는 useRef를 이용하여 dom에 접근한다.
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();
}
};
//state를 객체로 묶지않고 하나씩 쪼갰다고 보면 된다. state를 선언하는 방식임.
const onChangeInput = (e) => {
setValue(e.target.value);
};
return (
<>
<div>{first} 곱하기 {second} 는? </div>
<form onSubmit={onSubmitForm}>
<input type="number" ref={inputRef} onChange={onChangeInput} value={value} />
<button id="button">입력!</button>
</form>
<div id="result"> {result} </div>
</>
);
}
</script>
<script type="text/babel">
ReactDOM.render(<div><GuGuDan /></div>, document.querySelector('#root'));
</script>
</body>
</html>
1) State
2) Ref 사용방법
3) console.log 결과
-> hooks에 console.log 를 하여 react chrome 툴로 결과를 확인해보면
4) 이전 state로 새 state를 만드는 경우
this.setState((prevState)=>{
return {result: '정답:'+prevState.value};
});
setResult((prevResult) => {
return {'정답'+ value} ;
});
setCounter((c)=> c+1) // 비동기 문제 발생 안함
❶ 구구단은 이렇게 마무리 되었습니다.
1) node는 웹팩을 돌리기 위한 javascript 실행기이기에 node는 우선 설치합니다.
2) 터미널 창에서 다음 명령어를 실행합니다.
> cd lecture
==> lecture 폴더 생성 후 이동해서 여기서 작업
> npm init
author: jisu
license: MIT
yes 후 enter
> npm i react react-dom
==> react와 react dom 설치. package.js에서 확인 가능
> npm i -D webpack webpack-cli
==> 웹팩과 웹팩-cli 설치. -D는 개발에서만 쓰인다는 뜻.
3) webpack.config.js 생성
module.exports = {
};
4) client.jsx 생성
const React=require('react');
const ReactDom = require('react-dom');
//React 와 ReactDom을 불러온다.
5) index.html 생성
<html>
<head>
<meta charset="UTF-8"/>
<title> webpack 설치 </title>
</head>
<body>
<div id ="root"></div>
<script src ="./dist/app.js"></script>
</body>
</html>
1) WordRelay.jsx 생성
const React = require('react')
const { Component } = React;
class WordRelay extends Component {
state= {
text: 'Hello, webpack',
};
render() {
return <h1>{this.state.text}</h1>
}
}
module.exports = WordRelay;
2) client.jsx 에 추가
const WordRelay =require('./WordRelay')
ReactDom.render(<WordRelay />, document.querySelector('#root'));
모듈 시스템을 통해 필요한 것만 불러 올 수 있다.
3) webpack.config.js 에 추가
-> 웹팩은 파일을 하나로 합쳐주는 역할을 합니다.
client.jsx와 WordRelay.jsx 파일을 합쳐서 app.js로 만들어야 합니다.
-> webpack.config.js로 실행됨.
const path= require('path'); // path를 가져와 path join을 하면 경로를 알아서 합쳐 준다.
module.exports={
name: 'wordrelay-setting',
mode: 'development', //실서비스:production 로 바꾸면 됨
devtool: 'eval', // 빠르게
resolve: { //resolve를 사용하면 확장자 입력하지 않아도 됨.
extensions: ['.js' , '.jsx'],
},
entry: { //입력 - 중요한 부분
app: ['./client', ], // WordRelay.jsx는 이미 다른 파일(client.jsx)이 불러오고 있으므로 쓸 필요 없다.
},
output : { //출력 - 중요한 부분
path: path.join(__dirname, 'dist'), // 현재 폴더 안의 dist 경로
filename: 'app.js',
},
};
두가지 방법
--> 이렇게 build 해보면 에러 메세지가 뜨는 데 그 이유는 바벨을 아직 설치 안했기 때문이다.
> npm i -D @babel/core @babel/preset-env @babel/preset-react babel-loader
==> core는 기본적인 것들,
preset-env는 사용자의 브라우저에 맞게 최신 문법을 옛날 문법으로 바꿔주는 것,
preset-react는 jsx를 지원,
loader는 바벨과 웹팩을 연결.
webpack.config.js 에 추가
module: {
rules: [{
test: /\.jsx?/,
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
}],
},
터미널
> npx webpack
==> 혹시 에러뜨면
> npm i -D @babel/plugin-proposal-class-properties
==> module의 options안에 plugins:[@babel/plugin-proposal-class-properties], 넣어준다.
실행해보면 ?!
터미널 창
> cd 1.구구단 > npm init ==> 구구단 디렉토리로 이동 후 설정 해주기 > npm i react react-dom > npm i -D webpack webpack-cli > npm i babel-loader @babel/core @babel/preset-env @babel/preset-react
webpack.config.js
const path= require('path'); // 'path' node 모듈 사용 module.exports={ mode: 'development', devtool: 'eval',//hidden-source-map resolve: { extensions: ['.js' , '.jsx'], // 확장자 생략 가능하게 해주는 것 }, entry: { //입력 app: './client', }, module: { rules: [{ test: /\.jsx?$/, loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'], }, }], }, output : { //출력 path: path.join(__dirname, 'dist'), filename: 'app.js', }, };
package.json
"dependencies"안에 있는 "@babel/core": "^7.14.6", "babel-loader": "^8.2.2",를 "devDependencies" 안에 넣어준다. "scripts" 안에는 "dev": "webpack", 를 넣어준다.
GuGuDan.jsx
const React =require('react'); const { useState, useRef } = React; // 구조분해 문법 사용 const GuGuDan = () => { const [first, setFirst ] = useState(Math.ceil(Math.random()* 9)); const [second, setSecond ] = useState(Math.ceil(Math.random()* 9)); const [value, setValue ] = useState(''); const [result, setResult ] = useState(''); const inputRef =useRef(null); 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(); } }; const onChangeInput = (e) => { setValue(e.target.value); }; return ( <> <div>{first} 곱하기 {second} 는? </div> <form onSubmit = {onSubmitForm}> <input type="number" ref = {inputRef} onChange = {onChangeInput} value ={value}/> <button id ="button">입력!</button> </form> <div id="result"> {result} </div> </> ); } module.exports = GuGuDan;
client.jsx
const React = require('react'); const ReactDom = require('react-dom'); const GuGuDan = require('./GuGuDan'); ReactDom.render(<GuGuDan />, document.querySelector('#root'));
webpack.config.js
preset = plugin 들의 모음
presets와 plugin 내용 추가const webpack = require('webpack'); //상단에서 웹팩 불러오기 ... presets: [ ['@babel/preset-env', { targets: { browsers: ['> 1% in KR'], //browserList- 한국에서 브라우저 점유율 1% 이상인 것은 다 지원하겠다 }, debug: true, }], '@babel/preset-react', ], plugins: [], }, }], }, plugins: [ new webpack.LoaderOptionsPlugin({ debug:true}), ], ...
작성법 : entry: -> module: -> plugins: -> output:
1) WordRelay.jsx
const React = require('react');
const { Component }= React;
class WordRelay extends Component {
state = {
word: '여지수',
value: '',
result: '',
};
onSubmitForm = (e) =>{
e.preventDefault();
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 = (e) =>{
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) client.jsx
const React = require('react');
const ReactDom = require('react-dom');
const WordRelay = require('./WordRelay');
ReactDom.render(<WordRelay />, document.querySelector('#root'));
3) webpack.config.js
const path= require('path'); // 'path' node 모듈 사용
module.exports={
mode: 'development',
devtool: 'eval',//hidden-source-map
resolve: {
extensions: ['.js' , '.jsx'], // 확장자 생략 가능하게 해주는 것
},
entry: { //입력
app: './client',
},
module: {
rules: [{
test: /\.jsx?$/,
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', {
targets: {
browsers: ['> 1% in KR'],
},
debug: true,
}],
'@babel/preset-react',
],
plugins: ['@babel/plugin-proposal-class-properties'],
},
}],
},
plugins:[],
output :
path: path.join(__dirname, 'dist'),
filename: 'app.js',
},
};
4) index.html
<html>
<head>
<meta charset="UTF-8" />
<title>끝말잇기</title>
</head>
<body>
<div id="root"></div>
<script src="./dist/app.js"></script>
</body>
</html>
자동으로 build 되게 해줍니다.
1) 터미널 창
> npm i react-refresh @pmmmwh/react-refresh-webpack-plugin -D
> npm i -D webpack-dev-server
==> 개발용 서버 설치
2) package.json 내용 변경
"scripts": 안에 "dev": "webpack serve --env development" 추가
3) webpack.config.js 내용 변경
const RefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
...
plugins: [
'@babel/plugin-proposal-class-properties',
'react-refresh/babel',
],
},
}],
},
plugins: [
new RefreshWebpackPlugin()
],
...
devServer: { //프론트개발 편의를 위해 서버 설정
publicPath: '/dist/', //output의 path
hot: true,
} //역할 : hot reloading- 변경점이 생기면 그에 따라 저장했던 결과물을 수정해줌.
//장점 : 기존 데이터 유지하면서 화면을 바꾸어줌 !!
};
실행 후 http://localhost:8080/ 접속!
-> 수정사항이 실시간으로 반영 됩니다.
WordRelay.jsx
const React = require("react");
const { useState, useRef } = 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("딩동댕");
setWord(value);
value("");
} 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;