TIL - React Hooks

김수지·2020년 1월 27일
2

TILs

목록 보기
27/39

Today What I Learned

Javascript를 배우고 있습니다. 매일 배운 것을 이해한만큼 정리해봅니다.


지난 2주 프로젝트에서 React만을 이용해서 채팅 서비스를 구현했다. 처음에는 상태 관리가 그렇게 많이 필요할까 했는데 메인 채팅창을 구현해보고 나니 앱 전체에 20개 가까이 되는 상태가 존재했고, 형제 컴포넌트들에 영향을 주는 경우 state 끌어올리기를 해서 app.js와 mainPage.js가 비정상적으로 state 대두가 되어 있었다. 이벤트 핸들러도 state x n배였기 때문에 나중에는 어떤 핸들러 함수가 어느 컴포넌트에 있는지 기억해내는 것도 아주 골치가 아팠다.

오늘 따로 hooks를 살펴봤고, 그 내용을 간단히 정리한다.

React Hooks

  1. Hook은 React 16.8버전에서 추가된 개념이다. 노마드코더 강의에 의하면 recompose라는 라이브러리 개발자가 react 팀에 합류한 뒤에 나온 방식이라고 한다.
  2. Hooks를 사용하면 functional Component만으로 React를 이용해 함수형 프로그래밍을 구현할 수 있다.
  3. hooks는 기존에 작성한 React 코드에 이어서 hooks 형식으로 작성할 수 있다. 즉, 이전의 코드를 바꾸지 않고도 새로운 방식을 접목할 수 있다.
  4. class Component가 가지고 있던 lifeCycle 일부도 hooks에서 사용할 수 있다.
  5. hooks는 React 공식 문서에 작성된 것 이외에도 custom Hooks를 만들 수 있다는 점이 장점이다.

Hooks 종류

1. useState

  • class Component에서 state를 설정하고 setState하는 방식을 대체하는 hook이다.
  • 원형
const [state, setState] = useState(initialState);
  • 예문
function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}
  • 함수 설명
  1. useState에서 2가지 종류를 배열로 꺼내올 수 있다.
  2. 하나는 상태를 담을 변수이고, 다른 하나는 변수가 담고 있는 상태값을 변경시킬 수 있는 함수이다.

2. useEffect

  • class Component의 componentDidMount, componentDidUpdate, componentWillUnmount에 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 등의 작업 하는 방식을 대체하는 hook이다.
  • 원형
useEffect(didUpdate);
  • 예문
useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // Clean up the subscription
    subscription.unsubscribe();
  };
}); //},[]} <- componenetDidMount 시에만 가동하길 원하는 경우
  • 함수 설명
  1. 컴포넌트가 dom에 렌더된 후나 상태의 변경이 발생했을 때 useEffect 안에 정의된 내용이 실행된다.
  2. useEffect는 componentDidMount나 componentDidUpdate 2가지 경우 모두에 발생하기 때문에 만약 didMount에만 함수를 적용시키고 싶다면 함수의 2번째 인자로 []을 입력하면 된다.
  3. componentDidMount와 특정 조건이 변경되었을 때에만 componentDidUpdate를 사용하고 싶다면 함수의 2번째 인자로 [특정조건값]을 넣으면 된다.

3. useContext

  • context 객체? React.createContext(디폴트값)로 생성된다. context를 사용하면 모든 하위 컴포넌트를 일일이 통하지 않고도 원하는 값을 컴포넌트 트리 깊숙한 곳에서 사용할 수 있다.
  • context의 값은 이 Hook을 호출하는 컴포넌트에 가장 가까이에 있는 <MyContext.Provider>의 value prop에 의해 결정된다.
  • 원형
const value = useContext(MyContext);
  • 예문
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}
  • 함수 설명
  1. 먼저 React.createContext(디폴트값)으로 context 객체를 만든다.
  2. 해당 context를 사용하려고 하는 컴포넌트를 <context객체명.Provider value={값}></ context객체명.Provider>로 감싼다.
  3. 이렇게 Provider로 감긴 컴포넌트에 물려있는 하위 컴포넌트에서는 props 명시가 없더라도 이 context를 내려 받는다.
  4. 따라서 context 객체 값이 변경될 때마다 Provider에 감싼 컴포넌트와 그 하위(nested) 컴포넌트들은 새롭게 렌더가 된다.
  5. 마지막으로 context에 담은 value를 사용하기 원하는 컴포넌트에서 useContext(context객체명)를 변수에 담고, 사용하기 원하는 태그에서 value를 사용한다.

4. useReducer

  • redux까지는 아니더라도 hooks 안에서 reducer 개념을 사용할 수 있는 hook이다.
  • 원형
const [state, dispatch] = useReducer(reducer, initialArg, init);
  • 예문
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() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
  1. state는 처음 initialState로 지정된다.
  2. dispatch를 통해서 특정 action이 잡히면, dispatch는 해당 action을 reducer에게 넘긴다.
  3. reducer는 전달 받은 action의 type에 따라 switch/case문에 맞춰 newState를 반환한다.
  4. state는 newState로 변경된다.
function init(initialCount) {
  return {count: initialCount};
}

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

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>

        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}
  1. useReducer의 세번째 전달인자로 init이 있다.
  2. init을 통해서 초기화를 약간 지연시키면서 외부에서 initialState를 받아 올 수 있다.

React Hooks를 사용할 때, Redux는???

  • 지난 dev log에서 나는 redux를 더 익히거나 hooks를 새롭게 배우는 것으로 프로젝트 이후를 정한 바 있다. hooks는 '클래스형 컴포넌트 없이 리액트를 만들 수 있다' 정도만 알았던 때라 hooks를 사용하게 되면 redux와 같은 상태 관리 라이브러리도 필요 없어지는 것인지 궁금했었다.
  • 그런데 React 공식문서에서 hooks를 살펴보니 하위 컴포넌트로 props를 내려주는 부분에는 크게 다른 것이 없는 듯 했다. 물론 context 등을 사용하면 되지만 이건 hooks가 아닐 때도 가능했던 것 같다. 결국 redux처럼 storage를 만들고 컴포넌트와 별개로 전역으로 상태 관리를 할 수 있는 라이브러리와의 연관성을 못 찾아냈다.
  • 모르는 것이 있으면 구박사님(Dr.goo)한테 물어보라고 했다. 누군가 나와 같은 질문을 많이 던져서 그런지 구글링을 해보니 아주 좋은 번역 글이 있었다.
  • 번역 React Hooks가 Redux를 대체할 수 있냐고 물어보지 마세요
  • 결론은 애초에 내 프로젝트가 복잡한 상태관리와 프롭스 관계로 구성되어 있다면 react이던 react hooks이던 상태관리 라이브러리는 별개로 필요하다는 것이었다. 예문을 보니 react-redux에서도 hooks를 쓰는 것 같았다. (아.. 결국 둘 다 알아야 하는 것이었구나...ㅋㅋ)
  • 그래.. 그럼 다음에는 Redux를 공부하는 것으로....ㅎㅎ 배움에는 꼼수 불가능.
profile
선한 변화와 사회적 가치를 만들고 싶은 체인지 메이커+개발자입니다.

2개의 댓글

comment-user-thumbnail
2020년 1월 28일

좋은 글 감사합니다! 정리가 잘 되서 내용이 머리에 쏙쏙 들어오네요. 글을 읽다보니 useReducer를 useRender라고 잘못 쓰신 부분이 두군데 있어서 댓글 남겨봅니다.

1개의 답글