Notice
Recent Posts
Archives
Today
Total
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
Recent Comments
관리 메뉴

우당탕탕 개발일지

[React] Hooks 왜쓰는데?? : 함수형 컴포넌트는 라이징스타 본문

React

[React] Hooks 왜쓰는데?? : 함수형 컴포넌트는 라이징스타

devchop 2024. 5. 18. 22:32

React 에는 Class형 컴포넌트Funtional 컴포넌트 두 가지 종류가 있다. 두 가지 방법의 차이가 무엇이고, 어떤것을 더 많이 사용할까??

 

클래스 컴포넌트는 코드가 복잡하고 속도가 느리지만 더 많은 기능을 제공한다. 이에 비해 함수형 컴포넌트는 코드가 간단하고 속도가 빠른 대신, 기능에 제한이 있었다.  어떤 기능의 제한이냐 하면, 아래의 생명주기를 이용할 수 없다는 극한의 단점이었다. 

react의 생명주기

 

react의 생명주기는 위 그림처럼 Mouting, Updating, UnMouning 3단계로 이루어져있다. 처음 시작할때, 시작 한뒤 리렌더링할 때, 그리고 마운팅을 종료할 때 각각 알맞는 작업을 수행하고 싶을 것이다.  하지만 함수형 컴포넌트에서는 이 생명주기를 이용할 수 없었다. 그래서 성능이 더 좋고 코드가 간결함에도 불구하고 class형 컴포넌트를 주로 사용했었다.

 

더보기


Class형 컴포넌트
는 아래와 같이 사용할 수 있다. Component 를 상속받아 사용하고, render()함수 내에 코드를 작성한다. 

import React, { Component } from "react";

class AppClass extends Component {
  render() {
    return <div>Class</div>;
  }
}

export default AppClass;

 

Funtional 컴포넌트는 아래와 같이 사용할 수 있다. render() 함수가 필요없다. 

import React from "react";
import "./app.css";
import AppClass from "./components/app_class";
import AppFunc from "./components/app_func";

function App() {
  return (
    <div>
      <AppClass />
      <AppFunc />
    </div>
  );
}

export default App;

 

 

그럼 그냥 class형 컴포넌트만 있으면 되지않느냐? 라고 하기엔.. class형 컴포넌트도 단점이 꽤나 있다 .

예를들어 A페이지에서도 <유저데이터가져오기> 기능을 사용하고, B 페이지에서도 <유저데이터 가져오기> 기능을 사용한다고 해보자. 그럼 우리 개발자들은 <유저데이터가져오기> 함수를 따로 빼서 재사용하고싶은 욕구를 느낄것이다.

이것을 제공하는 기능이 HOC (Higher Order Component) 이다.

 

 

그런데 한 페이지에서 재사용되는 코드가 많을 경우엔 어떻게될까?? 개발을 하다보면 유저데이터 가져오기, 유저목록을 리스트형식으로 출력하기..등등 재사용 되는 코드가 많아질 것이다. HOC에서는 코드를 가져와 쓰려면 wrapper component 를 생성해서 페이지를 감싸야하는데, 이게 많아지면 어떻게될까?? 아래 코드처럼 눈알이 빠지게된다. 그리고 babel에서 코드를 변환할때 코드 길이도 굉장히 길어진다. 코드가 길다는것 => 성능의 이슈가 있다는 것을 의미한다. 

react wrapper hell!

 

 

 

이때 혜성처럼 Hooks 가 등장한다.

Hooks??

hooks 는 함수형 컴포넌트에서 react 생명주기를 사용할 수 있도록 도와주는 기능이다.  Hooks를 사용하면 Mount, Updating, UnMount 를 모두 useEffect()라는 명령어 하나로 사용할 수 있다.  코드를 따로 빼서 작성한 뒤 재사용하는것도 물론 가능하다. HOC없이 말이다!! Hooks는 HOC 대신 Custom hooks를 이용하여 컴포넌트를 분리 및 재사용할 수 있다. 이것은 wrapper component의 늪에서 눈알을 잃어버릴 일이 없다는 것을 의미한다.

또한 Hooks를 사용하게 되면 코드가 클래스형 컴포넌트보다 훨씬더 간결해진다. 사람도 읽기 편하고, Babel도 일하기 편해진다 

 

