React 新特性 Hooks 講解及實例(三)

本文是 React 系列的第三篇前端

React 新特性講解及實例(一)git

React 新特性 Hooks 講解及實例(二)github

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!segmentfault

使用 Context Hooks

clipboard.png

使用 Context ,首先頂層先聲明 Provier 組件,並聲明 value 屬性,接着在後代組件中聲明 Consumer 組件,這個 Consumer 子組件,只能是惟一的一個函數,函數參數便是 Context 的負載。若是有多個 Context ,ProviderConsumer 任意的順序嵌套便可。數組

此外咱們還能夠針對任意一個 Context 使用 contextType 來簡化對這個 Context 負載的獲取。但在一個組件中,即便消費多個 Context,contextType 也只能指向其中一個性能優化

clipboard.png

在 Hooks 環境中,依舊可使用 Consumer,可是 ContextType 做爲類靜態成員確定是用不了。Hooks 提供了 useContext,不但解決了 Consumer 難用的問題同時也解決了 contextType 只能使用一個 context 的問題。ide

來個使用類形式的例子:函數

class Foo extends Component {
  render() {
    return (
      <CountContext.Consumer>
        {
          count => <h1>{count}</h1>
        }
      </CountContext.Consumer>
    )
  }
}

function App (props) {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button type="button"
        onClick={() => {setCount(count + 1) }}
      >
        Click({count})
      </button>
      <CountContext.Provider value={count}>
        <Counter />
      </CountContext.Provider>
    </div>
  )
}

以上就不說解釋了,第一篇已經講過了,接着將 Foo 改爲用 contextType 的形式:性能

class Foo extends Component {
  static contextType = CountContext;
  render() {
    const count = this.context
    return (
      <h1>{count}</h1>
    )
  }
}

接着使用 useContext 形式:學習

function Foo () {
  const count = useContext(CountContext)
  return (
    <h1>{count}</h1>
  )
}

useContext 接收一個 context 對象(React.createContext 的返回值)並返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的 <CountContext.Provider> 的 value prop 決定。

當組件上層最近的 <CountContext.Provider> 更新時,該 Hook 會觸發重渲染,並使用最新傳遞給 CountContext provider 的 context value 值。

別忘記 useContext 的參數必須是 context 對象自己:

  • 正確: useContext(MyContext)
  • 錯誤: useContext(MyContext.Consumer)
  • 錯誤: useContext(MyContext.Provider)

調用了 useContext 的組件總會在 context 值變化時從新渲染。若是重渲染組件的開銷較大,你能夠 經過使用 memoization 來優化。

使用 Memo Hooks

meno 用來優化函數組件重渲染的行爲,當傳入屬性值都不變的狀況下,就不會觸發組件的重渲染,不然就會觸發組件重渲染。

useMemo 與 memo

meno針對的是一個組件的渲染是否重複執行,而 useMemo 定義的是一個函數邏輯是否重複執行。

clipboard.png

來個粟子:

function Foo (props) {
  return (
    <h1>{props.count}</h1>
  )
}
function App (props) {
  const [count, setCount] = useState(0);

  const double = useMemo(() => {
    return count * 2
  }, [count])
  
  return (
    <div>
      <button type="button"
        onClick={() => {setCount(count + 1) }}
      >
        Click({count}) double: ({double})
      </button>
      <Foo count={count}/>
    </div>
  )
}

運行結果:

圖片描述

如上所示,useMemo 語法與 useEffect 是一致的。第一個參數是須要執行的邏輯函數,第二個參數是這個邏輯依賴輸入變量組成的數組,若是不傳第二個參數,這 useMemo 的邏輯每次就會運行,useMemo 自己的意義就不存在了,因此須要傳入參數。因此傳入空數組就只會運行一次,策略與 useEffect 是同樣的,但有一點比較大的差別就是調用時機,useEffect 執行的是反作用,因此必定是渲染以後才執行,但 useMemo 是須要返回值的,而返回值能夠直接參與渲染,所以 useMemo 是在渲染期間完成的。

接下來改造一下 useMemo,讓它依賴 count 以下:

const double = useMemo(() => {
  return count * 2
}, [count])

接着只有當 count 變化時,useMemo 纔會執行。

圖片描述

再次修改 useMemo, 以下:

const double = useMemo(() => {
  return count * 2
}, [count === 3])

如今能判定,count 在等於 3 以前,因爲這個條件一直保存 false 不變,double 不會從新計算,因此一直是 0,當 count 等於 3,double 從新計算爲 6,當 count 大於 3,double 在從新計算,變成 8,而後就一直保存 8 不變。

圖片描述

記住,傳入的 useMemo 的函數會在渲染期間執行,請不要在這個函數內部執行與渲染無關的放任,諸如反作用這類操做屬於 useEffect 的適用範疇,而不是 useMemo

