Dev

React Hooks

Joo 2019. 2. 9. 14:59

이 발표를 보고나서 Dan Abramov에게 묘한 매력을 느꼈다. 영어도 잘 못하면서 팔로우를 시작했다. Reactjs에 대해서 이런저런 것을 많이 보게 되었다. 그리고 어느 덧 발표했던 기능이 정식출시했다. 이름은 React Hooks다.

기본 개념

Hooks소개의 Motivation에도 말하고 있지만 Hooks의 기본 목적인 간결함이다. 다음의 코드는 Hooks를 사용한 코드다.

import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

이 코드가 얼마나 간결한 코드인가하면 Hooks를 사용하지 않은 다음의 코드를 보면 알 수 있다.

import React, { Component } from 'react';

class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };

    this.handleChangeCount = this.handleChangeCount.bind(this);
  }

  handleChangeCount() {
    this.setState({
      count: this.state.count + 1
    })
  }

  render() {
    const { count } = this.state;
    return (
      <div>
        <p>You clicked {count} times</p>
        <button onClick={() => this.handleChangeCount()}>
          Click me
        </button>
      </div>
    );
  }
}

동일한 동작을 하는 코드지만 뭔가 많이 길다. count++를 하기 위해서 함수를 만들고 bind도 한다. 그리고 state.count를 만들기 위해서 constructor도 만들고 아무 의미없는 super(props)도 넣어줘야 했다. render에서 this를 생략하기 위해서 const로 별도의 변수를 만들기도 했다. Hooks를 사용한 코드는 사용하지 않은 코드에서 count를 컨트롤 하기 위해 만들었던 14줄의 코드를 단 한 줄로 만들었다.

const [count, setCount] = useState(0);

여기서 사용한 useState가 바로 Hooks의 하나다.

useState

useState는 componentstate를 관리하기 위한 hook이다. useState의 코드만 놓고 보면 사실 이전과 크게 다르지 않은 것 같다. 하지만 사용에는 큰 차이가 있다. hooks를 사용할 때는 기본적으로 componentclass가 아닌 function으로 만든다. 그래서 setState를 하기 위한 handler도 필요없고 자연스레 handler를 bind할 필요도 없어진다. javascript의 클로저로 인해 어디서든 바로 setState에 값을 넘겨주면 된다. state값도 마찬가지다.

// before
this.state = { state: initialState };
this.setState({ state: newValue });

// hooks
const [state, setState] = useState(initialState);
setState(newValue);

다른 유용한 hook들도 몇개 더 살펴보자.

useEffect

useEffect는 쉽게 말해 componentDidUpdate라고 보면 된다. 다른 점은 componentDidMount, componentWillUnmount가 하는 일도 한다. 기본구조는 다음과 같다.

useEffect(() => {
  // BODY

  return () => {
    // CLEAN-UP
  };
}, [TARGET]);

React Component의 lifecycle상에서 언제 실행되는지를 보면 쉽게 무슨 일을 하는지 파악할 수 있다.

  • Mounting에서는 render후에 componentDidMount가 실행된다. Hooks에서는 BODY 부분이 실행된다.
  • Unmounting에서는 componentWillUnmount가 실행된다. Hooks에서는 CLEAN-UP 부분이 실행된다.
  • Updating에서는 render후에 componentDidUpdate가 실행된다. Hooks에서는 CLEAN-UP이 실행된 후 BODY가 실행된다. Updating은 이전에도 그렇지만 props, state가 변경되면 수행된다.

여기까지만 보면 useEffect 내부가 굉장히 커지고 복잡해질 것 같다. 하지만 TARGET으로 인해 이 복잡성이 크게 줄어든다. TARGET은 변경을 추적할 대상이다. 아래의 코드에서 첫번째 useEffectcount라는 state값이 변경된 경우에만 동작한다. 두번째 useEffectprops.post.title 값이 변경된 경우에만 동작한다. TARGET을 생략하면 모든 값에 대해서 동작한다.

useEffect(() => { ... }, [count]);
useEffect(() => { ... }, [props.post.title]);

물론 componentDidUpdate를 잘 사용하지않는 나로서는 조금 번거롭게 느껴지기도 한다.

useRef

useRefReact.createRef와 거의 같다. 다른 점은 initialValue를 사용할 수 있다는 점이다. 하지만 내가 사용하는 대부분의 경우 refcontainerchild의 검증을 위해서 사용했다. 그래서 이게 있다고 refform에 사용할 것 같지는 않다.

useReducer

useReducer는 코드만 보면 redux에서 사용하던 reducer와 흡사하다. 하지만 이것은 redux가 아니다. 여러 컴포넌트에 useReducer로 만든 reducer를 같이 사용할 수 있다면 완전히 대체할 수도 있을 것 같다.

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

정리한 것 외에도 여러가지 Hook이 있고 hook을 만들 수도 있다. 어쨌거나 코드 스타일을 OOP에서 Function으로 바꿔야 해서 좀 더 javascript다운 코드를 만들 수 있을 것 같고 무엇보다 간결한 코드를 만들 수 있을 것 같다. 테스트도 더 쉬워진 것 같다. 얼른 해보고 싶다.

+ 추가: 몇개 변경해봤는데 너무 좋다. redux를 어떻게 써야하는 지를 몰라서 일단 중단했는데 알아보고나서 더 해야겠다. 

Reference

반응형