일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 코드스테이츠
- [AI 5기] 연습 문제집
- 프론트개발
- 리트코드
- [파이썬 실습] 기초 문제
- 자바스크립트
- 자바스크립트 날씨 웹 만들기
- 프로그래머스
- 삼항연산자
- 간단한 날씨 웹 만들기
- leetcode
- 자바스크립트 날씨
- RN 프로젝트
- 자바스크립트 sort()
- [파이썬 실습] 중급 문제
- 프론트개발공부
- 엘리스 ai 트랙
- [파이썬 실습] 심화 문제
- 날씨 웹 만들기
- 부트캠프
- JavaScript
- HTML
- 엘리스
- 개발일기
- 자바스크립트 split()
- 개발공부
- 자바스크립트 reduce()
- 엘리스 AI 트랙 5기
- reactnativecli
- 코딩부트캠프
- Today
- Total
개발조각
[React] useReducer 본문
컴포넌트의 state를 생성하고 관리하기 위해서는 useState훅을 사용해 왔는데 리액트에서 state관리를 위한 또 다른 훅이 있다.
그건 바로 useReducer라는 훅이다.
useReducer 개념 설명
useReducer는 useState처럼 state를 생성하고 관리할 수 있게 해주는 도구이다.
useReducer는 언제 사용하는 걸까?
여러 개 하위값을 포함하고 있는 복잡한 state를 다뤄야 될 때 useState대신에 useReducer를 사용하면
코드를 깔끔하게 쓸 수 있고, 유지보수도 쉬워진다.
// State
{
teacher: 'James',
students: ['Kim', 'Ann', 'John'],
count: 3,
locations: [
{country: 'Korea', name: 'A'},
{country: 'Australia', name: 'B'},
]
}
useReducer를 사용하기전 알아야 될 점
useReducer는 Reducer, Dispatch, Action 3가지로 이루어져 있다.
Reducer는 state를 업데이트를 해주는 역할을 한다.
(컴포넌트의 State를 업데이트 시켜주고 싶으면 Reducer를 통해서 해야 된다.)
- 거래내역 : State
- 은행 : Reducer
- 철수의 요구 행위 : Dispatch
- 만원을 출금해주세요 : Action
철수가 거래내역(State)을 업데이트하기 위해서는 요구(Dispatch)에 "만원을 출금해 주세요."(Action)라는 내용을 담아 은행(Reducer)에 전달하면 은행(Reducer)은 내용(Action) 내용대로 거래내역(State)을 업데이트 해주는 것이다.
철수는 은행(Reducer)에 다른 내용(Action)을 보냄으로써 예금, 출금, 송금 등 복잡한 일을 할 수 있다.
위의 내용을 컴포넌트 관점에서 보자면
State를 업데이트 시켜주기 위해 Dispatch함수에 인자로 Action을 넣어서 Reducer에게 전달하면
Reducer가 컴포넌트의 State를 Action안에 들어있는 내용으로 업데이트를 시킨다.
예제1 (은행)
- reducer - state를 업데이트하는 역할 (은행)
- dispatch - state 업데이트를 위한 요구
- action - 요구의 내용
const reducer = (state, action) => {};
money state는 이곳에서만 수정 가능
여기서의 reducer는 useReducer의 인자로 전달
const [money, dispatch] = useReducer(reducer, 0);
reducer를 통해 money state를 수정할 때마다 dispatch를 불러줄 거고
dispatch는 useReducer가 만들어주는 함수인데 action이라는 인자를 넣어줄 예정
const reducer = (state, action) => {
console.log("reducer가 일을 합니다!", state, action);
};
const UseReducer1 = () => {
const [number, setNumber] = useState(0);
const [money, dispatch] = useReducer(reducer, 0); // 0은 여기서 0
return (
<div>
<h2>useReducer 은행에 오신것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
step="1000"
/>
<button
onClick={() => {
dispatch(); // undefined는 dispatch의 인자로 아무것도 안넣었기 때문
}}
>
예금
</button>
<button>출금</button>
</div>
);
};
export default App;
예금을 클릭하면 이와 같은 콘솔이 찍힌다.
0은 money state는 0으로 초기값
undefined는 dispatch인자로 아무것도 주지 않았기 때문이다.
수정 후 다시 예금 클릭
<button
onClick={() => {
dispatch({ type: "deposit", payload: number });
}}
>
function App() {
return (
<>
<UseReducer1 />
</>
);
}
const reducer = (state, action) => {
console.log("reducer가 일을 합니다!", state, action);
return state + action.payload;
};
const UseReducer1 = () => {
const [number, setNumber] = useState(0);
const [money, dispatch] = useReducer(reducer, 0);
return (
<div>
<h2>useReducer 은행에 오신것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
step="1000"
/>
<button
onClick={() => {
dispatch({ type: "deposit", payload: number });
}}
>
예금
</button>
<button>출금</button>
</div>
);
};
export default App;
useReducer도 useState와 같이 state가 바뀔 때마다 컴포넌트를 렌더링이 되기 때문에 화면에 출력된다.
예금 말고도 출금도 해야 되기 때문에 reducer가 payload를 항상 더해주면 안 된다.
action에 있는 type에 따라서 다르게 업데이트를 해줘야 된다.
그래서 reducer안에 if else문, switch문을 많이 쓴다.
const reducer = (state, action) => {
console.log("reducer가 일을 합니다!", state, action);
switch (action.type) {
case "deposit":
return state + action.payload;
default:
return state;
}
};
useReducer의 장점은 전달받은 action대로만 state를 업데이트시켜준다.
만약 알 수 없는 타입을의 action을 보냈다면 Reducer는 아무 일도 하지 않고 이전의 State를 리턴해준다.
=> 실수를 줄여준다.
출금기능도 추가
const reducer = (state, action) => {
console.log("reducer가 일을 합니다!", state, action);
switch (action.type) {
case "deposit":
return state + action.payload;
case "withdraw":
return state - action.payload;
default:
return state;
}
};
const UseReducer1 = () => {
const [number, setNumber] = useState(0);
const [money, dispatch] = useReducer(reducer, 0);
return (
<div>
<h2>useReducer 은행에 오신것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
step="1000"
/>
<button
onClick={() => {
dispatch({ type: "deposit", payload: number });
}}
>
예금
</button>
<button
onClick={() => {
dispatch({ type: "withdraw", payload: number });
}}
>
출금
</button>
</div>
);
};
export default App;
코드를 좀 더 깔끔하게 만들고 싶다면
const ACTION_TYPES = {
deposite: "deposit",
withdraw: "withdraw",
};
const reducer = (state, action) => {
console.log("reducer가 일을 합니다!", state, action);
switch (action.type) {
case ACTION_TYPES.deposit:
return state + action.payload;
case ACTION_TYPES.withdraw:
return state - action.payload;
default:
return state;
}
};
const UseReducer1 = () => {
const [number, setNumber] = useState(0);
const [money, dispatch] = useReducer(reducer, 0);
return (
<div>
<h2>useReducer 은행에 오신것을 환영합니다.</h2>
<p>잔고: {money}원</p>
<input
type="number"
value={number}
onChange={(e) => setNumber(parseInt(e.target.value))}
step="1000"
/>
<button
onClick={() => {
dispatch({ type: ACTION_TYPES.deposit, payload: number });
}}
>
예금
</button>
<button
onClick={() => {
dispatch({ type: ACTION_TYPES.withdraw, payload: number });
}}
>
출금
</button>
</div>
);
};
export default App;
예제 2 (출석부 2)
추가 기능 구현 (add-student)
const reducer = (state, action) => {
console.log(state, action);
switch (action.type) {
case "add-student":
const name = action.payload.name;
const newStudent = {
id: Date.now(),
name,
isHere: false,
};
return {
count: state.count + 1,
students: [...state.students, newStudent],
};
default:
return state;
}
};
const initialState = {
count: 0,
students: [],
};
const UseReducer2 = () => {
const [name, setName] = useState("");
const [studentsInfo, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>출석부</h1>
<p>총 학생 수: {studentsInfo.count}</p>
<input
type="text"
placeholder="이름을 입력해주세요"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button
onClick={() =>
dispatch({ type: "add-student", payload: { name } })
}
>
추가
</button>
{studentsInfo.students.map((student) => {
return <Student name={student.name} key={student.id} />;
})}
</div>
);
};
const Student = ({ name }) => {
return (
<div>
<span>{name}</span>
<button>삭제</button>
</div>
);
};
export default App;
삭제 기능 구현 (delect-student)
const reducer = (state, action) => {
console.log(state, action);
switch (action.type) {
case "add-student":
const name = action.payload.name;
const newStudent = {
id: Date.now(),
name,
isHere: false,
};
return {
count: state.count + 1,
students: [...state.students, newStudent],
};
case "delete-student":
return {
count: state.count - 1,
students: state.students.filter(
(student) => student.id !== action.payload.id
),
};
default:
return state;
}
};
const initialState = {
count: 0,
students: [],
};
const UseReducer2 = () => {
const [name, setName] = useState("");
const [studentsInfo, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>출석부</h1>
<p>총 학생 수: {studentsInfo.count}</p>
<input
type="text"
placeholder="이름을 입력해주세요"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button
onClick={() =>
dispatch({ type: "add-student", payload: { name } })
}
>
추가
</button>
{studentsInfo.students.map((student) => {
return (
<Student
key={student.id}
name={student.name}
dispatch={dispatch}
id={student.id}
/>
);
})}
</div>
);
};
const Student = ({ name, dispatch, id }) => {
return (
<div>
<span>{name}</span>
<button
onClick={() => {
dispatch({ type: "delete-student", payload: { id } });
}}
>
삭제
</button>
</div>
);
};
export default App;
출석했는지 안 했는지 기능 구현 (mark-student)
const reducer = (state, action) => {
console.log(state, action);
switch (action.type) {
case "add-student":
const name = action.payload.name;
const newStudent = {
id: Date.now(),
name,
isHere: false,
};
return {
count: state.count + 1,
students: [...state.students, newStudent],
};
case "delete-student":
return {
count: state.count - 1,
students: state.students.filter(
(student) => student.id !== action.payload.id
),
};
case "mark-student":
return {
count: state.count,
students: state.students.map((student) => {
if (student.id === action.payload.id) { // payload의 id라면
return { ...student, isHere: !student.isHere }; // student의 나머지 속성은 같고 isHere만 바꾸기
}
return student; // 나머지 student는 동일하게
}),
};
default:
return state;
}
};
const initialState = {
count: 0,
students: [],
};
const UseReducer2 = () => {
const [name, setName] = useState("");
const [studentsInfo, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>출석부</h1>
<p>총 학생 수: {studentsInfo.count}</p>
<input
type="text"
placeholder="이름을 입력해주세요"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<button
onClick={() =>
dispatch({ type: "add-student", payload: { name } })
}
>
추가
</button>
{studentsInfo.students.map((student) => {
return (
<Student
key={student.id}
name={student.name}
dispatch={dispatch}
id={student.id}
isHere={student.isHere}
/>
);
})}
</div>
);
};
const Student = ({ name, dispatch, id, isHere }) => {
return (
<div>
<span
style={{
textDecoration: isHere ? "line-through" : "none",
color: isHere ? "gray" : "black",
}}
onClick={() => {
dispatch({ type: "mark-student", payload: { id } });
}}
>
{name}
</span>
<button
onClick={() => {
dispatch({ type: "delete-student", payload: { id } });
}}
>
삭제
</button>
</div>
);
};
export default App;
reducer를 사용했기 때문에 state를 업데이트하는 모든 로직을 reducer안에 담을 수 있게 되었다.
각각의 action이 정해져 있어 reducer 예상한 데로만 state를 변경해 준다.
※ 이 글은 별코딩 리액트 훅 강의를 보고 정리한 글입니다.
'React' 카테고리의 다른 글
[React] Custom Hooks (0) | 2023.09.20 |
---|---|
[React] React.memo (0) | 2023.09.20 |
[React] useCallback (0) | 2023.09.19 |
[React] useMemo (0) | 2023.09.15 |
[React] useContext + Context API (0) | 2023.09.15 |