你能夠把 useMemo 做爲性能優化的手段,但不要把它當成語義上的保證。

使用 useCallback Hooks

接下先看一下使用 memo 優化子組件的例子。

const Foo = memo (function Foo (props) {
  console.log('Counter render')
  return (
    <h1>{props.count}</h1>
  )
})

function App (props) {
  const [count, setCount] = useState(0);

  const double = useMemo(() => {
    return count * 2
  }, [count === 3])

  return (
    <div style={{padding:'100px'}}>
      <button type="button"
        onClick={() => {setCount(count + 1) }}
      >
        Click({count}) double: ({double})
      </button>
      <Foo count={double}/>
    </div>
  )
}

使用 memo 包裹 Foo 組件,這樣只有當 double 變化時,Foo 組件纔會從新渲染,執行裏面的 log,運行結果以下:

圖片描述

如今在給 Foo 中的 h1 添加一個 click 事件:

const Foo = memo (function Foo (props) {
  console.log('Counter render')
  return (
    <h1 onClick={props.onClick}>{props.count}</h1>
  )
})

而後在 App 組件中聲明 onClick 並傳給 Foo 組件:

function App (props) {
  ...
  const onClick = () => {
    console.log('Click')
  }

  return (
    <div style={{padding:'100px'}}>
      ...
      <Foo count={double} onClick={onClick}/>
    </div>
  )
}

看下運行效果:

圖片描述

能夠看出,每次點擊,無論 double 是否有變化, Foo 組件都會被渲染。那就說明每次 App 從新渲染以後, onClick 句柄的變化,致使 Foo 也被連帶從新渲染了。count 常常變化能夠理解,可是 onClick 就不該該常常變化了,畢竟只是一個函數而已,因此咱們要想辦法讓 onClick 句柄不變化。

想一想咱們上面講的 useMemo,能夠這樣來優化 onClick:

const onClick = useMemo(() => {
  return () => {
    console.log('Click')
  }
}, [])

因爲咱們傳給 useMemo 的第二個參數是一個空數組,那麼整個邏輯就只會運行一次,理論上咱們返回的 onClick 就只有一個句柄。

運行效果:

圖片描述

如今咱們把 useCallback 來實現上頁 useMemo 的邏輯。

const onClick = useCallback(() => {
  console.log('Click')
},[])

若是 useMemo 返回的是一個函數,那麼能夠直接使用 useCallback 來省略頂層的函數。

useCallback(fn, deps) 至關於 useMemo(() => fn, deps)

你們可能有一個疑問,useCallback 這幾行代碼明明每次組件渲染都會建立新的函數,它怎麼就優化性能了呢。

注意,你們不要誤會,使用 useCallback 確實不能阻止建立新的函數,但這個函數不必定會被返回,也就是說這個新建立的函數可能會被拋棄。useCallback解決的是解決的傳入子組件的函數參數過多變化,致使子組件過多渲染的問題,這裏須要理解好。

上述咱們第二個參數傳入的空數組,在實際業務並無這麼簡單,至少也要更新一下狀態。舉個粟子:

function App (props) {
  ... 
  const [clickCount, setClickCount] = useState(0);
  const onClick = useCallback(() => {
    console.log('Click')
    setClickCount(clickCount + 1)
  },[clickCount, setClickCount])
  ...
}

在 APP 組件中在聲明一個 useState,而後在 onClick 中調用 setClickCount,此時 onClick 依賴 clickCount,setClickCount

其實這裏的 setClickCount 是不須要寫的,由於 React 能保證 setState 每次返回的都是同個句柄。不信,能夠看下官方文檔 :

clipboard.png

這裏的場景,除了直接使用 setClickCount + 1 賦值之外, 還有一種方式甚至連 clickCount都不用依賴。setState 除了傳入對應的 state 最新值之外,還能夠傳入一個函數,函數的參數即這個 state 的當前值,返回就是要更新的值:

const onClick = useCallback(() => {
  console.log('Click')
  setClickCount((clickCount) => clickCount + 1)
},[])

小結

memo 根據屬性來決定是否從新渲染組件同樣,useMemo 能夠根據指定的依賴不決定一段函數邏輯是否從新執行,從而優化性能。

若是 useMemo 的返回值是函數的話,那麼就能夠簡寫成 useCallback 的方式,只是簡寫而已,實際並無區別。

須要特別注意的是,當依賴變化時,咱們能判定 useMemo 必定從新執行。可是,即便依賴不變化咱們不能假定它就必定不會從新執行,也就是說,它能夠會執行,就是考慮內在優化結果。

咱們能夠把 useMemo, useCallback 當作一個錦上添花優化手段,不能夠過分依賴它是否從新渲染,由於 React 目前沒有打包票說必定執行或者必定不執行。

交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索