ContextAPI + useReducer Hands On

shleecloud·2022년 7월 2일
0
post-custom-banner

들어가며

최근 오디오 재생 기능을 구현하면서 중복 재생이 되는 문제를 해결했다. 오디오를 재생하려고 함수를 실행할때마다 오디오 재생 함수에서 new Audio() 메소드를 실행하니 리렌더링 과정에서 계속 실행되면서 여러개의 Audio 객체가 만들어졌다.

중복 재생을 차단해야 돼서 고민하던 중 리렌더링을 막으려면 결국 상태로 관리해야 된다는 결론에 도달해서 ContextAPI + useReducer 조합을 사용하게 됐다.

ContextAPI

컴포넌트 전체에서 사용하는 데이터를 제공한다.

  • 데이터를 (hook이 아니어도 된다) 상속하지 않고 공통으로 사용할 수 있게 만든다.
  • 불필요한 상속을 줄여준다.

useReducer

상태 업데이트를 컴포넌트 외부에서 할 수 있다.

  • 상태 갱신 함수를 별도의 파일로 분리할 수 있다.
    (useState와 결정적인 차이점이다.)
  • 컴포넌트 밖에서 복잡한 상태 업데이트를 구현할 수 있다.

An alternative to useState.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values. It also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

적용

감을 다 잃어서 많이 해맸다. create-react-app 이후 HandsOn 느낌으로 만들어봤다. 가장 간단한 형태를 만들고 차근차근 집어가며 만드는게 큰 도움이 된다.

reducer/CounterContext.jsx

  • 컴포넌트 전체를 감싸기 위해서 만드는 컴포넌트다.
    단순한 형태라서 컴포넌트로 보이지 않는게 햇갈리는 포인트다.
  • value 키를 props로 전달받아 공유할 데이터(또는 상태)를 입력한다.
import {createContext} from 'react';

const CounterContext = createContext(null);

export default CounterContext;

reducer/counterReducer.js

  • 리듀서를 만든다.
  • useReducer 함수의 인자값으로 콜백 형태로 들어가서 객체를 반환한다.
const counterReducer = (state, action) => {
    switch (action.type) {
        case '@get':
            return state;
        case '@plus':
            return state + 1;
        case '@minus':
            return state - 1;
        default:
            return state;
    }
};

export default counterReducer;

App.jsx

  • ContextApi로 전체 컴포넌트를 감싸준다.
  • 인자값으로 어떤 값을 보내줄지 지정할 수 있다.
import {useReducer} from 'react';
import Movie from './movie/Movie';
import CounterContext from './reducer/CounterContext';
import counterReducer from './reducer/counterReducer';

function App() {
    const [counterState, counterDispatch] = useReducer(counterReducer, 0);

    return (
        <CounterContext.Provider value={{counterState, counterDispatch}}>
            <div className="App">
                <p>App.js</p>
                <Movie />
            </div>
        </CounterContext.Provider>
    );
}

export default App;

movie/Movie.jsx

  • 실제로 사용하는 파일이다. useContext 훅을 불러와서 인자값으로 감쌀때 사용했던 ConuterContext 컴포넌트를 입력한다.
  • 반환되는 값을 변수에 할당하면 그 변수는 CounterContextvalue 인자값에 할당된 데이터가 할당된다.
import {useContext} from 'react';
import CounterContext from '../reducer/CounterContext';

const Movie = () => {
    const counterReducer = useContext(CounterContext);

    return (
        <div>
            <hr />
            <p>Movie.jsx</p>
            <p>counter state = {counterReducer.counterState}</p>
            <button onClick={() => counterReducer.counterDispatch({type: '@plus'})}>+</button>
            <button onClick={() => counterReducer.counterDispatch({type: '@minus'})}>-</button>
        </div>
    );
};

export default Movie;
profile
블로그 옮겼습니다. https://shlee.cloud
post-custom-banner

0개의 댓글