乐观 URL 状态

真实应用中,URL 更新不只是 setSearchParams

用户可能连续输入、切换筛选器、点击分页。Decurl 在 React Router 集成层提供 optimistic search state:

  1. 用户调用 setter。
  2. Decurl 立即更新本地 snapshot,让 UI 先响应。
  3. 多次 patch 会被合并或重放。
  4. 到达 flush 时机后,再调用 React Router navigate。
  5. React Router 确认 location 后,store 与实际 URL 对齐。

为什么需要 optimistic

没有 optimistic 时,UI 很容易被 router 更新节奏限制。尤其是输入框或复杂筛选器,用户期望界面立即反应。

Decurl 的 setter 会先把 patch 应用到本地 search snapshot:

setValues({ q: 'router', page: 1 });

组件读取到的 values 会立刻变化,不需要等待浏览器地址栏完成更新。

Patch 重放

当多个更新连续发生时,Decurl 会按顺序重放 patch。

每一次 setSearchValuesetSearchValues 调用,都会在本地 store 中形成一个待处理的 update entry。entry 记录这次更新的 patch 和 navigate options;在 flush 之前,这些 entry 会先作用在本地 search snapshot 上,让组件立即读到新值。

setValues({ q: 'router', page: 1 });
setValues((values) => ({ page: values.page + 1 }));

重放时,Decurl 会按 entry 创建顺序依次应用 patch。第二个 updater 会基于第一个 patch 后的中间值计算,而不是基于旧 URL。

这对分页、筛选器联动和批量更新很重要。

Flush 策略

需要配置 flush 策略时,可以在 SearchProvider 上设置 store options。

<SearchProvider flushDelay={80} flushMode="debounce">
  <App />
</SearchProvider>

常见策略:

策略适合场景
throttle用户连续点击时限制 URL 写入频率
debounce输入框搜索、筛选器输入中等待用户停顿
flushDelay: 0尽快刷新到 router

外部 location 变化

浏览器 back/forward 或其他组件触发 navigation 时,React Router 的 location 可能从外部改变。

Decurl 会把外部 location 视为新的事实来源,并丢弃不再适用的 pending patch。

这保证了 URL 的最终语义仍然由 router location 决定,而不是由本地 store 单方面决定。

与其他 search params 管理方式共存

Decurl 可以和其他基于 React Router 的 search params 管理方式共存,例如 React Router 自带的 useSearchParams

Decurl 把 React Router 的 location 作为唯一可信输入。只要其他工具也是通过 React Router 更新 URL,Decurl 就会在 location 变化后重新读取当前 URL,并按 Search Fields 重新 decode。

这个特性对已有项目的渐进迁移比较友好。你可以先让新页面或部分字段使用 Decurl,其他历史代码继续使用原有的 search params 管理方式。如果 flushDelay 较短,Decurl 的 pending entry 通常会很快写入 URL,和其他管理器发生竞争的时间窗口也会比较小。

需要注意的是,Decurl 不会理解其他 search params 管理器内部的状态。如果 Decurl 还有尚未 flush 到 URL 的 pending entry,而另一个管理器先更新了 React Router location,Decurl 会以新的 location 为准,并清理这些尚未提交的 entry。

因此,迁移期可以让不同工具共存;但在同一个页面里,仍然建议让同一组 search params 由一种方式负责,避免多个管理器同时更新同一个 key。

这个行为保证了 URL 始终是最终状态来源:一旦 React Router location 被确认,Decurl 会放弃本地尚未提交的中间状态。

默认 navigate 行为

Search state 更新默认使用 { replace: true },因为输入和筛选器变化通常不应该制造大量 history entry。

如果某次更新需要创建新的 history entry,可以在 setter 的第二个参数中覆盖默认值:

setValues({ page: 2 }, { replace: false });

其他选项及默认值见 Navigate options