[TIL]Hooks (1)(useState, useEffect)

JunSung Choi·2020년 2월 20일
0

React Hooks

목록 보기
1/4

드디어 오늘부터 hooks를 배우고 알게 된 내용들을 하나씩 기록해보려 한다.
여기에 기록될 내용들은 노마드코더의 실전형 리액트 Hooks 무료강의, velopert님의 리액트의 Hooks 완벽 정복하기, React Hooks 공식문서를 바탕으로 나만의 Hooks를 만들어보며 배우게 된 내용들을 적을 예정이다.

먼저 리액트 hooks 공식문서의 Motivation 을 보면

컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵습니다.
복잡한 컴포넌트들은 이해하기 어렵습니다.
Class은 사람과 기계를 혼동시킵니다.

라는 클래스 컴포넌트의 문제때문에 class 없이 React 기능들을 사용할 수 있도록 하는 방법을 권장하는것 같다. 나는 위 같은 문제를 겪어 본 것도 있고 아직 경험하지 못한 것도 있지만 클래스 컴포넌트 방식과 함수형 컴포넌트 방식의 장단점을 알아야 나중에 내가 개발할 서비스와 개발환경에 적절히 쓸 수 있을거라 생각했다.

Hooks의 기원부터 살펴보면 Hooks는 recompose 라는 라이브러리에서 시작되었다고 한다.
위 링크에 들어가보면 recompose의 사용 방법이 지금의 hooks와 매우 유사한데 이것을 React팀이 인수하여 지금의 Hooks가 릴리즈 되었다고 한다.

useState

useState는 함수 컴포넌트 안에서 state를 사용할 수 있게 해준다.
useState로 넘겨주는 인자로 state의 초기 값을 설정해줄 수 있다.
state는 클래스와 달리 객체일 필요는 없고, 숫자 타입과 문자 타입을 가질 수 있다.
useState는 state 변수, 이 변수를 갱신할 수 있는 함수 두가지를 반환한다.

// 기본적으로 아래와 같이 사용한다.
const [count, setCount] = useState(0);
const App = () => {
   const [count, setCount] = useState(1);
   const increment = () => setCount(count + 1);
   const decrement = () => setCount(count - 1);

   return (
     <div className="App">
       <h1>Hello! {count}</h1>
       <button onClick={increment}>Increment</button>
       <button onClick={decrement}>Decrement</button>
     </div>
   );
 }

위와 같이 count를 증가/감소 할 수 있는 함수 컴포넌트를 만들어 봤다.
처음으로 hooks를 사용해 본 느낌은 this를 생각하지 않아도 된다는 것코드 라인이 줄어든다는 것이다. 굉장히 보기에 간단하다.

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 1
    }
  }

  incrementCount = () => {
    const { count } = this.state;
    this.setState({ count: count + 1 });
  };

  decrementCount = () => {
    const { count } = this.state;
    this.setState({ count: count - 1 });
  };

  render() {
    const { count } = this.state;
    return (
      <div className="App">
        <h1>Hello Component! {count}</h1>
        <button onClick={this.incrementCount}>incrementCount</button>
        <button onClick={this.decrementCount}>decrementCount</button>
      </div>
    );
  }
}

위는 함수 컴포넌트로 작성했던 코드를 클래스 컴포넌트 방식으로 작성한 경우다. 로직이 간단하여 이것도 코드량이 많은 편은 아니지만 state의 초기화, this를 사용해 state객체 접근, 이에 따른 디스트럭쳐링 등 생각해야 할 부분들이 많다.

useEffect

useEffect는 컴포넌트가 렌더링 될때와 업데이트 될때
즉, 클래스형 컴포넌트의 componentDidMount, componentDidUpdate와 같은 역할을 한다.
useEffect의 첫번째 인자는 function, 두번째 인자는 dependency다.
dependency란, 두번째 인자로 받은 값이 변할 때 useEffect가 실행되는 것이다.

