[React] Hooks로 끝내는 리액트 상태관리

이찬형·2020년 3월 29일
4
post-thumbnail

React Hooks

리액트 v16.8 에 도입된 Hooks는 함수형 컴포넌트에서 상태관리가 가능하게끔 만들어줍니다.

지금까지 프로젝트를 만들면서 특정 상태를 컴포넌트에게 전달하기 위해선
Container-Presenter 패턴의 구조를 만들고 로직을 Container에서 처리한 후 Presenter에게 props로 넘겨줬었죠?

때문에 Container는 클래스형 컴포넌트로, Presenter는 함수형 컴포넌트로 작성하는 것이 일반적이었습니다.

물론 좋은 방법이에요. state가 컨테이너에만 존재하기 때문에 구조가 복잡하지 않다면 이 쪽이 더 쉽게 구현할 수 있습니다.

하지만 함수형 컴포넌트인 Presenter에서 따로 가변적인 값을 가지고 싶다면 어떠헥 해야 할까요?

또 Presenter 안에 Header 컴포넌트, Footer 컴포넌트 등 다른 친구들이 들어있고 여기에서 state를 사용하려면 어떻게 해야 할까요?

Container에서 state를 정의하고, props로 값을 넘기고, Presenter에서 다시 각각의 컴포넌트에 props로 넘겨줘야해요.

자식 컴포넌트들이 하나 둘 많아진다면 부모 컨테이너들은 자신들이 쓰지 않을 props도 컨테이너로부터 받아서 넘겨줘야 된다는 문제가 생깁니다.

이것들을 해결하기 위해 React Hooks가 등장해요.

얘는 함수형 컴포넌트에서 state를 가질 수 있도록 도와줍니다.
뿐만 아니라, 따로 저장소를 만들어서 필요한 컴포넌트가 언제든 state에 접근할 수 있도록 만들어줘요.

지금까지 이야기한 것들을 코드로 보며 사용법을 익혀봅시다!!

useState

useState는 함수형 컴포넌트에서 state를 쓸 수 있도록 해줍니다.
이 함수는 배열을 리턴해요.

const hooks = useState();
console.log(hooks);

콘솔로그를 찍어보면 아래와 같이 나옵니다.

배열 안엔 정의되지 않은 값 하나와 함수 하나가 들어있네요.
첫 번째 값이 관리할 state, 두 번째 값이 state를 다룰 함수에요!!

이 친구를 ES6의 문법을 써서 멋있게 바꿔줍시다.

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

이렇게 하면 count라는 state를 가지게 되고, useState()에 인자를 주면서 그 값으로 초기화가 된 거예요.

setCount는 이 state를 관리할 수 있는 함수입니다.
이 함수에 전달되는 인자가 곧 state가 되는 거죠.

+/- 버튼에 의해 작동하는 카운터를 만들어봅시다.

import React, { useState } from "react";

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

  const handlePlus = () => setCount(count + 1);
  const handleMinus = () => setCount(count - 1);

  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={handlePlus}>+</button>
      <button onClick={handleMinus}>-</button>
    </div>
  );
};

export default Count;

정리하겠습니다.

  • useState() 함수는 배열을 반환, 인자가 곧 state의 초기값
  • const [ count, setCount ] = useState(0) 형태로 사용
  • 두 번째 요소인 함수는 첫 번째 요소인 state의 값을 변화시킴
    - 이 함수가 바꾼 state값은 저장됨!!

useEffect

useState로 함수형 컴포넌트에서 state 관리가 가능하게 되었습니다.

그렇다면 state가 업데이트 됐을 때, 컴포넌트가 죽을 때와 같은 라이프사이클 컨트롤이 필요하겠죠??

class형 컴포넌트에선 componentDidMount, ComponentDidUpdate 등을 사용해 로직을 넣어줬어요.

이와 똑같은 역할을 해주는 것이 useEffect() 입니다.

사용해보기 전에 먼저 Count 컴포넌트에 input 태그를 줄게요.

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

const Count = () => {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  const handlePlus = () => setCount(count + 1);
  const handleMinus = () => setCount(count - 1);

  const handleChange = event => {
    const text = event.target.value;
    setText(text);
  };

  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={handlePlus}>+</button>
      <button onClick={handleMinus}>-</button>
      <br />
      <input tpye="text" onChange={handleChange} />
      <div>{text}</div>
    </div>
  );
};

