useEffect與useLayoutEffect

React Hook讓無狀態組件擁有了許多隻有有狀態組件的能力,如自更新能力(setState,使用useState),訪問ref(使用useRef或useImperativeMethods),訪問context(使用useContext),使用更高級的setState設置(useReducer),及進行相似生命週期的階段性方法(useEffect或useLayoutEffect)。javascript

固然還有一些Hook,帶來了一些新功能,如useCallback,這是對事件句柄進行緩存,useState的第二個返回值是dispatch,可是每次都是返回新的,使用useCallback,可讓它使用上次的函數。在虛擬DOM更新過程當中,若是事件句柄相同,那麼就不用每次都進行removeEventListner與addEventListner。最後就是useMemo,取得上次緩存的數據,它能夠說是useCallback的另外一種形式。html

useState: setState
useReducer: setState
useRef: ref
useImperativeMethods: ref
useContext: context
useCallback:能夠對setState的優化
useMemo: useCallback的變形
useLayoutEffect:相似componentDidMount/Update, componentWillUnmount
useEffect: 相似於setState(state, cb)中的cb,老是在整個更新週期的最後才執行

從上面的描述來看useEffect的時期是很是晚,能夠保證頁面是穩定下來再作事情。可是useEffect與useLayoutEffect與有狀態組件的生命週期鉤子又有一點不同。java

useEffect(function(){
  //dosomething
  return function(){
  //dosomething     
  }
 }, inputs)

useEffect與useLayoutEffect的第一個參數是一個函數(初始函數),這函數還會返回另外一個清理用的函數(清理函數,在官方文檔中沒有明確的文字,都註釋使用了clean up的字眼,就姑且這樣叫)。當某個無狀態組件要在某個階段執行這些鉤子,它會優先執行清理函數再執行初始函數。react

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">

   <script src="./react.development.js"></script>
   <script src="./react-dom.development.js"></script>
   <script type='text/javascript' src="./lib/babel.js"></script>
</head>
<body>

    <div id='root' class="root">
    </div>
    <script type='text/babel'>
        
var container = document.getElementById('root');
var {useState, useEffect, useLayoutEffect} = React;
function Example() {
    const [count, setCount] = useState(0);
    const [text, setText] = useState('');
    var a = useRef("xxx")
    useEffect(() => {//初始函數
      console.log(a, 'useEffect')
       document.title = `You clicked ${count} times`;
       return () =>{//清理函數
        console.log(a, 'end useEffect')
        document.title = `remove`;
       }
     });
     useLayoutEffect(() => {//初始函數
      console.log(a, 'useLayoutEffect')
       document.title = `You clicked ${count} times`;
       return () =>{//清理函數
        console.log(a, 'end useLayoutEffect')
        document.title += '!!!';
       }
     });
    console.log(count, '更新Example')
    return (
      <quoteblock>
        <p>You clicked {count} times</p>
        <button onClick={() => setCount(count + 1)}>
          Click me
        </button>
        <input ref={a} value={text} onChange={function(e){
          setText(e.target.value)
        }} />
        <span>共{text.length}個字符</span><Child />
      </quoteblock>
    );
}

class App extends React.Component{
    state = {
      aaa: 1
    }
    onClick(){
      this.setState(function(s){
        return {
          aaa: s.aaa +1
        }
      })
    }
    componentDidMount(){
      console.log("app mount")
    }
    componentDidUpdate(){
      console.log("app update")
    }
    render(){
      return <div>{this.state.aaa < 10 ? <Example />: null}
              <h1 onClick={this.onClick.bind(this)}>{ this.state.aaa}</h1>
        </div>
    }
}

class Child extends React.Component {
    componentDidMount(){
      console.log("Child mount")
    }
    componentDidUpdate(){
      console.log("Child update")
    }
    render(){
      return <span>Child</span>
    }
}
ReactDOM.render(<App />, container)
    
    </script>
</html>

初次渲染的界面與日誌
clipboard.png
clipboard.png緩存

若是咱們向input輸入內容,就會發現它每次都會先進行 useEffect與useLayout的清理函數,再執行他們的初始函數。而且發現useEffect的函數會在最後才執行,它會晚於包含它的父函數。咱們能夠點擊頁面上的h1標籤,就能夠證實這一點。babel

clipboard.png

點擊h1會不斷遞增數字,到10時會銷供Example這個無狀態組件與它的子組件Child。下面是數字到10時的界面與日誌。app

clipboard.png

在個人迷你React框架中是這樣實現這兩個鉤子框架

export function useEffect(create, inputs) {
    return dispatcher.useEffect(create, inputs, PASSIVE, 'passive', 'unpassive');
}
export function useLayoutEffect(create, inputs) {
    return dispatcher.useEffect(create, inputs, HOOK, 'layout', 'unlayout');
}
export var dispatcher = {
    //略...
    useEffect(create, inputs, EffectTag, createList, destoryList) {
        let fiber = getCurrentFiber();
        let cb = dispatcher.useCallbackOrMemo(create, inputs);
        if (fiber.effectTag % EffectTag) {
            fiber.effectTag *= EffectTag;
        }
        let updateQueue = fiber.updateQueue;
        let list = updateQueue[createList] ||  (updateQueue[createList] = []);
        updateQueue[destoryList] ||  (updateQueue[destoryList] = []);
        list.push(cb);
    },
    //略...
};

它們就是執行時機不同。dom

當目前React Hook仍是實驗性質,不排除會改變。目前有9種鉤子,其實以前有十種,useMutationEffect前不久已經完蛋了。useMemo與useCallback很相近,但以爲useMemo的使用場合不多,不知它會不會廢掉。useEffect很差用,不像useLayoutEffect那麼明顯能夠與有狀態組件的生命週期鉤子相對應。useImperativeMethods這個名字起得很差,可能之後也會調整。固然這只是個人見解。函數

React Hook是一個很棒的設計,它實際上是將有狀態組件的更新機制(setState/forceUpdate)的內部實現進行了更普遍的應用。當它的API穩定下來我會與你們分享它們更深層次的實現。

相關文章
相關標籤/搜索