프로젝트 안에서 전역적으로 사용 할 수 있는 값을 관리할 수 있다.
const UserDispatch = React.createContext();
Context 를 만들 땐 다음과 같이 React.createContext()
를 사용한다. 파라미터로는 Context 의 기본 값.
Context 가 생성되면 Context 내부에 Provider 라는 컴포넌트가 들어있는데, 이 컴포넌트를 통하여 Context 의 값을 정할 수 있다. 이 컴포넌트를 사용할 때, value
라는 값을 설정해주면 됨.
<UserDispatch.Provider value={dispatch}>...</UserDispatch.Provider>
이렇게 설정해주면 Provider 에 의해 감싸진 컴포넌트 중 어디에서든지 Context 의 값을 바로 조회하여 사용 가능하다.
앞서 UserList 컴포넌트는 onToggle
과 onRemove
를 전달하기 위한 중간다리 역할만 함. UserList 에서 해당 함수를 직접 사용하지도 않으므로 컴포넌트를 여러개 전달하는 과정이 번거로울 때 이러한 Context API 를 이용.
import './App.css';
import React, {useRef, useState, useMemo, useCallback, useReducer }from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
import useInputs from './hooks/useInputs';
import use_Inputs from './hooks/useNewInputs';
function counterActiveUsers(users) {
console.log('counting active users...');
return users.filter(user => user.active).length;
}
function reducer(state, action) {
switch (action.type) {
case 'CREATE_USER':
return {
inputs: initialState.inputs,
users: state.users.concat(action.user)
}
case 'REMOVE_USER':
return {
...state,
users : state.users.filter(user => user.id != action.userId)
}
case 'TOGGLE_USER':
return {
...state,
users : state.users.map(user => user.id === action.userId? {...user, active : ! user.active } : user)
}
default:
return state;
}
}
const initialState = {
inputs : {
username: '',
email: ''
},
users : [
{
id: 1,
username: 'velopert',
email: 'public.velopert@gmail.com',
active : true,
},
{
id: 2,
username: 'tester',
email: 'tester@gmail.com',
active : false,
},
{
id: 3,
username: 'liza',
email: 'liz@gmail.com',
active : false,
},
]
}
export const UserDispatch = React.createContext(null);
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
const nextId = useRef(4);
const { users } =state;
const [{username, email}, onChange, reset] = use_Inputs({
username : '',
email: ''
});
...
return (
<UserDispatch.Provider value ={dispatch}>
<CreateUser username={username} email={email} onChange={onChange} onCreate={onCreate}/>
< UserList users = {users} onRemove={onRemove} onToggle={onToggle}/>
<div>number of Active users : {count}</div>
</UserDispatch.Provider>
);
}
export default App;
이렇게 되면,
export const UserDispatch = React.createContext(null);
로 내보내준것을
import { UserDispatch } from './App';
로 불러올 수 있다.
import React, {useContext} from 'react'
import { UserDispatch } from './App';
const User = React.memo(function User({ user }) {
const dispatch = useContext(UserDispatch);
return (
<div>
<b
style={{
cursor: 'pointer',
color: user.active? 'green' : 'black'
}}
onClick={()=> { dispatch({ type : 'TOGGLE_USER', userId: user.id})}}
>
{user.username}
</b>
<span>({user.email})</span>
<button onClick={() => { dispatch({ type : 'REMOVE_USER', userId : user.id})}}>삭제</button>
</div>
)
});
CreateUser 컴포넌트를 props을 전달하지 않고 구현하기
import React, { useContext, useRef } from 'react'
import { UserDispatch } from './App';
import useInputs from './hooks/useNewInputs'
function CreateUser() {
const dispatch = useContext(UserDispatch);
const [{username, email}, onChange, reset] = useInputs({ username : '', email : ''})
const nextId = useRef(4)
return (
<div>
<input
name="username"
placeholder='계정명'
onChange={onChange}
value={username}
/>
<input
name="email"
placeholder='이메일'
onChange={onChange}
value={email}
/>
<button onClick={() => { dispatch({
type : 'CREATE_USER',
user : {
id : nextId.current ++,
username,
email
}
})
reset();
}}>등록</button>
</div>
);
}
export default React.memo(CreateUser);
리액트에서는 배열이나 객체를 업데이트 할 때 직접 수정하는 대신 새로운 객체를 만들어준다.
npm install immer
import produce from 'immer
produce
함수를 사용할 때에는 첫번째 파라미터에는 수정하고 싶은 상태 , 두번째 파라미터에는 어떻게 업데이트 할 지를 정의하는 함수를 넣는다.
produce 가 반환하는 것은 함수라는 것을 기억하자
import produce from 'immer';
const state = {
number: 1,
dontChangeMe: 2
};
const nextState = produce(state, draft => {
draft.number +=1;
})
이전의 reducer 를
function reducer(state, action) {
switch (action.type) {
case 'CREATE_USER':
return produce(state, draft => {
draft.users.push(action.user);
})
case 'REMOVE_USER':
return produce(state, draft => {
const index = draft.users.findIndex(user => user.id === action.id);
draft.users.splice(index, 1);
})
case 'TOGGLE_USER':
return produce(state, draft => {
const user = draft.users.find(user => user.id === action.id);
user.active = !user.active;
});
default:
return state;
}
}
와 같이 바꿀 수도 있다.
객체의 깊은 곳에 원하는 값이 위치할 때 효과적
const [todo, setTodo] = useState({
text: 'Hello',
done: false
});
const onClick = useCallback(() => {
setTodo(todo => ({
...todo,
done: !todo.done
}));
}, []);
위처럼 함수형 업데이트를 할 때, Immer 를 사용하면 함수가 더 간결해 질 수도 있다.
import produce from 'immer';
import { useCallback } from 'react';
const [todo, setTodo] = useState({
text: 'Hello',
done: false,
});
const onClick = useCallback(() => {
setTodo(
produce(draft => {
draft.done = !draft.done;
})
)
}, [])
단, 일반 js 코드보다 조금 더 느리고, 구형 브라우저 및 react-native 환경에서는 지원되지 않는다.
함수형
import React from 'react';
function Hello({ color, name, isSpecial }) {
return (
<div style={{ color }}>
{isSpecial && <b>*</b>}
안녕하세요 {name}
</div>
);
}
Hello.defaultProps = {
name: '이름없음'
};
export default Hello;
import React, { Component } from 'react'
class Hello extends Component {
render() {
const { color, name, isSpecial } = this.props;
return (
<div style{{ color }}>
{isSpecial && <b>*</b>}
안녕하세요 {name}
</div>
)
}
}
Hello.defaultProps = {
name : '이름없음'
};
export default Hello
클래스형 컴포넌트에는 render()
메서드가 꼭 있어야 한다. props
을 조회할 때에는 this.props
를 이용하면 된다.
defaultProps
는 함수형과 똑같이 해도되고, 클래스 내부에서 static
키워드와 함께 선언할 수도 있음
import React, { Component } from 'react'
class Hello extends Component {
static defaultProps = {
name : '이름없음'
};
render() {
const { color, name, isSpecial } = this.props;
return (
<div style{{ color }}>
{isSpecial && <b>*</b>}
안녕하세요 {name}
</div>
)
}
}
export default Hello
귀찮음 나중에 할래
가장 일반적인 경우는 props
를 건네주지 않아서!
function User({ user, onToggle }) {
if (!user) {
return null;
}
...
}
Users.defaultProps = {
onToggle: () => {
console.warn('onToggle is missing!');
}
};
이런식으로 props
가 없을 때의 default 값을 설정해주자
생명주기를 이용하여 메서드를 이용하여 우리가 예외처리하지 못한 에러가 발생했을 때 사용자에게 알려주기
import React, { Component } from '
react';
class ErrorBoundary extends Component {
state = {
error: false
};
componentDidCatch(error, info) {
console.log('에러가 발생했습니다.');
console.log({
error,
info
});
this.setState({
error: true
});
}
render() {
if (this.state.error) {
return <h1>에러 발생!</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
import React from 'react';
import User from './User';
import ErrorBoundary from './ErrorBoundary';
function App() {
const user = {
id: 1,
username: 'velopert'
};
return (
<ErrorBoundary>
<User />
</ErrorBoundary>
);
}
export default App;
정보를 흡수해가겠습니다