1. 上集回顧

2. Fibonacci Sequence
3. Memoization

4. 衍伸閱讀 ----> Redux

React 的渲染機制 - 1

即使狀態沒變化,只要調用了setState 就會觸發 render

parent 

component

對函數式組件來說,狀態值改變時才會觸發 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

Immutable

記得一開始學都被說要這樣寫 

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

 

其實更不划

下集預告:Memoization

在此先提一個先備知識

Fibonacci Sequence

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 次該函式

 O(n^2)

 O(n^2) -> O(n) ?

Memoization(Tabulation)

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 次該函式

 O(n)

拉回來,所以這跟 useMemo 有什麼關係?

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; 
}

Source Code

Jㄍ4.......?

{
    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

Fiber Node

//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; 
}

參考資料

 

8/29 淺入淺出 useMemo

By Jay Chou

8/29 淺入淺出 useMemo

  • 299