Skip to content

Commit f2a378e

Browse files
author
wangyi
committed
add equalityFn and shallowEqual
1 parent d9e5afc commit f2a378e

File tree

13 files changed

+150
-40
lines changed

13 files changed

+150
-40
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.eslintrc.js
2+
example/
23
test/
34
jest.config.js
45
babel.config.js

docs/api.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ This api function is a react hook, it can be used to extract data from a sharing
4949
export function useAgentSelector<T extends OriginAgent<S>, S, R>(
5050
entry: T,
5151
mapStateCallback: (state: T['state']) => R,
52+
equalityFn?:(prev: R, current: R) => boolean,
5253
): R
5354
```
5455

5556
* entry - the model instance object.
56-
* mapStateCallback - a callback function for extracting data from model state.
57+
* mapStateCallback - a callback function for extracting data from model state. The callback param state is a current model state.
58+
* equalityFn - optional param, a callback function to compare the previous extracted data with the current one. If this function returns `true`, `useAgentSelector` do not make its consumer (react component) rerender, no matter if the extracted data changes. Param `prev` is the previous extracted data, and param `current` is the current one.
5759

5860
This function returns data extracted from a model state directly.
5961

@@ -75,6 +77,19 @@ export function useAgentMethods<T extends OriginAgent<S>, S>(
7577

7678
This function returns a model like instance which has an empty state property. It only provides model methods.
7779

80+
## shallowEqual
81+
82+
This api function is a shortcut `equalityFn` callback for using api [useAgentSelector](/api?id=useagentselector). It compares `prev` extracted data and `current` extracted data with a shallow `key-value` equality comparator.
83+
84+
```typescript
85+
function shallowEqual<R>(prev:R, current:R):boolean
86+
```
87+
88+
* prev - one data for comparing with another.
89+
* current - one data for comparing with another.
90+
91+
It returns a `boolean` value. If the two params are equal, it returns `true`, otherwise it returns `false`.
92+
7893
## ~~useAgent~~
7994

8095
(not recommend)

docs/changes.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
## v3.2.5 2021-04-10
22

33
* [new] add API [useAgentSelector](/api?id=useagentselector) for optimizing the model sharing.
4-
* [new] add API [useAgentMethods](/api?id=useagentmethods) for optimizing the model sharing.
4+
* [new] add API [useAgentMethods](/api?id=useagentmethods) for optimizing the model sharing.
5+
6+
## v3.2.7 2021-04-11
7+
8+
* [update] API [useAgentSelector](/api?id=useagentselector) add a new param `equalityFn` for comparing current extracted data with the previous one, and then deside if its consumer (react component) should be rerendered.
9+
* [new] add API [shallowEqual](/api?id=shallowequal) for a shallow comparing with two data.

docs/guides.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,12 @@ Be careful in `env.strict`, you'd better not set it to be `false`. For it will m
211211

212212
By using `model sharing`, we can share state updating between different components. But this feature brings problem too, that causes model sharing consumers( react components ) always render together, no matter if the state change is necessary for urrent consumer. From `use-agent-reducer@3.2.5`, we provide some new API functions for resolving this problem. You can use API function `useAgentSelector` to extract data from a model state which is truly used in component. And also, you can extract methods from a sharing model by using another API `useAgentMethods`.
213213

214-
API `useAgentSelector` extracts data from a state, and only the extracted data change can cause its consumer (react component) rerender.
214+
API [useAgentSelector](/api?id=useagentselector) extracts data from a state, and only the extracted data change may cause its consumer (react component) rerender. You can also use `equalityFn` to show if the consumer should rerender with a new extracted data.
215+
The `shallowEqual` is a simple `equalityFn`, you can use it directly.
215216

216217
```typescript
217218
import {weakSharing} from 'agent-reducer';
218-
import {useAgentSelector} from 'use-agent-reducer';
219+
import {useAgentSelector,shallowEqual} from 'use-agent-reducer';
219220

