useReducer는 useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 값으로 업데이트해 주고 싶을 때 사용하는 hook이다. reducer는 현재 state, 그리고 업데이트를 위해 필요한 정보를 담은 action 값을 전달받아 새로운 상태를 반환하는 함수이다.
function reducer(state, action){
return {...}
}
새로운 상태를 만들 때는 반드시 불변성을 지켜주어야한다.
import React, {useReducer} from 'react'
function reducer(state, action){
switch(action.type){
case `INCREMENT`:
return {value : state.value+1}
case `DECREMENT`:
return {value : state.value-1}
default :
return state
}
}
const Counter = ()=> {
const [state, dispatch]=useReducer(reducer, {value:0})
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b>입니다
</p>
<button onClick={()=>dispatch({type:'INCREMENT'})}>+1</button>
<button onClick={()=> dispatch({type:'DECREMENT'})}>-1</button>
</div>
)
}
export default Counter
useReducer(reducer, {value:0})
부분을 보면 첫 번째 파라미터로는 reducer 함수를, 두 번째 파라미터에는 해당 reducer의 기본 값을 넣어주었다. 이때 hook을 사용하며 state와 dispatch 함수를 받아온다.
state : 현재 가르키고 있는 상태
dispatch : action을 발생시키는 함수 -> dispatch(action) 호출을 해주면 리듀서 함수가 호출된다.
reducer의 가장 큰 장점은 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다는 것.
import {useReducer} from "react"
function reducer(state, action){
return {
...state,
[action.name]:action.value
}
}
const Info =()=>{
const [state, dispatch] =useReducer(reducer, {name:'', nickname:''})
const {name, nickname} = state
const onChange =(e)=>{
dispatch(e.target)
}
return(
<div>
<>
<input name="name"value={name} onChange ={onChange}/>
<input name="nickname" value={nickname} onChange ={onChange}/>
</>
<div>
<b>이름 : </b> {name}
</div>
<div>
<b>닉네임 : </b> {nickname}
</div>
</div>
)
}
export default Info;
useReducer에서의 액션은 그 어떤 값도 사용 가능하다. 위 예제는 이벤트 객체가 지니고 있는 e.target 값을 액션 값으로 사용했다.
console.log( e.target )
출력 값 👉 <input name="name" value ="호박쿵야">
useMemo를 사용하면 함수형 컴포넌트 내부에서 발생하는 연산을 최적화할 수 있다.
리스트에 숫자를 추가하면 추가된 숫자들의 평균을 보여주는 함수형 컴포넌트를 작성해보자.
import {useState} from 'react'
const getAverage =numbers =>{
console.log('평균 값 계산 중 ---');
if(numbers.length===0){
return 0
}
const sum= numbers.reduce((a,b)=> a+b)
return sum/numbers.length
}
const Average = ()=>{
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange =(e)=>{
setNumber(e.target.value)
}
const onInsert = (e)=>{
const newList = list.concat(parseInt(number))
setList(newList)
setNumber('')
}
return(
<div>
<input value ={number} onChange ={onChange}/>
<button onClick={onInsert}> 등록 </button>
<ul>
{list.map((value, index) =>
<li key={index}>{value}</li>
)}
</ul>
<div>
<b>평균 값 : </b>{getAverage(list)}
</div>
</div>
)
}
export default Average;
👉 실행 결과
출력은 잘 되지만 숫자를 등록할때 뿐만 아니라 input 내에서 숫자를 변경할 때도 getAverage()가 호출되는 문제점이 있다. -> 렌더링할 때마다 계산
이제 useMemo를 이용해 최적화를 해보자.
최적화는 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산을 실행하고, 원하는 값이 바뀌지 않았다면 이전에 연산했던 결과를 다시 사용하는 방식이다.
const getAverage =numbers =>{...
}
const Average = ()=>{
const [list, setList] = useState([]);
const [number, setNumber] = useState('');
const onChange =(e)=>{...}
const onInsert = (e)=>{...}
//추가된 부분
const avg = useMemo(()=> getAverage(list), [list]);
return(
<div>
<input value ={number} onChange ={onChange}/>
<button onClick={onInsert}> 등록 </button>
<ul>
{list.map((value, index) =>
<li key={index}>{value}</li>
)}
</ul>
<div>
//변경된 부분
<b>평균 값 : </b>{avg}
</div>
</div>
)
}
🧸✨ 이 포스팅은 '리액트를 다루는 기술' 책을 기반으로 공부한 내용을 정리한 내용입니다.