Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into feat/use-lock-fn
Browse files Browse the repository at this point in the history
# Conflicts:
#	package-lock.json
#	packages/hooks/src/index.ts
  • Loading branch information
brickspert committed Nov 2, 2020
2 parents 5aa5dc6 + 89e97f3 commit b8a075c
Show file tree
Hide file tree
Showing 16 changed files with 7,508 additions and 11,263 deletions.
18,093 changes: 6,988 additions & 11,105 deletions package-lock.json

Large diffs are not rendered by default.

296 changes: 165 additions & 131 deletions packages/hooks/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import useFavicon from './useFavicon';
import useCountDown from './useCountDown';
import useWebSocket from './useWebSocket';
import useLockFn from './useLockFn';
import useTrackedEffect from './useTrackedEffect';

const useControlledValue: typeof useControllableValue = function (...args) {
console.warn(
Expand Down Expand Up @@ -121,6 +122,7 @@ export {
useReactive,
useFavicon,
useCountDown,
useTrackedEffect,
useWebSocket,
useLockFn,
};
2 changes: 1 addition & 1 deletion packages/hooks/src/useDebounceFn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function useDebounceFn<T extends Fn>(fn: T, options?: DebounceOptions) {
);

return {
run: debounced as T,
run: (debounced as unknown) as T,
cancel: debounced.cancel,
flush: debounced.flush,
};
Expand Down
34 changes: 34 additions & 0 deletions packages/hooks/src/useReactive/demo/demo3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useEffect, useState } from 'react';
import { useReactive } from 'ahooks';

export default () => {
const state = useReactive({ count: 0 });
const [stateCount, setStateCount] = useState(0);

const state2 = useReactive({ count: 0 });
const [stateCount2, setStateCount2] = useState(0);

// 依赖的是对象,因为始终同一个引用,所以不会执行
useEffect(() => {
setStateCount(stateCount + 1);
}, [state]);

// 依赖的基础数据类型,所以只要改变就会重新执行
useEffect(() => {
setStateCount2(stateCount2 + 1);
}, [state2.count]);

return (
<div>
<button style={{ marginTop: 20 }} onClick={() => (state.count += 1)}>
stateCount + 1
</button>
<p>stateCount:{stateCount}</p>

<button style={{ marginTop: 20 }} onClick={() => (state2.count += 1)}>
stateCount2 + 1
</button>
<p>stateCount2:{stateCount2}</p>
</div>
);
};
9 changes: 6 additions & 3 deletions packages/hooks/src/useReactive/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,21 @@ It offers data reactivity when manipulating states and views, in which case `use

<code src="./demo/demo1.tsx" />

### array
### Array

<code src="./demo/demo2.tsx" />

### notice

<code src="./demo/demo3.tsx" desc="`useReactive` returns a proxy object which always has the same reference. If `useEffect`, `useMemo`, `useCallback` and props passed to child component rely on the proxy, none of the above will be invoked by any changes to the proxy." />

## API

```js
let state = useReactive(initialValue:object);
const state = useReactive(initialValue:object);
```

## initialState
## Params

| Params | Description | Type | Default |
|--------------|---------------|----------|---------|
Expand Down
13 changes: 12 additions & 1 deletion packages/hooks/src/useReactive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@ import { useRef, useState } from 'react';

import useCreation from '../useCreation';

const proxyMap = new WeakMap();

function observer<T extends object>(initialVal: T, cb: () => void) {
return new Proxy<T>(initialVal, {
const existingProxy = proxyMap.get(initialVal);

// 添加缓存 防止重新构建proxy
if (existingProxy) {
return existingProxy;
}
const proxy = new Proxy<T>(initialVal, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
return typeof res === 'object' ? observer(res, cb) : Reflect.get(target, key);
Expand All @@ -14,6 +22,9 @@ function observer<T extends object>(initialVal: T, cb: () => void) {
return ret;
},
});

proxyMap.set(initialVal, proxy);
return proxy;
}

function useReactive<S extends object>(initialState: S): S {
Expand Down
9 changes: 6 additions & 3 deletions packages/hooks/src/useReactive/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@ group:

### 数组操作

<code src="./demo/demo2.tsx" />
<code src="./demo/demo2.tsx" />

### 注意

<code src="./demo/demo3.tsx" desc="`useReactive`产生可操作的代理对象一直都是同一个引用,`useEffect` , `useMemo` ,`useCallback` ,`子组件属性传递` 等如果依赖的是这个代理对象是**不会**引起重新执行。" />

## API

```js
let state = useReactive(initialValue: object);
const state = useReactive(initialState: object);
```

## initialState
## 参数

| 参数 | 说明 | 类型 | 默认值 |
|--------------|----------------|----------|--------|
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/src/useThrottleFn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function useThrottleFn<T extends Fn>(fn: T, options?: ThrottleOptions) {
);

return {
run: throttled as T,
run: (throttled as unknown) as T,
cancel: throttled.cancel,
flush: throttled.flush,
};
Expand Down
122 changes: 122 additions & 0 deletions packages/hooks/src/useTrackedEffect/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { renderHook } from '@testing-library/react-hooks';
import useTrackedEffect from '../index';

describe('useTrackedEffect', () => {
//We use a array to store which dependency has changed
var changedDepIndexes = [];
var prevDependencies = [];
var currentDependencies = [];
const mockEffectCleanup = jest.fn();
const mockEffectCallback = jest.fn().mockReturnValue(mockEffectCleanup);
const mockEffectWithTracked = jest.fn().mockImplementation((changes, prevDeps, curDeps) => {
//This effect callback accept an addition parameter which contains indexes of dependecies which changed their equalities.
changedDepIndexes = changes;
prevDependencies = prevDeps;
currentDependencies = curDeps;
return mockEffectCleanup;
});
it('should be defined', () => {
expect(useTrackedEffect).toBeDefined();
});
it("should run provided effect and return single changed dependecy's index ", () => {
let var1 = 0;
let var2 = '0';
let var3 = { value: 0 };
const { rerender } = renderHook(() =>
useTrackedEffect(mockEffectWithTracked, [var1, var2, var3]),
);
expect(mockEffectWithTracked).toHaveBeenCalledTimes(1);
rerender();
expect(changedDepIndexes).toHaveLength(3);
changedDepIndexes = [];
var1++;
rerender();
expect(changedDepIndexes).toHaveLength(1);
expect(changedDepIndexes[0]).toEqual(0);
});
it('should run provided effect and return correct dependencies (previous and current)', () => {
let var1 = 0;
let var2 = '0';
let var3 = { value: 0 };
const { rerender } = renderHook(() =>
useTrackedEffect(mockEffectWithTracked, [var1, var2, var3]),
);
expect(mockEffectWithTracked).toHaveBeenCalledTimes(1);
rerender();
expect(changedDepIndexes).toHaveLength(3);
changedDepIndexes = [];
var1++;
var2 = '1';
rerender();
expect(prevDependencies[0]).toEqual(0);
expect(currentDependencies[0]).toEqual(1);
expect(prevDependencies[1] === '0').toEqual(true);
expect(currentDependencies[1] === '1').toEqual(true);
changedDepIndexes = [];
var2 = '2';
rerender();
expect(prevDependencies[1]).toEqual('1');
expect(currentDependencies[1]).toEqual('2');
});
it(" should run provided effect and return multiple changed dependecy's indexes", () => {
let var1 = 0;
let var2 = '0';
let var3 = { value: 0 };
const { rerender } = renderHook(() =>
useTrackedEffect(mockEffectWithTracked, [var1, var2, var3]),
);
expect(mockEffectWithTracked).toHaveBeenCalledTimes(1);
rerender();
expect(changedDepIndexes).toHaveLength(3);
changedDepIndexes = [];
var1++;
var2 = '1';
rerender();
expect(changedDepIndexes).toHaveLength(2);
expect(changedDepIndexes[0]).toEqual(0);
expect(changedDepIndexes[1]).toEqual(1);
changedDepIndexes = [];
var2 = '2';
rerender();
expect(changedDepIndexes).toHaveLength(1);
expect(changedDepIndexes[0]).toEqual(1);
});
it('should run provided effect and return empty if no dependency changed', () => {
let var1 = 0;
let var2 = '0';
let var3 = { value: 0 };
const { rerender } = renderHook(() =>
useTrackedEffect(mockEffectWithTracked, [var1, var2, var3]),
);
expect(mockEffectWithTracked).toHaveBeenCalledTimes(1);
rerender();
expect(changedDepIndexes).toHaveLength(3);
changedDepIndexes = [];
var1 = 0;
rerender();
expect(changedDepIndexes).toHaveLength(0);
});
it('should run provided effect and make sure reference equality is correct', () => {
let var1 = 0;
let var2 = '0';
let var3 = { value: 0 };
const { rerender } = renderHook(() =>
useTrackedEffect(mockEffectWithTracked, [var1, var2, var3]),
);
expect(mockEffectWithTracked).toHaveBeenCalledTimes(1);
rerender();
expect(changedDepIndexes).toHaveLength(3);
changedDepIndexes = [];
var3.value = 123;
rerender();
expect(changedDepIndexes).toHaveLength(0);
});

it('should run clean-up provided on unmount as a normal useEffect', () => {
const { unmount } = renderHook(() => useTrackedEffect(mockEffectCallback));
expect(mockEffectCleanup).not.toHaveBeenCalled();

unmount();
expect(mockEffectCleanup).toHaveBeenCalledTimes(1);
});
});
60 changes: 60 additions & 0 deletions packages/hooks/src/useTrackedEffect/demo/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* title: Default usage
* desc: Display the changed deps when effect function is executed.
*
* title.zh-CN: 基础用法
* desc.zh-CN: 查看每次 effect 执行时发生变化的依赖项
*/

import React, { useLayoutEffect, useState } from 'react';
import { useTrackedEffect } from 'ahooks';

export default () => {
const [dep1, setDep1] = useState(0);
const [dep2, setDep2] = useState(0);
const [dep3, setDep3] = useState(0);
const [depActiveList, setDepActiveList] = useState([false, false, false]);
const [text, setText] = useState('Hi there...');
const toggleDep = (idx) => {
const newList = [...depActiveList];
newList[idx] = !newList[idx];
setDepActiveList(newList);
};
const mutateDep = () => {
setText(``);
depActiveList[0] && setDep1((c) => c + 1);
depActiveList[1] && setDep2((c) => c + 1);
depActiveList[2] && setDep3((c) => c + 1);
};
useTrackedEffect(
(changes) => {
setText(`Index of changed dependencies: ` + changes);
return () => {
// do something
};
},
[dep1, dep2, dep3],
);
return (
<div>
<p>
<input type="checkbox" checked={depActiveList[0]} onChange={() => toggleDep(0)} />
&nbsp;Dependency 0 : {dep1}
</p>
<p>
<input type="checkbox" checked={depActiveList[1]} onChange={() => toggleDep(1)} />
&nbsp;Dependency 1 : {dep2}
</p>
<p>
<input type="checkbox" checked={depActiveList[2]} onChange={() => toggleDep(2)} />
&nbsp;Dependency 2 : {dep3}
</p>
<p>
<button type="button" onClick={mutateDep}>
Increase count
</button>
</p>
<p>{text}</p>
</div>
);
};
35 changes: 35 additions & 0 deletions packages/hooks/src/useTrackedEffect/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: useTrackedEffect
nav:
title: Hooks
path: /hooks
group:
title: LifeCycle
path: /life-cycle
---

# useTrackedEffect

A hook of useEffect that allow us to track which dependencies caused the effect to trigger.

## Examples

### Basic usage

<code src="./demo/demo1.tsx" />

## API

```javascript
useTrackedEffect(
effect: (changes:[], previousDeps:[], currentDeps:[]) => (void | (() => void | undefined)),
deps?: deps,
)
```

### Params

| Property | Description | Type | Default |
|---------|----------------------------------------------|------------------------|--------|
| effect | Executable function | (changes:array, previousDeps: array, currentDeps: array) => (void | (() => void | undefined)) | - |
| deps | Optionally, pass in objects that depend on changes | array \| undefined | - |
23 changes: 23 additions & 0 deletions packages/hooks/src/useTrackedEffect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect, useRef, DependencyList } from 'react';

const useTrackedEffect = (effect, deps?: DependencyList) => {
const previousDepsRef = useRef<DependencyList>();
const diffTwoDeps = (deps1, deps2) => {
//Let's do a reference equality check on 2 dependency list.
//If deps1 is defined, we iterate over deps1 and do comparison on each element with equivalent element from deps2
//As this func is used only in this hook, we assume 2 deps always have same length.
return deps1
? deps1.map((_ele, idx) => (deps1[idx] !== deps2[idx] ? idx : -1)).filter((ele) => ele >= 0)
: deps2
? deps2.map((_ele, idx) => idx)
: [];
};
useEffect(() => {
let changes = diffTwoDeps(previousDepsRef.current, deps);
const previousDeps = previousDepsRef.current;
previousDepsRef.current = deps;
return effect(changes, previousDeps, deps);
}, deps);
};

export default useTrackedEffect;
Loading

0 comments on commit b8a075c

Please sign in to comment.