형제 컴포넌트 간의 상태 전달을 Context API를 통해 실현해보자.
내가 원하는 작업은 형제 컴포넌트(quiz, result) 간의 데이터 전송이고, 검색해 본 결과 전역상태관리를 할 수 있게 해주는 context api를 사용해야 한다는 것을 알게 되었다.
전역상태관리로 유명한 리덕스와 마찬가지로 글로벌하게 상태관리가 가능하다. Context ApI는 따로 설치하지 않아도 리액트에서 기본으로 지원하는 API기 때문에 호출해서 쓰기만 하면된다.
Context는 수단일 뿐 상태관리 자체는 useState와 useReducer 로 하게 된다.
리덕스와의 주요 차이는 '성능'이다.
Redux
는 컴포넌트에서 전역상태의 특정 값을 의존하게 될 때 해당 값이 바뀔 때에만 리렌더링이 되도록 최적화가 되어있기 때문에 전역상태 중 의존하지 않는 값이 바뀌게 될 때에는 컴포넌트에서 낭비 렌더링이 발생하지 않게된다.
반면 Context
에는 성능 최적화가 이뤄지지 않는다. 컴포넌트에서 만약 Context의 특정 값을 의존하는 경우, 해당 값 이외에 다른 값이 변경 될 때에도 컴포넌트에서는 리렌더링이 발생한다.
따라서, 서로 관련이 없는 상태라면 같은 Context를 따로 만들어줘야한다.
src폴더 안에 Contexts폴더를 만들고, 그 안에 ButtonContext.tsx 파일을 생성한다.
전역으로 관리할 state를 모아놓을 provider(store).
// context.js
import { createContext, useContext, useState } from 'react';
export const ResultContext = createContext(undefined);
// createContext 선언
export function ResultContextProvider({ children }) {
const [result, setResult] = useState([]); ////글로벌하게 관리할 state
const value = {
result,
setResult,
};
return <ResultContext.Provider value={value}>{children}</ResultContext.Provider>;
}
export function useResultContext() {
return useContext(ResultContext);
} //다른 컴포넌트에서 context를 사용할 때 쓰임.
가장 최상단 컴포넌트인 Routes.js 컴포넌트에서 ResultContextProvider를 불러와서 기존 내용을 감싸주어야 한다. 부모 컴포넌트에 두 형제가 접근 가능하기 때문에 부모 컴포넌트에 provider를 불러와야 하는 것.
import { ResultContextProvider } from './Contexts/ResultContext'; //provider 불러오기
function Routes() {
return (
<ResultContextProvider> //컴포넌트 감싸주기
<Router>
<Switch>
<Route exact path="/" component={Main} />
<Route exact path="/quiz" component={Quiz} />
<Route exact path="/result" component={Result} />
</Switch>
</Router>
</ResultContextProvider>
);
}
context에 입력했던 state를 useContext로 사용할 컴포넌트로 가져온다.
//Result.js
import {useResultContext } from '../../Contexts/ResultContext';
// useContext 가져오기
function Result() {
const { result } = useResultContext();
return (
<ResultWrapper>
{result.map((dog, idx) => {
return (
<Card
key={idx}
img={dog.img}
name={dog.name}
averageSize={dog.averageSize}
description={dog.description}
/>
);
})}
</ResultWrapper>
);
}
// Quiz.js 자식 button.js
import { useResultContext } from '../../Contexts/ResultContext';
function Button({ id, question, symbol, selections, number, setNumber }) {
const [userAnswers, setUserAnswers] = useState({});
const { setResult } = useResultContext();
const checkAnswer = (QId: number, AId: number) => {
const key = QId;
const obj = { [`${key}`]: AId };
setUserAnswers({ ...userAnswers, ...obj });
if (QId === 10) {
axios
.post(ANSWERAPI, userAnswers)
.then((res) => {
setResult(res.data);
})
.catch((error) => console.log(error));
}
};