本文是 React 系列的第三篇前端
React 新特性 Hooks 講解及實例(二)github
想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!數組
使用 Context ,首先頂層先聲明 Provier
組件,並聲明 value
屬性,接着在後代組件中聲明 Consumer
組件,這個 Consumer
子組件,只能是惟一的一個函數,函數參數便是 Context
的負載。若是有多個 Context
,Provider
和 Consumer
任意的順序嵌套便可。性能優化
此外咱們還能夠針對任意一個 Context
使用 contextType
來簡化對這個 Context
負載的獲取。但在一個組件中,即便消費多個 Context
,contextType
也只能指向其中一個ide
在 Hooks 環境中,依舊可使用 Consumer
,可是 ContextType
做爲類靜態成員確定是用不了。Hooks 提供了 useContext
,不但解決了 Consumer 難用的問題同時也解決了 contextType
只能使用一個 context
的問題。函數
來個使用類形式的例子:post
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,歡迎 加羣 互相學習。
我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!
關注公衆號,後臺回覆福利,便可看到福利,你懂的。