1. 上集回顧
2. Fibonacci Sequence
3. Memoization
4. 衍伸閱讀 ----> Redux
即使狀態沒變化,只要調用了setState 就會觸發 render
對函數式組件來說,狀態值改變時才會觸發 render 函數的調用。
無論是 functional 或 Class ,一但父層重新render,子層的 render 都會被觸發
class Content extends React.Component {
shouldComponentUpdate(nextProps, nextState){
return !shallowEqual(this.props, nextProps)
|| !shallowEqual(this.state, nextState);
}
render () {
return <div>{this.props.text}</div>
}
}
所以實務上會是如此 shallow compare
shallowEqual
=> 可以理解成修正 js 一些怪異bug的 "===" 比較
=> 只比較一層,所以在比較 Object 時有可能會發生錯誤
NaN === NaN // false
+0 === -0 // true
記得一開始學都被說要這樣寫
this.setState({
obj: {
...this.state.obj,
id: 2
}
})
this.setState({
list: [...this.state.arr, 123]
})
// 不能這樣
const newObject = this.state.obj
newObject.id = 2;
this.setState({
obj: newObject
})
// 也不能這樣
const arr = this.state.arr;
arr.push(123);
this.setState({
list: arr
})
但這樣是不是全部 component 都用 pureComponent 或 React.memo 就讚讚?
不是,因為若是你這個 component 會頻繁更新,你這樣反而每次都會多算一次 shallowCompare
其實更不划
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, …
每個數字都是前兩個數字的和,例如 1+1=2, 1+2=3, 2+3=5, 3+5=8, ...,以此類推。
function fibonacci (position) {
if (position <= 2) {
// position === 1 || position === 2
return 1
} else {
return fibonacci(position - 1) + fibonacci(position - 2)
}
}
fibonacci(6) // 5, fibonacci(4) + fibonacci(3)
fibonacci(3) ,需要執行 3 次該函式
fibonacci(5) ,需要執行 9 次該函式
fibonacci(7) ,需要執行 25 次該函式
fibonacci(10) ,需要執行 109 次該函式
fibonacci(20) ,需要執行 13259 次該函式
fibonacci(30) ,需要執行 1664079 次該函式
if (cache) {
return cache
} else {
...運算完把結果存進 cache
}
function fibonacci (position) {
if (position <= 2) {
// position === 1 || position === 2
return 1
} else {
return fibonacci(position - 1) + fibonacci(position - 2)
}
}
fibonacci(6) // 5, fibonacci(4) + fibonacci(3)
function fibMemo (position, cache = []) {
if (cache[position]) {
// 如果在 cache 中有找到該 position 的數值,則直接回傳不用重算
return cache[position]
} else {
// 如果在 cache 中沒有找到該 position 的數值,則計算該 position 的數值,並存到 cache 中
if (position <= 2) {
cache[position] = 1
} else {
cache[position] = fibMemo(position - 1, cache) + fibMemo(position - 2, cache)
}
return cache[position]
}
}
fibMemo (3) ,需要執行 3 次該函式
fibMemo (5) ,需要執行 10 次該函式
fibMemo (7) ,需要執行 21 次該函式
fibMemo (10) ,需要執行 38 次該函式
fibMemo (20) ,需要執行 75 次該函式
fibMemo (30) ,需要執行 132 次該函式
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
...
const saveBtn = useMemo(() => (
<button
style={{
...styles.syncBtn,
...(dirty && !submitting ? styles.synbBtnDirty : {}),
}}
onClick={syncToServer}
type="button">
{submitting ? <LoadingSpinner /> : '儲存'}
</button>
), [dirty, submitting, syncToServer]);
...
//react/packages/react-reconciler/src/ReactFiberHooks.js
export function useMemo<T>(
nextCreate: () => T,
inputs: Array<mixed> | void | null,
): T {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [nextCreate];
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
const prevInputs = prevState[1];
if (areHookInputsEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
const nextValue = nextCreate();
workInProgressHook.memoizedState = [nextValue, nextInputs];
return nextValue;
}
{
stateNode: new Component, // instance of component
type: Component, // reference of component class
alternate: null, // reference of WIP or current (base on state)
key: null, // Important Key!!!
updateQueue: null, // batch queue
memoizedState: {count: 0}, // now state (rendered on screen)
pendingProps: {}, // next props
memoizedProps: {}, // now props (rendered on screen)
tag: 1, // element type (Component / input ... for organize priority)
effectTag: 0, // type of DOM change (PLACEMENT / UPDAET / DELETION)
nextEffect: null // reference of next fiber node
}
溫故知新 -> 小白的 slide
//react/packages/react-reconciler/src/ReactFiberHooks.js
export function useMemo<T>(
nextCreate: () => T,
inputs: Array<mixed> | void | null,
): T {
currentlyRenderingFiber = resolveCurrentlyRenderingFiber();
workInProgressHook = createWorkInProgressHook();
const nextInputs =
inputs !== undefined && inputs !== null ? inputs : [nextCreate];
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
const prevInputs = prevState[1];
if (areHookInputsEqual(nextInputs, prevInputs)) {
return prevState[0];
}
}
const nextValue = nextCreate();
workInProgressHook.memoizedState = [nextValue, nextInputs];
return nextValue;
}