#2 끝말잇기 / 함수 컴포넌트, 웹팩

71221 단어 ReactReact

인프런에 올라간 제로초님의 강의를 보고 정리한 내용입니다.
https://www.inflearn.com/course/web-game-react


코드

클래스 버전

WordReplay.jsx

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

class WordRelay extends Component {

  state = {
    word: '제로초',
    value: '',
    answer: '',
  }
  onSubmitForm = (e) => {  //onSubmitForm : 엔터로 정보가 전송시 실행
    e.preventDefault(); // 창이 새로고침 되는 걸 방지
    if (this.state.word[this.state.word.length - 1] === this.state.value[0]) {
      console.log( this.state.word[this.state.word.length - 1] +" == " + this.state.value[0]);

      this.setState({
        word : this.state.value,
        value : '',
        answer : '정답!',
      })
      this.input.focus();
    }
      else {
        this.setState({
          value : '',
          answer : '땡!',
        })
        this.input.focus();
      }
  
  }
  onRefInput = (e) => {
    this.input = e; //html의 input을 이 리액트 컴포넌트에서 input으로 불러올 수 있게 된다.
  }

  onChangeInput = (e) => {
    this.setState({value: e.target.value});
  }

  render(){
    return (
    <>
    <div>{this.state.word}</div>
    <form onSubmit={this.onSubmitForm}> 
      <input ref={this.onRefInput} value={this.state.value} onChange={this.onChangeInput}/>
      <div>{this.state.answer}</div>
    </form>

    </>
    )}
}
module.exports = WordRelay;

함수 버전

WordReplay.jsx

const React = require('react');
const { useState, useRef } = React;

const WordRelay = () => {

  const [word, setWord] = useState('제로초');

  const [value, setValue] = useState('');
  const [answer, setAnswer] = useState('');
  const inputRef = useRef(null);

  const onSubmitForm = (e) => {  //onSubmitForm : 엔터로 정보가 전송시 실행
    e.preventDefault(); // 창이 새로고침 되는 걸 방지
    if (word[word.length - 1] === value[0]) {
      console.log( word[word.length - 1] +" == " + value[0]);
        setWord(value);
        setValue('');
        setAnswer('정답!');
        
      inputRef.current.focus();
    }
      else {
        setValue('');
        setAnswer('땡!');
        inputRef.current.focus();
      }
  
  }

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

    return (
    <>
    <div>{word}</div>
    <form onSubmit={onSubmitForm}>
      <label htmlFor="word"></label> 
      <input ref={inputRef} value={value} id="word" className="word" onChange={onChangeInput}/>
      <button>클릭!</button>
      <div>{answer}</div>
    </form>

    </>
    )}
    
module.exports = WordRelay;

#1-9 ~ 2-2 함수형 컴포넌트

document로 제어하기 보다는 리액트를 믿고 데이터만 조작하는 방식?

setState 시 render 함수가 실행된다.

useState로 동적인 값을 제어할 수 있다.

  • const [number, setNumber] = useState(0);
  • 상태의 기본값을 파라미터로 넣어서 호출한다.
  • 이 함수를 호출해주면 배열이 반환된다.
    • 첫번째 원소는 현재 상태, 두번째 원소는 Setter 함수.
    • useState의 괄호 안에 초기값을 설정해준다.
  • 배열 비구조화 할당으로 줄여서 쓴 것.
  • number는 변수, setNumber는 변수를 변경하는 함수.

함수형 컴포넌트 안에 써야 한다.

const [number, setNumber] = useState(0);

const numberState = useState(0);
//numberState 배열 생성, 0 : 변수, 1 : 변수 변경 함수 
const number = numberState[0];
const setNumber = numberState[1];

hook

Hooks 는 리액트 v16.8 에 새로 도입된 기능.

함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 그리고 렌더링 직후 작업을 설정하는 useEffect 등의 기능등을 제공.

React.Fragment는 리턴할 때

를 감싸는 것을 대신해서 쓸 수 있다.

<script type="text/babel">

		function GuGuDan() {
	
		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();
		
    const onChangeInput = (e) => {
      setValue(e.target.value);
    }
    const onSubmitForm = (e) => {
      e.preventDefault();
const answer = first * second;
if (parseInt(value) === answer) {
  setResult("정답" + value);
  setFirst(Math.ceil(Math.random() * 9));
  setSecond(Math.ceil(Math.random() * 9));
  setValue('');
}
else {
  setResult("오답...");
  setValue('');

}
inputRef.current.focus();    }

		return (
      <React.Fragment>
      <h1>{first} 곱하기 {second}?</h1>
					<form onSubmit={onSubmitForm}>
						<input ref={inputRef} onChange={onChangeInput} value={value}/>
						<button>submit</button>
						</form>
						<h3>{result}</h3>
      </React.Fragment>
    )
	  }
	
    </script>
    <script type="text/babel">
      // 웹에다가 실제로 렌더링 해주는 역할, LikeButton을 root 태그에 붙임 
	  ReactDOM.render(<GuGuDan/> , document.querySelector("#root"));
    </script>

