예제는 인프런의 제로초, "조현영"님의 강의를 들으면서 공부한 내용입니다.
1. useReducer
1) useReducer 사용 코드
리액트에 hooks가 생기기면서 useEffect, useState 등을 사용하는데
state가 많아지면 관리가 어려워지고 틱택토 예시에서는 Table에서 Tr 컴포넌트를 거쳐 Td 컴포넌트로 Table의 state를 전달해야하는데 이렇게 많은 state를 전달하기엔 비효율적이다. useReducer를 사용하면 state를 한 번에 작성할 수 있고 관리하기가 쉬워진다.
useReducer를 사용하여 state를 관리하면 state의 변경 방법 등이 존재하는데 아래에서 소개함
아래 코드는 useReducer를 통해 주석처리로 선언한 state를 한번에 묶어서 선언한 코드로 아래의 형태를 따른다.
const React = require('react');
const { useState,useReducer,useCallback } = React;
const Table = require('./Table');
const initialState = {
winner: '',
turn:'O',
tableData : [['','',''],['','',''],['','','']],
}
const reducer = (state, action) => {
switch(action.type) {
case 'set_winner':
return{...state, winner: action.winner};
}
}
const TicTacToe=()=> {
// cosnt [winner, setWinner] = useState('');
// cosnt [turn, setTurn] = useState('O');
// cosnt [tableData, setTableData] = useState([['','',''],['','',''],['','','']]);
const [state, dispatch] = useReducer(reducer, initialState);
const onClickTable = useCallback(()=>{
dispatch({type:'set_winnder', winner:'O'});
},[]);
return (
<>
<Table onClick = {onClickTable} tableData={state.tableData}/>
{state.winner && <div>{state.winner}님 승리!</div>}
</>
);
}
module.exports = TicTacToe;
2) useReducer 사용법 설명
const [state, dispatch] = useReducer(reducer, initialState);
우선 위와 같은 형태로 useReducer를 호출한다.
두 인자를 보면 reducer는 함수이고 initialState는 객체가 들어간다.
state는 이제 실제 사용할 때 state.winner 이렇게 state는 initialState객체를 가리킨다.
※ reducer는 비동기 함수이다.
const initialState = {
winner: '',
turn:'O',
tableData : [['','',''],['','',''],['','','']],
}
const reducer = (state, action) => {
switch(action.type) {
case 'set_winner':
return {...state, winner: action.winner};
}
}
const TicTacToe=()=> {
// cosnt [winner, setWinner] = useState('');
// cosnt [turn, setTurn] = useState('O');
// cosnt [tableData, setTableData] = useState([['','',''],['','',''],['','','']]);
const [state, dispatch] = useReducer(reducer, initialState);
const onClickTable = useCallback(()=>{
dispatch({type:'set_winnder', winner:'O'});
},[]);//컴포넌트의 prop로 전달하는 함수는 전부 useCallback으로 작성해야 문제가 안생김. 11장 참고
return (...);
}
module.exports = TicTacToe;
initialState에는 state를 한번에 모은 객체이다.
reducer에서 state가 어떻게 변경될지를 지정한다.
dispatch({}); 호출로 reducer에서 state가 어떻게 변경될지 결정하는데 dispacth의 매개변수는 객체를 지정하는데 action 객체를 지정하는 것이다.
action을 해석해서 직접 state를 바꿔주는 것은 reducer 함수에서 수행함.
(action을 dispacth할 때마다 reducer가 실행되는 것임.)
그리고 reducer에서 직접 state에 접근해 값을 변경하는 것이 아닌 return {...state, winner: action.winner};이와 같은 방법으로 state의 변경을 해야한다.
state의 새로운 객체를 (얕은)복사로 만들어 주고 바뀔 state 값만 적어줘야한다. (스프레드 문법)
※ 리액트에서는 state의 불변성으로 setState를 하던 reducer를 통해 state를 변경하던 항상 새로운 객체를 생성하고 그 객체 안에 바뀌는 state 정보만 적어주는 방식을 사용했다. 이러한 방법은 리액트에서 어떤 방법이던 state를 변경하는데 있어 가장 기본 형식이며 이는 리액트의 state 불변성 때문이다.
const initialState = {
winner: '',
turn:'O',
tableData : [['','',''],['','O',''],['','','']],
}
const reducer = (state, action) => {
switch(action.type) {
case SET_WINNER:
return {...state, winner: action.winner};
case CLICK_CELL:
const tableData = [...state.tableData] //기존 tableData를 얕은 복사
tableData[action.row] = [...tableData[action.row]];
tableData[action.row][action.cell] = state.turn;
return {
...state,
tableData,
}
}
}
const TicTacToe=()=> {
...
}
import React, { PureComponent } from 'react';
class Test extends PureComponent {
state = {
array:[],
}
onClick = () => {
this.setState({
array: [...this.state.array, 1],
});
}
render() {
conosle.log("랜더링!");
return (
<div>
<button onClick = {this.onClick}>클릭</button>
</div>
);
}
}
module.exports = Tes
따라서 위 두 코드 중 첫 번째 코드를 보면 reducer에서 state를 변경하기 위해 객체를 지정해줘야하는데
이는 두 번째 코드와 같이 기존의 state를 변경할 때 객체는 스프레드 문법으로 변경되는 state인 배열은 새로운 배열 객체를 지정해줘야했다. 이것과 동일하게 state의 불변성을 지켜주기 위해서 reducer에서도 배열 객체 형태인 state의 값을 변경하려고 할 때 일일이 얕은 복사로 새로운 배열 객체를 만들어줘야하는 단점이 있다. (immer라는 라이브러리를 통해 가독성 문제를 해결할 수 있다.)
위와 같이 해주는 이유는 리액트는 기본적으로 state의 불변성 원칙을 지켜줘야한다. 이것을 따르지 않았을 때 오류가 발생하진 않지마 문제가 발생한다.
예를 들어 PureComponent를 상속한 컴포넌트에서 배열, 객체와 같은 참조 타입의 state의 setState 호출은 객체의 참조가 바뀔때 리액트는 리랜더링을 한다. 단순히 state의 값을 변경하는 것은 리랜더링을 하지 않는다. 이것만 봐도 state는 불변성이다 라는 것을 알 수 있다.
따라서 reducer에서 state를 변경할 때도 컴포넌트인 Tr, Td의 state 값을 변경하려고한다면 tableData 참조 타입을 변경하기 위해 얕은 복사 방법을 사용한다.
'React 라이브러리' 카테고리의 다른 글
14. React-Router-Dom (0) | 2022.06.05 |
---|---|
13. Context API (0) | 2021.12.26 |
11. 로또추첨기: useMemo, useCallback, useEffect(review) (0) | 2021.12.02 |
10. 가위 바위 보 게임 만들기: 라이프 사이클, 고차함수와 Q&A (0) | 2021.11.18 |
10. hooks란 (0) | 2021.11.13 |