220221
// model sharing reference
221222
const sharingRef = weakSharing(()=> Model);
@@ -226,9 +227,24 @@ const sharingRef = weakSharing(()=> Model);
226227
// and extract data from state by a state mapping function.
227228
// Only the extracted data change can cause its consumer rerender.
228229
const renderNeeds = useAgentSelector(sharingRef.current, (state)=> state.renderNeeds);
230+
231+
// tell `useAgentSelector` to rerender consumer (component),
232+
// while equalityFn returns false.
233+
// The param `prev` is the previous extracted data,
234+
// and `current` is the current extracted data
235+
function equalityFn<R>(prev:R, current:R):boolean{
236+
// `shallowEqual` compares the `key-values`
237+
// between `prev` and `current`,
238+
// if all of them are equal, it returns true.
239+
// This function only compares data own properties.
240+
// You can also use `shallowEqual` directly to `useAgentSelector`.
241+
return shallowEqual(prev, current);
242+
}
243+
244+
const renderNeeds = useAgentSelector(sharingRef.current, ({renderNeeds})=> renderNeeds,equalityFn);
229245
```
230246

231-
API `useAgentMethods` only provides methods from a model like instance, it never causes a rerender.
247+
API [useAgentMethods](/api?id=useagentmethods) only provides methods from a model like instance, it never causes a rerender.
232248

233249
```typescript
234250
import {weakSharing, MiddleWarePresets} from 'agent-reducer';

