[React] 웹 게임-3 숫자야구

ji_silver·2020년 7월 23일
0

[React] 웹 게임

목록 보기
3/8
post-thumbnail

1. import와 require 비교

1) require: node의 모듈 시스템

//NumberBaseball.jsx
module.exports = NumberBaseball; 

//client.jsx
const NumberBaseball = require('./NumberBaseball');

NumberBaseball class가 module.exports 담겨서 다른파일에서 require로 불러오기

2) import: ES2015(ES6) 모듈 시스템

export const hello = 'hello'; // 가져올 때 import { hello }
export default NumberBaseball; // 가져올 때 import NumberBaseball

import React, { Component } from 'react';
//따라서 React는 export default, Component는 export const Component
  • exports 되는 게 객체나 배열이면 구조 분해 가능
  • default는 한 번만 사용 가능
  • 엄밀히 따지면 export default와 module.exports 다르지만 호환은 가능

node로 webpack을 돌리면 import 사용 시 에러가 나지만 babel이 import를 require로 바꿔줌
(node에서는 require, react에서는 import와 export 사용)

2. 리액트 반복문(map)

  • 리액트에서는 반복문을 map 메서드로 사용
  • 달라지는 부분을 배열로 만들고 받아오기. 공통되는 부분은 return
<ul>
    {['사과', '바나나', '포도', '귤', '감', '배', '밤',].map((v) => {
        return (
            <li>{v}</li>
        );
    })}
</ul>

3. 리액트 반복문(key)

  • 달라지는 부분을 2차원 배열 또는 객체로 만들기
  • 리액트가 key를 보고 같은 컴포넌트인지 아닌지 판단.
    key는 고유한 값이 들어가야함
<ul>
    {[
        { fruit: '사과', taste: '달다' },
        { fruit: '바나나', taste: '맛없다' },
        { fruit: '포도', taste: '새콤하다' },
        { fruit: '귤', taste: '시다' },
        { fruit: '감', taste: '떫다' },
        { fruit: '배', taste: '맛있다' },
        { fruit: '밤', taste: '맛있다' },
    ].map((v, i) => { //두번째는 인덱스
        return (
            <li key={v.fruit + v.taste}><b>{v.fruit}</b> - {v.taste}</li>
        );
    })}
</ul>

화살표 함수 사용 시 return 생략 가능. 중괄호가 없으면 return을 뜻함

.map((v, i) =>
    <li key={v.fruit + v.taste}><b>{v.fruit}</b> - {v.taste}</li>
)

4. 컴포넌트 분리와 props

  • 컴포넌트를 분리하는 이유: 재사용성, 가독성, 성능 최적화(반복문 사용 시 성능 문제 발생)
  • 컴포넌트 분리 시 매개변수 v, i를 사용하기 위해 연결해주는 props 필요
    NumberBaseball class가 Try의 부모!

Try.jsx

import React, { Component } from 'react';

class Try extends Component {
    render() {
        return(
            <li>
                <b>{this.props.value.fruit}</b> - {this.props.index}
                <div>컨텐츠</div>
                <div>컨텐츠1</div>
                <div>컨텐츠2</div>
                <div>컨텐츠3</div>
            </li>
        );
    }
}

export default Try;

NumberBaseball.jsx

import Try from './Try';
...
fruits = [
    { fruit: '사과', taste: '달다' },
    { fruit: '바나나', taste: '맛없다' },
    { fruit: '포도', taste: '새콤하다' },
    { fruit: '귤', taste: '시다' },
    { fruit: '감', taste: '떫다' },
    { fruit: '배', taste: '맛있다' },
    { fruit: '밤', taste: '맛있다' },
];

render() {
    return (
        <>
            ...
            <ul>
                {this.fruits.map((v, i) => {
                    return (
                        <Try key={v.fruit + v.taste} value={v} index={i} />
                    );
                })}
            </ul>
        </>
    );
}