hook 에서는 React.useRef()로 DOM에 접근한다.

hook을 사용하면 코드 길이가 확연히 줄어든다.

state가 변하면 바뀐 부분만이 아닌 함수 전체가 통째로 실행된다.

  • 클래스형 컴포넌트에서는 랜더 함수만 재실행.

함수 컴포넌트에서도 setState를 써서 state를 하나의 객체로 합칠 수도 있지만 효율적이지는 않다.

  • const [state, setState]React.useState()
  • setState마다 모든 state 값을 설정해줘야 한다.
    • 하나라도 빼먹는다면 해당 state가 사라진다.

비동기이기 때문에 render를 여러 번 실행하더라도 랜더가 끝날 때까지 멈추는 일은 없다.

ref

https://chanhuiseok.github.io/posts/react-7/

리액트에서 DOM에 접근하기 위한 방법. html에서 id, class 붙이는 것과 동일한 맥락

<input ref={(ref) => {this.input=ref}}/>

  • this.input으로 해당 input 태그에 접근할 수 있다.

html에서는 class 대신 className, for 대신 htmlFor로 써줘야 힌다.


#2-3 웹팩 설치

html에서 리액트 불러오고, babel 불러오지 않고 npm에서 불러오기.

package.json 생성 및 관리

create-react-app을 쓰면 자동으로 세팅가능.

웹팩

스크립트의 양이 포화되는 것을 방지, 유지보수를 위해 코드를 압축한다.

src로 다른 파일에서 가져오려니 스크립트 상 중복이 발생한다.

수많은 코드들을 하나로 합쳐서 하나의 자바스크립트 파일로 만드는 것이 웹팩.

웹팩을 하기 위해 노드가 필요. (자바스크립트 실행기)

서비스 할때 쓰이는 것은 dependencies, 개발할 때 쓰이는 것은 devDependencies에 들어간다.

client.jsx

node의 모듈 시스템으로 npm에 설치했던 것을 불러올 수 있다.

webpack.config.js

웹팩을 설정하는 파일.

npm init으로 packjson을 설정할 수 있다.
npm i react react-dom
npm i -D webpack webpack-cli

-D는 개발에서만 쓰인다는 것을 의미한다.


#2-4 모듈, 웹팩 설정

파일을 쪼갤 때는 클래스 컴포넌트에 extends React.Compont, 마지막 줄에 module.exports 로 내보내주어야 한다.

해당 파일이 필요하면 require나 import로 불러와서 사용한다.

웹팩을 써서 최종적으로 하나의 파일(app.js)로만 합쳐져야 한다.

웹팩

const path = require('path');

module.exports = {
	mode : "development", // 상용화는 production
	devtool : "eval",
	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'
	} // 출력
};

웹팩은 webpack.config.js로 모든 게 정해진다.

기본적인 구조.

path는 node에서 경로를 조작하기 쉽게 한다.

  • C:\users\zerocho... 를 현재 경로 기준으로 검색할 수 있게 된다.

webpack만 터미널에 입력하면 알아서 output에 설정된 파일로 모듈들을 합쳐준다.

자바스크립트 파일 내부에서 다른 파일이 불러오는 파일은 적어주지 않아도 알아서 추적해서 불러온다.

resolve의 extensions을 쓰면 확장자를 적지 않아도 자동으로 추적한다.


#2-5 웹팩 빌드

webpack으로 실행하려고 했지만 등록되지 않았을 때

package.json의 scripts 객체에 dev 키값으로 webpack 설정 후 npm run (dev)

npx webpack

webpack 명령어를 실행하면 entry를 읽어서 output 한 파일로 만들어준다.

  • 원래대로라면 dist 부분에 App.js 가 만들어져야 한다.

babel을 추가해야만 jsx를 사용할 수 있다.

정규표현식 숙지할 필요

강의의 버전과 현재의 버전이 차이가 너무 남...

npm i -D @babel/core : babel의 기본
npm i -D @babel/preset-env : 브라우저에 맞게 최신 문법을 호환(옛날문법으로)
npm i -D @babel/preset-react : jsx 지원가능
npm i -D babel-loader : babel과 react 연결


#2-6 웹팩으로 빌드

const path = require('path');

module.exports = {
	mode : "development", // 상용화는 production
	devtool : "eval",
	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'
	} 
};

entry에 들어 있는 파일들은 module이 변환시켜 준다.

  • 최소한의 모듈만 쓰다가 필요할 때 추가해주는 편이 더 낫다.

require(), import는 다른 파일의 모듈을 포함한다는 점에서 동일하지만 import는 ES6에서만 사용되는 문법이라는 점, require가 어디서든 쓸 수 있는 반면 import는 파일의 시작 부분에만 쓸 수 있다는 점이 다르다.


#2-7 @babel/preset-env와 플러그인

const path = require('path');

