2021.11.20
타입스크립트와 ContextAPI 활용

props 를 여러 컴포넌트를 거쳐 값을 자식 컴포넌트에게 전달해주지 않고도, contextAPI 를 사용하면 컴포넌트가 어느 위치에 있던 다른 컴포넌트에게 바로 사용할 수 있도록 값을 전달해줄 수 있다. 이를 타입스크립트에서 활용하려고 한다.

타입 선언하기

// 필요한 타입들을 미리 선언

type Color = 'red' | 'orange' | 'yellow';

// 상태를 위한 타입
type State = {
  count: number;
  text: string;
  color: Color;
  isGood: boolean;
};

// 모든 액션들을 위한 타입
type Action =
  | { type: 'SET_COUNT'; count: number }
  | { type: 'SET_TEXT'; text: string }
  | { type: 'SET_COLOR'; color: Color }
  | { type: 'TOGGLE_GOOD' };

context 만들기

context 는 statedispatch 2가지를 따로 생성하였다.

statedispatch 를 각각 필요한 컴포넌트에서만 사용하도록 해서 불필요한 렌더링을 방지할 수 있다.

 

dispatch 의 경우 <Generic> 으로 dispatch 함수에 대한 타입을 작성해주어야 하는데,

이 때 React 에 있는 Dispatch 를 불러와 generic 으로 <Action> 을 작성해주면 된다.

type FooValue = {
    foo: number;
}

// createContext<Generic 으로 관리하고 싶은 값 또는 null>(null(기본값));
const FooContext = createContext<FooValue | null>(null);

// context 생성
// 1. 상태를 다루는 context
const SampleStateContext = createContext<State | null>(null);

// 2. reducer 사용 시 dispatch 를 관리하는 context 
const SampleDispatchContext = createContext<Dispatch<Action> | null>(null);
더보기

위의 구조가 복잡하다고 느껴지면 다음과 같이 정리할 수도 있다.

type SampleDispatch = Dispatch<Action>;

const SampleStateContext = createContext<State | null>(null);
const SampleDispatchContext = createContext<SampleDispatch | null>(null);

Provider 구현

SampleProvider 에서 useReducer 를 사용하고

SampleStateContext.ProviderSampleDispatchContext.Providerchildren 을 감싸서 반환한다.

export function SampleProvider({ children }: SampleProviderProps) {
    const [state, dispatch] = useReducer(reducer, {
        count: 0,
        text: 'hello',
        color: 'red',
        isGood: true
    });

    return (
        // sample state 와 dispatch 감싸는 순서는 상관없다.
        <SampleStateContext.Provider value={state}>
            <SampleDispatchContext.Provider value={dispatch}>
                {children}
            </SampleDispatchContext.Provider>
        </SampleStateContext.Provider>
    )
}

커스텀 Hook 만들기

context 를 통해서 관리하고 있는 value 들을 다른 컴포넌트에서 사용하려면 useContext 를 사용해야 한다.

context 로 만든 statedispatch 를 쉽게 사용할 수 있도록 커스텀 Hook 을 만들려고 한다.

 

이때 만들어준 커스텀 Hook 에서 useSampleStateuseSampleDispatch 의 null 체킹을 해주는 것이 중요하다.

만약에 Context 가 가지고 있는 값이 유효하지 않으면 에러를 발생시키는 코드를 작성해준다.

이를 통해 커스텀 Hook 을 사용하게 될 때도 각 Hooks 함수들이 반환하는 값이 언제나 유효한 것을 보장할 수 있다.

export function useSampleState() {
    const state = useContext(SampleStateContext);
    if (!state) throw new Error('Cannot find SampleProvider'); // null 체킹
    return state;
}

export function useSampleDispatch() {
    const dispatch = useContext(SampleDispatchContext);
    if (!dispatch) throw new Error('Cannot find SampleProvider'); // null 체킹
    return dispatch;
}

 

이제 만든 hook 들을 가지고 이전에 만들었던 ReducerSample.tsx 파일에 적용해보면 이전의 코드와 동일하게 잘 적용된 것을 볼 수 있다.

import React from "react";
import { useSampleDispatch, useSampleState } from "./SampleContext";



function ReducerSample() {
	// 커스텀 Hook 함수 적용
    const state = useSampleState();        
    const dispatch = useSampleDispatch();

    const setCount = () => dispatch({ type: 'SET_COUNT', count: 5 });
    const setText = () => dispatch({ type: 'SET_TEXT', text: 'bye' });
    const setColor = () => dispatch({ type: 'SET_COLOR', color: 'orange' });
    const toggleGood = () => dispatch({ type: 'TOGGLE_GOOD' });

    return (
        <div>
            <p>
                <code>count: </code> {state.count}
            </p>
            <p>
                <code>text: </code> {state.text}
            </p>
            <p>
                <code>color: </code> {state.color}
            </p>
            <p>
                <code>isGood: </code> {state.isGood ? 'true' : 'false'}
            </p>
            <div>
                <button onClick={setCount}>SET_COUNT</button>
                <button onClick={setText}>SET_TEXT</button>
                <button onClick={setColor}>SET_COLOR</button>
                <button onClick={toggleGood}>TOGGLE_GOOD</button>
            </div>
        </div>
    );
}

export default ReducerSample;

이 때 이것을 렌더링하면 SampleProvider 를 사용하지 않아 오류가 뜬다.

 

App.tsx

SampleProvider 로 감싸준 뒤에 렌더링 하면 잘 작동되는 것을 확인할 수 있다.

const App: React.FC = () => {
  return (
    <SampleProvider>
      <ReducerSample />
    </SampleProvider>
  );
}

 

이처럼 타입스크립트를 사용해 contextAPI 를 사용하는 것은 자바스크립트를 사용하는 것과 크게 차이나지는 않지만, 

<Generic> 을 작성해주고, 커스텀 Hook 을 만들 때 null 체킹을 해주어야 한다는 차이가 있다.