在使用hooks的過程中,大家可能會(huì)有一些疑惑。比如為什么useState只能在函數(shù)最外層調(diào)用,useEffect第二個(gè)參數(shù)的作用等。今天,我們來實(shí)現(xiàn)幾個(gè)簡(jiǎn)單的hooks,并從中了解一些其原理。
1.useState
我們首先來實(shí)現(xiàn)useState。函數(shù)式組件沒有實(shí)例,每次渲染都會(huì)重新執(zhí)行useState函數(shù)。
我們聲明一個(gè)lastState來保存上一次的狀態(tài)。第一次渲染的時(shí)候執(zhí)行useState,此時(shí)lastState沒有值,將useState的參數(shù)作為初始值。在useState中定義一個(gè)setState的方法,來改變lastState的值。另外每次setState后要重新render。最終useState返回一個(gè)數(shù)組,里面包含上一個(gè)狀態(tài)和改變狀態(tài)的方法。以下為useState的簡(jiǎn)單實(shí)現(xiàn)。
let lastState;
function useState(initialState) {
lastState = lastState || initialState;
function setState(newState) {
lastState = newState;
render();
}
return [lastState, setState];
}
function Counter() {
let [state, setState] = useState(0);
return (
<>
<p>{state}</p>
<button onClick={() => setState(state + 1)}>+</button>
</>
)
}
function render() {
ReactDOM.render(
<Counter />,
document.getElementById('root')
);
}
render();
如果有多個(gè)state的時(shí)候,那么上面的寫法就不適用了。lastState應(yīng)該聲明為一個(gè)數(shù)組,并且需要相對(duì)應(yīng)有一個(gè)索引。要注意的是每次執(zhí)行useState后需要讓index加一,并且每次render后索引需要重置為0。
let lastState = [];
let index = 0;
function useState(beginState) {
lastState[index] = lastState[index[|| beginState;
const currentIndex = index;
function setState(newState) {
lastState[currentIndex] = newState;
render();
}
return [lastState[index++], setState];
}
function App() {
let [state, setState] = useState(0);
return (
<div>
<span>{state}</span>
<button onClick={() => setState(state + 1)}>+</button>
</div>
)
}
function render() {
index = 0;
ReactDOM.render(
<App />,
document.getElementById('root')
);
}
render();
可以看出,每次渲染時(shí)state跟index是一一對(duì)應(yīng)的,所以這也是不能把useState放到條件語句中的原因。所以在使用 Hook 的時(shí)候,我們應(yīng)該在函數(shù)組件最外層使用。
2.useReducer
reducer接收兩個(gè)參數(shù),原有狀態(tài)和動(dòng)作,通過派發(fā)動(dòng)作來改變狀態(tài),最后返回一個(gè)新狀態(tài)。useReducer的實(shí)現(xiàn)和useState相似。
function reducer(state, action) {
if (action.type === 'add') {
return state + 1;
} else {
return state;
}
}
let lastState;
function useReducer(reducer, beginState) {
lastState = lastState || beginState;
function dispatch(action) {
lastState = reducer(lastState, action);
render();
}
return [lastState, dispatch];
}
function Counter() {
let [state, dispatch] = useReducer(reducer, 0);
return (
<div>
<span>{state}</span>
<button onClick={() => dispatch({ type: 'add' })}>+</button>
</div>
)
}
3、useEffect
effect在這里是副作用的意思,我們可以在這個(gè)hooks中執(zhí)行一些有副作用的行為,比如操作dom,通過ajax發(fā)送網(wǎng)絡(luò)請(qǐng)求等。它里邊的函數(shù)會(huì)在組件每次render后執(zhí)行。而第二個(gè)參數(shù)我們稱之為依賴項(xiàng),如果其中的元素在每次渲染時(shí)和前一次相比沒有發(fā)生變化,就不會(huì)觸發(fā)這個(gè)副作用。下邊的實(shí)現(xiàn)沒有包括銷毀副作用的功能。
let lastDependencies;
function useEffect(callback, dependencies) {
if (lastDependencies) {
let changed = !dependencies.every((item, index) => {
return item == lastDependencies[index];
});
if (changed) {
callback();
lastDependencies = dependencies;
}
} else {//沒有渲染過
callback();
lastDependencies = dependencies;
}
}
function App() {
let [number, setNumber] = useState(0);
useEffect(() => {
console.log('看看數(shù)字是否變化了呢', number);
}, [number]);
return (
<div>
<span>{number}</span>
<button onClick={() => setNumber(number + 1)}>+</button>
</div>
)
}
function render() {
ReactDOM.render(
<Counter />,
document.getElementById('root')
);
}
render();
當(dāng)依賴項(xiàng)為空的時(shí)候,我們會(huì)發(fā)現(xiàn)它只會(huì)執(zhí)行一次,此時(shí)我們可以用useEffect來模擬componentDidMount生命周期。依賴項(xiàng)的比較是用的‘==’而不是‘===’,因此我們可以得知依賴項(xiàng)的對(duì)比是淺比較。
我們對(duì)于effect的實(shí)現(xiàn)比較簡(jiǎn)單,還有很多細(xì)節(jié)沒有體現(xiàn)出來。在上面的代碼中,每次執(zhí)行useEffect都會(huì)打印最新的number值,那么它是如何讀取到最新的state的呢。并不是number的值在“不變”的effect中發(fā)生了改變,而是每一次渲染中的effect的count值都來自于它屬于的那次渲染。因?yàn)閑ffect是在渲染完成后執(zhí)行的,我們可以把它當(dāng)作渲染結(jié)果的一部分。
4、useCallback和useMemo
在之前的文章中我們有了解過這兩個(gè)性能優(yōu)化的hooks。它們的實(shí)現(xiàn)和前面的hooks也很相似。
let lastCallback;
let lastCallbackDependencies;
function useCallback(callback, dependencies) {
if (lastCallbackDependencies) {
let changed = !dependencies.every((item, index) => {
return item == lastCallbackDependencies[index];
});
if (changed) {
lastCallback = callback;
lastCallbackDependencies = dependencies;
}
} else {
lastCallback = callback;
lastCallbackDependencies = dependencies;
}
return lastCallback;
}
let lastMemo;
let lastMemoDependencies;
function useMemo(callback, dependencies) {
if (lastMemoDependencies) {
let changed = !dependencies.every((item, index) => {
return item == lastMemoDependencies[index];
});
if (changed) {
lastMemo = callback();
lastMemoDependencies = dependencies;
}
} else {//沒有渲染過
lastMemo = callback();
lastMemoDependencies = dependencies;
}
return lastMemo;
}
在本文中我們實(shí)現(xiàn)了一個(gè)最簡(jiǎn)單的hooks,主要借助的是數(shù)組這個(gè)簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),一定成程度上了解了hooks的原理。但是在React的源碼中,它是通過類似單鏈表的形式而不是數(shù)組。有空應(yīng)該去讀一讀源碼,菜會(huì)有更深入的理解。