Skip to content

Commit

Permalink
docs: update README translations
Browse files Browse the repository at this point in the history
  • Loading branch information
asmyshlyaev177 committed Aug 1, 2024
1 parent 9509a01 commit e159666
Show file tree
Hide file tree
Showing 2 changed files with 279 additions and 103 deletions.
208 changes: 146 additions & 62 deletions README.CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<div align="center">
<img src="/assets/logo.svg?raw=true" alt="库标志"/>

<div>轻松在不相关的 React 组件之间共享状态,具有 IDE 自动完成和 TS 验证功能。无需任何麻烦或样板代码。</div>
<div>轻松在不相关的React组件之间共享状态,具有IDE自动完成和TS验证功能。无需任何麻烦或样板代码。</div>
</div>

<div align="center">
Expand All @@ -20,50 +20,51 @@

<div align="center">

<h4 align="center">如果发现bug,请不要犹豫,立即提出问题</h4>
<h4 align="center">如果发现bug,请随时提出issue</h4>

![演示-gif](/~https://github.com/asmyshlyaev177/state-in-url/assets/19854148/c9802601-4d42-4362-b3e4-37ff87c3b97f)

<a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a> |
<a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">演示</a> |
<a href ="https://codesandbox.io/p/github/asmyshlyaev177/state-in-url/main?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clyk5bd9y00062v6jspcfrkx7%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clyk5bd9x00022v6jyg71cr9e%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clyk5bd9x00042v6jsos2y043%2522%257D%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clyk5bd9x00052v6j5r632b12%2522%257D%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clyk5bd9x00022v6jyg71cr9e%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clyk5bd9w00012v6j83rq3bvo%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clyk5bd9x00022v6jyg71cr9e%2522%252C%2522activeTabId%2522%253A%2522clyk5bd9w00012v6j83rq3bvo%2522%257D%252C%2522clyk5bd9x00052v6j5r632b12%2522%253A%257B%2522id%2522%253A%2522clyk5bd9x00052v6j5r632b12%2522%252C%2522activeTabId%2522%253A%2522clyk5bdjx000b2v6jfc8ae464%2522%252C%2522tabs%2522%253A%255B%257B%2522type%2522%253A%2522SETUP_TASKS%2522%252C%2522id%2522%253A%2522clyk5bdjx000b2v6jfc8ae464%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A2222%252C%2522id%2522%253A%2522clyk5cjbo004d2v6j3u55k74g%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522ENV_SETUP%2522%252C%2522id%2522%253A%2522clyk5h8dp000r2v6j0r7kc7qq%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%252C%2522clyk5bd9x00042v6jsos2y043%2522%253A%257B%2522id%2522%253A%2522clyk5bd9x00042v6jsos2y043%2522%252C%2522activeTabId%2522%253A%2522clyk5cbuv001q2v6joegrxxv6%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clyk5bd9x00032v6jtfhj316o%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522NEW_TERMINAL%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522id%2522%253A%2522clyk5cbuv001q2v6joegrxxv6%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522cleanupDist%2522%252C%2522id%2522%253A%2522clyk5dgvo005i2v6jj950sxft%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D" target="_blank">Demo on Codesandbox</a>

<hr />

给项目加个 ⭐️ 以示支持
添加⭐️来支持项目

<hr />

</div>

## 为什么使用 state-in-url?
## 为什么使用state-in-url

`state-in-url` 是一个简单的状态管理工具,具有 URL 同步功能
`state-in-url`是一个简单的状态管理工具,可以与URL同步。在不相关的React组件之间共享复杂状态,将状态同步到URL,对TS友好,兼容NextJS

# 使用场景

- 🙃 在不改变 URL 的情况下在不同组件间共享状态,是信号和其他状态管理工具的良好替代品
- 🔗 包含完整应用程序状态的可共享 URL
- 🔄 在页面重新加载时轻松保持状态
- 🧠 在不相关的客户端组件间同步数据
- 🧮 在 URL 中存储未保存的用户表单
- 🙃 在不改变URL的情况下,在不同组件之间共享状态,是信号和其他状态管理工具的良好替代方案
- 🔗 包含完整应用程序状态的可共享URL
- 🔄 在页面重新加载之间轻松持久化状态
- 🧠 在不相关的客户端组件之间同步数据
- 🧮 在URL中存储未保存的用户表单

# 特性

- 🧩 **简单**: 无需提供者、减速器、样板代码或新概念
- 📘 **TypeScript 支持和类型安全**: 保留数据类型和结构,通过 IDE 建议、强类型和 JSDoc 注释增强开发体验
- ⚛️ **框架灵活性**: 为 Next.js 和 React.js 应用程序提供单独的钩子,以及纯 JS 函数
-**测试充分**: 单元测试和 Playwright 测试
-**快速**: 最小化重新渲染
- 🪶 **轻量级**: 零依赖,占用空间小
- 🧩 **简单**无需提供者、reducers、样板代码或新概念
- 📘 **TypeScript支持和类型安全**保留数据类型和结构,通过IDE建议、强类型和JSDoc注释增强开发体验
- ⚛️ **框架灵活性**:为Next.js和React.js应用程序提供单独的钩子,以及用于纯JS的函数
-**经过充分测试**:单元测试和Playwright测试
-**快速**:最少的重新渲染
- 🪶 **轻量级**零依赖,占用空间小

## 目录

- [安装](#安装)
- [Next.js 的 `useUrlState`](#nextjs-的-useurlstate-钩子)
- [React.js 的 `useUrlEncode`](#reactjs-的-useurlencode-钩子)
- [纯 JS 使用的 `encodeState``decodeState`](#纯-js-使用的-encodestate-和-decodestate-辅助函数)
- [自动同步状态到 URL](#自动同步状态)
- [低级 `encode``decode` 函数](#encode-和-decode-辅助函数)
- [Next.js的`useUrlState`](#nextjs的useurlstate钩子)
- [React.js的`useSharedState`](#reactjs的usesharedstate钩子)
- [React.js的`useUrlEncode`](#reactjs的useurlencode钩子)
- [纯JS使用的`encodeState``decodeState`](#纯js使用的encodestate和decodestate辅助函数)
- [自动同步状态到URL](#自动同步状态)
- [低级`encode``decode`函数](#encode和decode辅助函数)
- [最佳实践](#最佳实践)
- [注意事项](#注意事项)
- [联系与支持](#联系与支持)
Expand All @@ -84,15 +85,15 @@ yarn add state-in-url
pnpm add state-in-url
```

### 2. 编辑 tsconfig.json
### 2. 编辑tsconfig.json

`tsconfig.json` 中设置 `"moduleResolution": "Node16"``"moduleResolution": "NodeNext"`
`tsconfig.json``compilerOptions`中设置`"moduleResolution": "Node16"``"moduleResolution": "NodeNext"`

## Next.js 的 useUrlState 钩子
## Next.js的useUrlState钩子

[文档](packages/urlstate/useUrlState/README.md)
[文档](ackages/urlstate/next/useUrlState#api)

`useUrlState` 是一个为 Next.js 应用程序设计的自定义 React 钩子,使客户端组件之间的通信变得简单。它允许你共享任何复杂的状态并将其与 URL 搜索参数同步,提供了一种在页面重新加载时保持状态和通过 URL 共享应用程序状态的方法
`useUrlState`是一个为Next.js应用程序设计的自定义React钩子,使客户端组件之间的通信变得简单。它允许你共享任何复杂的状态并将其与URL搜索参数同步,提供了一种在页面重新加载之间持久化状态并通过URL共享应用程序状态的方法

### 使用示例

Expand All @@ -112,33 +113,33 @@ pnpm add state-in-url
```typescript
'use client'
import { useUrlState } from 'state-in-url';
import { useUrlState } from 'state-in-url/next';

import { countState } from './countState';

function MyComponent() {
// 用于从服务器组件使用 searchParams
// 用于从服务器组件使用searchParams
// 例如 export default async function Home({ searchParams }: { searchParams: object }) {
// const { state, updateState, updateUrl } = useUrlState(countState, searchParams);
const { state, updateState, updateUrl } = useUrlState(countState);

// 不会让你意外地直接修改状态,需要 TS
// 不允许你意外地直接修改状态,需要TS
// state.count = 2 // <- 错误

return (
<div>
<p>计数: {state.count}</p>
<p>计数{state.count}</p>

<button onClick={() => updateUrl({ count: state.count + 1 }), { replace: true }}>
增加 (更新 URL)
增加(更新URL
</button>

// 与 React.useState 相同的 API
// 与React.useState相同的API
<button onClick={() => updateState(currState => ({...currState, count: currState.count + 1 }) )}>
增加 (仅本地)
增加仅本地
</button>
<button onClick={() => updateUrl()}>
同步更改到 URL
同步更改到URL
// 或者不同步,只共享状态
</button>

Expand Down Expand Up @@ -168,12 +169,12 @@ export const userSettings: UserSettings {

```typescript
'use client'
import { useUrlState } from 'state-in-url';
import { useUrlState } from 'state-in-url/next';

import { userSettings } from './userSettings';

function SettingsComponent() {
// `state` 将从 UserSettings 类型推断!
// `state` 将从UserSettings类型推断!
const { state, updateUrl } = useUrlState(userSettings);

const toggleTheme = () => {
Expand All @@ -183,12 +184,12 @@ function SettingsComponent() {
}));
};

// 空闲时同步状态到 url
// 空闲时同步状态到URL
const timer = React.useRef(0 as unknown as NodeJS.Timeout);
React.useEffect(() => {
clearTimeout(timer.current);
timer.current = setTimeout(() => {
// 将按内容而不是引用比较状态,并仅对新值触发更新
// 将通过内容而不是引用比较状态,只为新值触发更新
updateUrl(state);
}, 500);

Expand All @@ -200,10 +201,10 @@ function SettingsComponent() {
return (
<div>
<h2>用户设置</h2>
<p>主题: {state.theme}</p>
<p>字体大小: {state.fontSize}px</p>
<p>主题{state.theme}</p>
<p>字体大小{state.fontSize}px</p>
<button onClick={toggleTheme}>切换主题</button>
{/* 其他用于更新其他设置的 UI 元素 */}
{/* 其他UI元素用于更新其他设置 */}
</div>
);
}
Expand All @@ -215,7 +216,7 @@ function Component() {

return (
<div>
<p>通知功能 {state.notifications ? '开启' : '关闭'}</p>
<p>通知 {state.notifications ? '开启' : '关闭'}</p>
</div>
)
}
Expand All @@ -228,7 +229,7 @@ function Component() {
React.useEffect(() => {
clearTimeout(timer.current);
timer.current = setTimeout(() => {
// 将按内容而不是引用比较状态,并仅对新值触发更新
// 将通过内容而不是引用比较状态,只为新值触发更新
updateUrl(state);
}, 500);

Expand All @@ -238,11 +239,76 @@ function Component() {
}, [state, updateUrl]);
```

#### 任意状态形状 (不推荐)
#### 服务器端渲染示例

```typescript
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`组件中使用钩子

这是一个棘手的部分,因为使用app router的nextjs不允许从服务器端访问searchParams。有一个使用中间件的解决方法,但它并不漂亮,而且可能在nextjs更新后停止工作。

```typescript
// 添加到适当的`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;
}

// 目标layout组件
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>
);
}
```

#### 任意状态形状示例(不推荐)

```typescript
'use client'
import { useUrlState } from 'state-in-url';
import { useUrlState } from 'state-in-url/next';

const someObj = {};

Expand All @@ -251,55 +317,73 @@ function SettingsComponent() {
}
```

## React.js 的 `useUrlEncode` 钩子
## React.js的`useSharedState`钩子
用于在任何React组件之间共享状态的钩子,已在Next.js和Vite中测试。

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

export const someState = { name: '' };

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

[文档](packages/urlstate/useSharedState/README.md)

## React.js的`useUrlEncode`钩子

[文档](packages/urlstate/useUrlEncode/README.md)

## 纯 JS 使用的 `encodeState``decodeState` 辅助函数
## 纯JS使用的`encodeState``decodeState`辅助函数

[文档](packages/urlstate/encodeState/README.md)

## `encode``decode` 辅助函数
## `encode``decode`辅助函数

[文档](packages/urlstate/encoder/README.md)

## 最佳实践

- 将状态形状定义为常量以确保一致性
- 使用 TypeScript 以增强类型安全性和自动完成功能
- 避免在 URL 参数中存储敏感信息
- 对频繁更新使用 `updateState`,使用 `updateUrl` 同步更改到 url
- 使用此[扩展](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors)以获得可读的 TS 错误
- 将您的状态形状定义为常量,以确保一致性
- 使用TypeScript以增强类型安全性和自动完成功能
- 避免在URL参数中存储敏感信息
- 使用`updateState`进行频繁更新,使用`updateUrl`同步更改到URL
- 在Next.js中使用`Suspence`包装客户端组件
- 使用这个[扩展](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors)以获得可读的TS错误

## 注意事项

1. 只能传递可序列化的值,`Function``BigInt``Symbol` 不起作用,`ArrayBuffer` 之类的东西也可能不行。
2. Vercel 服务器将头部大小(查询字符串和其他内容)限制为 **14KB**,因此请将 URL 状态保持在约 5000 字以内。 https://vercel.com/docs/errors/URL_TOO_LONG
1. 只能传递可序列化的值,`Function``BigInt``Symbol`不会工作,可能类似`ArrayBuffer`的东西也不行。
2. Vercel服务器限制头部(查询字符串和其他内容)的大小为**14KB**,所以保持您的URL状态在~5000个单词以下。https://vercel.com/docs/errors/URL_TOO_LONG
3. 已在使用app router的`next.js` 14中测试,不计划支持pages。

## 本地运行

克隆此仓库,运行 `npm install` 然后
克隆此仓库,运行`npm install`然后

```sh
npm run dev
```

转到 [localhost:3000](http://localhost:3000)
转到[localhost:3000](http://localhost:3000)

## 联系与支持

- 创建 [GitHub issue](/~https://github.com/asmyshlyaev177/state-in-url/issues) 以报告错误、请求功能或提出问题
- 创建[GitHub issue](/~https://github.com/asmyshlyaev177/state-in-url/issues)报告bug、提出功能请求或提问

## [更新日志](/~https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)

## 许可证

本项目采用 [MIT 许可证](/~https://github.com/asmyshlyaev177/state-in-url/blob/main/LICENSE)
本项目采用[MIT许可证](/~https://github.com/asmyshlyaev177/state-in-url/blob/main/LICENSE)

## 灵感来源

[在 Vue 中使用 URL 存储状态](https://dev.to/jacobandrewsky/using-url-to-store-state-in-vue-275c)
[在Vue中使用URL存储状态](https://dev.to/jacobandrewsky/using-url-to-store-state-in-vue-275c)

[在 URL 中存储状态](https://antonz.org/storing-state/)
[在URL中存储状态](https://antonz.org/storing-state/)

[NextJS useSearchParams](https://nextjs.org/docs/app/api-reference/functions/use-search-params)
Loading

0 comments on commit e159666

Please sign in to comment.