useReducer
란?
useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해주고 싶을 때 사용하는 Hooks
리듀서
란?
현재 상태, 액션 값(업데이트를 위해 필요한 정보를 담은) 전달 받아 새로운 상태 반환하는 함수
useReducer
의 액션 객체
→ 어떤 값도 가능 (type 없거나, 문자열/숫자여도 ok.)
예시 코드
1. 기본
import React, { useReducer } from "react";
function reducer(state, action) {
// action.type에 따라 다른 작업 수행
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;
2. 인풋의 경우
import React, { 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>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
파라미터
장점
컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다.
연산 최적화 (함수형 컴포넌트 내부)
→ 렌더링 하는 과정에서 특정 값이 바뀌었을 때만 연산 실행
import React, { useState, useMemo } 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 = () => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
};
// list 배열의 내용이 바뀔 때만 getAverage 호출
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>
);
};
export default Average;
만들어둔 함수 재사용 가능 (렌더링 성능 최적화)
파라미터
생성하고 싶은 함수
배열 (어떤 값이 바뀌었을 때 함수를 새로 생성해야 하는지 명시)
빈 배열
컴포넌트가 렌더링될 때 만들었던 함수 → 재사용
배열 값이 있어야 할 경우
함수 내부에서 상태 값에 의존해야 할 때
→ 그 값을 반드시 두 번재 파라미터 안에 포함 시켜야 함.
예시 코드
import React, { useState, useMemo, useCallback } 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 = useCallback((e) => {
setNumber(e.target.value);
}, []); // 컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
const nextList = list.concat(parseInt(number));
setList(nextList);
setNumber("");
}, [list, number]); // list 혹은 number가 바뀌었을 때만 함수 생성
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>
);
};
export default Average;
useRef를 사용하여 ref 설정 시
→ useRef를 통해 만든 객체 안의 current 값이 실제 엘리먼트를 가리킴
예시 코드
import React, { useRef } from "react";
(생략)
const Average = () => {
const inputEl = useRef(null);
(생략)
const onInsert = useCallback(() => {
(생략)
inputEl.current.focus();
}, [list, number]); // list 혹은 number가 바뀌었을 때만 함수 생성
return (
<div>
<input value={number} onChange={onChange} ref={inputEl} />
<button onClick={onInsert}>등록</button>
</div>
로컬 변수란?
렌더링과 상관없이 바뀔 수 있는 값
컴포넌트 로컬 변수 사용 시에도 useRef 활용 가능
주의
렌더링 관련x 값
관리할 때만 사용할 것코드 예제 (함수형)
import React, { useRef } from "react";
const RefSample = () => {
const id = useRef(1);
const setId = (n) => {
id.current = n;
};
const printId = () => {
console.log(id.current);
};
return <div>RefSample</div>;
};
export default RefSample;
여러 컴포넌트에서 비슷한 기능 공유 시
→ Hooks 제작 → 로직 재사용 가능
예시 코드
useInput.js
useInput
라는 Hook으로 분리 import { useReducer } from "react";
function reducer(state, action) {
return {
...state,
[action.name]: action.value,
};
}
export default function useInput(initialForm) {
const [state, dispatch] = useReducer(reducer, initialForm);
const onChange = () => {
dispatch(e.target);
};
return [state, onChange];
}
Info.js
import React from "react";
import useInputs from "./useInputs";
const Info = () => {
const [state, onChange] = useInputs({
name: "",
nickname: "",
});
const { name, nickname } = state;
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b> {nickname}
</div>
</div>
</div>
);
};
export default Info;