리액트 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는 함수형 컴포넌트에서 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;
정리하겠습니다.
const [ count, setCount ] = useState(0)
형태로 사용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으로 넘겨줍니다!!
각각의 컴포넌트에서 상태관리 하는 법을 알았으니, 이제 한 곳에 상태를 저장하는 법을 배워봅시다.
이 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