그럼 클래스형 컴포넌트로 Todo 리스트를 만든 것과 , 함수형 컴포넌트로 TodoList를 만든것을 비교해보자.

 

더보기

Class 컴포넌트를 사용해 Todo 구현

import React, { Component } from "react";
import "./App.css";

export default class App extends Component {
  btnStyle = {
    color: "#fff",
    border: "none",
    padding: "5px 9px",
    borderRadius: "50%",
    cursor: "pointer",
    float: "right",
  };

  getStyle = (completed) => {
    return {
      padding: "10px",
      borderBottom: "1px #ccc dotted",
      textDecoration: completed ? "line-through" : "none",
    };
  };

  state = {
    todoData: [
      {
        id: "1",
        title: "study",
        completed: false,
      },
      {
        id: "2",
        title: "clean room",
        completed: true,
      },
    ],
    inputValue: "",
  };

  handleRemove = (id) => {
    //let newTodoData = this.todoData.filter(data=> data.id !== id);
    let newTodoData = this.state.todoData.filter((data) => data.id !== id);
    this.setState({ todoData: newTodoData });
  };

  handleSubmit = (event) => {
    event.preventDefault();
    let newTodo = {
      id: Date.now(),
      title: this.state.inputValue,
      completed: false,
    };
    this.state.todoData.push(newTodo);
    this.setState({
      todoDate: [...this.state.todoData, newTodo],
      inputValue: "",
    });
  };

  handleComplete = (id) => {
    //console.log(e);
    let newTodo = this.state.todoData.map((data) => {
      if (data.id === id) {
        data.completed = !data.completed;
      }
      return data;
    });

    this.setState({ todoDate: newTodo });
  };

  render() {
    return (
      <div className="container">
        <div className="todoBlock">
          <div className="title">
            <h1>할일 목록</h1>
          </div>
          {this.state.todoData.map((data) => (
            <div style={this.getStyle(data.completed)} key={data.id}>
              <input
                type="checkbox"
                defaultChecked={data.completed}
                onChange={() => {
                  this.handleComplete(data.id);
                }}
              />{" "}
              {data.title}
              <button
                style={this.btnStyle}
                onClick={() => {
                  this.handleRemove(data.id);
                }}
              >
                x
              </button>
            </div>
          ))}

          <form style={{ display: "flex" }} onSubmit={this.handleSubmit}>
            <input
              type="text"
              name="value"
              style={{ flex: "10", padding: "5px" }}
              placeholder="해야할 일을 입력하세요."
              value={this.state.inputValue}
              onChange={(event) => {
                this.setState({ inputValue: event.target.value });
              }}
            />
            <input
              type="submit"
              value="입력"
              className="btn"
              style={{ flex: "1" }}
            />
          </form>
        </div>
      </div>
    );
  }
}

 

위 코드를 함수형으로 변환

 

import React, { useState } from "react";
import "./App.css";
import List from "./components/List";
import Form from "./components/Form";

export default function App() {
  const [todoData, setTodoData] = useState([]);
  const [inputValue, setInputValue] = useState("");

const btnStyle = {
    color: "#fff",
    border: "none",
    padding: "5px 9px",
    borderRadius: "50%",
    cursor: "pointer",
    float: "right",
  };

  const getStyle = (completed) => {
    return {
      padding: "10px",
      borderBottom: "1px #ccc dotted",
      textDecoration: completed ? "line-through" : "none",
    };
  };

  const handleRemove = (id) => {
    //let newTodoData = this.todoData.filter(data=> data.id !== id);
    let newTodoData = todoData.filter((data) => data.id !== id);
    setTodoData(newTodoData);
  };

  const handleComplete = (id) => {
    //console.log(e);
    let newTodo = todoData.map((data) => {
      if (data.id === id) {
        data.completed = !data.completed;
      }
      return data;
    });

    setTodoData(newTodo);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    let newTodo = {
      id: Date.now(),
      title: inputValue,
      completed: false,
    };

    setTodoData((prev) => [...prev, newTodo]); //add item
    setInputValue("");
  };

  return (
    <div className="container">
      <div className="todoBlock">
        <div className="title">
          <h1>할일 목록</h1>
        </div>
        <div>
      {todoData.map((data) => (
        <div style={getStyle(data.completed)} key={data.id}>
          <input
            type="checkbox"
            defaultChecked={data.completed}
            onChange={() => {
              handleComplete(data.id);
            }}
          />{" "}
          {data.title}
          <button
            style={btnStyle}
            onClick={() => {
              handleRemove(data.id);
            }}
          >
            x
          </button>
        </div>
      ))}
    </div>

        <Form
          inputValue={inputValue}
          setInputValue={setInputValue}
          handleSubmit={handleSubmit}
        />
      </div>
    </div>
  );
}

 

 

