实现一个自定义 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<typeof useAxios>
,它把我们带到了 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】:为了让编译器对() => 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:useLocalStorageState