본문 바로가기

오픈소스/노드

[Node] React 정리(17) - Context API 를 사용한 전역 값 관리

 

리액트로 프로젝트를 생성하여 개발하는 과정 중 리액트의 필수 문법 및 기초적인 부분, 심화 과정 등을 정리한 문서입니다. 정리한 부분에는 제가 이해하는 관점에서만 정리를 하였기 때문에 초점은 보여주는 형식이 아닌 제가 필요할 때 사용하려는 목적이 담겨져 있습니다. 이번 예제에서는 전역 관리를 하기 위한 Context API 를 사용할 예정입니다.

 

 현재 정리부터는 소스코드를 제공할 예정입니다. 제공하는 소스코드를 이용하여 따라하실 수 있고 개념을 확실히 잡을 꺼 같아서 올리게 되었습니다. 소스코드 자료는 아래 링크를 참고해주세요.

https://github.com/libtv/React_Note/tree/master/15.%20useReducer_Exam

 

GitHub - libtv/React_Note: This is a notebook organized by React.

This is a notebook organized by React. Contribute to libtv/React_Note development by creating an account on GitHub.

github.com

 

러면 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 를 어디서든지 조회해서 사용해줄 수 있게 해주면 코드의 구조가 훨씬 깔끔해질 수 있습니다.