1. state는 더이상 필요없어

state = {
    todoData: [
      {
        id: "1",
        title: "study",
        completed: false,
      },
      {
        id: "2",
        title: "clean room",
        completed: true,
      },
    ],
    inputValue: "",
  };
  
  
this.setState({ todoData: newTodoData }); //change data
this.state.todoData //get data

클래스형 컴포넌트에서는 위와 같이 state를 선언하고, 값을 참조하려면 this.state.[dataName] 으로 접근해야만 했다. 

//데이터 선언 getter와 setter
const [todoData, setTodoData] = useState([]);
const [inputValue, setInputValue] = useState("");

setTodoData(newTodoData); //change value
console.log(todoData); //get value

함수형 컴포넌트에서는 이렇게나 간단해졌다. 데이터를 가져와 사용할때도, 번거로운 this.state 같은 문구를 앞에 붙이지 않고 깔-끔하게 변수이름만 가져와 사용할 수 있다. 

 

2. 함수호출도 간편하게! 

클래스형 컴포넌트에서 함수를 호출할때도 this. 이 필요한데, 함수형에서는 이런 this를 찾아볼수가 없다. 

//클래스형에서는 this.handlerComplete();
<input
	type="checkbox"
    defaultChecked={data.completed}
    onChange={() => { this.handleComplete(data.id);}}
/>

//함수형에서는 handleComplete();
<input
	type="checkbox"
    defaultChecked={data.completed}
    onChange={() => { handleComplete(data.id);}}
/>

 

 

3. Hooks 에서 코드분리 > Custom hooks 사용하기

자주 사용할 것 같은 List 와 Form 부분을 아래처럼 코드에 분리할 수 있다. import 를 넣은 뒤 각 커스텀 컴포넌트에서 필요한 값들을 인자로 넘겨주면 된다. 

//App.js

import List from "./components/List";
import Form from "./components/Form";

return(
    ...
    <List todoData={todoData} setTodoData={setTodoData} />

    <Form
        inputValue={inputValue}
        setInputValue={setInputValue}
        handleSubmit={handleSubmit}
    >
);
//List.js

import React from "react";

export default function List({ todoData, setTodoData }) {
  const btnStyle = {
    ...
  };

  const getStyle = (completed) => {
    ...
  };

  const handleRemove = (id) => {
   ...
  };

  const handleComplete = (id) => {
    ...
  };

  return (
    <div>
      {todoData.map((data) => (
        <div style={getStyle(data.completed)} key={data.id}>
          <input
            type="checkbox"
            defaultChecked={data.completed}
            onChange={() => {
              handleComplete(data.id);
            }}
          />{" "}
          {data.title}
          <button
            style={btnStyle}
            onClick={() => {
              handleRemove(data.id);
            }}
          >
            x
          </button>
        </div>
      ))}
    </div>
  );
}

 

List함수가 시작될 때 인자로 todoData 변수와 setTodoData() 함수를 받고 있는 것을 볼 수 있다.  실제로 App.js에서 List를 호출할때 두가지를 인자로 넘겨주고있다. 이렇게 하면 Wrapper없이 List.js 를 App.js에서 사용할 수 있게된다. 

 

지금까지 클래스형 컴포넌트와 함수형컴포넌트의 차이 /장단점을 알아보았다. 함수형컴포넌트를 라이징 스타로 만들어준 Hooks에게 박수를..!

 

Hoos 문서는 요기요기

https://ko.legacy.reactjs.org/docs/hooks-intro.html

 

Hook의 개요 – React

A JavaScript library for building user interfaces

ko.legacy.reactjs.org