본문 바로가기
JavaScript/React

React) Hooks - useReducer, useMemo

by 박채니 2022. 11. 15.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

Hooks - useReducer, useMemo

 

useReducer

- useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 하고 싶을 때 사용

- 현재 상태, 업데이트를 위해 필요한 정보를 담은 액션 값을 전달 받아 새로운 상태를 반환

※ 참고하면 좋은 영상

https://www.youtube.com/watch?v=E7bNzWrlKTE 

 

카운터 구현하기

 

Counter.js

import { 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;

useReducer의 첫 번째 파라미터리듀서 함수를 넣어주고, 두 번째 파라미터에는 해당 리듀서의 기본 값을 넣어줍니다.

state 값(현재 가리키고 있는 상태)과 dispatch 함수(액션을 발생시키는 함수)를 받아오게 되고,

dispatch(action) 형태로 함수 안에 파라미터로 액션 값을 넣어주면 리듀서 함수가 호출 됩니다.

 

App.js

import Counter from './Counter';

const App = () => {
  return <Counter />;
}

export default App;

처음 렌더링 시
+1 클릭 시 (0 기준)
-1 클릭 시 (0 기준)

 

인풋 상태 관리하기

 

Info.js

import { useState, useEffect, 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;

e.target 값을 액션 값으로 사용하여 [name] : [value]로 간단하게 처리해줄 수 있습니다.

 

App.js

import Info from './Info';

const App = () => {
  return <Info />;
}

export default App;

 


useMemo

- 함수 컴포넌트 내부에서 발생하는 연산을 최적화

- 렌더링하는 과정에서 특정 값이 바뀌었을 때만 연산 실행, 특정 값이 바뀌지 않았다면 이전 연산 결과 사용

 

Average.js - useMemo 사용하지 않는 버전

import { useState } from "react";

const getAverage = numbers => {
    console.log('평균값 계산 중...');
    
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((agg, num) => agg + num);
    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(number)));
        setList(nextList);
        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;

 

App.js

import Average from './Average';

const App = () => {
  return <Average />;
}

export default App;

평균값이 잘 나타나지만, 등록이 아닌 onChange 이벤트가 발생할 때마다 getAverage 함수가 작동하는 것을 확인할 수 있습니다.

해당 작업은 불필요하겠죠? 이 때 useMemo Hook을 사용하여 최적화 해줄 수 있습니다.

 

Average.js

import { useState, useMemo } from "react";

const getAverage = numbers => {
    console.log('평균값 계산 중...');
    
    if(numbers.length === 0) return 0;
    const sum = numbers.reduce((agg, num) => agg + num);
    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(number)));
        setList(nextList);
        setNumber('');
    }
	
    // 코드 추가
    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;

특정 값을 [list]로 지정해주었고, list가 변경될 때만 연산을 실행 그렇지 않으면 이전 연산 결과를 재사용하여 더이상 onChange 이벤트가 발생했을 때 연산 실행을 하지 않습니다.