// 기본적으로 아래와 같이 사용할 경우 컴포넌트 초기 렌더시에 "Hello"가 콘솔에 찍힌다.
const App = () => {
  useEffect(() => {
    console.log("Hello!");
  });
  return (
    <div className="App">
    </div>
  );
componentDidUpdate(prevProps, prevState) {
  if (prevProps.value !== this.props.value) {
    doSomething();  
  }
}

만약 어떤 특정 값이 변경이 될 때만 호출되게 하고 싶을 때 클래스형 컴포넌트라면 위와 같이 작성 했을 것 이다.

const App = () => {
  const [count, setCount] = useState(0);
  const [number, setNumber] = useState(100);
  useEffect(() => {
    console.log("Hello");
  }, [count]);
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>count증가</button>
      <button onClick={() => setNumber(count + 1)}>number증가</button>
    </div>
  )
};

위와 같이 작성할 때 useEffect는 초기 렌더시, 배열 형태로 dependency로 넘겨준 count가 변경될 때만 실행된다.
때문에 number가 변경 될때는 useEffect가 실행되지 않는다. 만약 빈 배열을 넘겨준다면 useEffect는 초기에만 실행되고 어떠한 값을 변경 하더라도 실행되지 않을 것 이다.

뒷정리 함수 사용

만약 컴포넌트가 언마운트 되기 전이나, 업데이트 되기 직전에 어떠한 작업을 수행하고 싶다면 useEffect에서 뒷정리 함수(cleanup)를 반환해줘야 한다.
(아래 코드는 velopert님 블로그 예제 참조)

App.js

import React, {  useState } from 'react';
import Counter from "./Counter";
import Info from "./Info";

const App = () => {
  const [visible, setVisible] = useState(false);
  return (
    <div>
      <button 
        onClick={() => {
          setVisible(!visible);
        }}
      >
        {visible ? "숨기기" : "보이기"}
      </button>
      <br />
      {visible && <Info />}
    </div>
  );
};

export default App;

Info.js

import React, { useState, useEffect } from "react";

const Info = () => {
  const [name, setName] = useState("");
  const [nickname, setNickname] = useState("");

  useEffect(() => {
    console.log("effect");
    console.log(name);
    return () => {
      console.log("cleanup");
      console.log(name);
    };
  });

  const onChangeName = e => {
    setName(e.target.value);
  };

  const onChangeNickname = e => {
    setNickname(e.target.value);
  };

  return (
    <div>
      <div>
        <input value={name} onChange={onChangeName} />
        <input value={nickname} onChange={onChangeNickname} />
      </div>
      <div>
        <div>
          <b>이름:</b> {name}
        </div>
        <div>
          <b>닉네임: </b>{nickname}
        </div>
      </div>
    </div>
  );
};
export default Info;

위 코드로 실행 시 Info 컴포넌트가 보일 때 "effect", 사라질 때 "cleanup" 로그가 찍힌다.
또한, name에 value를 입력 시(렌더링이 될 때마다) effect와 cleanup이 모두 찍히는데 뒷정리 함수는 업데이트 되기 직전의 값을 보여준다.
여기서도 뒷정리 함수가 언마운트시에만 호출되게 하려면 빈 배열[]을 전달해주면 된다.

정리

hooks를 처음 써보았는데 확실히 간단하고 명료한 느낌이다. 하지만 이게 정말 클래스보단 좋은가? 하는 질문에는 많은 코드를 작성해보고 여러 lifecycle 방식 대용으로 써보고 redux까지 붙여 큰 로직을 구현해봐야 알 수 있을것 같다. 아직까지 클래스 컴포넌트 방식이 익숙하기도 하고...
그렇다고 '클래스 방식이 나쁘다, 함수형 컴포넌트가 백번 좋다'. 가 아닌 각각의 장단점이 있다고 생각한다. 그 각각의 장단점을 정확히 알고 실무에서 쓸 수 있도록 성장하기 위해 지금 배움을 계속 이어나가야 할 것 같다.

profile
Frontend Developer

0개의 댓글