-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'origin/master' into feat/use-lock-fn
# Conflicts: # package-lock.json # packages/hooks/src/index.ts
- Loading branch information
Showing
16 changed files
with
7,508 additions
and
11,263 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
packages/hooks/src/useTrackedEffect/__tests__/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)} /> | ||
Dependency 0 : {dep1} | ||
</p> | ||
<p> | ||
<input type="checkbox" checked={depActiveList[1]} onChange={() => toggleDep(1)} /> | ||
Dependency 1 : {dep2} | ||
</p> | ||
<p> | ||
<input type="checkbox" checked={depActiveList[2]} onChange={() => toggleDep(2)} /> | ||
Dependency 2 : {dep3} | ||
</p> | ||
<p> | ||
<button type="button" onClick={mutateDep}> | ||
Increase count | ||
</button> | ||
</p> | ||
<p>{text}</p> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | - | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.