Skip to content

Latest commit

 

History

History
395 lines (283 loc) · 12.3 KB

README.KO.md

File metadata and controls

395 lines (283 loc) · 12.3 KB

[English] | 한국어 | 简体中文

state-in-url logo
IDE 자동 완성 및 TS 검증과 함께 관련 없는 React 컴포넌트 간에 상태를 쉽게 공유할 수 있습니다. 번거로움이나 보일러플레이트 없이 사용 가능합니다.

npm Tests Codacy Badge Commitizen friendly npm bundle size (minified + gzip)

버그를 발견하셨다면 주저하지 마시고 이슈를 열어주세요

데모-gif

데모 | DEMO2 |


프로젝트를 지원하려면 ⭐️을 추가해주세요!


state-in-url을 사용해야 하는 이유는?

state-in-url은 URL 동기화와 함께 간단한 상태 관리를 제공합니다. 관련 없는 React 컴포넌트 간에 복잡한 상태를 공유하고, 상태를 URL과 동기화하며, TS 친화적이고 NextJS와 호환됩니다.

사용 사례

  • 🙃 URL을 변경하지 않고 다른 컴포넌트 간에 상태를 공유합니다. 시그널 및 기타 상태 관리 도구의 대안으로 좋습니다.
  • 🔗 전체 애플리케이션 상태가 포함된 공유 가능한 URL
  • 🔄 페이지 새로고침 시 상태를 쉽게 유지
  • 🧠 관련 없는 클라이언트 컴포넌트 간 데이터 동기화
  • 🧮 저장되지 않은 사용자 양식을 URL에 저장

특징

  • 🧩 간단함: 프로바이더, 리듀서, 보일러플레이트 또는 새로운 개념이 필요 없습니다.
  • 📘 TypeScript 지원 및 타입 안전성: 데이터 타입과 구조를 보존하고, IDE 제안, 강력한 타입 지정 및 JSDoc 주석으로 개발자 경험을 향상시킵니다.
  • ⚛️ 프레임워크 유연성: Next.js와 React.js 애플리케이션을 위한 별도의 훅과 순수 JS를 위한 함수 제공
  • 철저한 테스트: 단위 테스트 및 Playwright 테스트
  • 빠름: 최소한의 리렌더링
  • 🪶 경량: 작은 크기를 위해 의존성이 없습니다.

목차

설치

1. 패키지 설치

# npm
npm install --save state-in-url
# yarn
yarn add state-in-url
# pnpm
pnpm add state-in-url

2. tsconfig.json 수정

tsconfig.jsoncompilerOptions에서 "moduleResolution": "Node16" 또는 "moduleResolution": "NodeNext"로 설정하세요.

Next.js용 useUrlState 훅

문서

useUrlState는 Next.js 애플리케이션을 위한 사용자 정의 React 훅으로, 클라이언트 컴포넌트 간의 통신을 쉽게 만듭니다. 복잡한 상태를 공유하고 URL 검색 매개변수와 동기화할 수 있게 해주어, 페이지 새로고침 시 상태를 유지하고 URL을 통해 애플리케이션 상태를 공유할 수 있습니다.

사용 예시

기본

  1. 상태 형태 정의

    // countState.ts
    // 상태 형태는 상수에 저장해야 하며, 객체를 직접 전달하지 마세요
    export const countState: CountState = { count: 0 }
    
    type CountState = { count: number }
  2. 임포트 및 사용

'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);
}

layout 컴포넌트에서 훅 사용

이는 까다로운 부분입니다. 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.js용 useSharedState

React 컴포넌트 간에 상태를 공유하기 위한 훅으로, Next.js와 Vite에서 테스트되었습니다.

'use client'
import { useSharedState } from 'state-in-url';

export const someState = { name: '' };

function SettingsComponent() {
  const { state, setState } = useSharedState(someState);
}

문서

React.js용 useUrlEncode

문서

encodeStatedecodeState 헬퍼

문서

encodedecode 헬퍼

문서

모범 사례

  • 일관성을 위해 상태 형태를 상수로 정의하세요
  • 향상된 타입 안전성과 자동 완성을 위해 TypeScript를 사용하세요
  • URL 매개변수에 민감한 정보를 저장하지 마세요
  • 빈번한 업데이트에는 updateState를, URL에 변경 사항을 동기화하려면 updateUrl을 사용하세요
  • Next.js에서 클라이언트 컴포넌트를 감싸려면 Suspense를 사용하세요
  • 읽기 쉬운 TS 오류를 위해 이 확장 프로그램을 사용하세요

주의사항

  1. 직렬화 가능한 값만 전달할 수 있습니다. Function, BigInt 또는 Symbol은 작동하지 않으며, ArrayBuffer와 같은 것들도 마찬가지일 것입니다.
  2. Vercel 서버는 헤더(쿼리 문자열 및 기타 항목)의 크기를 14KB로 제한하므로 URL 상태를 ~5000 단어 이하로 유지하세요. https://vercel.com/docs/errors/URL_TOO_LONG
  3. app 라우터가 있는 next.js 14에서 테스트되었으며, pages를 지원할 계획은 없습니다.

로컬에서 실행

이 저장소를 복제하고 npm install을 실행한 다음

npm run dev

localhost:3000으로 이동하세요

연락 및 지원

  • 버그 리포트, 기능 요청 또는 질문을 위해 GitHub 이슈를 생성하세요

라이선스

이 프로젝트는 MIT 라이선스에 따라 라이선스가 부여됩니다.

영감

Vue에서 URL을 사용하여 상태 저장하기

URL에 상태 저장하기

NextJS useSearchParams