기존에는
{
"id": "1",
"username": "Baron",
"text": "아리 누나 너무 이뻐요",
"foward": "AHRI",
"portrait": "https://velog.velcdn.com/images/laejunkim/post/2a6accb8-e1c2-4834-b9b9-e5fb745e8e66/image.webp"
}
이런식으로 portrait 에 사용할 img 를 velog에 올려두고 해당 이미지의 주소를 전달 하는 방식으로 사용했었다. 그러나 이런 방법은 어플리케이션의 속도 저하의 원인이 될 수 있다는 피드백을 받고 내부 파일을 직접 사용하는 것으로 수정하고자 함.
이미 한번 리액트 프로젝트에서 내부 img를 사용해본 경험은 있다. 그때는 src 폴더 내의 img를 import 해오고 적당한 이름을 붙여서 사용하는 방식이었는데 이번에는 그것이 쉽지 않다. 데이터를 별도의 json 파일에 저장해두고 그걸 가져다 쓰는 방식을 사용하고 있기 때문.
따라서 다른 방법을 강구해야 한다.
리액트에서 img를 사용하기 위해 img를 import 해오는것 이외에 다른 방법이 있다. 바로 public folder를 이용하는것.
CRA로 리액트 프로젝트를 만들면 기본적으로 존재하는 public 폴더가 있는데
별도의 설정을 하지 않은 경우 경로를 적을때 그 public 폴더가 시작점이 된다.
public 폴더 안에 이미지를 넣었으면 바로 해당 이미지 파일 이름을 불러와서 사용이 가능하고, public 폴더 안에 img 폴더 안에 portrait 폴더를 만들어서 거기에 이미지가 있는 경우라면
"portrait": "/img/portrait/Baron.webp" 이런식으로 경로를 적어주면 된다.
{
"id": "2",
"username": "칼날부리",
"text": "아칼리 춤 너무 잘춘다 ㄷㄷ",
"foward": "AKALI",
"portrait": "/img/portrait/bladebird.webp"
},
해당 방법을 적용한 이후의 더미 데이터의 일부이다. 이로써 리액트에서 img를 이용 하는 방법을 두가지나 알게 되었다.
컴포넌트의 수가 많아지다 보니 props-drilling이 나타나게 되었다. 지금은 작은 프로젝트이니 그럭저럭 견딜만한 수준이지만 훨씬 규모가 큰 프로젝트라면 코드의 가독성이 많이 떨어질 것이다.
위 사진은 props를 불필요하게 많이 전달하고 있는 상황을 보여준다. 그리고 저기서 끝이 아니고 사진에 보이는 컴포넌트들은 또 각자 child 컴포넌트를 가지며 거기로 props를 전달하고 있다.
context를 이용하여 여러 컴포넌트에서 공통적으로 사용되는 state는 전역 context로 관리하도록 한다.
여러 state들이 있지만 만들어진 컴포넌트에서 바로 사용되는 state들을 제외하면 context로 처리할만한 state는 fanLetters 와 chosenMember 이렇게 두개이다.
fanLetters 를 사용하는 컴포넌트 >> Letters, SubmitLetter, Detail
setFanLetters 를 사용하는 컴포넌트 >> SubmitLetter, Detail
chosenMember 를 사용하는 컴포넌트 >> Header,Letters
setChosenMember 를 사용하는 컴포넌트 >> Header
정리해보면 이렇게 되는데 context를 도입하고 나면 중간단계를 거칠 필요없이 바로 사용할 컴포넌트에서 직접 사용할 수 있게 된다.
1) 먼저 기본 세팅을 한다.
import { createContext } from "react";
const FanLettersContext = createContext(null);
export default FanLettersContext;
2) 위에서 만든 FanLettersContext 로 context를 사용할 컴포넌트들이 모두 포함될 수 있는 컴포넌트를 감싼다. 내 경우에는 App.jsx가 될 것이다
// App.jsx 의 리턴문을 감싼 모습
return (
<>
<GlobalStyle />
<FanLettersContext.Provider
value={{
fanLetters: fanLetters,
setFanLetters: setFanLetters,
chosenMember: chosenMember,
setChosenMember: setChosenMember,
}}
>
<Router />
</FanLettersContext.Provider>
</>
);
이때 반드시 Provider 라고 명시해 주어야 하며 value 객체를 전달해야 한다. value객체에는 이미 App.jsx 에서 useState로 선언해둔 상태들을 그대로 넣어 주면 된다.
3) 여기까지 했으면 이제 중간에 props를 전달만 했던 컴포넌트들에서 props를 모두 제거해야 한다. props를 제거만 해주고 context와 관련된 별도의 코드를 작성할 필요는 없다. 여기서는 context를 사용하지 않을 것이기 때문.
4) 사용할 컴포넌트에서 사용할 준비를 한다. 예시로 Header 컴포넌트를 살펴보자.
import FanLettersContext from "store/fan-letters";
import React, { useState, useContext } from "react";
// (중략)
function Header() {
const ctx = useContext(FanLettersContext);
// (중략)
ctx.setChosenMember(member);
// (중략)
return (
<StHeaderContainer $chosenMember={ctx.chosenMember}>
<StHeaderTitle onClick={titleClickHndlr}>
K/DA 팬레터 사이트
</StHeaderTitle>
// (후략)
context 활용과 관련이 있는 코드만 가져온 모습. 먼저 위에서 만들어둔 context를 import 해와야 하고, 리액트에서 useContext도 import 해야한다.
그 다음 컴포넌트 함수 안에 useContext를 선언하고 변수에 할당한다. 이 경우엔 ctx에 할당했다.
이후엔 기존에 props를 받아서 사용했던 모든 부분을 ctx.자료명 으로 바꿔 적으면 된다. 원래 props.chosenMember 였던 부분이 ctx.chosenMember로 바뀌는 것이라고 생각하면 된다.
사진에서 보이듯이 props-drilling 문제를 완전히 없앨수 있었다. 대단히 만족스럽다. [상황파악] 에서 제시한 사진과 같은 부분의 코드인데 쓸데없는 props 전달이 완전히 사라진 모습이 인상적이다.