本文是 React 系列的第三篇前端
React 新特性 Hooks 講解及實例(二)github
想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!segmentfault
使用 Context ,首先頂層先聲明 Provier
組件,並聲明 value
屬性,接着在後代組件中聲明 Consumer
組件,這個 Consumer
子組件,只能是惟一的一個函數,函數參數便是 Context
的負載。若是有多個 Context
,Provider
和 Consumer
任意的順序嵌套便可。數組
此外咱們還能夠針對任意一個 Context
使用 contextType
來簡化對這個 Context
負載的獲取。但在一個組件中,即便消費多個 Context
,contextType
也只能指向其中一個性能優化
在 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
的組件總會在 context
值變化時從新渲染。若是重渲染組件的開銷較大,你能夠 經過使用 memoization 來優化。
meno 用來優化函數組件重渲染的行爲,當傳入屬性值都不變的狀況下,就不會觸發組件的重渲染,不然就會觸發組件重渲染。
meno
針對的是一個組件的渲染是否重複執行,而 useMemo
定義的是一個函數邏輯是否重複執行。
來個粟子:
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 做爲性能優化的手段,但不要把它當成語義上的保證。
接下先看一下使用 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
每次返回的都是同個句柄。不信,能夠看下官方文檔 :
這裏的場景,除了直接使用 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...
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。