whatisthis?

220315 [TIL] Today I Learned 💯 React.js - (1) 본문

WEB STUDY/REACT

220315 [TIL] Today I Learned 💯 React.js - (1)

thisisyjin 2022. 3. 15. 13:11

React.js   

Day 03 React Hooks + WebPack 

 

 

 

1 / React Hooks

 

리액트에서 추천하는 것 = Hooks의 사용.

Class Component를 자제하고, 함수형 프로그래밍을 위한 Hooks를 권고함.

 

클래스 컴포넌트 class Button extends React.Component {
// 생략
render () {

return (
<button></button>
)
}
함수형 컴포넌트 const Button = () => {
// 생략

return <button></button>
}

 

원래도 함수형 컴포넌트가 있었는데,

그전에는 state나 ref 등을 클래스에서만 쓸 수 있었다.

 

💡 함수형에서도 state랑 ref를 쓸 수 있게 만든 것 = React Hooks

(+ useEffect)

 

 

 

useState()

 

React.useState() 사용.

대신, 클래스에서처럼 객체 형태로 한꺼번에 선언은 못하고,

state {
    first: Math.ceil(Math.random() * 9),
    second: Math.ceil(Math.random() * 9),
    value: "",
    result: "",
}

하나하나 쪼개서 선언해줘야함.

- 구조분해 할당 🔻

더보기

Destructuring assignment (구조분해 할당)

 

 

구조 분해 할당 - JavaScript | MDN

구조 분해 할당 구문은 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식입니다.

developer.mozilla.org

= 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JavaScript 표현식

[a, b, ...rest] = [10, 20, 30, 40, 50];

console.log(rest);
// expected output: Array [30,40,50]
// 변수에 기본값을 할당하면, 분해한 값이 undefined일 때 그 값을 대신 사용합니다.

var a, b;

[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7

 

 

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

setState도 state 하나마다 하나씩 다 쪼개서 선언해줘야함. 

(state-setState쌍)

 

단, class안에 state가 존재했듯이,

함수형 컴포넌트 내부에 존재해야함.

 

 

 

ref

 

클래스에서는 ref를 사용하기 위해서 아래와 같이 했었다.

input = null;
 
 
 // 중략

 <input ref={el => (this.input = el)} />
 
 
 // 중략
 
 this.input.focus();

DOM을 input에 대입하는 함수를 ref={}안에 넣어주고,

필요한 부분에 this.input.focus()를 해줬었다.

 

 

이제 Hooks에서는 React.useRef()를 이용해서 간단히 구현할 수 있다!

🔻

let inputRef = React.useRef(null); 
 
 // 중략
 
<input ref={inputRef} />

 // 중략
 
inputRef.current.focus();

 

1. 변수 선언 - React.useRef(초기값);

 

2. ref속성에 inputRef 변수 대입.

 

3. 필요한 부분에 inputRef.current.focus() 해주기.

- 그냥 focus()말고 current.focus 해줘야한다!

 

 

 

 

지난번에 예제로 제작해본 구구단 게임을

클래스에서 함수형, 즉 Hooks로 변경해보자.

 

const 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("");

  let inputRef = React.useRef(null);

  const onFormSubmit = e => {
    e.preventDefault();
    if (parseInt(value) === first * second) {
      setResult("⭕ 정답입니다");
      setFirst(Math.ceil(Math.random() * 9));
      setSecond(Math.ceil(Math.random() * 9));
      setValue("");
      inputRef.current.focus();
    } else {
      setResult("❌ 오답입니다");
      setValue("");
      inputRef.current.focus();
    }
  };

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

  return (
    <div>
      <h1>💯구구단 게임💯</h1>
      <div>
        📃 {first} 곱하기 {second} 는?
      </div>
      <form onSubmit={onFormSubmit}>
        <input
          ref={inputRef}
          type="number"
          required
          placeholder="정답 입력"
          value={value}
          onChange={onInputChange}
        />
        <button type="submit">입력</button>
      </form>
      <div>{result}</div>
    </div>
  );
};

 

 

🔎 class 컴포넌트 코드와 비교해보자!

더보기
class GuGuDan extends React.Component {
  state = {
    first: Math.ceil(Math.random() * 9),
    second: Math.ceil(Math.random() * 9),
    value: "",
    result: "",
    line: "",
  };

  onFormSubmit = e => {
    e.preventDefault();
    if (parseInt(this.state.value) === this.state.first * this.state.second) {
      this.setState(prevState => {
        return {
          result: "⭕정답입니다",
          value: "",
          first: Math.ceil(Math.random() * 9),
          second: Math.ceil(Math.random() * 9),
          line:
            prevState.first +
            "*" +
            prevState.second +
            "=" +
            prevState.first * prevState.second,
        };
      });
      this.input.focus();
    } else {
      this.setState(() => {
        return { result: "❌틀렸습니다", value: "" };
      });
      this.input.focus();
    }
  };

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

  input;

  render() {
    return (
      <div>
        <h1>💯구구단 게임💯</h1>
        <div>
          📃 {this.state.first} 곱하기 {this.state.second} 는?
        </div>
        <form onSubmit={this.onFormSubmit}>
          <input
            ref={c => (this.input = c)}
            type="number"
            required
            placeholder="정답 입력"
            value={this.state.value}
            onChange={this.onInputChange}
          />
          <button type="submit">입력</button>
        </form>
        <span>LOG 🔎 {this.state.line}</span>
        <div>{this.state.result}</div>
      </div>
    );
  }
}

 

 

 

크게 달라진 점을 뽑아보면 다음과 같다.

 

1 / state를 객체로 나타내지 않고, 각 state를 분리해서 구조분해할당으로 선언했다.

 >> const [ state, setState ] = React.useState('초기값'); 과 같이 선언.

 

2 / ref 변수를 React.useRef()로 선언하고, JSX안에선 ref={inputRef}와 같이 속성값에 변수를 대입했다.

>> focus()대신 inputRef.current.focus()로 포커스를 주었다.

 

 

 

 

 

 

함수형 setState

 

지난번에 이전 state를 사용할땐 setState를 함수형으로 적어줘야한다고 했었다.

 

🙋‍♂️함수형 setState를 언제 쓰는건지?

- setState안에 this.state(즉, 이전 state)가 들어가면
- 함수형으로 만들고, return { } 을 해주자!
this.setState((prevState) => {
        return {
          line: prevState.first + prevState.second
        };
});

 

Hooks에도 마찬가지로 예전 상태가 필요하다면 함수형으로 setState를 적어준다!

setResult("정답입니다!" + value);

🔻

setResult((prevResult) => {return "정답입니다! " + value});

 

 

 

 

Hooks의 리렌더링

 

함수형 컴포넌트 역시 state가 바뀔때마다 해당 함수가 재실행되기 때문에 (=리렌더링)

함수가 안에 많이 있다면 계속 재생성되어서 속도가 느려질수도 있다. >> 어쩔수 없는 부분임.

 

cf> class형에서는 클래스의 메서드로 따로 빼서 선언했기 때문에 (onFormSubmit, onInputChange)

render()안에 부분만 재실행됬었다.

 

하지만, Hooks에서는 어쩔 수 없이 함수 전체가 다시 실행된다.

 

 

 

 

 

+) React에서 ❌ 못쓰는 속성

 