docs/tutorial.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ check [sourceCode](https://github.com/filefoxper/use-agent-reducer/blob/master/e
682682
model:
683683

684684
```typescript
685-
import {middleWare, MiddleWarePresets, MiddleWares, OriginAgent} from "agent-reducer";
685+
iimport {middleWare, MiddleWarePresets, MiddleWares, OriginAgent} from "agent-reducer";
686686
import {FetchParams, PriorLevel, SearchParams, State, Todo} from "@/type";
687687
import {fetchTodoList, fetchTodoListWithDelay} from "@/service";
688688
@@ -727,9 +727,8 @@ export default class SimpleTodoList implements OriginAgent<State> {
727727
private async fetchDataSource(searchParams: SearchParams, currentPage: number, pageSize: number): Promise<State> {
728728
const fetchParams = {...searchParams, currentPage, pageSize};
729729
const {content: dataSource, total} = await fetchTodoList(fetchParams);
730-
// we do not copy searchParams here,
731-
// we can use new feature of 'agent-reducer@3.2.0' to update state synchronously in component.
732-
return {searchParams, dataSource, currentPage, pageSize, total};
730+
// Copy searchParams here
731+
return {searchParams:{...searchParams}, dataSource, currentPage, pageSize, total};
733732
}
734733
735734
// this method should works with a page navigation
@@ -752,8 +751,8 @@ export default class SimpleTodoList implements OriginAgent<State> {
752751
page:
753752

754753
```typescript
755-
import React, {memo, useCallback, useEffect, useState} from 'react';
756-
import {RunEnv, useAgentReducer, useMiddleWare} from "use-agent-reducer";
754+
import React, {memo, useCallback, useEffect} from 'react';
755+
import {RunEnv, useAgentReducer, useMiddleWare, useAgentMethods, useAgentSelector} from "use-agent-reducer";
757756
import SimpleTodoList, {SearchParamsModel} from "./model";
758757
import {ContentInput, PageContent, PriorLevelSelect, SearchContent} from "@/components";
759758
import {Button, Pagination, Table} from "antd";
@@ -766,20 +765,30 @@ import {MiddleWarePresets, weakSharing} from "agent-reducer";
766765
// shares state updating with each other.
767766
// the model created by `agent-reducer` API `weakSharing`,
768767
// often be reset back, if there is no living `Agent` built on it.
769-
const searchParamsModel = weakSharing(()=>SearchParamsModel);
768+
const searchParamsModel = weakSharing(() => SearchParamsModel);
770769
771-
const simpleTodoList = weakSharing(()=>SimpleTodoList);
770+
const simpleTodoList = weakSharing(() => SimpleTodoList);
772771
773772
const SearchParamComponent = memo(() => {
774773
775-
const {state, changeSearchContent, changeSearchPriorLevel} = useAgentReducer(searchParamsModel.current);
774+
const {state, changeSearchContent, changeSearchPriorLevel, feedback} = useAgentReducer(searchParamsModel.current);
776775
777776
// `Agent` bases on object simpleTodoList,
778777
// If we use class `SimpleTodoList` as a model,
779778
// `useAgentReducer` should create a private model object inside,
780779
// and then, no state updating can be shared now.
781780
// So, model sharing only works on 'Agents' base on a same model object.
782-
const {search} = useAgentReducer(simpleTodoList.current);
781+
// API `useAgentMethods` can optimize our component,
782+
// it never leads its consumer (component) rerender.
783+
const {search} = useAgentMethods(simpleTodoList.current);
784+
785+
// API `useAgentSelector` can optimize our component,
786+
// it only leads its consumer (component) rerender, when the extracted data changes.
787+
const searchParams = useAgentSelector(simpleTodoList.current, ({searchParams}) => searchParams);
788+
789+
useEffect(() => {
790+
feedback(searchParams);
791+
}, [searchParams]);
783792
784793
const handleSubmit = useCallback(async () => {
785794
// submit current searchParams with model object `simpleTodoList`
@@ -799,8 +808,6 @@ const SearchParamComponent = memo(() => {
799808
800809
export default function NewFeatures() {
801810
802-
const {feedback} = useAgentReducer(searchParamsModel.current);
803-
804811
// `Agent` bases on model `simpleTodoList`
805812
const agent = useAgentReducer(simpleTodoList.current);
806813
@@ -818,7 +825,6 @@ export default function NewFeatures() {
818825
// handle page change
819826
const handleChangePage = useCallback(async (currentPage: number, pageSize: number = 10) => {
820827
// feedback searchParams with model object `searchParamsModel`.
821-
feedback(state.searchParams);
822828
await changePageLatest(currentPage, pageSize);
823829
}, [state]);
824830
@@ -846,6 +852,6 @@ export default function NewFeatures() {
846852
}
847853
```
848854

849-
`Agents` base on a same model object can update state synchronously. Using this feature of `agent-reducer` can make your code more simple.
855+
`Agents` base on a same model object can update state synchronously. Using this feature of `agent-reducer` can make your code more simple. And use API [useAgentSelector](/api?id=useagentselector) and [useAgentMethods](/api?id=useagentmethods) can make your component render more less with `agent-reducer` model sharing.
850856

851857
You can do more things to make a search page model better.

docs/zh/api.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ function useMiddleWare<T extends OriginAgent<S>, S>(
4949
export function useAgentSelector<T extends OriginAgent<S>, S, R>(
5050
entry: T,
5151
mapStateCallback: (state: T['state']) => R,
52+
equalityFn?:(prev: R, current: R) => boolean,
5253
): R
5354
```
5455

5556
* entry - 模型实例 object
5657
* mapStateCallback - state 提取方法,用于提取当前模型实例 state 的部分数据,如果被提取数据保持不变则不会触发组件渲染。
58+
* equalityFn - 可选,用于对比 mapStateCallback 在模型 state 改变前后产生的数据,如果该函数返回 `true`,则 `useAgentSelector` 忽略 mapStateCallback 提取值的变化状况,不触发组件渲染。
5759

5860
该方法返回值即为被提取数据。
5961

@@ -75,6 +77,19 @@ export function useAgentMethods<T extends OriginAgent<S>, S>(
7577

7678
该方法返回值为忽略了 state 数据的模型实例。
7779

80+
## shallowEqual
81+
82+
该方法可对两个数据进行浅对比,并判断两个数据是否等价。可用作[useAgentSelector](/zh/api?id=useagentselector)的`equalityFn`回调参数。
83+
84+
```typescript
85+
function shallowEqual<R>(prev:R, current:R):boolean
86+
```
87+
88+
* prev - 对比数据之一.
89+
* current - 对比数据之一.
90+
91+
如果浅对比等价则返回 `true` ,否则返回 `false` 。
92+
7893
## ~~useAgent~~
7994

8095
(不推荐)

docs/zh/changes.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
## v3.2.5 2021-04-10
22

33
* [新增] 添加 API [useAgentSelector](/zh/api?id=useagentselector), 用于优化模型共享的组件性能。
4-
* [新增] 添加 API [useAgentMethods](/zh/api?id=useagentmethods), 用于优化模型共享的组件性能。
4+
* [新增] 添加 API [useAgentMethods](/zh/api?id=useagentmethods), 用于优化模型共享的组件性能。
5+
6+
## v3.2.7 2021-04-11
7+
8+
* [更新] API [useAgentSelector](/zh/api?id=useagentselector) 增加提取数据等价对比功能,进一步优化性能。
9+
* [新增] 添加 API [shallowEqual](/zh/api?id=shallowequal),用于快速浅对比两个数据是否等价。

docs/zh/guides.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,11 @@ describe("设置运行环境 RunEnv",()=>{
203203

204204
通过使用 `agent-reducer` 的模型共享特性,我们可以很容易地在不同组件间同步渲染数据,但这也给我们的组件带来了一定的负担,因为只要模型 state 有变更,无论变更数据是否对当前组件有用,必然会引起渲染行为,这不是我们愿意看到的。自 `use-agent-reducer@3.2.5` 起,我们提供了相关的性能优化接口:`useAgentSelector``useAgentMethods`
205205

206-
API 接口 `useAgentSelector` ,可用于提取当前组件需要的部分数据,如果该部分数据没有发生改变,则不会触发当前组件的渲染。
206+
API 接口 [useAgentSelector](/zh/api?id=useagentselector) ,可用于提取当前组件需要的部分数据,如果该部分数据没有发生改变,则不会触发当前组件的渲染。通过使用 `equalityFn` 对前后两次 state 提取数据进行对比,可进一步优化渲染性能,若对比返回值为 `true` ,则忽略提取数据的改变状况,不触发当前组件的渲染
207207

208208
```typescript
209209
import {weakSharing} from 'agent-reducer';
210-
import {useAgentSelector} from 'use-agent-reducer';
210+
import {useAgentSelector, shallowEqual} from 'use-agent-reducer';
211211

212212
// 共享模型引用
213213
const sharingRef = weakSharing(()=> Model);
@@ -217,9 +217,18 @@ const sharingRef = weakSharing(()=> Model);
217217
// 使用共享模型实例,通过 callback 从 state 中提取当前组件需的要数据,
218218
// 如果提取数据保持不变,则不会触发组件渲染
219219
const renderNeeds = useAgentSelector(sharingRef.current, (state)=> state.renderNeeds);
220+
221+
// 提取数据对比器,若返回 true ,则不触发组件渲染
222+
function equalityFn<R>(prev:R, current:R):boolean{
223+
// 浅对比 API,方便使用者,
224+
// 可直接用在 useAgentSelector 上
225+
return shallowEqual(prev, current);
226+
}
227+
228+
const renderNeeds = useAgentSelector(sharingRef.current, (state)=> state.renderNeeds,equalityFn);
220229
```
221230

222-
API 接口 `useAgentMethods` , 不会触发当前组件渲染,该接口只提供了当前组件需要使用的模型方法。
231+
API 接口 [useAgentMethods](/zh/api?id=useagentmethods) , 不会触发当前组件渲染,该接口只提供了当前组件需要使用的模型方法。
223232

224233
```typescript
225234
import {weakSharing, MiddleWarePresets} from 'agent-reducer';

example/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
"moment": "^2.24.0",
2020
"classnames": "^2.2.6",
2121
"babel-polyfill": "^6.26.0",
22-
"agent-reducer": "3.2.6",
23-
"use-agent-reducer": "3.2.3"
22+
"agent-reducer": "3.2.7",
23+
"use-agent-reducer": "3.2.5"
2424
},
2525
"devDependencies": {
2626
"@babel/core": "7.2.2",

example/src/newFeatures/index.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import React, {memo, useCallback, useEffect, useState} from 'react';
2-
import {RunEnv, useAgentReducer, useMiddleWare} from "use-agent-reducer";
1+
import React, {memo, useCallback, useEffect} from 'react';
2+
import {RunEnv, useAgentReducer, useMiddleWare, useAgentMethods, useAgentSelector} from "use-agent-reducer";
33
import SimpleTodoList, {SearchParamsModel} from "./model";
44
import {ContentInput, PageContent, PriorLevelSelect, SearchContent} from "@/components";
55
import {Button, Pagination, Table} from "antd";
@@ -12,20 +12,30 @@ import {MiddleWarePresets, weakSharing} from "agent-reducer";
1212
// shares state updating with each other.
1313
// the model created by `agent-reducer` API `weakSharing`,
1414
// often be reset back, if there is no living `Agent` built on it.
15-
const searchParamsModel = weakSharing(()=>SearchParamsModel);
15+
const searchParamsModel = weakSharing(() => SearchParamsModel);
1616

17-
const simpleTodoList = weakSharing(()=>SimpleTodoList);
17+
const simpleTodoList = weakSharing(() => SimpleTodoList);
1818

1919
const SearchParamComponent = memo(() => {
2020

21-
const {state, changeSearchContent, changeSearchPriorLevel} = useAgentReducer(searchParamsModel.current);
21+
const {state, changeSearchContent, changeSearchPriorLevel, feedback} = useAgentReducer(searchParamsModel.current);
2222

2323
// `Agent` bases on object simpleTodoList,
2424
// If we use class `SimpleTodoList` as a model,
2525
// `useAgentReducer` should create a private model object inside,
2626
// and then, no state updating can be shared now.
2727
// So, model sharing only works on 'Agents' base on a same model object.
28-
const {search} = useAgentReducer(simpleTodoList.current);
28+
// API `useAgentMethods` can optimize our component,
29+
// it never leads its consumer (component) rerender.
30+
const {search} = useAgentMethods(simpleTodoList.current);
31+
32+
// API `useAgentSelector` can optimize our component,
33+
// it only leads its consumer (component) rerender, when the extracted data changes.
34+
const searchParams = useAgentSelector(simpleTodoList.current, ({searchParams}) => searchParams);
35+
36+
useEffect(() => {
37+
feedback(searchParams);
38+
}, [searchParams]);
2939

3040
const handleSubmit = useCallback(async () => {
3141
// submit current searchParams with model object `simpleTodoList`
@@ -45,8 +55,6 @@ const SearchParamComponent = memo(() => {
4555

4656
export default function NewFeatures() {
4757

48-
const {feedback} = useAgentReducer(searchParamsModel.current);
49-
5058
// `Agent` bases on model `simpleTodoList`
5159
const agent = useAgentReducer(simpleTodoList.current);
5260

@@ -64,7 +72,6 @@ export default function NewFeatures() {
6472
// handle page change
6573
const handleChangePage = useCallback(async (currentPage: number, pageSize: number = 10) => {
6674
// feedback searchParams with model object `searchParamsModel`.
67-
feedback(state.searchParams);
6875
await changePageLatest(currentPage, pageSize);
6976
}, [state]);
7077

0 commit comments

Comments
 (0)