검색 params
라우트 params는 앱이 어느 페이지에 있는지 알려줍니다. 검색 params(쿼리 params라고도 불림)는 해당 페이지가 어떻게 보여야 하는지를 설명합니다. 어떤 필터가 활성화되어 있는지, 리스트가 어떤 기준으로 정렬되어 있는지, 결과의 어느 페이지가 표시되고 있는지를 나타냅니다. 검색 params는 URL에서 물음표 뒤에 key/value 쌍으로 위치하며, /vans?type=rugged처럼 보이고, 추가 쌍들은 앰퍼샌드로 연결됩니다: /vans?type=rugged&sort=price. URL의 일부이기 때문에 새로고침을 해도 유지되고 공유 링크에 포함되므로, useState가 관리할 수 있는 것과는 다른 종류의 상태입니다.
URL에 들어가야 하는 상태
useState로 관리하는 상태는 메모리에 살아있습니다. 페이지를 새로고침하면 초기값으로 리셋되고, URL을 친구에게 복사해주면 친구는 여러분의 선택이 전혀 반영되지 않은 상태에서 시작합니다. 많은 상태에는 이게 정확하게 맞습니다. 하지만 어떤 상태에는 손실이 됩니다. 밴 리스트를 rugged 타입이고 특정 가격대 이하인 것으로 좁혀서 보고 있다면, 새로고침 후에도 그 필터링된 보기를 유지하고 싶을 겁니다. 그리고 복사한 링크를 열면 다른 사람도 같은 큐레이션된 리스트를 봐야 합니다.
코스에서 제시하는 유용한 테스트는 이렇습니다. 사용자가 이 페이지를 정확하게 다시 방문하거나 공유했을 때 같은 결과를 얻을 수 있어야 할까요? 그렇다면 그 상태를 React 밖으로 꺼내 검색 param으로 URL에 넣는 걸 고려해보세요. 필터링, 정렬, 페이지네이션이 전형적인 후보입니다. 그러면 URL이 그 상태에 대한 유일한 정보 출처가 되고, 컴포넌트는 state나 props에서 파생하듯 URL에서 파생한 것을 렌더링합니다.
useSearchParams로 params 읽기
React Router는 useSearchParams 훅을 통해 쿼리 문자열을 노출하고, 그 형태는 의도적으로 useState와 가깝습니다. 현재 값과 setter를 담은 배열입니다.
import { useSearchParams } from 'react-router-dom'
export default function CharacterList() {
const [searchParams, setSearchParams] = useSearchParams()
const typeFilter = searchParams.get('type')
// ...
}searchParams는 평범한 객체가 아니라 브라우저의 네이티브 URLSearchParams 객체의 인스턴스이므로, 그 메서드들을 통해 상호작용합니다. .get('type')은 type param의 값을 문자열로 반환하고, 그 param이 URL에 없으면 null을 반환합니다. 그 null이 활성화된 필터가 없다는 것을 코드가 알 수 있게 합니다. .toString()은 전체 세트를 type=sith&sort=price 같은 쿼리 문자열로 직렬화하며, 앞의 물음표는 포함하지 않습니다.
버전 참고
여기 예제들은 react-router-dom에서 import하며, 이는 v6과 v7 모두에서 작동합니다. Routing에서 v7이 패키지들에 대해 어떤 변화를 가져왔는지 다룹니다.
param에서 리스트 필터링하기
param을 손에 들었으니, 필터링은 컴포넌트 맨 위에서 일반 JavaScript입니다. state도 없고, effect도 없습니다. param을 읽고 배열을 필터링하는 것 모두 빠르므로, 매 렌더링마다 작업을 다시 하고 URL에서 나온 결과를 렌더링하는 것도 괜찮습니다.
const typeFilter = searchParams.get('type')
const displayedCharacters = typeFilter
? characters.filter(char => char.type.toLowerCase() === typeFilter.toLowerCase())
: characters
const charEls = displayedCharacters.map(char => (
<li key={char.name}>{char.name}</li>
))삼항 연산자는 필터가 없는 경우를 처리합니다. .get이 null을 반환하면 전체 리스트가 표시됩니다. 이 방식으로 어떤 배열을 렌더링할지 고르는 것은 조건부 렌더링에서 사용한 것과 같은 파생-렌더링 이동인데, state 대신 URL에 의해 구동됩니다. 대소문자 불일치도 주의하세요. 데이터는 "Sith"를 저장하지만 손으로 편집한 링크는 Sith나 sith를 가질 수 있으므로, 비교 전에 양쪽을 소문자로 변환합니다.
Links로 params 설정하기
검색 param을 URL에 넣는 가장 직접적인 방법은 to가 물음표로 시작하는 Link입니다. React Router는 앞의 ?를 보고 현재 라우트에 남겨두면서 새 쿼리 문자열로 바꿉니다. 그러면 컴포넌트가 다시 렌더링되고 필터링이 다시 실행됩니다.
<Link to="?type=jedi">Jedi</Link>
<Link to="?type=sith">Sith</Link>
<Link to=".">Clear</Link>초기화할 때는 to="."가 쿼리 없이 현재 경로로 이동합니다. to=""도 작동하며, 코스에서는 더 명시적이므로 점을 사용합니다. Links는 필터가 고정된 가시적 선택지 세트일 때 가장 잘 맞습니다. 실제 앵커 태그로 렌더링되므로 사용자가 필터링된 보기를 새 탭에서 열거나 클릭하기 전에 주소를 복사할 수 있습니다.
setter 함수로 params 설정하기
useSearchParams의 두 번째 요소는 setter이며, useState의 setter처럼 대체 값이나 콜백을 받습니다. 버튼은 Link처럼 React Router 생태계의 일부가 아니므로, 일반 이벤트 핸들러에서 setter를 호출합니다.
<button onClick={() => setSearchParams({ type: 'jedi' })}>Jedi</button>
<button onClick={() => setSearchParams({ type: 'sith' })}>Sith</button>
<button onClick={() => setSearchParams({})}>Clear</button>setter는 뭘 받든 유연합니다. '?type=jedi' 같은 문자열(물음표 있거나 없거나)이 작동하지만, 여기 보여주는 객체 형태가 가장 자주 보게 될 형태입니다. 빈 객체는 모든 것을 초기화합니다. 새 params가 고정된 선택지 클릭이 아니라 로직에서 나올 때 setter를 사용하세요. 폼에서 값 읽기, 사용자가 입력할 때 반응하기, 한 번에 여러 params 설정하기 같은 경우입니다.
대체는 다른 params를 지웁니다
지금까지의 두 가지 접근 방식은 전체 쿼리 문자열을 하드코딩합니다. type이 앱이 사용하는 유일한 param일 때는 괜찮지만, URL이 관련 없는 다른 것, 예를 들어 ?name=jill&type=jedi도 포함하는 순간, 그 links나 buttons 중 하나를 클릭하면 전체 쿼리 문자열이 대체되고 name=jill은 사라집니다. 초기화 버튼들은 더 노골적입니다. URL의 모든 param을 지우는데, 이 컴포넌트가 건드린 적이 없는 것들도 포함됩니다. 프로젝트가 그 한 가지 param만 가질 거라고 확신하면 하드코딩도 괜찮습니다. 그렇지 않으면 병합을 원합니다.
기존 params와 병합하기
Links의 경우, to prop은 문자열을 받으므로, 해결책은 렌더링 중에 실행되는 작은 헬퍼입니다. 현재 params를 새로운 URLSearchParams에 복사하고, 이동하는 한 가지 key를 변경한 다음, 결과를 직렬화합니다. 훅에서 searchParams를 읽으므로 컴포넌트 안에 살아있습니다.
// CharacterList 안에, searchParams를 읽을 수 있도록
function genNewSearchParamString(key, value) {
const sp = new URLSearchParams(searchParams)
if (value === null) {
sp.delete(key)
} else {
sp.set(key, value)
}
return `?${sp.toString()}`
}<Link to={genNewSearchParamString('type', 'jedi')}>Jedi</Link>
<Link to={genNewSearchParamString('type', 'sith')}>Sith</Link>
<Link to={genNewSearchParamString('type', null)}>Clear</Link>이것은 React Router가 제공하는 것이 아니라 순수 JavaScript입니다. URLSearchParams 생성자는 기존 params 객체를 출발점으로 받기도 좋습니다. .set은 한 가지 key를 업데이트하거나 추가하고, null을 전달하면 헬퍼에게 key를 .delete하라고 신호를 보냅니다. 그래서 "Clear"는 이제 type만 제거하고 URL의 나머지 것들은 그대로 둡니다.
setter의 경우, 콜백 형태를 사용하세요. 콜백은 이전 params 객체를 받고, 한 가지 key를 조정한 다음, 그것을 반환합니다.
// CharacterList 안에, setSearchParams를 호출할 수 있도록
function handleFilterChange(key, value) {
setSearchParams(prevParams => {
if (value === null) {
prevParams.delete(key)
} else {
prevParams.set(key, value)
}
return prevParams
})
}<button onClick={() => handleFilterChange('type', 'jedi')}>Jedi</button>
<button onClick={() => handleFilterChange('type', null)}>Clear</button>한 가지 놀라운 점이 있는데, 코스에서 이를 지적합니다. useState updater와 달리, 이전 상태를 변경하는 것이 금지되어 있는 곳과 달리, 여기서는 prevParams에서 직접 .delete와 .set을 호출하고 반환하는 것이 괜찮습니다. 이제 links와 buttons 모두 자신들이 소유한 param만 변경합니다.
useSearchParams는 읽을 수 있게 합니다. searchParams.get('type')은 값을 반환하거나, param이 없으면 null을 반환합니다. Link로 쿼리 문자열 또는 setter 함수로 params를 설정한 다음, URL이 말하는 대로 리스트를 필터링할 수 있습니다.
다음: 보호된 라우트. 앱의 한 분기가 먼저 누군지 묻습니다.

