JS Library/React

Context API 전역 값 관리

원2 2022. 5. 26. 13:55
728x90
반응형

이때까지 만들었던 것들은 App 컴포넌트에서 onToggle, onRemove 가 구현되어 있고 이 함수들은 UserList 컴포넌트를 거쳐서 각 User 컴포넌트들에 전달이 되고 있음

그중 UserList 컴포넌트는 onToggle과 onRemove를 전달하기 위해서 다리 역할만 하고 있음

 

특정 함수를 특정 컴포넌트를 거쳐서 원하는 컴포넌트에 전달하는 작업은 리액트로 개발을 하다 보면 자주 발생하는 작업인데 컴포넌트 한 개 정도는 괜찮지만 여러 개가 된다면 번거로운 상황이 발생할 것임

 

그럴 때 Context API와 dispatch를 함께 사용하면 복잡한 구조를 해결할 수 있다

Context API를 사용하면 프로젝트 안에서 전역적으로 사용할 수 있는 값을 관리할 수 있음

여기서 값은 상태 일 수도 함수 일 수도 외부라이브러리 인스턴스 일 수도 DOM 일수도 있다


 

Context API 기본문법

const UserDispatch = React.createContext(null);

React.createContext() 함수를 사용하고 createContext 파라미터에는 Context 기본값을 설정할 수 있음

여기의 기본값은 Context 를 사용할 때 값을 따로 지정하지 않을 경우 사용되는 기본 값


나중에 사용하고 싶을 때 아래처럼 불러와서 사용할 수 있음

import { UserDispatch } from './App';

 


 

Context 를 만들면 Context 안에 Provider 라는 컴포넌트가 들어있는데 이 컴포넌트를 통하여 Context 값을 정할 수 있다.

<UserDispatch.Provider value={dispatch}>...</UserDispatch.Provider>

Provider 에게 감싸진 컴포넌트 중 어디서든지 Context 의 값을 다른곳에서 바로 조회해서 사용 가능


만들어보자

App.js

import React, {useCallback, useMemo, useReducer, useRef} from 'react';
import CreateUser from './components/CreateUser';
import UserList from "./components/UserList";
import useInputs from "./hooks/useInputs";

function countActiveUsers(users) {
    console.log("active 값이 ture 인 유저의 수를 센는중...");
    return users.filter(user => user.active).length;
}

const initialState = {
    users: [
        {
            id: 1,
            username: 'velopert',
            email: 'public.velopert@gmail.com',
            active: true
        },
        {
            id: 2,
            username: 'tester',
            email: 'tester@example.com',
            active: false
        },
        {
            id: 3,
            username: 'liz',
            email: 'liz@example.com',
            active: false
        }
    ]
};

// 불변성을 지켜주기 위해서 spread 연산자를 사용
function reducer(state, action) {
    switch (action.type) {
        case 'CREATE_USER' :
            return {
                inputs: initialState.inputs,
                users: state.users.concat(action.user)
            };
        case 'TOGGLE_USER' :
            return {
                ...state,
                users: state.users.map(user =>
                    user.id === action.id ? {...user, active: !user.active } : user
                )
            };
        case 'REMOVE_USER' :
            return {
                ...state,
                users: state.users.filter(user => user.id !== action.id)
            };
        default:
            return state;

    }
}
// UserDispatch 라는 이름으로 출력
export const UserDispatch = React.createContext(null);

function App() {
    const [state, dispatch] = useReducer(reducer, initialState);
    const { users } = state;
    const [{ username, email }, onChange, reset] = useInputs({
        username: '',
        email: ''
    });
    const nextId = useRef(4);

    const onCreate = useCallback(() => {
        dispatch({
            type: 'CREATE_USER',
            user: {
                id: nextId.current,
                username,
                email
            }
        });
        reset();
        nextId.current += 1;
    }, [username, email, reset]);

    const onToggle = useCallback(id => {
        dispatch({
            type: 'TOGGLE_USER',
            id
        });
    },[]);

    const onRemove = useCallback(id => {
        dispatch({
            type: 'REMOVE_USER',
            id
        });
    },[]);

    const count = useMemo(() => countActiveUsers(users), [users]);
    return (
        // 감싸기
        <UserDispatch.Provider value={dispatch}>
            <CreateUser
                username={username}
                email={email}
                onChange={onChange}
                onCreate={onCreate} />
            <UserList users={users} onToggle={onToggle} onRemove={onRemove} key={users.id}/>
            <div>활성 사용자 수 : {count}</div>
        </UserDispatch.Provider>
    );
}
export default App;

Context.Provider 가 아래 전체를 감싸고 있음ㅇㅇ


onToggle 과 onRemove , UserList 에 props 전달하는 것 삭제

App.js

import React, {useCallback, useMemo, useReducer, useRef} from 'react';
import CreateUser from './components/CreateUser';
import UserList from "./components/UserList";
import useInputs from "./hooks/useInputs";

function countActiveUsers(users) {
    console.log("active 값이 ture 인 유저의 수를 센는중...");
    return users.filter(user => user.active).length;
}

const initialState = {
    users: [
        {
            id: 1,
            username: 'velopert',
            email: 'public.velopert@gmail.com',
            active: true
        },
        {
            id: 2,
            username: 'tester',
            email: 'tester@example.com',
            active: false
        },
        {
            id: 3,
            username: 'liz',
            email: 'liz@example.com',
            active: false
        }
    ]
};

// 불변성을 지켜주기 위해서 spread 연산자를 사용
function reducer(state, action) {
    switch (action.type) {
        case 'CREATE_USER' :
            return {
                inputs: initialState.inputs,
                users: state.users.concat(action.user)
            };
        case 'TOGGLE_USER' :
            return {
                ...state,
                users: state.users.map(user =>
                    user.id === action.id ? {...user, active: !user.active } : user
                )
            };
        case 'REMOVE_USER' :
            return {
                ...state,
                users: state.users.filter(user => user.id !== action.id)
            };
        default:
            return state;

    }
}
// UserDispatch 라는 이름으로 출력
export const UserDispatch = React.createContext(null);