5. 주석과 메서드 바인딩

1) 주석

{/* jsx 주석 */}

2) 바인딩

render() 밖에서 화살표 함수를 쓰지 않으면 this가 undefined가 되어 연결해주는 bind()함수를 써야함.
화살표 함수를 사용하면 자동으로 바인딩

6. 숫자야구 만들기

  • push() 사용 시 뭐가 바뀌었는지 감지 x, 예전 state와 현재 state가 다르면 render

NumberBaseball.jsx

import React, { Component } from 'react';
import Try from './Try';

function getNumbers() { //숫자 4개를 겹치지 않고 랜덤하게 뽑는 함수
    const candidate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    const array = [];
    for (let i = 0; i < 4; i += 1) {
        const chosen = candidate.splice(Math.floor(Math.random() * (9 - i)), 1)[0];
        array.push(chosen);
    }
    return array;
}


class NumberBaseball extends Component {
    state = {
        result: '',
        value: '',
        answer: getNumbers(),
        tries: [],
    };

    onSubmitForm = (e) => {
        const { result, value, tries, answer } = this.state;
        e.preventDefault();
        if (value === answer.join('')) { //답이 맞는 지 비교
            this.setState((prevState) => {
                return {
                    result: '홈런!',
                    tries: [...prevState.tries, { try: value, result: '홈런!' }],
                }
            });
            alert('게임을 다시 시작합니다!');
            this.setState({
                value: '',
                answer: getNumbers(),
                tries: [],
            });
        } else { // 답 틀렸으면
            const answerArray = value.split('').map((v) => parseInt(v));
            let strike = 0;
            let ball = 0;
            if (tries.length >= 9) { // 10번 이상 틀렸을 때
                this.setState({
                    result: `10번 넘게 틀려서 실패! 답은 ${answer.join(',')}였습니다!`,
                });
                alert('게임을 다시 시작합니다!');
                this.setState({
                    value: '',
                    answer: getNumbers(),
                    tries: [],
                });
            } else {
                for (let i = 0; i < 4; i += 1) {
                    if (answerArray[i] === answer[i]) {
                        strike += 1;
                    } else if (answer.includes(answerArray[i])) {
                        ball += 1;
                    }
                }
                this.setState((prevState) => {
                    return {
                        tries: [...prevState.tries, { try: value, result: `${strike} 스트라이크, ${ball} 볼입니다` }],
                        value: '',
                    };
                });
            }
        }
    };

    onChangeInput = (e) => {
        console.log(this.state.answer);
        this.setState({
            value: e.target.value,
        });
    };

    render() {
        const { result, value, tries } = this.state;
        return (
            <>
                <h1>{result}</h1>
                <form onSubmit={this.onSubmitForm}>
                    <input maxLength={4} value={value} onChange={this.onChangeInput} />
                </form>
                <div>시도: {tries.length}</div>
                <ul>
                    {tries.map((v, i) => {
                        return (
                            <Try key={`${i + 1}차 시도: `} tryInfo={v} />
                        );
                    })}
                </ul>
            </>
        );
    }
}

export default NumberBaseball;

Try.jsx

import React, { Component } from 'react';

class Try extends Component {
    render() {
        const { tryInfo } = this.props; // 구조 분해 문법
        return (
            <li>
                <div>{tryInfo.try}</div>
                <div>{tryInfo.result}</div>
            </li>
        );
    }
}

export default Try;

7. 숫자야구 Hooks로 전환하기

NumberBaseball.jsx

import React, { useState } from 'react';
import Try from './Try';

function getNumbers() {
    const candidate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
    const array = [];
    for (let i = 0; i < 4; i += 1) {
        const chosen = candidate.splice(Math.floor(Math.random() * (9 - i)), 1)[0];
        array.push(chosen);
    }
    return array;
}

