리액트로 프로젝트를 생성하여 개발하는 과정 중 리액트의 필수 문법 및 기초적인 부분, 심화 과정 등을 정리한 문서입니다. 정리한 부분에는 제가 이해하는 관점에서만 정리를 하였기 때문에 초점은 보여주는 형식이 아닌 제가 필요할 때 사용하려는 목적이 담겨져 있습니다. 이번 예제에서는 전역 관리를 하기 위한 Context API 를 사용할 예정입니다.
현재 정리부터는 소스코드를 제공할 예정입니다. 제공하는 소스코드를 이용하여 따라하실 수 있고 개념을 확실히 잡을 꺼 같아서 올리게 되었습니다. 소스코드 자료는 아래 링크를 참고해주세요.
https://github.com/libtv/React_Note/tree/master/15.%20useReducer_Exam
그러면 Context API 에 대해 배워보도록 합시다.
# Contents
- Context API 사용하는 이유
- Context API 사용 방법
# Context API 사용하는 이유
우리가 현재 만들고 있는 프로젝트를 보면, App 컴포넌트에서 userList를 생성합니다. 생성된 userList의 파라미터에는 users와 onToggle, onDelete 를 가져오게 되고, 가져온 파라미터의 onToggle과 onDelete 함수는 userList에서 사용하지 않고 그대로 넘겨서 userInfo에서 사용하게 됩니다.
아래 내용을 통해 UserInfo 에서만 사용하는 것을 보실 수 있습니다. UserList.js
import react, { useEffect } from "react";
function UserList({ users, onToggle, onDelete }) {
return (
<div>
{users.map((user) => {
return <UserInfo user={user} onToggle={onToggle} onDelete={onDelete} />;
})}
</div>
);
}
function UserInfo({ user, onToggle, onDelete }) {
useEffect(() => {
console.log("user 값이 설정됨");
console.log(user);
return () => {
console.log("user 가 바뀌기 전..");
console.log(user);
};
}, [user]);
return (
<div>
<h2>
<b
style={{
backgroundColor: user.active ? "tomato" : "white",
}}
onClick={() => {
return onToggle(user.id);
}}
>
{user.id}
</b>{" "}
{user.name} {user.email}{" "}
<button
onClick={() => {
return onDelete(user.id);
}}
>
삭제
</button>
</h2>
</div>
);
}
export default react.memo(UserList);
지금과 같이 특정 함수를 특정 컴포넌트를 거쳐서 원하는 컴포넌트에게 전달하는 작업은 리액트로 개발을 하다보면 자주 발생 할 수 있는 작업인데요. 3~4개 이상의 컴포넌트를 거쳐서 전달을 해야 하는 일이 발생하게 된다면 이는 매우 번거로울 것 입니다.
리액트의 Context API 를 사용하면, 프로젝트 안에서 전역적으로 사용 할 수 있는 값을 관리 할 수 있습니다. 여기서 제가 "상태" 가 아닌 "값" 이라고 언급을 했는데요, 이 값은 꼭 상태를 가르키지 않아도 됩니다. 이 값은 함수일수도 있고, 어떤 외부 라이브러리 인스턴스일수도 있고 심지어 DOM 일 수도 있습니다.
우선, Context API 를 사용해여 새로운 Context 를 만드는 방법을 알아보겠습니다.
Context 를 만들 땐 다음과 같이 React.createContext() 라는 함수를 사용합니다.
React.createContext() 의 사용 방법은 아래와 같습니다.
const UserDispatch = React.createContext(null);
createContext 의 파라미터에는 Context 의 기본값을 설정할 수 있습니다. 여기서 설정하는 값은 Context 를 쓸 때 값을 따로 지정하지 않을 경우 사용되는 기본 값 입니다.
Context 를 만들면, Context 안에 Provider 라는 컴포넌트가 들어있는데 이 컴포넌트를 통하여 Context 의 값을 정할 수 있습니다. 이 컴포넌트를 사용할 때, value 라는 값을 설정해주면 됩니다.
Context 의 사용 방법은 아래와 같습니다.
<UserDispatch.Provider value={dispatch}>...</UserDispatch.Provider>
그럼 직접 코드를 통해 구현해보도록 하겠습니다.
먼저 App.js에서 Context를 정의하고 내보내는 코드를 작성하겠습니다.
코드는 아래와 같습니다. App.js
import "./App.css";
import react, { useCallback, useReducer, useRef } from "react";
import UserList from "./UserList.js";
import CreateUser from "./CreateUser";
import useInput from "./hooks/useInput";
function App() {
const nextIndex = useRef(4);
const UserDispatch = react.createContext(null);
function reducer(state, action) {
switch (action.type) {
case "CREATE_USER":
return {
users: state.users.concat(action.user),
};
case "TOGGLE_USER":
return {
users: state.users.map((user) => {
return user.id === action.userId ? { ...user, active: true } : user;
}),
};
case "DELETE_USER":
return {
users: state.users.filter((user) => {
return user.id !== action.userId;
}),
};
default:
break;
}
return state;
}
const initForm = { name: "", email: "" };
const [form, onChange, reset] = useInput(initForm);
const initialState = {
users: [
{ id: 0, name: "John", email: "john@gmail.com", active: true },
{ id: 1, name: "Smith", email: "smith@gmail.com", active: false },
{ id: 2, name: "Park", email: "park@gmail.com", active: false },
{ id: 3, name: "Kim", email: "kim@gmail.com", active: false },
],
};
const [state, dispatch] = useReducer(reducer, initialState);
const { users } = state;
const { name, email } = form;
const onClick = useCallback(() => {
dispatch({
type: "CREATE_USER",
user: {
id: nextIndex.current++,
name,
email,
active: false,
},
});
reset();
}, [users, name, email]);
const onToggle = useCallback(
(userId) => {
dispatch({
type: "TOGGLE_USER",
userId,
form,
});
},
[users]
);
const onDelete = useCallback(
(userId) => {
dispatch({
type: "DELETE_USER",
userId,
form,
});
},
[users]
);
return (
<div>
<UserDispatch.Provider value={dispatch}>
<CreateUser name={name} email={email} onChange={onChange} onClick={onClick} />
<UserList users={users} onToggle={onToggle} onDelete={onDelete} />
</UserDispatch.Provider>
<div>활성화된 사용자 수 : 0</div>
</div>
);
}
export default App;
Context는 export로 정의한 후, CreateUser와 UserList 안에 Context의 값이 공개되도록 안에 태그에다가 설정해놓았습니다.
export로 정의하였기 때문에 Context 태그 내에 있는 컴포넌트 들은 import 로 값을 가져올 수 있습니다.
App 에서 onToggle 과 onRemove 를 지우시고, UserList 에게 props를 전달하는것도 지우세요.
해당 코드는 아래와 같습니다. App.js
import "./App.css";
import react, { useCallback, useReducer, useRef } from "react";
import UserList from "./UserList.js";
import CreateUser from "./CreateUser";
import useInput from "./hooks/useInput";
export const UserDispatch = react.createContext(null);
function App() {
const nextIndex = useRef(4);
function reducer(state, action) {
switch (action.type) {
case "CREATE_USER":
return {
users: state.users.concat(action.user),
};
case "TOGGLE_USER":
return {
users: state.users.map((user) => {
return user.id === action.userId ? { ...user, active: true } : user;
}),
};
case "DELETE_USER":
return {
users: state.users.filter((user) => {
return user.id !== action.userId;
}),
};
default:
break;
}
return state;
}
const initForm = { name: "", email: "" };
const [form, onChange, reset] = useInput(initForm);
const initialState = {
users: [
{ id: 0, name: "John", email: "john@gmail.com", active: true },
{ id: 1, name: "Smith", email: "smith@gmail.com", active: false },
{ id: 2, name: "Park", email: "park@gmail.com", active: false },
{ id: 3, name: "Kim", email: "kim@gmail.com", active: false },
],
};
const [state, dispatch] = useReducer(reducer, initialState);
const { users } = state;
const { name, email } = form;
const onClick = useCallback(() => {
dispatch({
type: "CREATE_USER",
user: {
id: nextIndex.current++,
name,
email,
active: false,
},
});
reset();
}, [users, name, email]);
return (
<div>
<UserDispatch.Provider value={dispatch}>
<CreateUser name={name} email={email} onChange={onChange} onClick={onClick} />
<UserList users={users} />
</UserDispatch.Provider>
<div>활성화된 사용자 수 : 0</div>
</div>
);
}
export default App;
지웠던 onToggle 과 onRemove 메소드는 직접 UserList에서 구현할 것입니다. 여기서 해당 값을 불러오는 함수는 아래와 같습니다. 먼저 useContext 와 export 한 함수를 불러오고 아래와 같은 구문을 사용하게 되면 저장된 값을 불러올 수 있게 됩니다.
import react, { useContext } from "react";
import { UserDispatch } from "./App.js";
const dispatch = useContext(UserDispatch);
이를 이용하여 UserList.js를 수정하도록 하겠습니다.
해당 코드는 아래와 같습니다. UserList.js
import react, { useEffect, useContext } from "react";
import { UserDispatch } from "./App.js";
function UserList({ users }) {
return (
<div>
{users.map((user) => {
return <UserInfo user={user} />;
})}
</div>
);
}
function UserInfo({ user }) {
const dispatch = useContext(UserDispatch);
useEffect(() => {
console.log("user 값이 설정됨");
console.log(user);
return () => {
console.log("user 가 바뀌기 전..");
console.log(user);
};
}, [user]);
return (
<div>
<h2>
<b
style={{
backgroundColor: user.active ? "tomato" : "white",
}}
onClick={() => {
return dispatch({
type: "TOGGLE_USER",
userId: user.id,
});
}}
>
{user.id}
</b>{" "}
{user.name} {user.email}{" "}
<button
onClick={() => {
return dispatch({
type: "DELETE_USER",
userId: user.id,
});
}}
>
삭제
</button>
</h2>
</div>
);
}
export default react.memo(UserList);
자, 작업이 끝났습니다. 이렇게 Context API 를 사용해서 dispatch 를 어디서든지 조회해서 사용해줄 수 있게 해주면 코드의 구조가 훨씬 깔끔해질 수 있습니다.
'오픈소스 > 노드' 카테고리의 다른 글
[Node] React 정리(18) - styled-components (0) | 2021.10.19 |
---|---|
[Node] Immer 라이브러리를 이용하여 더 쉬운 불변성 관리 (0) | 2021.10.19 |
[Node] React 정리(16) - 커스텀 Hooks 만들기 (0) | 2021.10.19 |
[Node] React 정리(15) - useReducer를 사용한 코드 예제 (0) | 2021.10.19 |
[Node] React 정리(14) - useReducer를 사용하여 상태 업데이트 분리 (0) | 2021.10.19 |