useState with URLs:如何用useSearchParams持续保持状态

462 阅读5分钟

React的 useState钩子是在React组件的上下文中持久化状态的一个好方法。这篇文章演示了一个简单的React钩子,它将状态存储在URL查询字符串中,建立在React RoutersuseSearchParams 钩子之上。

useState

useState 钩子的使用看起来像这样:

const [greeting, setGreeting] = useState('hello world');

// ....

setTotal('hello John'); // will set greeting to 'hello John'

然而,使用useState 有一个缺点;该状态是不持久的,不能共享。因此,如果你想让别人看到你在一个应用程序中能看到的东西,你就得依赖他们执行让你的应用程序进入当前状态的相同操作。

这样做既费时又容易出错,所以如果有一个简单的方法来分享状态,那不是很好吗?

一个有状态的URL

在用户之间共享状态的一个有效方法是使用URL,而不需要一个持久化的后台。一个URL可以包含所需的状态,其形式是路由和查询链/搜索参数。搜索参数特别强大,因为它们完全是通用的和可定制的。

多亏了URLSearchParams API,我们可以在不往返服务器的情况操作查询参数--这是我们可以建立的一个基础;只要不超过URL的限制(大约2000个字符),我们就可以自由地在你的URL中持久化状态。

useSearchParams

如果你正在使用React,React Router项目使消耗URL中的状态,特别是以querystring或搜索参数的形式,很简单。它通过 useSearchParams钩子:

import { useSearchParams } from "react-router-dom";

const [searchParams, setSearchParams] = useSearchParams();

const greeting = searchParams.get('greeting');

// ...

setSearchParams({ 'greeting': 'bonjour' }); // will set URL like so https://our-app.com?greeting=bonjour - this value will feed through to anything driven by the URL

这是一个很好的机制,可以在本地和以可共享的方式持久保存状态。

这种方法的一个重要好处是,它不需要发布到服务器上。它只是使用浏览器的API,如URLSearchParams API。改变一个查询字符串参数完全是在本地即时发生的。

useSearchParamsState 钩子

useSearchParams 钩子不做的是维护其他查询字符串或搜索参数。

如果你在你的应用程序中维护多个状态,这可能意味着多个查询字符串或搜索参数。那么,一个允许我们更新状态而不丢失其他状态的钩子将是非常有用的。

此外,如果我们不需要先获取searchParams 对象,然后再对其进行操作,那就更好了。现在是我们的useSearchParamsState 钩子的时候了:

import { useSearchParams } from "react-router-dom";

export function useSearchParamsState(
    searchParamName: string,
    defaultValue: string
): readonly [
    searchParamsState: string,
    setSearchParamsState: (newState: string) => void
] {
    const [searchParams, setSearchParams] = useSearchParams();

    const acquiredSearchParam = searchParams.get(searchParamName);
    const searchParamsState = acquiredSearchParam ?? defaultValue;

    const setSearchParamsState = (newState: string) => {
        const next = Object.assign(
            {},
            [...searchParams.entries()].reduce(
                (o, [key, value]) => ({ ...o, [key]: value }),
                {}
            ),
            { [searchParamName]: newState }
        );
        setSearchParams(next);
    };
    return [searchParamsState, setSearchParamsState];
}

上面的钩子可以大致被认为是useState<string> ;但是将该状态存储在URL中。

让我们思考一下它是如何工作的。当初始化时,该钩子需要两个参数:

  • searchParamName:这是保存状态的querystring参数的名称。
  • defaultValue:如果querystring中没有值,这就是后备值。

然后该钩子继续包裹useSearchParams 钩子。它询问searchParams 中提供的searchParamName ,如果它不存在,就返回到defaultValue

setSearchParamsState 方法的定义看起来有点复杂,但基本上它所做的就是获取现有搜索参数的内容,并为当前属性应用新的状态。

也许值得在这里停顿一下,观察一下潜伏在这个实现中的一个观点。实际上,为同一个搜索参数设置多个值是有效的。虽然这是有可能的,但这在某种程度上是很少被使用的--这个实现只允许任何给定的参数有一个单一的值,因为这是相当有用的行为。

有了这一切,我们就有了一个可以这样使用的钩子:

const [greeting, setGreeting] = useSearchParamsState("greeting", "hello");

上面的代码返回了一个greeting ,这个值来自于greeting 搜索参数。它还返回一个setGreeting 函数,允许我们设置greeting 值。这和useState 的API是一样的,所以对于React的用户来说,应该感到很习惯。巨大的!

在你的网站上持久化查询系统

现在,我们有了这个令人兴奋的机制,它允许我们在我们的URL中存储状态,并且,作为一个结果,通过向别人发送我们的URL,很容易分享状态。

同样有用的是一种在我们的网站上导航而不丢失状态的方法。想象一下,我选择了一个日期范围并存储在我的URL中。当我从一个屏幕点击到另一个屏幕时,我希望能保持这个状态。我不想在每个屏幕上都要重新选择日期范围。

我们怎样才能做到这一点呢?嗯,事实证明这很容易。我们所需要的是useLocation 钩子和相应的location.search 属性。这代表了查询字符串,因此,每次我们渲染一个链接时,我们只需像这样包含它:

 class="ts language-ts">const [location] = useLocation();

return (<Link to={`/my-page${location.search}`}>Page</>)

现在,当我们在我们的网站上导航时,这个状态将被保持。

总结

在这篇文章中,我们创建了一个useSearchParamsState 钩子,它允许状态被持久化到URL中,以达到共享的目的。