React vol.7 (useReducer, useContext)
개요
React JS 를 공부하면서 기본을 정리해두고자 작성
useReducer?
좀 더 복잡한 state의 관리가 필요할 때 사용되어지는 훅
useReducer 구성요소 state, dispatch, action and reducer function
dispatch를 이용해 action을 작동시키고 어떤 action을 취할지는 action의 type or 본인이 원하는대로 reducer function 안에 구별자를 넣어서 구성해 주면 된다. 즉 state의 상태변화는 dispatch -> action -> state변경 순서로 이루어진다.
usage
import { useEffect, useReducer } from "react";
const DUMMY_DATA = [
{ id: "e1", title: "Toile", amount: 94.12, date: new Date(2020, 7, 14) },
{ id: "e2", title: "New TV", amount: 799.49, date: new Date(2022, 2, 12) },
];
const expensesReducer = (state, action) => {
if (action.type === "INIT_EXPENSES") {
return action.val;
}
if (action.type === "ADD_EXPENSE") {
return [...state, action.val];
}
return DUMMY_DATA;
};
function App() {
const [expenses, dispatch] = useReducer(expensesReducer, DUMMY_DATA);
const addExpenseHandler = (data) => {
dispatch({
type: "ADD_EXPENSE",
val: data,
});
};
useEffect(() => {
if (localStorage.getItem("expenses")) {
const temp = JSON.parse(localStorage.getItem("expenses")).map((item) => {
return {
...item,
date: new Date(item.date),
};
});
dispatch({
type: "INIT_EXPENSES",
val: temp,
});
}
}, []);
}
useReducer vs useState
더 복잡한 state의 update가 있는 경우..
구체적으로는 어떤 state가 변경이 되면 그 state가 다른 state에도 영향을 준다면 그 두개의 state를 묶어서 관리할 때 useReducer를 쓰면 좀더 우아하게? 코드를 관리할 수 있는거 같다. 물론 묶어서 관리하는 것도 state로도 충분히 가능하다.
개인적으로는 reducer function 부분은 component 밖에서 관리할 수 있는 점이 꽤 매력적으로 와 닿았다.
Context API
컴포넌트 간에 props로 데이터를 전달할려고 하다보면 앱이 커지면 커질수록 상당히 번거로워 지는 경우가 있다.
그럴 경우 context api는 컴포넌트들에게 전역적으로 데이터를 공유하도록 나온 개념
context/auth-context.js
import React from "react";
import { useState } from "react";
export const AuthContext = React.createContext({
// 초기값과 이 context에 대한 정보를 IDE(VScode)에 제공한다. (타입을 선언한 효과도 난다는말)
isLoggedIn: false,
onLogin: () => {},
onLogout: () => {},
});
export const AuthContextProvider = (props) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const loginHandler = () => {
setIsLoggedIn(true);
};
const logoutHandler = () => {
setIsLoggedIn(false);
};
return (
<AuthContext.Provider
value={{ isLoggedIn, onLogin: loginHandler, onLogout: logoutHandler }}
>
{props.children}
</AuthContext.Provider>
);
};
React.createContext()를 이용해 AuthContext 객체를 생성한다.
이건 컴포넌트를 포함하고 state를 가지고 있는 객체이며 컴포넌트 자체는 아니므로 jsx에서 쓰일 수는 없는데
그 대신에 Provider라는 컴포넌트를 가지고 있어서 마치 부모 컴포넌트 처럼 사용할 수 있다.
AuthContext.Provider의 props로써 value를 제공해주면, 이 제공되어진 친구들을 Provider로 감싸져 있는 모든 컴포넌트에서 쓸 수 있다. 그럼 이 Provider는 어디를 감싸야 할까?
index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { AuthContextProvider } from "./store/auth-context";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<AuthContextProvider>
<App />
</AuthContextProvider>
</BrowserRouter>
);
처음에는 App.js에 넣었지만, App 컴포넌트는 상태를 가지고 자식컴포넌트에 데이터를 뿌려주는 로직도 포함되어 있어서 context에 관련된 것들을 분리하는게 좋다. 그렇게해서 index.js에서 App 컴포넌트의 상위에 Provider컴포넌트를 감싸주었다.
context는 변경이 잦을 때는 되도록 사용하지 않도록 권장되어져 있다.(공식문서)
변경이 잦지만 Global하게 사용하고 싶은 경우에는 Redux를 사용하라고 한다.