const NumberBaseball = () => {
    const [result, setResult] = useState('');
    const [value, setValue] = useState('');
    const [answer, setAnswer] = useState(getNumbers());
    const [tries, setTries] = useState([]);

    const onSubmitForm = (e) => {
        e.preventDefault();
        if (value === answer.join('')) {
            setResult('홈런!');
            setTries((prevTries) => { // 이전 tries 배열로 새 tries 배열을 만들기 때문에 함수형으로 만들기
                return [...prevTries, { try: value, result: '홈런!' }]
            });
            alert('게임을 다시 시작합니다!');
            setValue('');
            setAnswer(getNumbers());
            setTries([]);
        } else {
            const answerArray = value.split('').map((v) => parseInt(v));
            let strike = 0;
            let ball = 0;
            if (tries.length >= 9) {
                setResult(`10번 넘게 틀려서 실패! 답은 ${answer.join(',')}였습니다!`);
                alert('게임을 다시 시작합니다!');
                setValue('');
                setAnswer(getNumbers());
                setTries([]);
            } else {
                console.log(answer.join(''));
                for (let i = 0; i < 4; i += 1) {
                    if (answerArray[i] === answer[i]) {
                        strike += 1;
                    } else if (answer.includes(answerArray[i])) {
                        ball += 1;
                    }
                }
                setTries((prevTries) => [...prevTries, { try: value, result: `${strike} 스트라이크, ${ball} 볼입니다` }]);
                setValue('');
            }
        }
    };

    const onChangeInput = (e) => {
        setValue(e.target.value);
    };

    return (
        <>
            <h1>{result}</h1>
            <form onSubmit={onSubmitForm}>
                <input maxLength={4} value={value} onChange={onChangeInput} />
            </form>
            <div>시도: {tries.length}</div>
            <ul>
                {tries.map((v, i) => {
                    return (
                        <Try key={`${i + 1}차 시도: `} tryInfo={v} />
                    );
                })}
            </ul>
        </>
    );
};

export default NumberBaseball;

Try.jsx

import React from 'react';

const Try = ({tryInfo}) => {
  return (
    <li>
      <div>{tryInfo.try}</div>
      <div>{tryInfo.result}</div>
    </li>
  );
};

export default Try;

8. shouldComponentUpdate

  • state, props가 바뀌거나 setState 호출 시 랜더링 됨.
    건들이지 않은 컴포넌트도 다시 랜더링 👉 성능 문제 발생
  • shouldComponentUpdate()의 return 값이 true면 랜더링, false 랜더링 하지 않게 구현

9. PureComponent와 React.memo

1) PureComponent

shouldComponentUpdate()에서 true, false 할 것인지를 state가 바뀌었는지 안 바뀌었는지 판단 후 자동으로 구현한 컴포넌트

import React, { PureComponent } from 'react';

class Test extends PureComponent { ...

❗ 단 state엔 복잡한 객체구조는 안 쓰는 게 좋음

2) React.memo

Hooks에서 사용하는 리랜더링 방지 함수

const React = require('react');
const { memo } = React; 

const Try = memo(({ tryInfo }) => {...

10. React.createRef

  • class에서 ref를 Hooks와 비슷하게 만들 수 있음
  • this.inputRef.current.focus(); 로 접근 가능
import React, { Component, createRef } from 'react';
...
InputRef = createRef();

함수형 방식은 세밀한 작업을 하고싶을 때 사용

11. props와 state 연결하기

  • render() 안에 this.setState() 사용 시 무한반복이 되므로 사용 X
  • props는 부모가 바꿔야하며 자식이 바꿀 수 없음
    만약 props를 바꿔야한다면 state로 만들어서 바꾸기
const Try = memo(({ tryInfo }) => {
    const [result, setResult] = useState(tryInfo.result);

    const onClick = () => {
        setResult('1');
    }

    return (
        <li>
            <div>{tryInfo.try}</div>
            <div onClick={onClick}>{tryInfo.result}</div>
        </li>
    );
});
profile
🚧개발중🚧

0개의 댓글