<HTML>태그의 속성 중에 class와 for은 js문법상 존재하는 것 (예약어)이므로

class 대신 className
for 대신 htmlFor 으로 적어주자.

 

 


 

 

 

2 / WebPack 

 

 

React 할때 노드를 알아야한다?

>> javascript 실행기를 알아야한다.

웹팩을 돌리기 위한 javascript를 실행해야함.

 

 

 

 

step1

현재 프로젝트 루트에서 터미널 명령어

$ npm init 을 입력하자.

 

이런식으로 하나하나 입력해주고, 마지막으로 yes를 눌러주면

루트 폴더에 자동으로 📃package.json 이 생긴다!

 

 

step2

$ npm i react react-dom 

 

react와 react-dom을 설치해줌.

 

 

이런식으로 node_modules 폴더가 생성된다.

>> 이 안에는 라이브러리들이 존재함.

 

package.json에는 라이브러리들의 dependencies들이 존재하는 것.

(실제 라이브러리들은 node_modules 에 존재함)

 

 

 

step 3

 

$ npm i -D webpack webpack-cli

 

 

🔻  🔎npm install 옵션  살펴보기

 

npm-install | npm Docs

Install a package

docs.npmjs.com

-D 
package.json에서 dev-dependencies에 기록됨.
(개발할때만 필요한 것)

