React 常用 hooks 之 useSyncedQueryParam:如何将组件内的状态与路由参数双向绑定

业务背景

B 端后台系统中常见的业务场景就是表单列表页面,当用户A浏览第三页表单时,通过 url 分享给用户B后希望B用户打开链接后页面和用户A看到的数据一致。这就需要将页面url参数解析并赋值到组件内状态,并且组件内部的状态更改后同步到url中(以便二次分享)。

解决方案

使用浏览器的原生 window.locationURLSearchParams 来实现这个功能。以下是一个示例,展示如何手动控制 URL 和组件状态的双向绑定,以实现分页功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import React, { useState, useEffect } from 'react';

function useQuery() {
return new URLSearchParams(window.location.search);
}

function MyComponent() {
const query = useQuery();

const [pageNum, setPageNum] = useState(Number(query.get('pageNum')) || 1);
const [pageSize, setPageSize] = useState(Number(query.get('pageSize')) || 10);

useEffect(() => {
const params = new URLSearchParams(window.location.search);
if (pageNum) {
params.set('pageNum', pageNum);
}
if (pageSize) {
params.set('pageSize', pageSize);
}
window.history.replaceState(null, '', '?' + params.toString());
}, [pageNum, pageSize]);

const handlePageChange = (newPageNum) => {
setPageNum(newPageNum);
};

const handlePageSizeChange = (newPageSize) => {
setPageSize(newPageSize);
};

return (
<div>
<h1>分页组件</h1>
<p>Page Number: {pageNum}</p>
<p>Page Size: {pageSize}</p>
<button onClick={() => handlePageChange(pageNum + 1)}>Next Page</button>
<button onClick={() => handlePageSizeChange(20)}>Set Page Size to 20</button>
</div>
);
}

export default MyComponent;

通过上面这种方式,可以实现 URL 参数和组件状态的双向绑定,使得分页信息可以在 URL 和组件状态之间保持同步。

如果每个页面都这样手控控制就会造成大量冗余代码,可以抽离成通用 hook, 这样其他组件使用时可以直接命名参数名、参数值。

抽离通用 hook:useSyncedQueryParam

以下是实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useState, useEffect } from 'react';

function useQueryParams() {
return new URLSearchParams(window.location.search);
}

function useSyncedQueryParam(paramName, defaultValue) {
const query = useQueryParams();
const [value, setValue] = useState(query.get(paramName) || defaultValue);

useEffect(() => {
const params = new URLSearchParams(window.location.search);
if (value !== undefined && value !== null) {
params.set(paramName, value);
} else {
params.delete(paramName);
}
window.history.replaceState(null, '', '?' + params.toString());
}, [value, paramName]);

return [value, setValue];
}

export default useSyncedQueryParam;

可以在组件中使用这个通用 Hook,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from 'react';
import useSyncedQueryParam from './useSyncedQueryParam';

function MyComponent() {
const [pageNum, setPageNum] = useSyncedQueryParam('pageNum', 1);
const [pageSize, setPageSize] = useSyncedQueryParam('pageSize', 10);

const handlePageChange = (newPageNum) => {
setPageNum(newPageNum);
};

const handlePageSizeChange = (newPageSize) => {
setPageSize(newPageSize);
};

return (
<div>
<h1>分页组件</h1>
<p>Page Number: {pageNum}</p>
<p>Page Size: {pageSize}</p>
<button onClick={() => handlePageChange(pageNum + 1)}>Next Page</button>
<button onClick={() => handlePageSizeChange(20)}>Set Page Size to 20</button>
</div>
);
}

export default MyComponent;

这样就可以在多个组件中复用 useSyncedQueryParam Hook,轻松实现 URL 参数和组件状态的双向绑定。