useReducer
useReducer
๋ ์ปดํฌ๋ํธ์ reducer๋ฅผ ์ถ๊ฐํ๋ React Hook์
๋๋ค.
const [state, dispatch] = useReducer(reducer, initialArg, init?)
- ๋ ํผ๋ฐ์ค
- ์ฌ์ฉ๋ฒ
- ํธ๋ฌ๋ธ ์ํ
- dispatch๋ก action์ ํธ์ถํด๋ ์ค๋๋ state ๊ฐ์ด ์ถ๋ ฅ๋ฉ๋๋ค.
- dispatch๋ก action์ ํธ์ถํด๋ ํ๋ฉด์ด ์ ๋ฐ์ดํธ๋์ง ์์ต๋๋ค.
- reducer์ state ์ผ๋ถ๊ฐ dispatch๋ ์ดํ์ undefined๊ฐ ํ ๋น๋ฉ๋๋ค.
- reducer์ ๋ชจ๋ state๊ฐ dispatch๊ฐ ์ด๋ฃจ์ด ์ง ํ undefined๊ฐ ํ ๋น๋ฉ๋๋ค.
- โToo many re-rendersโ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
- reducer์ ์ด๊ธฐํ ํจ์๊ฐ ๋๋ฒ ํธ์ถ๋ฉ๋๋ค.
๋ ํผ๋ฐ์ค
useReducer(reducer, initialArg, init?)
useReducer
๋ฅผ ์ปดํฌ๋ํธ์ ์ต์์์ ํธ์ถํ๊ณ , reducer๋ฅผ ์ด์ฉํด state๋ฅผ ๊ด๋ฆฌํฉ๋๋ค.
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
์๋์์ ๋ ๋ง์ ์์๋ฅผ ํ์ธํ์ธ์.
๋งค๊ฐ๋ณ์
reducer
: state๊ฐ ์ด๋ป๊ฒ ์ ๋ฐ์ดํธ ๋๋์ง ์ง์ ํ๋ ๋ฆฌ๋์ ํจ์์ ๋๋ค. ๋ฆฌ๋์ ํจ์๋ ๋ฐ๋์ ์์ ํจ์์ฌ์ผ ํ๋ฉฐ, state์ action์ ์ธ์๋ก ๋ฐ์์ผ ํ๊ณ , ๋ค์ state๋ฅผ ๋ฐํํด์ผ ํฉ๋๋ค. state์ action์๋ ๋ชจ๋ ๋ฐ์ดํฐ ํ์ ์ด ํ ๋น๋ ์ ์์ต๋๋ค.initialArg
: ์ด๊ธฐ state๊ฐ ๊ณ์ฐ๋๋ ๊ฐ์ ๋๋ค. ๋ชจ๋ ๋ฐ์ดํฐ ํ์ ์ด ํ ๋น๋ ์ ์์ต๋๋ค. ์ด๊ธฐ state๊ฐ ์ด๋ป๊ฒ ๊ณ์ฐ๋๋์ง๋ ๋ค์init
์ธ์์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค.- ์ ํ์ฌํญ
init
: ์ด๊ธฐ state๋ฅผ ๋ฐํํ๋ ์ด๊ธฐํ ํจ์์ ๋๋ค. ์ด ํจ์๊ฐ ์ธ์์ ํ ๋น๋์ง ์์ผ๋ฉด ์ด๊ธฐ state๋initialArg
๋ก ์ค์ ๋ฉ๋๋ค. ํ ๋น๋์๋ค๋ฉด ์ด๊ธฐ state๋init(initialArg)
๋ฅผ ํธ์ถํ ๊ฒฐ๊ณผ๊ฐ ํ ๋น๋ฉ๋๋ค.
๋ฐํ๊ฐ
useReducer
๋ 2๊ฐ์ ์๋ฆฌ๋จผํธ๋ก ๊ตฌ์ฑ๋ ๋ฐฐ์ด์ ๋ฐํํฉ๋๋ค.
- ํ์ฌ state. ์ฒซ๋ฒ์งธ ๋ ๋๋ง์์์ state๋
init(initialArg)
๋๋initialArg
๋ก ์ค์ ๋ฉ๋๋ค (init
์ด ์์ ๊ฒฝ์ฐinitialArg
๋ก ์ค์ ๋ฉ๋๋ค). dispatch
ํจ์.dispatch
๋ state๋ฅผ ์๋ก์ด ๊ฐ์ผ๋ก ์ ๋ฐ์ดํธํ๊ณ ๋ฆฌ๋ ๋๋ง์ ์ผ์ผํต๋๋ค.
์ฃผ์ ์ฌํญ
useReducer
๋ Hook์ด๋ฏ๋ก ์ปดํฌ๋ํธ์ ์ต์์ ๋๋ ์ปค์คํ Hook์์๋ง ํธ์ถํ ์ ์์ต๋๋ค. ๋ฐ๋ณต๋ฌธ์ด๋ ์กฐ๊ฑด๋ฌธ์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ํ ๊ฒฝ์ฐ ์๋ก์ด ์ปดํฌ๋ํธ๋ฅผ ์ถ์ถํ๊ณ ํด๋น ์ปดํฌ๋ํธ๋ก state๋ฅผ ์ฎ๊ฒจ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.- Strict Mode์์๋ ์ฐ์ฐํ ๋น์์์ฑ์ ์ฐพ์๋ด๊ธฐ ์ํด reducer์ init ํจ์๋ฅผ ๋๋ฒ ํธ์ถํฉ๋๋ค. ๊ฐ๋ฐ ํ๊ฒฝ์์๋ง ํ์ ๋ ๋์์ด๋ฉฐ, ๋ฐฐํฌ ๋ชจ๋์๋ ์ํฅ์ ๋ฏธ์น์ง ์์ต๋๋ค. reducer์ init ํจ์๊ฐ ์์ ํจ์๋ผ๋ฉด(๊ทธ๋์ผ๋ง ํ๋ฏ์ด) ๋ก์ง์ ์ด๋ ํ ์ํฅ๋ ๋ฏธ์น์ง ์์ต๋๋ค. ํธ์ถ ์ค ํ๋์ ๊ฒฐ๊ณผ๋ ๋ฌด์ํฉ๋๋ค.
dispatch
ํจ์
useReducer
์ ์ํด ๋ฐํ๋๋ dispatch
ํจ์๋ state๋ฅผ ์๋ก์ด ๊ฐ์ผ๋ก ์
๋ฐ์ดํธํ๊ณ ๋ฆฌ๋ ๋๋ง์ ์ผ์ผํต๋๋ค. dispatch
์ ์ ์ผํ ์ธ์๋ action์
๋๋ค.
const [state, dispatch] = useReducer(reducer, { age: 42 });
function handleClick() {
dispatch({ type: 'incremented_age' });
// ...
React๋ ํ์ฌ state
์ dispatch
๋ฅผ ํตํด ์ ๋ฌ๋ action์ ์ ๊ณต๋ฐ์ ํธ์ถ๋ reducer
์ ๋ฐํ๊ฐ์ ํตํด ๋ค์ state๊ฐ์ ์ค์ ํฉ๋๋ค.
๋งค๊ฐ๋ณ์
action
: ์ฌ์ฉ์์ ์ํด ์ํ๋ ํ๋์ ๋๋ค. ๋ชจ๋ ๋ฐ์ดํฐ ํ์ ์ด ํ ๋น๋ ์ ์์ต๋๋ค. ์ปจ๋ฒค์ ์ ์ํด action์ ์ผ๋ฐ์ ์ผ๋ก action์ ์ ์ํ๋type
ํ๋กํผํฐ์ ์ถ๊ฐ์ ์ธ ์ ๋ณด๋ฅผ ํํํ๋ ๊ธฐํ ํ๋กํผํฐ๋ฅผ ํฌํจํ ๊ฐ์ฒด๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
๋ฐํ๊ฐ
dispatch
ํจ์๋ ์ด๋ค ๊ฐ๋ ๋ฐํํ์ง ์์ต๋๋ค.
์ฃผ์ ์ฌํญ
-
dispatch
ํจ์๋ ์ค์ง ๋ค์ ๋ ๋๋ง์ ์ฌ์ฉํ state ๋ณ์๋ง ์ ๋ฐ์ดํธ ํฉ๋๋ค. ๋ง์ฝdispatch
ํจ์๋ฅผ ํธ์ถํ ์งํ์ state ๋ณ์๋ฅผ ์ฝ๋๋ค๋ฉด ํธ์ถ ์ด์ ์ ์ต์ ํ๋์ง ์์ ๊ฐ์ ์ฐธ์กฐํ ๊ฒ์ ๋๋ค. -
Object.is
๋น๊ต๋ฅผ ํตํด ์๋กญ๊ฒ ์ ๊ณต๋ ๊ฐ๊ณผ ํ์ฌstate
๋ฅผ ๋น๊ตํ ๊ฐ์ด ๊ฐ์ ๊ฒฝ์ฐ, React๋ ์ปดํฌ๋ํธ์ ํด๋น ์ปดํฌ๋ํธ์ ์์ ์์๋ค์ ๋ฆฌ๋ ๋๋ง์ ๊ฑด๋๋๋๋ค. ์ด๊ฒ์ ์ต์ ํ์ ๊ด๋ จ๋ ๋์์ผ๋ก์จ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์ํ๊ธฐ ์ ์ ์ปดํฌ๋ํธ๊ฐ ํธ์ถ๋์ง๋ง, ํธ์ถ๋ ๊ฒฐ๊ณผ๊ฐ ์ฝ๋์ ์ํฅ์ ๋ฏธ์น์ง๋ ์์ต๋๋ค. -
React๋ state์ ์ ๋ฐ์ดํธ๋ฅผ batchํฉ๋๋ค. ์ด๋ฒคํธ ํธ๋ค๋ฌ์ ๋ชจ๋ ์ฝ๋๊ฐ ์ํ๋๊ณ
set
ํจ์๊ฐ ๋ชจ๋ ํธ์ถ๋ ํ์ ํ๋ฉด์ ์ ๋ฐ์ดํธ ํฉ๋๋ค. ์ด๋ ํ๋์ ์ด๋ฒคํธ์ ๋ฆฌ๋ ๋๋ง์ด ์ฌ๋ฌ๋ฒ ์ผ์ด๋๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค. DOM ์ ๊ทผ ๋ฑ ์ด๋ฅธ ํ๋ฉด ์ ๋ฐ์ดํธ๋ฅผ ๊ฐ์ ํด์ผํ ํน์ํ ์ํฉ์ด ์์ ๊ฒฝ์ฐflushSync
๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ฌ์ฉ๋ฒ
์ปดํฌ๋ํธ์ reducer ์ถ๊ฐํ๊ธฐ
state๋ฅผ reducer๋ก ๊ด๋ฆฌํ๊ธฐ ์ํด useReducer
๋ฅผ ์ปดํฌ๋ํธ์ ์ต์๋จ์์ ํธ์ถํฉ๋๋ค.
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
useReducer
๋ ์ ํํ 2๊ฐ์ ํญ๋ชฉ์ด ํฌํจ๋ ๋ฐฐ์ด ๋ฐํํฉ๋๋ค.
- state ๋ณ์์ ํ์ฌ state. ์ต์ด์๋ ์ฌ์ฉ์๊ฐ ์ ๊ณตํ ์ด๊ธฐ state๋ก ์ด๊ธฐํ๋ฉ๋๋ค.
dispatch
ํจ์. ์ํธ์์ฉ์ ๋์ํ์ฌ state๋ฅผ ๋ณ๊ฒฝํฉ๋๋ค.
ํ๋ฉด์ ์
๋ฐ์ดํธํ๋ ค๋ฉด ์ฌ์ฉ์๊ฐ ์ํํ ํ๋์ ์๋ฏธํ๋ action ๊ฐ์ฒด๋ฅผ ์ธ์๋กํ์ฌ dispatch
ํจ์๋ฅผ ํธ์ถํ์ธ์.
function handleClick() {
dispatch({ type: 'incremented_age' });
}
React๋ ํ์ฌ state์ action์ reducer ํจ์๋ก ์ ๋ฌํฉ๋๋ค. reducer๋ ๋ค์ state๋ฅผ ๊ณ์ฐํ ํ ๋ฐํํฉ๋๋ค. React๋ ๋ค์ state๋ฅผ ์ ์ฅํ ๋ค์ ์ปดํฌ๋ํธ์ ํจ๊ป ๋ ๋๋ง ํ๊ณ UI๋ฅผ ์ ๋ฐ์ดํธ ํฉ๋๋ค.
import { useReducer } from 'react'; function reducer(state, action) { if (action.type === 'incremented_age') { return { age: state.age + 1 }; } throw Error('Unknown action.'); } export default function Counter() { const [state, dispatch] = useReducer(reducer, { age: 42 }); return ( <> <button onClick={() => { dispatch({ type: 'incremented_age' }) }}> Increment age </button> <p>Hello! You are {state.age}.</p> </> ); }
useReducer
๋ useState
์ ๋งค์ฐ ์ ์ฌํ์ง๋ง, state ์
๋ฐ์ดํธ ๋ก์ง์ ์ด๋ฒคํธ ํธ๋ค๋ฌ์์ ์ปดํฌ๋ํธ ์ธ๋ถ์ ๋จ์ผํจ์๋ก ๋ถ๋ฆฌํ ์ ์๋ค๋ ์ฐจ์ด์ ์ด ์์ต๋๋ค. ์์ธํ ์ฌํญ์ useState
์ useReducer
๋น๊ตํ๊ธฐ๋ฅผ ์ฝ์ด๋ณด์ธ์.
reducer ํจ์ ์์ฑํ๊ธฐ
reducer ํจ์๋ ์๋์ ๊ฐ์ด ์ ์ธํฉ๋๋ค.
function reducer(state, action) {
// ...
}
์ดํ ๋ค์ state๋ฅผ ๊ณ์ฐํ ์ฝ๋๋ฅผ ์์ฑํ๊ณ , ๊ณ์ฐ๋ state๋ฅผ ๋ฐํํฉ๋๋ค. ๋ณดํต์ ์ปจ๋ฒค์
์ ๋ฐ๋ผ switch
๋ฌธ์ ์ฌ์ฉํฉ๋๋ค. switch
๋ ๊ฐ case
๋ฅผ ์ด์ฉํด ๋ค์ state๋ฅผ ๊ณ์ฐํ๊ณ ๋ฐํํฉ๋๋ค.
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Unknown action: ' + action.type);
}
Actions์ ๋ค์ํ ํํ๊ฐ ๋ ์ ์์ต๋๋ค. ํ์ง๋ง ์ปจ๋ฒค์
์ ๋ฐ๋ผ ์ก์
์ด ๋ฌด์์ธ์ง ์ ์ํ๋ type
ํ๋กํผํฐ๋ฅผ ํฌํจํ ๊ฐ์ฒด๋ก ์ ์ธํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์
๋๋ค. type
์ reducer๊ฐ ๋ค์ state๋ฅผ ๊ณ์ฐํ๋๋ฐ ํ์ํ ์ต์ํ์ ์ ๋ณด๋ฅผ ํฌํจํด์ผ ํฉ๋๋ค.
function Form() {
const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
function handleButtonClick() {
dispatch({ type: 'incremented_age' });
}
function handleInputChange(e) {
dispatch({
type: 'changed_name',
nextName: e.target.value
});
}
// ...
action type ์ด๋ฆ์ ์ปดํฌ๋ํธ ๋ด์์ ์ง์ญ์ ์ ๋๋ค. ๊ฐ action์ ๋จ์ผ ์ํธ์์ฉ์ ์ค๋ช ํ๋ฉฐ, ๋ฐ์ดํฐ์ ์ฌ๋ฌ ๋ณ๊ฒฝ ์ฌํญ์ ์ด๋ํ๋๋ผ๋ ํ๋์ ์ํธ์์ฉ๋ง์ ๋ํ๋ ๋๋ค. state์ ํํ๋ ์์์ ์ด์ง๋ง, ์ผ๋ฐ์ ์ผ๋ก ๊ฐ์ฒด๋ ๋ฐฐ์ด์ผ ๊ฒ์ ๋๋ค.
์์ธํ ๋ด์ฉ์ state ๋ก์ง์ reducer๋ก ์์ฑํ๊ธฐ๋ฅผ ์ฝ์ด๋ณด์ธ์.
์์ 1 of 3: ํผ (๊ฐ์ฒด)
์ด ์์ ์์๋ reducer๋ฅผ ์ด์ฉํด name
๊ณผ age
ํ๋๋ฅผ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ state๋ก ๊ด๋ฆฌํฉ๋๋ค.
import { useReducer } from 'react'; function reducer(state, action) { switch (action.type) { case 'incremented_age': { return { name: state.name, age: state.age + 1 }; } case 'changed_name': { return { name: action.nextName, age: state.age }; } } throw Error('Unknown action: ' + action.type); } const initialState = { name: 'Taylor', age: 42 }; export default function Form() { const [state, dispatch] = useReducer(reducer, initialState); function handleButtonClick() { dispatch({ type: 'incremented_age' }); } function handleInputChange(e) { dispatch({ type: 'changed_name', nextName: e.target.value }); } return ( <> <input value={state.name} onChange={handleInputChange} /> <button onClick={handleButtonClick}> Increment age </button> <p>Hello, {state.name}. You are {state.age}.</p> </> ); }
์ด๊ธฐ state ์ฌ์์ฑ ๋ฐฉ์งํ๊ธฐ
React๋ ์ด๊ธฐ state๋ฅผ ์ ์ฅํ ํ, ๋ค์ ๋ ๋๋ง์์๋ ์ด๋ฅผ ๋ฌด์ํฉ๋๋ค.
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
// ...
createInitialState(username)
์ ๋ฐํ๊ฐ์ด ์ด๊ธฐ ๋ ๋๋ง์๋ง ์ฌ์ฉ๋๋๋ผ๋ ํจ์๋ ๋งค ๋ ๋๋ง๋ง๋ค ํธ์ถ๋ ๊ฒ์
๋๋ค. ํจ์๊ฐ ํฐ ๋ฐฐ์ด์ด๋ ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ ๋ค๋ฃฐ ๊ฒฝ์ฐ์๋ ์ฑ๋ฅ์ ๋ญ๋น๊ฐ ๋ ์ ์์ต๋๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ๋ฐฉ๋ฒ์ผ๋ก๋ useReducer
์ 3๋ฒ์งธ ์ธ์์ ์ด๊ธฐํ ํจ์ ๋ฅผ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...
createInitialState()
์ฒ๋ผ ํจ์๋ฅผ ํธ์ถํด์ ์ ๋ฌํ๋ ๊ฒ์ด ์๋๋ผ, createInitialState
ํจ์ ์์ฒด๋ฅผ ์ ๋ฌํด์ผ ํ๋ค๋ ๊ฒ์ ๊ธฐ์ตํ์ธ์. ์ด ๋ฐฉ๋ฒ์ ์ด์ฉํ๋ฉด ์ด๊ธฐํ ์ดํ์ ์ด๊ธฐ state๊ฐ ๋ค์ ์์ฑ๋๋ ์ผ์ ๋ฐ์ํ์ง ์์ต๋๋ค.
์์ ์์ ์์๋ createInitialState
ํจ์๊ฐ username
์ ์ธ์๋ก ๋ฐ์ต๋๋ค. ๋ง์ฝ ์ด๊ธฐํ ํจ์๊ฐ ์ด๊ธฐ state๋ฅผ ๊ณ์ฐํ๋ ๊ฒ์ ์ด๋ค ์ธ์๋ ํ์ํ์ง ์๋ค๋ฉด, useReducer
์ ๋๋ฒ์งธ ์ธ์์ null์ ์ ๋ฌํ ์ ์์ต๋๋ค.
์์ 1 of 2: ์ด๊ธฐํ ํจ์ ์ ๋ฌ
์ด ์์ ์์๋ ์ด๊ธฐํ ๋จ๊ณ์์๋ง ๋์ํ๋ ํจ์์ธ createInitialState
๋ฅผ ์ด๊ธฐํ ํจ์๋ก ์ ๋ฌํฉ๋๋ค. ์ด ํจ์๋ ์ธํ์ ์
๋ ฅ ํ ๋ ๋ฐ์ํ๋ ๋ฆฌ๋ ๋๋ง ์ํฉ ๋ฑ์์๋ ํธ์ถ๋์ง ์์ต๋๋ค.
import { useReducer } from 'react'; function createInitialState(username) { const initialTodos = []; for (let i = 0; i < 50; i++) { initialTodos.push({ id: i, text: username + "'s task #" + (i + 1) }); } return { draft: '', todos: initialTodos, }; } function reducer(state, action) { switch (action.type) { case 'changed_draft': { return { draft: action.nextDraft, todos: state.todos, }; }; case 'added_todo': { return { draft: '', todos: [{ id: state.todos.length, text: state.draft }, ...state.todos] } } } throw Error('Unknown action: ' + action.type); } export default function TodoList({ username }) { const [state, dispatch] = useReducer( reducer, username, createInitialState ); return ( <> <input value={state.draft} onChange={e => { dispatch({ type: 'changed_draft', nextDraft: e.target.value }) }} /> <button onClick={() => { dispatch({ type: 'added_todo' }); }}>Add</button> <ul> {state.todos.map(item => ( <li key={item.id}> {item.text} </li> ))} </ul> </> ); }
ํธ๋ฌ๋ธ ์ํ
dispatch๋ก action์ ํธ์ถํด๋ ์ค๋๋ state ๊ฐ์ด ์ถ๋ ฅ๋ฉ๋๋ค.
dispatch
ํจ์์ ํธ์ถ์ ํ์ฌ ๋์ํ๊ณ ์๋ ์ฝ๋์ state๋ฅผ ๋ณ๊ฒฝํ์ง ์์ต๋๋ค.
function handleClick() {
console.log(state.age); // 42
dispatch({ type: 'incremented_age' }); // Request a re-render with 43
console.log(state.age); // Still 42!
setTimeout(() => {
console.log(state.age); // Also 42!
}, 5000);
}
์ด๋ฌํ ํ์์ State๊ฐ ์ค๋
์ท์ผ๋ก์ ์ฌ์ฉ๋๊ธฐ ๋๋ฌธ์ ์ผ์ด๋ฉ๋๋ค. state๋ฅผ ์
๋ฐ์ดํธํ๋ฉด ์๋ก์ด state๋ฅผ ์ด์ฉํ ๋ ๋ค๋ฅธ ๋ ๋๋ง์ด ์์ฒญ๋์ง๋ง, ์ด๋ฏธ ์คํ์ค์ธ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ด์ state
์๋ฐ์คํฌ๋ฆฝํธ ๋ณ์์๋ ์ํฅ์ ๋ฏธ์น์ง ์์ต๋๋ค.
๋ง์ฝ ๋ค์ state ๊ฐ์ ์๊ณ ์ถ๋ค๋ฉด, reducer ํจ์๋ฅผ ์ง์ ํธ์ถํด์ ๋ค์ ๊ฐ์ ๊ณ์ฐํด๋ณผ ์ ์์ต๋๋ค.
const action = { type: 'incremented_age' };
dispatch(action);
const nextState = reducer(state, action);
console.log(state); // { age: 42 }
console.log(nextState); // { age: 43 }
dispatch๋ก action์ ํธ์ถํด๋ ํ๋ฉด์ด ์ ๋ฐ์ดํธ๋์ง ์์ต๋๋ค.
React๋ ์ด์ state์ ๋ค์ state๋ฅผ ๋น๊ตํ์ ๋, ๊ฐ์ด ์ผ์นํ๋ค๋ฉด ์
๋ฐ์ดํธ๊ฐ ๋ฌด์๋ฉ๋๋ค. ๋น๊ต๋ Object.is
๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋๋ค. ์ด๋ฐ ํ์์ ๋ณดํต ๊ฐ์ฒด๋ ๋ฐฐ์ด์ state๋ฅผ ์ง์ ์ ์ผ๋ก ์์ ํ์ ๋ ๋ฐ์ํฉ๋๋ค.
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ๐ฉ Wrong: mutating existing object
state.age++;
return state;
}
case 'changed_name': {
// ๐ฉ Wrong: mutating existing object
state.name = action.nextName;
return state;
}
// ...
}
}
React๋ ๊ธฐ์กด์ state
๊ฐ์ฒด๊ฐ mutation๋ ์ํ๋ก ๋ฐํ๋๋ค๋ฉด ์
๋ฐ์ดํธ๋ฅผ ๋ฌด์ํฉ๋๋ค. ์ด๋ฌํ ํ์์ ๋ฐฉ์งํ๊ธฐ ์ํด์๋ ๊ฐ์ฒด๋ ๋ฐฐ์ด์ mutation์ํค์ง ์๊ณ ๊ฐ์ฒด state๋ฅผ ๋ณ๊ฒฝํ๊ฑฐ๋ ๋ฐฐ์ด state๋ฅผ ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค.
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// โ
Correct: creating a new object
return {
...state,
age: state.age + 1
};
}
case 'changed_name': {
// โ
Correct: creating a new object
return {
...state,
name: action.nextName
};
}
// ...
}
}
reducer์ state ์ผ๋ถ๊ฐ dispatch๋ ์ดํ์ undefined๊ฐ ํ ๋น๋ฉ๋๋ค.
๊ฐ๊ฐ์ case
๊ฐ ์๋ก์ด state๋ฅผ ๋ฐํํ ๋ ๊ธฐ์กด์ ์๋ ํ๋๋ฅผ ๋ชจ๋ ๋ณต์ฌํ๋์ง ํ์ธํด๋ณด์ธ์.
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
...state, // Don't forget this!
age: state.age + 1
};
}
// ...
์์ ์ฝ๋์์๋ ...state
๊ฐ ์๋ค๋ฉด ๋ค์ state๋ ์ค๋ก์ง age
ํ๋๋ง ํฌํจํ๊ฑฐ๋, ์๋ฌด๊ฒ๋ ํฌํจํ์ง ์์ ๊ฒ์
๋๋ค.
reducer์ ๋ชจ๋ state๊ฐ dispatch๊ฐ ์ด๋ฃจ์ด ์ง ํ undefined๊ฐ ํ ๋น๋ฉ๋๋ค.
state์ ์๊ธฐ์น ์์ undefined
๊ฐ ํ ๋น๋๊ณ ์๋ค๋ฉด case ์ค ํ๋์ return
์ด ๋๋ฝ๋์๊ฑฐ๋ action์ ํ์
์ด case
์ ์ง์ง์ด์ง์ง ์์์ ์ ์์ต๋๋ค. ์ด์ ๋ฅผ ์ฐพ๊ธฐ ์ํด switch๋ฌธ ๋ฐ์์ ์๋ฌ๋ฅผ throw ํ ์ ์์ต๋๋ค.
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ...
}
case 'edited_name': {
// ...
}
}
throw Error('Unknown action: ' + action.type);
}
์ด ์ธ์๋ ์ค์๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ํ์ ์คํฌ๋ฆฝํธ ๊ฐ์ ์ ์ ํ์ ์ฒด์ปค๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
โToo many re-rendersโ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
Too many re-renders. React limits the number of renders to prevent an infinite loop.
๋ผ๋ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค. ์ผ๋ฐ์ ์ผ๋ก๋ ๋ ๋๋ง ๊ณผ์ ์์ dispatch๊ฐ ์คํ๋ ๋ ์ด๋ฌํ ์ผ์ด ์ผ์ด๋ฉ๋๋ค. ๋ ๋๋ง์ dispatch๋ฅผ ์ผ๊ธฐํ๊ณ , dispatch๋ ๋ ๋๋ง์ ์ผ๊ธฐํ๋ฏ๋ก ๋ ๋๋ง ๋ฌดํ ๋ฃจํ๊ฐ ์ผ์ด๋ฉ๋๋ค. ์ด๋ฌํ ์ํฉ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์๋ชป ํธ์ถํ ๋ ์ข
์ข
๋ฐ์ํฉ๋๋ค.
// ๐ฉ Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>
// โ
Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>
// โ
Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>
์ค๋ฅ์ ์์ธ์ ์ฐพ์ ์ ์๋ ๊ฒฝ์ฐ์๋ ์ด๋ dispatch
ํจ์์์ ์๋ฌ๊ฐ ์์ฑ๋๋์ง ํ์ธํ๊ธฐ ์ํด ์ฝ์์ฐฝ์ ์ค๋ฅ ์์ ์๋ ํ์ดํ๋ฅผ ํด๋ฆญํ ํ ์๋ฐ์คํฌ๋ฆฝํธ ์คํ์ ์ฐพ์๋ณด์ธ์.
reducer์ ์ด๊ธฐํ ํจ์๊ฐ ๋๋ฒ ํธ์ถ๋ฉ๋๋ค.
React๋ ์๊ฒฉ ๋ชจ๋์ผ ๋ reducer์ ์ด๊ธฐํ ํจ์๋ฅผ ๋๋ฒ์ฉ ํธ์ถํฉ๋๋ค. ์ด ํ์์ ์ฝ๋ ์คํ์ ๋ฌธ์ ๊ฐ ๋์ง ์์ต๋๋ค.
์ด๋ฌํ ํ์์ ์ปดํฌ๋ํธ๊ฐ ์์ํจ์๋ก ์ ์ง๋ ์ ์๋๋ก ์ค์ง ๊ฐ๋ฐ ํ๊ฒฝ์์๋ง ์ผ์ด๋๋ฉฐ, ๋๊ฐ์ ํธ์ถ ์ค ํ๋๋ ๋ฌด์๋ฉ๋๋ค. ์ปดํฌ๋ํธ, ์ด๊ธฐํ ํจ์, reducer๊ฐ ์์ํ๋ค๋ฉด ๋ก์ง์ ์๋ฌด๋ฐ ์ํฅ์ ๋ฏธ์น์ง ์์ง๋ง, ์์ํ์ง ์๋ค๋ฉด ์ค์๋ฅผ ์์์ฑ ์ ์๋๋ก ์๋ ค์ค๋๋ค.
์์๋ก, ์๋์ ์์ํ์ง ์์ reducer ํจ์๋ state ๋ฐฐ์ด์ mutation์ ์ผ์ผํค๊ณ ์์ต๋๋ค.
function reducer(state, action) {
switch (action.type) {
case 'added_todo': {
// ๐ฉ Mistake: mutating state
state.todos.push({ id: nextId++, text: action.text });
return state;
}
// ...
}
}
React๋ reducer ํจ์๋ฅผ ๋ ๋ฒ ํธ์ถํ๋ฏ๋ก todo๊ฐ ๋ ๊ฐ ์ถ๊ฐ๋๋ ๊ฒ์ ๋ณผ ์ ์๊ณ , ์ด๋ฅผ ํตํด reducer ํจ์ ์์ฑ์ ์ค์๊ฐ ์๋ค๋ ๊ฒ์ ์์๋ผ ์ ์์ต๋๋ค. ์ด๋ฌํ ์ค์๋ ๋ฐฐ์ด์ mutation ํ์ง ์๊ณ ๊ต์ฒดํ๋ ๋ฐฉ๋ฒ์ ํตํด ์์ ํ ์ ์์ต๋๋ค.
function reducer(state, action) {
switch (action.type) {
case 'added_todo': {
// โ
Correct: replacing with new state
return {
...state,
todos: [
...state.todos,
{ id: nextId++, text: action.text }
]
};
}
// ...
}
}
์ด์ reducer ํจ์๋ ์์ํ๋ฏ๋ก, ์ฌ๋ฌ๋ฒ ํธ์ถ๋์ด๋ ๊ฐ์ ๊ฐ์ ๋ณด์ฅํ ์ ์์ต๋๋ค. React๋ ์์์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด ๊ฐ๋ฐ ํ๊ฒฝ์์ ๋๋ฒ์ฉ ํธ์ถํฉ๋๋ค. ์ค๋ก์ง ์ปดํฌ๋ํธ์ ์ด๊ธฐํ ํจ์, reducer ํจ์๋ง ์์ํ ํ์๊ฐ ์์ต๋๋ค. ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ ์์ํ ํ์๊ฐ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ ๋ ๋ฒ์ฉ ํธ์ถ๋์ง ์์ต๋๋ค.
์์ธํ ์ฌํญ์ ์ปดํฌ๋ํธ๋ฅผ ์์ํ๊ฒ ์ ์งํ๊ธฐ๋ฅผ ์ฝ์ด๋ณด์ธ์.