- 실제 서비스할때는 필요 없으므로!
- 실제 서비스할때 쓰이는 것들은 dependencies 로 기록됨.


📃 package.json
"dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "webpack": "^5.70.0",
    "webpack-cli": "^4.9.2"
  }​



-g
글로벌 설치.

npm install name@latest // 가장 최신버전
npm install name@0.0.1 // 해당 버전

 

설치가 완료되었다면, 웹팩 설정 파일인

webpack.config.js 파일루트 폴더에 만들고, 저장해준다.

 

// 웹팩 설정 파일


module.exports = {

};

- 추후에 점점 추가할 것임.

 

 

 

루트 폴더에 client.jsx 파일을 생성한 후

react와 react-dom을 require()로 불러온다.

 

const React = require("react");
const ReactDom = require("react-dom");

const WordRelay = require("./WordRelay");

ReactDom.render(<WordRelay />, document.querySelector("#root"));

🔺 client.jsx 

 

🔻 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>끝말잇기</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="./dist/app.js"></script>
  </body>
</html>

 

 

우리는 끝말잇기를 구현할 컴포넌트인 WordRelay.jsx를 따로 분리해서 생성하면 된다!

 

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

class WordRelay extends Component {
  state = {};

  render() {}
}

module.exports = WordRelay;

 

❗❗ 주의

- 유지보수를 쉽게하기 위해 컴포넌트별로 파일로 분리하는 것이다.
- BUT!  매 파일마다 React를 require   로 불러오고,
- module.exports = 컴포넌트명;    을 꼭 해주자.

 

html 파일에는 script src="./dist/app.js" 하나만 연결되어 있다.
따라서, 모든 jsx 파일들을 app.js 하나로 합쳐줘야함.
>> 웹팩이 필요해! 🚀

 

 

다시 웹팩 설정 파일인

webpack.config.js를 수정해보자.

 

module.exports = {
  name: "wordrelay-setting",
  mode: "development", // 실서비스에선 'production'으로
  devtool: "eval", // 빠르게
  }

우선 기본적으로 name(셋팅 이름), mode(개발용/배포용) , devtool을 적어주고,

 

가장 중요한 entry: {} 와 output: {} 을 지정해주자.

 

entry = 입력 

output = 출력

 

우리가 지금은 client.jsx와 WordRelay.jsx를 합쳐서

./dist/app.js로 만들어줘야 하므로

 

output은 ./dist/app.js 가 된다.

 

output: {
    path: path.join(__dirname, "dist"), //__dirname은 현재폴더경로
    filename: "app.js",
  },

이런식으로 path: path.join()을 이용해서

__dirname(=현재 폴더 경로)에 'dist'를 합쳐주면 된다.

 

참고로, 위 코드를 작성하면 자동으로 아래와같이 require('path') 코드가 생성됨.

const path = require("path");

🔻

노드 문법임. 아래 문서를 참고하자.

 

NodeJS에서 Path 사용방법 - Yohan's Developer Diary

요즘 NodeJs를 사용하여 Veeva BoilerPlate 3.0 작업을 하는 중이다. Gulp를 사용하면서 느꼈던 점이지만, path를 정확히 맞춰 구현하는 건 생각보다 짜증나는 일이다. Node에서는 이런 path들을 맞추기 위해

yohanpro.com

- Path를 맞추기 위한 Path 모듈. (디렉토리 경로 작업)

 

 

 

entry에는  client.jsx와 WordRelay.jsx가 들어가면 된다.

근데 여기서 client.jsx가 WordRelay.jsx를 불러오므로 client.jsx만 적어주면 된다!

 

 

>> 확장자는 생략하기 위해, resolve라는 프로퍼티를 추가하자.

(나중에 가면 css 등 다양한 확장자명이 등장하니까)

 

resolve: {
    extensions: [".js", ".jsx"],
  },

 

entry: {
    app: ["./client"],
  },

>> 배열의 형태로 넣어준다.

 

 

 

 

** 지금까지의 코드

 

📃 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", "WordRealy.jsx"],
  },
  output: {
    path: path.join(__dirname, "dist"), //__dirname은 현재폴더경로
    filename: "app.js",
  },
};

 

 


 

 

 

Practice - 끝말잇기 (Hooks 사용)