[React] 웹 게임 - ❷ 끝말잇기

지수토리·2021년 7월 15일
1

React

목록 보기
2/2
post-thumbnail

1. React Hooks

함수 component 에서도 state와 ref를 쓸 수 있는 것이 React hooks 입니다.
❶ 에서 했던 class component로 구성된 구구단 게임을 hooks 로 바꾸어 보겠습니다.

> class GuGuDan extends React.Component {} // React Class    
> const GuGuDan = () => {} // React Hooks 함수형이라고 생각하면 됩니다. 

hooks.html

<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>

2. Class와 Hooks 비교하기

1) State

  • Class는 setState 하나로 객체들을 묶습니다.
  • Hooks는 setFirst, setSecond, setValue, setResult 로 나누어 객체를 묶지 않고 하나씩 분리해 줍니다. ( '비구조화 할당'이라는 문법과 관련 )
    함수 component에 useState (use로 시작하는 것은 hooks) 를 사용합니다.
    ** 참고로 react는 setState를 모아서 한번에 처리하기 때문에 렌더링이 네번 일어나는게 아니라 한번에 일어납니다. => 비동기인 이유

2) Ref 사용방법

  • Class는 this.input.focus()
    input 선언 후 함수 만들어 this.input으로 dom에 접근
  • Hooks는 input.current.focus()
    React.useRef를 이용하여 간단하게 dom에 접근할 수 있습니다.

3) console.log 결과
-> hooks에 console.log 를 하여 react chrome 툴로 결과를 확인해보면

  • Class는 render 안만 재실행됩니다.
  • Hooks는 state를 바꾸면 함수 전체가 통째로 재실행되기 때문에 class보다는 조금 더 느릴 수가 있습니다.

4) 이전 state로 새 state를 만드는 경우

  • Class는
this.setState((prevState)=>{
return {result: '정답:'+prevState.value};
});
  • Hooks는
setResult((prevResult) => {
return {'정답'+ value} ;
});
 setCounter((c)=> c+1) // 비동기 문제 발생 안함

❶ 구구단은 이렇게 마무리 되었습니다.


3. 웹팩 설치하기

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>

4. 모듈 시스템과 웹팩 설정

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',
    },
};

5. 웹팩으로 빌드하기

두가지 방법

  • 1) 터미널에 npx webpack
  • 2) package.json 의 scripts 에 "dev": "webpack" 추가한 후 터미널에 npm run dev 실행

--> 이렇게 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], 넣어준다.

실행해보면 ?!

6. 구구단 웹팩으로 빌드하기

터미널 창

> 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'));

7. @babel/preset

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:

8. 끝말잇기 Class 만들기

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>

9. 웹팩데브서버와 핫리로딩

자동으로 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/ 접속!
-> 수정사항이 실시간으로 반영 됩니다.

10. 끝말잇기 Hooks로 전환하기

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;

0개의 댓글