前言
最近在看 React 的新文法——
React Hooks
,隻能一句話概括:React 文法真的是越來越強大,越寫代碼越少。
強烈推薦還沒看 React Hooks 的同學去學習下,這會讓你寫react 項目變得非常爽!
以前 React 元件可以看成是: 無狀态元件(function定義)和有狀态元件(class 定義),React Hooks 出現之後,我們基本所有的元件都可以用function定義,包括有組态元件,基本廢除了 寫 class 文法的 複雜性,讓我們寫代碼真正變成了函數式程式設計。
狀态 Hooks(useState)
這裡說明一點,react中所有的Hooks都是一種函數,函數都是用來實作特定功能的。
useState 提供了建立元件state的功能,用法:
const [count, setCount] = useState(0)
useState() 接受唯一一個狀态初始值參數,傳回包含狀态和改變狀态對應的函數的數組,這裡采用 數組解構方法獲得 狀态變量 count ,改變狀态方法 setCount。
強調一點:
useState() 傳入的初始值不一定非要是個對象,可以為普通資料類型,比如:Number,String等,初始值用作元件初次渲染。
setCount() 接受一個全新的state狀态,react會直接全部替換掉原來的state狀态,這點和 setState() 有所不同。
Example:
import React, { useState } from 'react';
function Example(){
const [ count , setCount ] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
</div>
)
}
export default Example;
當把 Example 元件渲染到頁面上時,可以通過點選按鈕實時改變 count 狀态,react 也會根據狀态改變重新渲染頁面。
執行副作用操作Hooks(useEffect)
之前在寫 react 元件時,往往我們在元件的生命周期函數裡面做一些額外的操作,比如發送ajax請求擷取資料,清除定時器和異步執行任務等。
我們發現這樣操作重複的代碼很多,而且如果對于 react 生命周期鈎子不熟悉的話,很容易出錯,于是乎,useEffect() Hooks 出現了。
useEffect() hooks 可以允許我們在 react 函數式元件中執行一些額外的副作用操作。
在 React 元件中有兩種常見副作用操作:
- 需要清除的,比如開啟的定時器,訂閱外部資料源等,這些操作如果在元件消亡後不及時清除會導緻記憶體洩漏。
- 不需要清除的,比如發起網絡請求,手動變更 DOM,記錄日志等。
在 react 官網中有一段話很重要:
如果你熟悉 React class 的生命周期函數,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個函數的組合。
Example:
import React, { useState , useEffect } from 'react';
function Example(){
const [ count , setCount ] = useState(0);
useEffect(()=>{
console.log(`You clicked ${count} times`)
})
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
</div>
)
}
export default Example;
我們在剛才的例子上新增了一個功能,每次我們 count 狀态改變的時候都會在控制台列印出我們點選的次數。可以看到使用 useEffect() Hooks 輕松實作。
強調一點:
- react首次渲染和之後的每次渲染都會調用一遍useEffect函數,而之前我們要用兩個生命周期函數分别表示首次渲染(componentDidMonut)和更新導緻的重新渲染(componentDidUpdate)。
- useEffect中定義的函數的執行不會阻礙浏覽器更新視圖,也就是說這些函數是異步執行的,而componentDidMonut和componentDidUpdate中的代碼都是同步執行的。
- 可以為 useEffect() 傳入第二個參數,它是一個數組,數組裡面表示這個副作用的操作依賴的狀态變量,換句話說:如果這個副作用的操作依賴的狀态變量沒有改變,則不會執行副作用操作。
元件之間傳值Hooks(useContext)
之前在用類聲明元件時,父子元件的傳值是通過元件屬性和props進行的,那現在使用方法(Function)來聲明元件,已經沒有了constructor構造函數也就沒有了props的接收,那父子元件的傳值就成了一個問題。React Hooks 為我們準備了useContext。
useContext它可以幫助我們跨越元件層級直接傳遞變量,實作共享。
Example:
一:利用 createContext 建立上下文
import React, { useState , createContext } from 'react';
// 建立一個 CountContext
const CountContext = createContext()
function Example(){
const [ count , setCount ] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={()=>{setCount(count+1)}}>click me</button>
{/* 将 context 傳遞給 子元件,context 值由value props決定*/}
<CountContext.Provider value={count}>
<Counter/>
</CountContext.Provider>
</div>
)
}
export default Example;
二:使用useContext 擷取上下文
對于要接收context的後代元件,隻需引入 useContext() Hooks 即可。
function Counter(){
const count = useContext(CountContext) //一句話就可以得到count
return (<h2>{count}</h2>)
}
強調一點:
useContext 的參數必須是 context 對象本身:
- 正确: useContext(MyContext)
- 錯誤: useContext(MyContext.Consumer)
- 錯誤: useContext(MyContext.Provider)
當元件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發重渲染,并使用最新傳遞給 <MyContext.Provider> 的 context value 值。
處理更為複雜state結構的Hooks(useReducer)
前面我們使用的 useState Hooks可以為元件提供 state和操作改變state狀态的方法,但往往有時候我們的state 結構更為複雜,例如 state 包含多個子值,或者下一個 state 依賴于之前的 state 等,這時候 useReducer 比 useState 更合适。
useReducer 的用法:
const [state, dispatch] = useReducer(reducer, initialArg, init);
useReducer 接收一個形如 (state, action) => newState 的 reducer,并傳回目前的 state 以及與其配套的 dispatch 方法。
我們可以使用 useReducer 來重新寫我們開篇計數器的demo:
Example:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
return state
}
}
// 定義 Counter 元件
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</div>
);
}
對于熟悉 redux 的夥伴,這種寫法很容易就能了解,每次通過點選按鈕分發一個action來更新state狀态資料。
強調一點:
React 不使用 state = initialState 這一由 Redux 推廣開來的參數約定。有時候初始值依賴于 props,是以需要在調用 Hook 時指定。如果你特别喜歡上述的參數約定,可以通過調用 useReducer(reducer, undefined, reducer) 來模拟 Redux 的行為,但我們不鼓勵你這麼做。
這裡是 react 官網提供的一句話,也就是說我們為 state 提供初始值的時候不能夠像 redux 中利用 ES6 預設參數來指定,必須得通過 useReducer來指定。這個切記,因為我也踩過坑!
對于 useReducer 和 useState的差別主要是以下兩點:
- 當 state 狀态值結構比較複雜時,使用 useReducer 更有優勢。
- 使用 useState 擷取的 setState 方法更新資料時是異步的;而使用 useReducer 擷取的 dispatch 方法更新資料是同步的。
性能優化Hooks(useMemo)
我們知道 使用class 形式的元件有着生命周期 shouldCompnentUpdate函數用來優化元件的性能,防止每次渲染造成昂貴的開銷。
使用function的形式來聲明元件,失去了shouldCompnentUpdate(在元件更新之前)這個生命周期,也就是說我們沒有辦法通過元件更新前條件來決定元件是否更新。而且在函數元件中,也不再區分mount和update兩個狀态,這意味着函數元件的每一次調用都會執行内部的所有邏輯,就帶來了非常大的性能損耗。
useMemo主要用來解決使用React hooks産生的無用渲染的性能問題。
舉個例子:
在 A 元件中有兩個子元件 B 和 C,當 A 元件中傳給 B 的 props 發生變化時,A 元件狀态會改變,重新渲染。此時 B 和 C 也都會重新渲染。其實這種情況是比較浪費資源的,現在我們就可以使用 useMemo 進行優化,B 元件用到的 props 變化時,隻有 B 發生改變,而 C 卻不會重新渲染。
Example:
// A 元件
import React from 'react';
export default ({ text }) => {
console.log('component A:', 'render');
return <div> A 元件:{ text }</div>
}
// B 元件
import React from 'react';
export default ({ text }) => {
console.log('component B:', 'render');
return <div> B 元件:{ text }</div>
}
// App 元件
import React, { useState, useMemo } from 'react';
import A from './ExampleA';
import B from './ExampleB';
export default () => {
const [a, setA] = useState('A');
const [b, setB] = useState('B');
const exampleA = useMemo(() => <A text={a} />, [a]);
const exampleB = useMemo(() => <B text={b} />, [b]);
return (
<div>
{ exampleA }
{ exampleB }
<br />
<button onClick={ () => setA('A改變了') }>修改傳給 A 的屬性</button>
<button onClick={ () => setB('B改變了') }>修改傳給 B 的屬性</button>
</div>
)
}
我們點選不同的按鈕,控制台都隻會列印一條輸出,改變 a 或者 b,A 和 B 元件都隻有一個會重新渲染。
其實 React 還有很多常用的 Hooks,比如用來擷取DOM元素 和儲存變量的useRef,優化函數式元件性能的useCallback等,對于這些的Hooks大家可以去react 官網了解下,相信大家應該也能搞清楚,這裡就不做進一步探讨。
React Hooks Api
結語
本篇文章出自于我們
web-study
倉庫,如果喜歡的話,歡迎給個 star!
位址: web-study
轉載于:https://www.cnblogs.com/dreamcc/p/11529193.html