export default Count;

우리는 두 개의 state를 관리하고 있습니다. 하나는 count, 또 하나는 text예요.

우선 컴포넌트가 마운트됐을 때 로그를 찍어보겠습니다.

useEffect(() => {
  console.log("Mount!");
}, []);

두 번째 파라미터로 빈 배열을 넘겼습니다. 만약 저게 없다면 마운트, 업데이트 됐을 때 모두 로그가 찍혀요.

그렇다면 특정 값만 업데이트됐을 때 로그를 찍으려면 어떻게 할까요??

useEffect(() => {
  console.log(text);
}, [text]);

배열 안에 상태를 관찰할 state를 넣어주면 됩니다.

추가적으로 컴포넌트가 사라질 때 실행되는 내용은 return으로 넘겨줍니다!!

useContext

각각의 컴포넌트에서 상태관리 하는 법을 알았으니, 이제 한 곳에 상태를 저장하는 법을 배워봅시다.

이 Context의 자식인 모든 컴포넌트들은 상태 저장소에 접근할 수 있어요.

간단한 템플릿을 만들게요.

one.js

import React from "react";
import Two from "./Two";

const One = () => {
  return (
    <>
      <Two />
    </>
  );
};

export default One;

two.js

import React, { useContext } from "react";
import Three from "./Three";

const Two = () => {
  return (
    <>
      <Three />
      <h1>Screen Two</h1>
    </>
  );
};

export default Two;

three.js

import React, { useContext } from "react";

const Three = () => {
  return <header>Hello, user!</header>;
};

export default Three;

three.js에서 one.js에 있는 state에 접근하려면 two.js에서 props를 넘겨받아야 합니다.

이 과정을 없애고, 저장소에서 꺼내오는 방법을 구현해볼게요.

먼저 저장소를 만들어봅시다.

const UserContext = React.createContext();

UserContext 안에 관리할 값이 담기게 됩니다.

이 저장소에 연결하기 위한 조건이 있는데, Provider의 하위 태그로 존재해야만 접근이 가능하다는 거예요.

const UserContextProvider = ({children}) => (
  <UserContext.Provider value={{ name: "lch", login: false }}>
    {children}
  </UserContext.Provider>
)

Provider 컴포넌트에 value를 줬습니다. 얘가 곧 UserContext에 들어가는 state가 돼요.

부모 태그가 될 수 있도록 감싸줍시다.

one.js

import React from "react";
import Two from "./Two";
import UserContextProvider from "./context";

const One = () => {
  return (
    <>
      <UserContextProvider>
        <Two />
      </UserContextProvider>
    </>
  );
};

export default One;

children을 주었기 때문에 모든 하위 컴포넌트들의 부모가 되는겁니다.

따라서 two.js, three.js 에서 UserContext에 접근이 가능해요.

three.js

import React, { useContext } from "react";
import { UserContext } from "./context";

const Three = () => {
  const user = useContext(UserContext);
  return <header>Hello, {user.name}!</header>;
};

export default Three;

context.js 에서 Provider 컴포넌트에 value로 객체를 넘겼어요.
따라서 UserContext state엔 그 객체가 그대로 들어갑니다.

value엔 다양한 값을 넣을 수 있어요.

context.js

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

export const UserContext = React.createContext();

const UserContextProvider = ({ children }) => {
  const [user, setUser] = useState({
    name: "Who are you?",
    login: false
  });

  const loginUser = () => setUser({ name: "lch", login: true });
  return (
    <UserContext.Provider value={{ ...user, loginUser }}>
      {children}
    </UserContext.Provider>
  );
};

export default UserContextProvider;

이번엔 useState로 상태를 하나 만들었어요.
loginUser() 함수는 setUser를 호출해 상태를 업데이트합니다.

이 상태값과 함수를 value에 객체로 넣었어요.
이제 다른 컴포넌트에서 상태를 바꾸는 함수까지 접근이 가능하게 된 것입니다.

마무리

useState와 useEffect는 알고 있어서 쓰기 편했는데
useContext는 오늘 처음 배우고 정리하며 쓴 거라 정신이 없네요 ㅠㅠ

조금 더 연습하고 다음 프로젝트를 진행할 땐 useContext로 상태관리를 해보려고 합니다.

감사합니다 :D

profile
WEB / Security

0개의 댓글