[English] | 한국어 | 简体中文
state-in-url
은 URL 동기화와 함께 간단한 상태 관리를 제공합니다. 관련 없는 React 컴포넌트 간에 복잡한 상태를 공유하고, 상태를 URL과 동기화하며, TS 친화적이고 NextJS와 호환됩니다.
- 🙃 URL을 변경하지 않고 다른 컴포넌트 간에 상태를 공유합니다. 시그널 및 기타 상태 관리 도구의 대안으로 좋습니다.
- 🔗 전체 애플리케이션 상태가 포함된 공유 가능한 URL
- 🔄 페이지 새로고침 시 상태를 쉽게 유지
- 🧠 관련 없는 클라이언트 컴포넌트 간 데이터 동기화
- 🧮 저장되지 않은 사용자 양식을 URL에 저장
- 🧩 간단함: 프로바이더, 리듀서, 보일러플레이트 또는 새로운 개념이 필요 없습니다.
- 📘 TypeScript 지원 및 타입 안전성: 데이터 타입과 구조를 보존하고, IDE 제안, 강력한 타입 지정 및 JSDoc 주석으로 개발자 경험을 향상시킵니다.
- ⚛️ 프레임워크 유연성: Next.js와 React.js 애플리케이션을 위한 별도의 훅과 순수 JS를 위한 함수 제공
- ⚙ 철저한 테스트: 단위 테스트 및 Playwright 테스트
- ⚡ 빠름: 최소한의 리렌더링
- 🪶 경량: 작은 크기를 위해 의존성이 없습니다.
- 설치
- Next.js용
useUrlState
- React.js용
useSharedState
- React.js용
useUrlEncode
- 순수 JS 사용을 위한
encodeState
및decodeState
- URL과 상태 자동 동기화
- 저수준
encode
및decode
함수 - 모범 사례
- 주의사항
- 연락 및 지원
- 변경 로그
- 라이선스
- 영감
# npm
npm install --save state-in-url
# yarn
yarn add state-in-url
# pnpm
pnpm add state-in-url
tsconfig.json
의 compilerOptions
에서 "moduleResolution": "Node16"
또는 "moduleResolution": "NodeNext"
로 설정하세요.
useUrlState
는 Next.js 애플리케이션을 위한 사용자 정의 React 훅으로, 클라이언트 컴포넌트 간의 통신을 쉽게 만듭니다. 복잡한 상태를 공유하고 URL 검색 매개변수와 동기화할 수 있게 해주어, 페이지 새로고침 시 상태를 유지하고 URL을 통해 애플리케이션 상태를 공유할 수 있습니다.
-
상태 형태 정의
// countState.ts // 상태 형태는 상수에 저장해야 하며, 객체를 직접 전달하지 마세요 export const countState: CountState = { count: 0 } type CountState = { count: number }
-
임포트 및 사용
'use client'
import { useUrlState } from 'state-in-url/next';
import { countState } from './countState';
function MyComponent() {
// 서버 컴포넌트에서 searchParams 사용 시
// 예: export default async function Home({ searchParams }: { searchParams: object }) {
// const { state, updateState, updateUrl } = useUrlState(countState, searchParams);
const { state, updateState, updateUrl } = useUrlState(countState);
// 실수로 상태를 직접 변경하지 못하게 합니다. TS 필요
// state.count = 2 // <- 오류
return (
<div>
<p>카운트: {state.count}</p>
<button onClick={() => updateUrl({ count: state.count + 1 }), { replace: true }}>
증가 (URL 업데이트)
</button>
// React.useState와 동일한 API
<button onClick={() => updateState(currState => ({...currState, count: currState.count + 1 }) )}>
증가 (로컬만)
</button>
<button onClick={() => updateUrl()}>
변경 사항을 URL에 동기화
// 또는 동기화하지 않고 상태만 공유
</button>
<button onClick={() => updateUrl(state)}>
초기화
</button>
</div>
)
}
interface UserSettings {
theme: 'light' | 'dark';
fontSize: number;
notifications: boolean;
}
export const userSettings: UserSettings {
theme: 'light',
fontSize: 16,
notifications: true,
}
'use client'
import { useUrlState } from 'state-in-url/next';
import { userSettings } from './userSettings';
function SettingsComponent() {
// `state`는 UserSettings 타입에서 추론됩니다!
const { state, updateUrl } = useUrlState(userSettings);
const toggleTheme = () => {
updateUrl(current => ({
...current,
theme: current.theme === 'light' ? 'dark' : 'light',
}));
};
// 유휴 상태일 때 상태를 URL에 동기화
const timer = React.useRef(0 as unknown as NodeJS.Timeout);
React.useEffect(() => {
clearTimeout(timer.current);
timer.current = setTimeout(() => {
// 참조가 아닌 내용으로 상태를 비교하고 새 값에 대해서만 업데이트를 실행합니다
updateUrl(state);
}, 500);
return () => {
clearTimeout(timer.current);
};
}, [state, updateUrl]);
return (
<div>
<h2>사용자 설정</h2>
<p>테마: {state.theme}</p>
<p>글꼴 크기: {state.fontSize}px</p>
<button onClick={toggleTheme}>테마 전환</button>
{/* 다른 설정을 업데이트하기 위한 UI 요소 */}
</div>
);
}
...
// 다른 컴포넌트
function Component() {
const { state } = useUrlState(defaultSettings);
return (
<div>
<p>알림은 {state.notifications ? '켜짐' : '꺼짐'}</p>
</div>
)
}
const timer = React.useRef(0 as unknown as NodeJS.Timeout);
React.useEffect(() => {
clearTimeout(timer.current);
timer.current = setTimeout(() => {
// 참조가 아닌 내용으로 상태를 비교하고 새 값에 대해서만 업데이트를 실행합니다
updateUrl(state);
}, 500);
return () => {
clearTimeout(timer.current);
};
}, [state, updateUrl]);
export default async function Home({ searchParams }: { searchParams: object }) {
return (
<Form sp={searchParams} />
)
}
// Form.tsx
'use client'
import React from 'react';
import { useUrlState } from 'state-in-url/next';
import { form } from './form';
const Form = ({ sp }: { sp: object }) => {
const { state, updateState, updateUrl } = useUrlState(form, sp);
}
이는 까다로운 부분입니다. Next.js의 app 라우터는 서버 사이드에서 searchParams에 접근할 수 없게 합니다. 미들웨어를 사용하는 해결책이 있지만, 이는 아름답지 않고 Next.js 업데이트 후 작동이 중단될 수 있습니다.
// 적절한 `layout.tsc`에 추가
export const runtime = 'edge';
// middleware.ts
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const url = request.url?.includes('_next') ? null : request.url;
const sp = url?.split?.('?')?.[1] || '';
const response = NextResponse.next();
if (url !== null) {
response.headers.set('searchParams', sp);
}
return response;
}
// 대상 레이아웃 컴포넌트
import { headers } from 'next/headers';
import { decodeState } from 'state-in-url/encodeState';
export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
const sp = headers().get('searchParams') || '';
return (
<div>
<Comp1 searchParams={decodeState(sp, stateShape)} />
{children}
</div>
);
}
'use client'
import { useUrlState } from 'state-in-url/next';
const someObj = {};
function SettingsComponent() {
const { state, updateUrl, updateState } = useUrlState<object>(someObj);
}
React 컴포넌트 간에 상태를 공유하기 위한 훅으로, Next.js와 Vite에서 테스트되었습니다.
'use client'
import { useSharedState } from 'state-in-url';
export const someState = { name: '' };
function SettingsComponent() {
const { state, setState } = useSharedState(someState);
}
- 일관성을 위해 상태 형태를 상수로 정의하세요
- 향상된 타입 안전성과 자동 완성을 위해 TypeScript를 사용하세요
- URL 매개변수에 민감한 정보를 저장하지 마세요
- 빈번한 업데이트에는
updateState
를, URL에 변경 사항을 동기화하려면updateUrl
을 사용하세요 - Next.js에서 클라이언트 컴포넌트를 감싸려면
Suspense
를 사용하세요 - 읽기 쉬운 TS 오류를 위해 이 확장 프로그램을 사용하세요
- 직렬화 가능한 값만 전달할 수 있습니다.
Function
,BigInt
또는Symbol
은 작동하지 않으며,ArrayBuffer
와 같은 것들도 마찬가지일 것입니다. - Vercel 서버는 헤더(쿼리 문자열 및 기타 항목)의 크기를 14KB로 제한하므로 URL 상태를 ~5000 단어 이하로 유지하세요. https://vercel.com/docs/errors/URL_TOO_LONG
- app 라우터가 있는
next.js
14에서 테스트되었으며, pages를 지원할 계획은 없습니다.
이 저장소를 복제하고 npm install
을 실행한 다음
npm run dev
localhost:3000으로 이동하세요
- 버그 리포트, 기능 요청 또는 질문을 위해 GitHub 이슈를 생성하세요
이 프로젝트는 MIT 라이선스에 따라 라이선스가 부여됩니다.