使用高阶组件实现页面的表单数据和state缓存

功能介绍

当页面上存在 Antd 的 From表单,切换到其他页面再切换回来,希望表单的数据和页面状态数据能够保留, 使用使用 @cachePage 高阶组件完成相应的功能

实现原理

定义一个Map缓存对象
使用高阶组件包装一层 包含 From 表单的组件,

在高阶组件被加载时,在 componentDidMonent 方法中判断缓存对象中是否有该页面的数据, 如果有数据,就通过 From 表单的 Api ,把数据设置到组件的 From 数据里面,

在高阶组件销毁时,在组件的 componentWillUnMonent 方法中 把被包装组件的From表单的数据取出来 放到缓存对象中。

-w369

高阶代码如下:

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import React, { Component } from 'react';
import moment from 'moment';

// 内存缓冲对象
const cacheMap = {};
const cacheKeyList = [];

/**
* 缓存路由页面表单 keep-alive
*
* @param {string} cacheKey 缓存的key,对应每个路由页面的key, 默认 路由页面组件名称
* @propsMethod props.cachePage.clear() 清除缓存,并标记这次表单将不被缓存
*
*/
export const cachePage = ({
cacheKey,
} = {}) => {
return (ComposedComponent) => {
let isError = false;
if (!cacheKey) {
// console.warn('缺失 CachePage 必填参数 cacheKey');
isError = true;
}
if (cacheKeyList.indexOf(cacheKey) >= 0) {
// console.warn('CachePage ,cacheKey 必须保证全局唯一,多一个页面不能有相同 cacheKey ');
// isError = true;
}
if (isError) {
return ComposedComponent;
}
cacheKeyList.push(cacheKey);

// 当前是否应该保存cache(如果被调用收到清理cache,就在组件销毁时不保存cache)
let shouldSave = true;
const mCacheKey = cacheKey
|| ComposedComponent.id
|| ComposedComponent.key;

// 获取cache
const getCache = () => {
let cache = cache = cacheMap[mCacheKey];
if (!cache) {
cache = {};
cacheMap[mCacheKey] = cache;
}
return cache;
};

// 保存cache
const setCache = (cache) => {
cacheMap[mCacheKey] = cache;
};

// 清除cache
const clearCache = (isClose) => {
delete cacheMap[mCacheKey] ;
if (isClose) {
shouldSave = false;
}
};

return class WrapComponent extends Component {
componentDidMount() {
const { form } = this.props;

const cacheData = getCache();
if (form && cacheData.form) {
const { setFieldsValue, getFieldProps } = form;
const cacheFormData = cacheData.form;
Object.keys(cacheFormData).forEach((formKey) => {
getFieldProps(formKey);
});
// 设置数据到表单
setFieldsValue(cacheFormData);
}
if (this.pageComponent && cacheData.pageState) {
// 设置数据到页面组件state
this.pageComponent.setState(cacheData.pageState);
}
shouldSave = true;
}

componentWillUnmount() {
if (shouldSave === false) {
return;
}
const { form } = this.props;
const cacheData = getCache();
// 获取页面组件表单数据
if (form) {
const { getFieldsValue, getFieldProps } = form;
cacheData.form = getFieldsValue();
}
// 获取页面组件state数据
if (this.pageComponent && this.pageComponent.state) {
cache.pageState = this.pageComponent.state;
}
// 保存数据到缓存
setCache(cache);
}

actionObj = {
clear() {
clearCache();
shouldSave = false;
},
};

render() {
// eslint-disable-next-line react/no-string-refs
return <ComposedComponent ref={(c) => { this.pageComponent = c; }} cachePage={this.actionObj} {...this.props} />;
}
};
};
};

/**
* 让 Form 数据 JSON 反序列号支持 Date
* @param key
* @param value
* @returns {*}
*/
function dateReviver(key, value) {
if (typeof value === 'string') {
const a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return moment(new Date(value));
}
}
return value;
}

export const onClosePage = (pathname) => {
if (pathnameToCacheKey[pathname]) {
pathnameToCacheKey[pathname](true);
}
};

参数介绍

  • cacheKey 缓存的key,对应每个路由页面的key, 必填参数

方法

在使用该高阶组件修饰的页面后, 页面的props内部会被注入一个cachePage对象,提供与以下操作缓存的方法:

  • props.cachePage.clear() : 清除缓存,并标记这次表单将不被缓存, 在页面组件列通过this.props.cachePage.clear(), 跳转到其他页面时,数据不会被缓存,并且清空上次缓存的数据

使用案例

在页面组件中加上这个注解,切换页面时,可以保留当前页面状态

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

import React, { PureComponent } from 'react';
import { Form, Input, } from 'antd';
import { cachePage } from '../../utils/Cache/cacheFromWrapper';

@Form.create()
@cachePage({ cacheKey: 'BasicFormPage' })
export default class BasicFormPage extends PureComponent {
const _style = { marginTop: 8 };
render(){
return (<Form{
hideRequiredMark
style={_style}
>
<FormItem
label="标题"
>
{getFieldDecorator('title', {
rules: [{
required: true, message: '请输入标题',
}],
})(
<Input placeholder="给目标起个名字" />
)}
</FormItem>
</From>);
}
}

功能扩展

  • 通过可以在高阶组件里面把数据缓存到 localStorage

注意事项

该高阶组件的表单缓存效果实现依赖 Antd 的 From.create()所以使用时,表单的创建必须用 antd 中的 From.create()生成表单