function App() {
    const [state, dispatch] = useReducer(reducer, initialState);
    const { users } = state;
    const [{ username, email }, onChange, reset] = useInputs({
        username: '',
        email: ''
    });
    const nextId = useRef(4);

    const onCreate = useCallback(() => {
        dispatch({
            type: 'CREATE_USER',
            user: {
                id: nextId.current,
                username,
                email
            }
        });
        reset();
        nextId.current += 1;
    }, [username, email, reset]);

    const count = useMemo(() => countActiveUsers(users), [users]);
    return (
        // 감싸기
        <UserDispatch.Provider value={dispatch}>
            <CreateUser
                username={username}
                email={email}
                onChange={onChange}
                onCreate={onCreate} />
            {/* UserList 에 props 를 전달하는 곳임 */}
            <UserList users={users} key={users.id}/>
            <div>활성 사용자 수 : {count}</div>
        </UserDispatch.Provider>
    );
}
export default App;

 

UserList

 

import React, {useEffect} from "react";

const User = React.memo(function User({user}) {

    useEffect(() => {
        console.log(user);
    });

    return (
        <div>
            <b
                style={{
                    cursor: 'pointer',
                    color: user.active ? 'green' : 'black'
                }}
                onClick={() => {}}
            >
                {user.username}
            </b>
            <span>({user.email})</span>
            <button onClick={() => {}}>삭제</button>
        </div>
    );
});

// 순서상 UserList 에서 on Toggle 을 받아오고, User 에서 호출
function UserList({users}) {
    return (
        <div>
            {users.map(user => (
                <User
                    user={user}
                    Key={user.id}
                />
            ))}
        </div>
    );
}

export default React.memo(UserList);

 

이제 onToggle / onRemove 를 porps 로 전달하지 않고 dispatch 를 사용한 것처럼 CreateUser 컴포넌트에서도 dispatch 를 사용해보자

 


App.js

 

import React, {useCallback, useMemo, useReducer, useRef} from 'react';
import CreateUser from './components/CreateUser';
import UserList from "./components/UserList";
import useInputs from "./hooks/useInputs";

function countActiveUsers(users) {
    console.log("active 값이 ture 인 유저의 수를 센는중...");
    return users.filter(user => user.active).length;
}

const initialState = {
    users: [
        {
            id: 1,
            username: 'velopert',
            email: 'public.velopert@gmail.com',
            active: true
        },
        {
            id: 2,
            username: 'tester',
            email: 'tester@example.com',
            active: false
        },
        {
            id: 3,
            username: 'liz',
            email: 'liz@example.com',
            active: false
        }
    ]
};

// 불변성을 지켜주기 위해서 spread 연산자를 사용
// 여기서 recuder 로 기능 구현
function reducer(state, action) {
    switch (action.type) {
        case 'CREATE_USER' :
            return {
                inputs: initialState.inputs,
                users: state.users.concat(action.user)
            };
        case 'TOGGLE_USER' :
            return {
                ...state,
                users: state.users.map(user =>
                    user.id === action.id ? {...user, active: !user.active } : user
                )
            };
        case 'REMOVE_USER' :
            return {
                ...state,
                users: state.users.filter(user => user.id !== action.id)
            };
        default:
            return state;

    }
}
// UserDispatch 라는 이름으로 출력
export const UserDispatch = React.createContext(null);

function App() {
    const [state, dispatch] = useReducer(reducer, initialState);
    const { users } = state;
    const count = useMemo(() => countActiveUsers(users), [users]);
    return (
        // 감싸기
        <UserDispatch.Provider value={dispatch}>
            <CreateUser />
            {/* UserList 에 props 를 전달하는 곳임 */}
            <UserList users={users} key={users.id}/>
            <div>활성 사용자 수 : {count}</div>
        </UserDispatch.Provider>
    );
}
export default App;

CreateUser.js

 

import React, {useContext, useRef} from "react";
import useInputs from "../hooks/useInputs";
import {UserDispatch} from "../App";

const CreateUser = () => {
    const [{username, email}, onChange, reset] = useInputs({
        username: '',
        email: ''
    });
    const nextId = useRef(4);
    const dispatch = useContext(UserDispatch);
    const onCreate = () => {
        dispatch({
            type: 'CREATE_USER',
            user: {
                id: nextId.current,
                username,
                email
            }
        });
        reset();
        nextId.current += 1;
    };

    return (
        <div>
            <input
                name="username"
                placeholder="계정명"
                onChange={onChange}
                value={username}
            />
            <input
                name="email"
                placeholder="이메일"
                onChange={onChange}
                value={email}
            />
            <button onClick={onCreate}>등록</button>
        </div>
    );
};

export default React.memo(CreateUser);

 

useReducer 를 사용하면 dispatch 를 Context API 를 사용해서 전역적으로 사용할 수 있게 해주면

컴포넌트에게 함수를 전달해줘야 하는 상황에서 코드의 구조가 훨씬 깔끔해질 수 있다.

https://react.vlpt.us/basic/22-context-dispatch.html

728x90
반응형

'JS Library > React' 카테고리의 다른 글

useReducer 요청 상태 관리  (0) 2022.06.09
API 연동하기, axios  (0) 2022.06.09
Immer 라이브러리  (0) 2022.05.26
커스텀 Hooks  (0) 2022.05.25
React.memo  (0) 2022.05.25
useCallback 를 사용하여 함수 재사용  (0) 2022.05.25