module.exports = {
	mode : "development", // 상용화는 production
	devtool : "eval",
	resolve : {
		extensions : [".js", ".jsx"],
	},
	
	entry : {
		app : ['./client'],
	}, // 입력
	module : {
		rules : [{
			test : /\.jsx?/,
			loader : 'babel-loader',
			options : {
				presets : [
					['@babel/preset-env', {
					target : {
						browser : ['last 2 chrome versions']
					},
				}], '@babel/preset-react'
				],
				plugins : [],
			},
		}]
	},
	plugins: [
		
	],
	output : {
		path : path.join(__dirname, 'dist'),
		filename : 'App.js'
	} // 출력
};

plugin의 모음이 preset.

preset에 중괄호로 옵션을 줄 수가 있다.

  • 웹팩에서 합쳐주는 모듈 말고 별도로 무언가를 추가하고 싶다면 pugins

웹팩의 핵심 구성 요소

  • entry - input 파일
  • Loaders -모듈
  • Plugin - 추가적으로 하고 싶은 작업
  • output - 결과로 나올 파일
  • mode - 개발자 용인지, 실용인지 (development)

entry 파일에 Loaders 모듈 적용, 플러그인 추가 적용 후 out으로 나온다.

기타 설정들(mode, devtool, resolve) 등은 위에 몰아넣는다.

babel/preset-env

  • preset는 플러그인들의 모음, browers의 조건에 해당하는 브라우저를 지원한다.

2-8 끝말잇기

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

class WordRelay extends React.Component {

  state = {
    word: '제로초',
    value: '',
    answer: '',
  }
  onSubmitForm = (e) => {  //onSubmitForm : 엔터로 정보가 전송시 실행
    e.preventDefault(); // 창이 새로고침 되는 걸 방지
    if (this.state.word[this.state.word.length - 1] === this.state.value[0]) {
      console.log( this.state.word[this.state.word.length - 1] +" == " + this.state.value[0]);

      this.setState({
        word : this.state.value,
        value : '',
        answer : '정답!',
      })
      this.input.focus();
    }
      else {
        this.setState({
          value : '',
          answer : '땡!',
        })
        this.input.focus();
      }
  
  }
  onRefInput = (e) => {
    this.input = e; //html의 input을 이 리액트 컴포넌트에서 input으로 불러올 수 있게 된다.
  }

  onChangeInput = (e) => {
    this.setState({value: e.target.value});
  }

  render(){
    return (
    <>
    <div>{this.state.word}</div>
    <form onSubmit={this.onSubmitForm}> 
      <input ref={this.onRefInput} value={this.state.value} onChange={this.onChangeInput}/>
      <div>{this.state.answer}</div>
    </form>

    </>
    )}
}
module.exports = WordRelay;

리액트로 폼을 다룰 때 vaule를 쓴다면 onChange를 쓰거나 defaultValue를 같이 써줘야만 한다.

form 내부의 button 태그가 존재한다면 이벤트를 주지 않아도 누르면 입력 처리가 된다.


#2-9 웹팩데브서버, 핫리로딩

자동으로 빌드하지 않으면 수정할 때마다 수동으로 빌드를 돌려야만 한다.

npm i -D react-refresh : 프론트용 새로고침
npm i -D react-refresh webpack-dev-server : 서버용 새로고침

핫리로딩 시 webpack serve --env development로 webpack-dev-server를 실행할 수 있다.

react-refresh와 react-webpack-plugin이 없어도 새로고침은 작동하지만, 기존의 데이터는 다 날아간다.

기존의 데이터를 유지하냐 하지 않느냐는 굉장히 중요한 요소다.


#2-10 클래스 → 훅

const React = require('react');
const { useState, useRef } = React;

const WordRelay = () => {

  const [word, setWord] = useState('제로초');

  const [value, setValue] = useState('');
  const [answer, setAnswer] = useState('');
  const inputRef = useRef(null);

  const onSubmitForm = (e) => {  //onSubmitForm : 엔터로 정보가 전송시 실행
    e.preventDefault(); // 창이 새로고침 되는 걸 방지
    if (word[word.length - 1] === value[0]) {
      console.log( word[word.length - 1] +" == " + value[0]);
        setWord(value);
        setValue('');
        setAnswer('정답!');
        
      inputRef.current.focus();
    }
      else {
        setValue('');
        setAnswer('땡!');
        inputRef.current.focus();
      }
  
  }
  const onRefInput = (e) => {
    inputRef = e; //html의 input을 이 리액트 컴포넌트에서 input으로 불러올 수 있게 된다.
  }

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

    return (
    <>
    <div>{word}</div>
    <form onSubmit={onSubmitForm}> 
      <input ref={inputRef} value={value} onChange={onChangeInput}/>
      <button>클릭!</button>
      <div>{answer}</div>
    </form>

    </>
    )}
module.exports = WordRelay;

const { useState, useRef } = React;

기존의 메소드는 const 붙여서 객체화

this.state로 쓰던 state는 useState 써서 따로따로 지정

ref는 current 붙여서 사용

  • inputRef.focus → inputRef.current.focus

HMR : 핫모듈리로더, 어떤 컴포넌트가 바뀌어서 수정되는지를 알려준다.

html 태그에 class 대신 className을 써야 한다.
label에서 for을 쓸 때 htmlFor을 써야 한다.

좋은 웹페이지 즐겨찾기