最近終因而下定決心將我負責的一個公司內部系統由react v15.4.1升級到v16.8.6。v16.8.6中最被推崇的一個特性應該就是react hook了,實際上手後爲能更順手的使用也是花了2個多小時的時間把react hook的實現源碼看了一遍。vue
一個函數式組件react
const FunctionComponent = () => {
return (
<div>hello functional component</div>
)
}
複製代碼
要在這個組件中觸發更新要怎麼作,下面咱們就作點努力,改造一下。api
// 下面的實現中click事件被觸發而後執行sayHello對say賦值hello,
// 這個過程並無觸發react的更新機制,因此頁面不會顯示hello
const FunctionComponent = () => {
let say = 'hello functional component'
const sayHello = () => {
say = 'hello'
}
return (
<>
<div>{say}</div>
<button onClick={sayHello}>say hello</button>
</>
)
}
複製代碼
在1中咱們已經改變了say的值,如今我只需想辦法觸發react的更新就能夠了。如何手動觸發react的更新,我想到了forceUpdate
數組
// 須要將say做爲一個FunctionComponent的外部變量
// 避免在更新觸發後函數式組件執行對say從新賦值
let say = 'hello functional component'
const FunctionComponent = props => {
const sayHello = () => {
// 改變狀態
say = 'hello'
// 手動觸發父組件的更新從而更新本身
props.update()
}
return (
<>
<div>{say}</div>
<button onClick={sayHello}>say hello</button>
</>
)
}
class Stage extends React.Component {
// 經過觸發父組件的更新來更新函數式組件
// update屬性用於觸發更新函數式組件
handleUpdate = () => {
this.forceUpdate()
}
render() {
return (
<div className="App">
<FunctionComponent update={this.handleUpdate} />
</div>
);
}
}
複製代碼
到此咱們已經達到了使用functional組件實現相似class組件更新機制的目的。緩存
經過上面的探索在不借助useState的狀況咱們也能實現functional組件的狀態更新。bash
問題是咱們每次都得手動調用forceUpdate(),不夠友好。並且很容易相信這個方式性能極低。函數
在整個hook的使用中有兩個對象你不能忽視(具體區別請查看源碼):post
使用示例性能
const [count, setCount] = useState(0)
複製代碼
每次執行useState都會生成一個hook對象ui
var hook = {
memoizedState: 傳入的初時值,若是useState接收的是函數則是函數執行的結果,
baseState: null,
queue: 保存對state的更新隊列,
baseUpdate: null,
next: null
};
複製代碼
在一個functional組件中調用多少次useState()
則會新建多少個hook對象,爲了維護這些hook對象react使用了一個WorkInProgressHook的鏈表保存——這一步處理你能夠理解成咱們在探索版本中將functional組件的狀態變量say作全局處理相同的目的
var queue = hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState
};
var dispatch = queue.dispatch = dispatchAction.bind(null,
currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
複製代碼
window.say
保存,而它經過以前建立的WorkInProgressHook.hook
保存props.update
,探索版本中咱們使用forceUpdate
更新父組件的方式來觸發更新,而它採用了一個更底層的方法scheduleWork
。不知道scheduleWork
?請參考我以前的react fiber簡單瞭解下functional組件有兩個特性註定他不能擁有狀態:
1,一個純函數每次調用都是全新的東西。要破壞這種特性就須要讓這個函數產生副總用——依賴全局變量(window.say、WorkInProgressHook.hook)。
2,以前版本中react官方提供setState
用於改變狀態觸發更新,但這個只存在與class組件,新版本的dispatch
給了咱們多一個選擇
小知識點:functional組件執行過程當中生成的WorkInProgressHook
最終會保存到當前組件對應fiber對象上
// WorkInProgressHook是一個鏈表結構
// firstWorkInProgressHook表示這個鏈表頭部指針
var renderedWork = currentlyRenderingFiber$1;
renderedWork.memoizedState = firstWorkInProgressHook;
複製代碼
當你認真看完前面useState
的實現過程,你會發現這樣一個過程:
若是你對react的fiber有必定了解的話對memoizedState必定不會陌生,咱們在class組件中的state最終也是保存到fiber的memoizedState
手動分割線-----------------------------------------
var effect = {
tag: tag,
create: useEffect的第一個參數傳入的函數,
destroy: destroy,
deps: useEffect的第二個參數更新依賴,
// Circular
next: null
};
複製代碼
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
renderedWork.updateQueue = componentUpdateQueue;
複製代碼
若是你對fiber的調用過程必定了解的話updateQueue你也不會陌生,這個地方存有react diff出來的反作用,用於在commit階段執行。
和useState有相同的實現過程。
不一樣點:
和useState的生成過程基本相似,不過生成的hook對象有點區別
var hook = {
memoizedState: [傳入的callback,傳人的更新依賴],
...
};
複製代碼
useCallback算是我我的比較喜歡的一個功能
class Stage extends React.Component{
handleClick = () => {
console.log('what are you 弄啥呢?')
}
render () {
return (
<>
<!-- 大多數狀況下都會建議這個寫法 -->
<div onClick={this.handleClick}>弄你</div>
<!-- 通常會吐槽這個寫法 -->
<div onClick={() => this.handleClick()}>弄你</div>
</>
)
}
}
複製代碼
第一種寫法保證執行render時屬性onClick的值都相同;而第二種寫法每次執行render都會生成一個新的函數,因此在diff時onClick每次都不一樣。
useCallback就用來幫助咱們在functional組件中實現了第一種性能更優的方式
和useState的生成過程基本相似,不過生成的hook對象有點區別
var hook = {
memoizedState: [計算過程的結果,傳人的更新依賴],
...
複製代碼
useMemo一樣是我我的比較喜歡的一個功能,在這以前一直但願有官方的支持,簡單說就是對一個計算過程的結果進行緩存。使用過vue的同窗能夠把它想象成vue的computed屬性(vue computed屬性的數據響應和依賴緩存實現過程)。從這一點上看vue領先react好幾年~~~~哈哈哈哈哈哈~~~~
官方提供的hook api不少,其中有幾個能夠接受第二個參數(不傳,或者是一個數組)。
做用:在某個依賴項改變時從新操做第一個參數
若是你使用過React.PureComponent就會知道,他經過對新舊props作一個淺比較來判斷是否須要更新組件。這第二個參數的原理一樣如此。
// 首先對deps作是否爲空的判斷
if (nextDeps !== null) {
var prevDeps = prevState[1];
// 而後比較新舊deps
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
// 比較新舊deps
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
// 對傳入的數組成員作淺比較
for (var i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
複製代碼
剩下的幾個hook API你們有興趣能夠自行研讀源碼,實現過程基本和上面幾個相同的思路
最後想說的是沒有黑魔法,若是你react的更新機制、fiber的過程、以及函數的特性有清晰的認識,理解hook也是很容易的。 這篇文章本質上也是一片源碼解讀類型的,可是不多涉及到一些具體實現。經過開篇的一個探索版本模糊認識hook的設計思路。全部這一切都是創建在reactv16版本fiber的優秀設計上。