-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdecorators.ts
180 lines (172 loc) · 7.63 KB
/
decorators.ts
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import { pick, debounce } from "../helpers/util";
import { queryToState, stateToQuery, IQueryParser, IQueryStringify } from "../helpers/convert";
import { filterQuery } from "../helpers/url";
import { isArray, isObject } from "../helpers/type";
import { deepEqual } from "../helpers/deepEqual";
export function syncQueryCb(callbackDeps?:string[]) {
return function (target: any, propertyKey: string) {
target.callbackName = propertyKey;
if (isArray(callbackDeps) && callbackDeps.length > 0) {
target.callbackDeps = callbackDeps;
}
};
}
const __SYNC_QUERY_DIFF_IGNORE__ = '__SYNC_QUERY_DIFF_IGNORE__';
const __SYNC_QUERY_CALLBACK_IGNORE__ = '__SYNC_QUERY_CALLBACK_IGNORE__';
export function SyncQueryFactory(stateList: string[], callbackName?:string, config?:SyncQueryConfig) {
return function(WrappedComponent) {
return syncQueryHOC(WrappedComponent, stateList, callbackName, config);
}
}
type SyncQueryConfig = {
wait?: number, // 函数防抖的等待时间, 单位 ms
callbackDeps?: string[], // callbackDeps 存放 state key 的数组,监听到 state 中对应key 的 value 变化时,会调用 callback(网络请求等)
// PS: callbackDeps 没有传入时,默认监听的内容等于 stateList
parser?: IQueryParser, // 解析器:用于将路由参数 query 解析到 state,默认是 JSON.parse
stringify?: IQueryStringify, // 生成器:用于生成 state 对应的 query 字符串,默认是 JSON.stringify
disableAutoSync?: boolean, // 是否关闭自动同步
}
export interface SyncQueryHost {
triggerSync
}
/**
* syncQueryHOC
* @param WrappedComponent
* @param stateList states are observed
* @param callbackName callbackName would be called when state difference is detected
* @param config SyncQueryConfig
*/
export function syncQueryHOC(WrappedComponent, stateList: string[], callbackName?:string, config?:SyncQueryConfig) : any{
if (!isObject(config)) {
config = {
wait: 300,
}
} else {
config = {
wait: 300,
...config,
}
}
return class Enhancer extends WrappedComponent {
private prevStateCache = {};
constructor(param) {
super(param);
this.state = {
...this.state,
...this.getStateFromURL(stateList),
}
this.reBindCallback(false, true);
this.prevStateCache = this.state;
if (config.disableAutoSync === true) {
this.triggerSync = () => {
this.stateDiffEffect(this.state);
}
}
this.stateDiffEffect = debounce(this.stateDiffEffect, config.wait).bind(this);
}
private getStateFromURL(stateList:string[]) {
const query = location.href.split('?')[1];
if (query == null) {
return;
}
return queryToState(query, stateList, config.parser);
}
private syncStateToURL(state:Object) {
const [locationAddress, oldQuery] = location.href.split('?');
const restQuery = filterQuery(oldQuery, (key, value) => (stateList.indexOf(key) === -1))
const query = stateToQuery(state, config.stringify);
const href = `${locationAddress}?${query}&${restQuery}`;
location.href = href;
}
private reBindCallback(diffIgnore:boolean = true, callBackIgnore?:boolean) {
this.callbackName = this.callbackName || callbackName;
if (this.callbackName == null) {
return;
}
if (typeof super[this.callbackName] !== 'function') {
console.error('sync-query: callback must be react component method name!!! Tips: SyncQueryFactory and syncQueryHOC must be closest with Component Class');
return;
}
const clone = Object.create(this);
clone.setState = (updater, callback) => {
this.setState(updater, callback, diffIgnore, callBackIgnore);
}
// super[callbackName] is super.prototype[callbackName], so it is not bound with _super.
this[this.callbackName] = super[this.callbackName].bind(clone);
this.callbackDeps = this.callbackDeps || config.callbackDeps;
}
componentDidMount() {
const result = super.componentDidMount && super.componentDidMount();
this.reBindCallback();
return result;
}
componentDidUpdate(prevProps, prevState) {
if (this.state[__SYNC_QUERY_DIFF_IGNORE__] === true || config.disableAutoSync === true) {
return (
super.componentDidUpdate &&
super.componentDidUpdate(prevProps, prevState)
);
}
this.stateDiffEffect(this.state);
return (
super.componentDidUpdate &&
super.componentDidUpdate(prevProps, prevState)
);
}
private stateDiffEffect(state) {
const prevState = this.prevStateCache;
if (prevState == null && state == null) {
console.error('sync-query: stateDiffEffect could not be null');
return;
}
// stateList diff
const pickedPrevState = pick(prevState, stateList);
const pickedState = pick(state, stateList);
const isDiff = !deepEqual(pickedPrevState, pickedState);
if (isDiff) {
this.syncStateToURL(pickedState);
}
if (this.state[__SYNC_QUERY_CALLBACK_IGNORE__] === true) {
console.warn('Ingore: sync-query: auto trigger callback,only after ComponentDidMount');
return;
}
// callbackDeps diff
const callbackDeps = this.callbackDeps;
if (callbackDeps == null) {
isDiff && this[this.callbackName] && typeof this[this.callbackName] === 'function' && this[this.callbackName]();
} else {
const pickedPrevState = pick(prevState, callbackDeps);
const pickedState = pick(state, callbackDeps);
const isDiff = !deepEqual(pickedPrevState, pickedState);
isDiff && this[this.callbackName] && typeof this[this.callbackName] === 'function' && this[this.callbackName]();
}
this.prevStateCache = state;
}
setState(updater, callback?:() => void, diffIgnore?:boolean, callBackIgnore?:boolean) {
// Ref: https://zh-hans.reactjs.org/docs/react-component.html#setstate
if (typeof updater === 'object') {
return super.setState(
{
...updater,
[__SYNC_QUERY_DIFF_IGNORE__]: diffIgnore,
[__SYNC_QUERY_CALLBACK_IGNORE__]: callBackIgnore,
},
callback,
);
}
if (typeof updater === 'function') {
return super.setState(
function (state, props) {
return {
...updater(state, props),
[__SYNC_QUERY_DIFF_IGNORE__]: diffIgnore,
[__SYNC_QUERY_CALLBACK_IGNORE__]: callBackIgnore,
}
},
callback,
);
}
return super.setState(updater, callback);
}
}
}