Udemy <한 입 크기로 잘라먹는 리액트>
- Props Drilling이 발생하면 유지 보수도 어렵고, 디버깅도 어려울 수 있다. 이런 상황을 위해 리액트는 상태를 좀 더 쉽게 관리할 수 있도록
createContext
메서드를 제공한다.
createContext
는 컴포넌트들이 상태를 등록하고 사용할 수 있는 Context를 제공한다. Context는 애플리케이션에서 전역적으로 접근하고 싶은 데이터를 저장할 수 있는 공간이다.
createContext
는 보통 컴포넌트 외부에서 정의하고 호출한다. 컴포넌트 리렌더링 시 새로운 Context를 계속 다시 만들면 비효율이 발생하고, 관리되는 상태에 문제가 생길 수 있기 때문이다.
createContext()
의 결과로 생성된 컨텍스트 객체에는 provider
와 consumer
라는 메서드가 존재한다. 상위 컴포넌트에서 컨텍스트 값을 지정하기 위해서 provider
를 사용하고, 하위 컴포넌트에서 읽기 위해서 consumer
를 사용한다. consumer
는 '대안적으로 드물게 사용되는' 방식이고(useContext
등장 전까지 사용되던 방법이고, 현재는 권장되지 않는다), 실제론 useContext(someContext)
를 일반적으로 사용한다. useContext
는 컴포넌트에서 컨텍스트를 읽고 구독할 수 있도록 해준 리액트 훅이다.
createContext()
로 생성한 컨텍스트 객체는 컴포넌트로 사용될 수 있다. 예를 들어 const ItemContext = createContext()
라고 선언-할당 되었으면, <ItemContext.Provider />
라고 컴포넌트처럼 사용할 수 있다.
<ItemContext.Provider />
와 같이 컨텍스트를 나타내는 컴포넌트 하위에는 데이터를 공유하려는 컴포넌트들을 위치시킨다. 그리고, value
라는 속성에 값으로 컨텍스트에 포함시키고 싶은 상태를 정의한다. 아래와 같은 방식이다.
return (
<>
<ItemContext.Provider value={{ todos, createItem, updateItem, deleteItem }}>
<Header date={date}></Header>
<Lists></Lists>
</ItemContext.Provider>
</>
);
- 컨텍스트를 사용해야 하는 위치에선
useContext
의 인자로 생성한 컨텍스트 객체(ItemContext
)를 전달하면 된다. 만약에 컨텍스트 객체를 생성한 파일 모듈과 컴포넌트 모듈이 분리되어 있다면 export
-import
한 후 사용해야 한다. 이렇게 useContext
의 인자로 컨텍스트 객체를 전달하면 useContext
를 호출한 컴포넌트에서 컨텍스트를 읽을 수 있다.
useContext
는 항상 호출하는 컴포넌트의 상위에서 가장 가까운 컴포넌트 객체.Provider
를 찾는다. useContext
를 호출하는 컴포넌트 안의 provider
는 고려하지 않는다.
createContext
로 생성된 컨텍스트는 관리하는 상태가 변경될 시 해당 상태를 구독한 컴포넌트들을 전부 자동으로 리렌더링 해준다.
createContext
로 컨텍스트 객체를 생성한 뒤 구독하도록 하는 방식에서 useMemo
, useCallback
, memo
등 훅이나 리액트 내장 메서드를 사용할 경우 전달되는 참조형 데이터 상태의 참조가 변경되는지 아닌지를 잘 확인해야 한다. 만약에 참조가 변경되지 않도록 최적화 처리를 한 객체-배열-함수인데 다른 상태들과 같은 컨텍스트에 묶여서 리렌더링이 발생하게 된다면 컨텍스트를 두 개로 분리해서 관리하는 걸 고려하는 게 필요하다.
- 리액트 SPA 애플리케이션 페이지 라우팅에 많이 사용되는 도구로
react-router
가 있다. react-router
를 사용하면 손쉽게 리액트 페이지를 라우팅 처리할 수 있다.
react-router
를 사용하려면 main.jsx
에 react-router-dom
에서 { BrowserRouter }
를 불러와 아래와 같이 <App />
컴포넌트를 감싸주면 된다.
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import { BrowserRouter } from 'react-router-dom';
createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
BrowserRouter
는 root
노드 하위에 있는 모든 컴포넌트들에게 page path에 대한 정보를 전달한다. 이때, 내부적으론 <Navigation.Provider />
와 <Location.Provider />
를 통해 경로 관련 정보가 전달된다.
App
컴포넌트에선 react-router-dom
의 Routers
와 Router
컴포넌트를 불러와 사용해 주면 된다. Routers
는 Router
컴포넌트들을 감싸주는 부모 역할을 하고, Route
컴포넌트는 접속하는 URL 경로에 따라 보여줄 페이지를 설정해 주는 데 사용한다.
Route
컴포넌트의 path
에는 접속 경로를, element
에는 해당 경로로 접속했을 때 보여줄 페이지 컴포넌트를 전달한다. 아래와 같은 형식으로 사용한다.
// ... 필요한 것들 import
import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path='/' element={<Home />}></Route>
<Route path='/new' element={<New />}></Route>
<Route path='/diary' element={<Diary />}></Route>
<Route path='*' element={<NotFound />}></Route>
</Routes>
);
}
- 잘못된 경로로 들어왔을 땐 에러 페이지를 내려줘야 하는데, 이땐
path
에 *
를 입력해준다. 그러면 위에 지정해 준 경로 이외의 모든 경로에 대해 <NotFound />
컴포넌트를 렌더링해 줄 수 있다.
react-router-dom
을 사용해서 페이지를 이동하는 기능을 만들 땐 Link
컴포넌트와 useNavigate
훅을 사용할 수 있다. Link
는 HTML의 <a>
태그와 유사하고, useNavigate
는 경로를 이동시켜주는 Navigate 함수를 반환해 준다.
- 아래와 같이 작성하면
<Link>
컴포넌트를 클릭했을 땐 to
에 설정해 준 경로로, onClickButton
함수가 이벤트 핸들러로 등록된 button
을 클릭하면 nav
네비게이터 함수에 설정한 경로로 이동할 수 있다.
- 리액트에서 동적 라우팅을 구현하는 건 URL Parameter, Query Parameter 두 가지 방식이 있다. URL Parameter는
/diary/1
과 같이 pathname 뒤에 /
로 구분하고 이동할 경로를 추가해 주는 방식이다. 해당 방식을 구현하기 위해선 <Route>
컴포넌트의 path
뒤에 접근할 URL Parameter 정보를 :id
와 같이 전달해줘야 한다(id
는 임의로 넣어준 값이다. :
뒤의 값은 이후에 useParam
으로 생성한 객체의 프로퍼티가 된다).
// Diary 페이지에 동적 URL Parameter 설정
// 설정 이후부턴 그냥 `/diary` 경로론 접근이 어렵고, 뒤에 id 정보를 붙여줘야 유효한 경로가 됨
<Route path='/diary/:id' element={<Diary />}></Route>
- URL Parameter로 설정한 값은
useParam
훅을 사용해 값을 받을 수 있음. 위와 같이 /diary/:id
로 경로를 설정했으면, const param = useParam();
으로 설정한 param
객체의 param.id
로 URL Parameter에 접근할 수 있다.
- Query Parameter는
Route
컴포넌트의 path
에는 별도의 처리를 하지 않아도 되고, 경로에 대한 정보를 사용하는 곳에서 useSearchParam
훅을 사용하면 path 정보에 접근할 수 있다.
- 사용 방식은
useState
와 흡사하다. params
은 쿼리 파라미터의 상태를 나타내고, setParams
는 쿼리 파라미터의 상태를 변경하는 호출 메서드를 의미한다.
import { useSearchParam } from
import { useSearchParams } from 'react-router-dom';
const Home = () => {
const [params, setParams] = useSearchParams();
console.log(params.get('value')); // ?value={value}에 들어가는 value 값을 캐치
return <>Home</>;
};
export default Home;