实现一个自定义 React Hook:useLocalStorageState

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现一个自定义 React Hook:useLocalStorageState相关的知识,希望对你有一定的参考价值。

参考技术A 大家好,我是前端西瓜哥。

最近做需求,需要将数据保存到 localStorage 里,在组件初始化的时候获取,然后修改该值的时候,要保存到本地的 localStorage 中。

倒是并不难。

很显然,这些逻辑完全可以封装为一个 React Hook,名字很容易想到为 useLocalStorageState。

逻辑并不复杂。就是在读和写的时候,加上 localStorage 的读写逻辑就好了。

用法如下:

其实这个实现还是比较粗糙的,只支持字符串格式,如果你要保存对象,需要自己手动 JSON.parse 和 JSON.stringify 来扩展了数据类型的范围。

我们可以加一下序列化和反序列化支持:

另外,JSON 序列化和反序列方法如果不够用,我们可以再加个自定义序列反序列化方法:

其实优秀的第三方 React Hook 库 ahooks 也有这个实现,我还是建议大家用一些比较成熟的轮子,我这里只是提供一下思路。

ahooks 的 useLocalStorageState 的源码:

https://github.com/alibaba/hooks/blob/v3.4.0/packages/hooks/src/useLocalStorageState/index.ts

如何测试使用自定义 TypeScript React Hook 的组件?

【中文标题】如何测试使用自定义 TypeScript React Hook 的组件?【英文标题】:How to test a component which is using a custom TypeScript React Hook? 【发布时间】:2020-01-15 11:58:42 【问题描述】:

我目前正在 Typescript 中编写一个 React 组件,它使用一个名为 useAxios 的 axios-hooks 钩子。这个钩子的一个使用例子是here:

 export const App = () => 
  const [ data, loading, error , refetch] = useAxios(
    "https://api.myjson.com/bins/820fc"
  );

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error!</p>;

  return (
    <div>
      <button onClick=e => refetch()>refetch</button>
      <pre>JSON.stringify(data, null, 2)</pre>
    </div>
  );
;

const rootElement = document.getElementById("root");
render(<App />, rootElement);

我试图弄清楚如何编写一个可以模拟 useAxios 钩子的测试。我尝试创建底层 axios 组件的模拟,但无法正常工作:

import React from "react"
import  render  from "@testing-library/react"
import  Test  from "../test"
import useAxios from "axios-hooks"

jest.mock("axios-hooks")
const mockedAxios = useAxios as jest.Mocked<typeof useAxios>

it("Displays loading", () => 
  // How to mock the return values in the following line?
  // mockedAxios.

  const  getByText  = render(<Test />)

  expect(getByText("Loading...")).toBeDefined()
)

我知道我不应该模拟作为底层依赖项的 axios,我应该能够模拟 useAxios,但无论如何我都会尝试。

我意识到这个问题在 SO 上已经被多次提及,但我可以找到解决这个特定用例的方法。

非常感谢任何帮助!

【问题讨论】:

如果你jest.mock("axios-hooks") 和后来的useAxious.mockResolvedValue(dataToRespond) 会发生什么?还是您需要基于 URL 的动态模拟? 当我使用打字稿时,我似乎无法做到这一点。我认为有办法使用 TS 做到这一点? 所以问题特定于 TS 中的类型检查,对吧?对我来说,它看起来像是测试运行,但模拟并没有在这样的事情上工作 @skyboyer 我用示例测试更新了我的问题。我试图弄清楚如何模拟 TS 中的钩子返回值。 【参考方案1】:

模拟模块并为每个测试设置useAxios 的预期结果,例如

jest.mock('axios-hooks');

import useAxios from 'axios-hooks';

test('App displays loading when request is fetching', () => 
  useAxios.mockReturnValue(Promise.resolve( loading: true ));
  // mount component
  // Verify "Loading" message is rendered
);

【讨论】:

我正在使用 TypeScript,但这个答案似乎不起作用 @BenSmith TS 不应该真正参与其中,究竟是什么不起作用? 看来确实如此。另请参阅我更新的问题,其中包含用 TS 编写的不完整测试。 @BenSmith TS 应该是非侵入式的(除非改变了),所以上面的代码应该运行。看看你的例子,你已经添加了const mockedAxios = useAxios as jest.Mocked&lt;typeof useAxios&gt;,它把我们带到了 TS 领域,但这真的有必要吗?您是否按原样尝试过没有任何 TS 的代码?【参考方案2】:

我自己想出了如何做到这一点。为了测试自定义钩子,我做了以下操作:

import * as useAxios from "axios-hooks"
jest.mock("axios-hooks")
const mockedAxios = useAxios as jest.Mocked<typeof useAxios>

it("Displays loading message", async () => 

  // Explicitly define what should be returned
  mockedAxios.default.mockImplementation(() => [
      
        data: [],
        loading: true,
        error: undefined
      ,
      () => undefined
    ])

  const  getByText  = render(<Test />)

  expect(getByText("Loading...")).toBeDefined()
)

【讨论】:

【参考方案3】:

为了让编译器对() =&gt; undefined 参数感到满意,我不得不跳过一些额外的环节。我不是双重as 的粉丝,但我不知道如何使它不那么冗长,因为我是 TS 新手。

import * as useAxios from 'axios-hooks';
import  AxiosPromise  from 'axios';
import React from 'react';
import Test from 'components/Test';
import  render  from '@testing-library/react';

jest.mock('axios-hooks');
const mockedUseAxios = useAxios as jest.Mocked<typeof useAxios>;

it('renders a loading message', async () => 
  mockedUseAxios.default.mockImplementation(() => [
    
      data: [],
      loading: true,
      error: undefined,
    ,
    (): AxiosPromise => (undefined as unknown) as AxiosPromise<unknown>,
  ]);

  const  getByText  = render(<Test />);

  expect(getByText('Loading...')).toBeDefined();
);

【讨论】:

您的答案与我的完全相同,因为返回函数的变化。你为什么不发表评论? 我考虑了这一点,但我的回答还包括额外的导入语句以及您的示例中缺少的相关导入。非常感谢您在这里提出问题并为故障排除提供良好的起点。有时,在单个标记实例中以整体合理的方法获得答案会很好。 好吧,说得很好,我很高兴能帮上忙。我经常参考我自己的答案来嘲笑这个库,因为它每次都让我着迷!

以上是关于实现一个自定义 React Hook:useLocalStorageState的主要内容,如果未能解决你的问题,请参考以下文章

如何写自定义 React Hook?

实现一个自定义 React Hook:useLocalStorageState

实现一个自定义 React Hook:useLocalStorageState

返回组件数组的 React 自定义 Hook

如何为每个测试模拟不同的 useLocation 值?

如何测试使用自定义 TypeScript React Hook 的组件?