React
의 Context API
를 사용하면, 프로젝트 안에서 전역적으로 사용 할 수 있는 값을 관리 할 수 있다. 이 값은 꼭 상태를 가르키지 않아도 된다.
이 값은 함수일수도 있고, 어떤 외부 라이브러리 인스턴스일수도 있고, DOM
일 수도 있다.
( Context API
를 사용해서 프로젝트의 상태를 전역적으로 관리 할 수도 있다.)
App.js
import React, { useRef, useReducer, useMemo, useCallback } from 'react'; import UserList from './UserList'; import CreateUser from './CreateUser'; import useInputs from './hooks/useInputs'; function countActiveUsers(users) { console.log('활성 사용자 수를 세는중...'); return users.filter(user => user.active).length; } const initialState = { users: [ { id:1, username: 'gyomni', email: 'hi1@gmail.com', active:true, }, { id:2, username: 'joy', email: 'hi2@gmail.com', active:false, }, { id:3, username: 'zoe', email: 'hi3@gmail.com', active:false, } ] }; 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 => user.id === action.id ? { ...user, active: !user.active } : user ) }; case 'REMOVE_USER': return { users: state.users.filter(user => user.id !== action.id) }; default: return state; } } function App() { const [{ username, email }, onChange, reset] = useInputs({ username: '', email: '' }); const [state, dispatch] = useReducer(reducer, initialState); const nextId = useRef(4); const { users } = state; 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 ( <> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onToggle={onToggle} onRemove={onRemove} /> <div>활성사용자 수 : {count}</div> </> ); } export default App;
->
App
컴포넌트 내부에서onToggle
,onRemove
가 구현된 다음에 이 두가지 함수가UserList
에게 전달이 된 상태.
UserList.js
import React, { useEffect } from "react"; const User = React.memo(function User({user, onRemove,onToggle}){ const {username, email, id, active} =user; useEffect(()=>{ console.log('user값이 설정됨'); console.log(user); return()=>{ console.log('user 값이 바뀌기전'); console.log(user); } },[user]); return( <div> <b style={{ color: active ?'green':'black', cursor:'pointer', }} onClick={()=>onToggle(id)} {username} </b> <span>({email})</span> <button onClick={()=>onRemove(id)}>삭제</button> </div> ); }) function UserList({users, onRemove,onToggle}){ // users배열을 props로 받아오기 return( <div> { users.map( (user)=>( <User user={user} key ={user.id} onRemove={onRemove} onToggle={onToggle} />) ) } </div> ); } export default React.memo(UserList);
UserList
컴포넌트에서는Props
로onRemove
,onToggle
를 받아와서 이 값을User
에게 주고 있다.UserList
컴포넌트는 일종의 다리 역할만 하고 있음.
(UserList
컴포넌트에서는 직접적으로onRemove
,onToggle
를 사용하는 일은 없는데,User
컴포넌트에게 전달해 줘야 하기 때문에App
컴포넌트에서onRemove
,onToggle
를UserList
에게 설정해 준 것.
그리고UserList
는User
컴포넌트에게 전달해준 것.)
특정 함수를 특정 컴포넌트를 거쳐서 원하는 컴포넌트에게 전달하는 작업은 리액트로 개발을 하다보면 자주 발생 할 수 있는 작업이다.
위와 같은 상황 처럼 컴포넌트 한개 정도를 거쳐서 전달하는 것은 문제가 되지 않는다.
이렇게 Props가 컴포넌트를 거쳐서 거쳐서 전달한다면 매우 번거로울 것이다.
코드로 확인해 보자면,
ContextSample.js
import React,{createContext, useContext} from "react"; function Child({text}){ return <div>안녕하세요? {text}</div> } function Parent({text}){ return <Child text ={text}/> } function GrandParent({text}){ return <Parent text ={text}/> } function ContextSample(){ return <GrandParent text="Good"/> } export default ContextSample;
현재 위의 컴포넌트의 구조를 보면
ContextSample
에서 설정한text="Good"
->GrandParent
의{text}
에서text ={text}
로 넘겨주고 ->Parent
의{text}
에서text ={text}
로 넘겨주고 ->Child
의{text}
에서안녕하세요? {text}
가 보여지게 됨.
- 여기서 원하는 루트는
ContextSample
에서 바로Child
의{text}
로 바로 넘겨주는 것.
=>Context API
를 사용해보자!
Context API
와 dispatch
를 함께 사용하면 해결 할 수 있다! 🤩
💡Context API
사용import React,{createContext, useContext} from "react"; const MyContext =React.createContext('defaltValue'); // context에서 사용할 기본값 넣어줌. function Child(){ const text = useContext(MyContext); // MyContext의 값을 불러와서 사용. return <div>안녕하세요? {text}</div> } function Parent({text}){ return <Child text ={text}/> } function GrandParent({text}){ return <Parent text ={text}/> } function ContextSample(){ return <GrandParent text="Good"/> } export default ContextSample;
useContext
:context
에 있는 값을 읽어와서 사용할 수 있게 해주는 리엑트에 내장된 hook.
(context
를 컴포넌트 내부에서 바로 조회할 수 있는 hook. )React.createContext()
:context
를 만들 때 사용하는 함수.
-> 파라미터 : 기본 값
-> 기본 값은Provider
컴포넌트가 사용되지 않을 때의 기본적인 값.- context의 value를 지정해 주지 않았기 때문에
defaltValue
가 나타가게됨.
MyContext
의 값을 지정해주고 싶다면?
=>ContextSample
에서MyContext
안에 있는Provider
라는 컴포넌트를 사용해서value
값을 설정해주면 된다 !
💡
Provider
사용import React,{createContext, useContext} from "react"; const MyContext =React.createContext('defaltValue'); function Child(){ const text = useContext(MyContext); return <div>안녕하세요? {text}</div> } function Parent(){ return <Child /> } function GrandParent(){ return <Parent/> } function ContextSample(){ return( <MyContext.Provider value="Good"> <GrandParent /> </MyContext.Provider> ) } export default ContextSample;
MyContext
안의Provider
컴포넌트를 사용해서GrandParent
를 감싸준다.MyContext.Provider
를 통해value
를 설정해줬다. =context
의 값이 설정된다.- 사용하지 않는 컴포넌트의
{text}
를 지워준다.Child
컴포넌트에서useContext
사용해서MyContext
에 있는 값을 그대로 불러와서text
값을 사용할 수 있게 됬다.MyContext
와 같은context
를 다른 파일에서도 작성할 수 있다.
-> 다른 파일에서 작성한 다음 내보낸 것을 불러와서 어디서든지 사용할 수 있다는 장점이 있다.
context
값은 유동적으로 변할 수 있다! =>useState
사용해서 변경하기.
💡
useState
사용import React,{createContext, useContext, useState} from "react"; const MyContext =React.createContext('defaltValue'); function Child(){ const text = useContext(MyContext); return <div>안녕하세요? {text}</div> } function Parent(){ return <Child /> } function GrandParent(){ return <Parent/> } function ContextSample(){ const[value, setvalue] = useState(true); return( <MyContext.Provider value={value ? 'Good' : 'Bad'}> <GrandParent /> <button onClick={()=> setvalue(!value)}>Click Me~~~!</button> </MyContext.Provider> ) } export default ContextSample;
- 값이 바뀔 때 마다 단계적(
ContextSample
->GrandParent
->Parent
->Child
)으로 값을 전달하는 것이 아니라,Provider
을 통해서MyContext
의 값을 특정 값으로 설정해주면Child
에서 바로MyContext
참조해서 쓸 수 있다.
context
를 사용하게 된다면 깊은 곳에 있는 컴포넌트에 바로 넣어줄 수 있게 된다!
문제 상황 : 상단의 App.js
를 보면 onToggle
,onRemove
이 두가지 함수를 User
컴포넌트에 주기 위해서 UserList
컴포넌트를 거쳐서 줘야 한다.
-> context
를 통해서 onToggle
,onRemove
를 직접 넣어 줄 수도 있겠지만, 그 대신 dispatch
만 따로 넣어주는 것으로 해결해보자!
App.js
import React, { useRef, useReducer, useMemo, useCallback, createContext } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs';
function countActiveUsers(users) {
console.log('활성 사용자 수를 세는중...');
return users.filter(user => user.active).length;
}
const initialState = {
users: [
{
id:1,
username: 'gyomni',
email: 'hi1@gmail.com',
active:true,
},
{
id:2,
username: 'joy',
email: 'hi2@gmail.com',
active:false,
},
{
id:3,
username: 'zoe',
email: 'hi3@gmail.com',
active:false,
}
]
};
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 =>
user.id === action.id ? { ...user, active: !user.active } : user
)
};
case 'REMOVE_USER':
return {
users: state.users.filter(user => user.id !== action.id)
};
default:
return state;
}
}
export const UserDispatch = React.createContext(null); // 기본값 필요없으므로 null 넣어줌.
function App() {
const [{ username, email }, onChange, reset] = useInputs({
username: '',
email: ''
});
const [state, dispatch] = useReducer(reducer, initialState);
const nextId = useRef(4);
const { users } = state;
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} />
<div>활성사용자 수 : {count}</div>
</UserDispatch.Provider>
);
}
export default App;
📍
createContext
불러오기import React, { useRef, useReducer, useMemo, useCallback, createContext } from 'react';
📍
UserDispatch
안의Provider
컴포넌트 사용하기return ( <UserDispatch.Provider value = {dispatch}> <CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate} /> <UserList users={users} onToggle={onToggle} onRemove={onRemove} /> <div>활성사용자 수 : {count}</div> </UserDispatch.Provider> );
App
컴포넌트에서reducer
통해서 받아온dispatch
를value
로 넣어준 것.const [state, dispatch] = useReducer(reducer, initialState);
UserList
import React, { useContext, useEffect } from "react";
import { UserDispatch } from "./App";
const User = React.memo(function User({user}){
const {username, email, id, active} =user;
const dispatch = useContext(UserDispatch);
useEffect(()=>{
console.log('user값이 설정됨');
console.log(user);
return()=>{
console.log('user 값이 바뀌기전');
console.log(user);
}
},[user]);
return(
<div>
<b
style={{
color: active ?'green':'black',
cursor:'pointer',
}}
onClick={()=>dispatch({
type:'TOGGLE_USER',
id
})}
>
{username}
</b>
<span>({email})</span>
<button onClick={()=>dispatch({
type : 'REMOVE_USER',
id
})}>삭제</button>
</div>
);
})
function UserList({users}){ // users배열을 props로 받아오기
return(
<div>
{
users.map(
(user)=>(
<User
user={user}
key ={user.id}
/>)
)
}
</div>
);
}
export default React.memo(UserList);
📍
useEffect
불러오기import React, { useContext, useEffect } from "react";
📍
dispatch
사용import { UserDispatch } from "./App"; . . . const dispatch = React.useContext(UserDispatch);
- 파라미터에는
App
에서 만든UserDispatch
넣어주기.
App
에서UserDispatch
를export
해줬기 때문에 상단에서 불러올 수 있음.
📍
onToggle
,onRemove
구현return( <div> <b style={{ color: active ?'green':'black', cursor:'pointer', }} onClick={()=>dispatch({ type:'TOGGLE_USER', id })} > {username} </b> <span>({email})</span> <button onClick={()=>dispatch({ type : 'REMOVE_USER', id })}>삭제</button> </div> ); })
App
컴포넌트에서 만든action
을 보면TOGGLE_USER
와REMOVE_USER
가 있었다 !
이것을User
컴포넌트에서 바로 사용.
만약 App
컴포넌트에서 reducer
을 사용하지 않고 useState
를 사용해서 내부에서 모든 것을 작업했더라면 dispatch
가 없기 때문에 UserDispatch
같은 context
를 만들어서 관리하는 것이 조금 어려워 질 수 있다.
물론, App
컴포넌트의 UserDispatch.Provider
에 value
에다가 setState
관련 함수를 넣어주는 방식으로 구현가능하지만, 덜 깔끔한 구조가 된다.
dispatch
를 관리하는 context
를 만들어서 필요한 곳에서 dispatch
를 불러와서 사용하면 굉장히 구조도 깔끔해지고, 코드 작성도 쉬워진다.
학습 : 벨로퍼트와 함께 하는 모던 리엑트