react hook中县好,為函數(shù)組件提供了useEffect鉤子,能夠在函數(shù)組件內(nèi)部模擬生命周期函數(shù),并可以在內(nèi)部執(zhí)行副作用操作鳖粟,下面來實(shí)現(xiàn)一下其基本功能:
首先總結(jié)一下其用法:
- useEffect要支持兩個(gè)參數(shù),第一個(gè)參數(shù)callback為函數(shù)類型拙绊,第二個(gè)參數(shù)可不傳向图,如果傳入,則必須為數(shù)組類型标沪。
- 如果第二個(gè)參數(shù)不傳榄攀,則每次狀態(tài)更改函數(shù)重新執(zhí)行都會(huì)執(zhí)行callback
- 如果第二個(gè)參數(shù)傳入空數(shù)組,則只有初始化時(shí)執(zhí)行一次
- 如果第二個(gè)參數(shù)傳入非空數(shù)組金句,則每當(dāng)依賴項(xiàng)中的狀態(tài)更改檩赢,都會(huì)執(zhí)行callback
實(shí)現(xiàn)代碼如下
// 用來存儲(chǔ)每次調(diào)用useEffect時(shí)傳入的依賴數(shù)組
let prevDepsAry = []
// 用索引記錄每個(gè)回調(diào)函數(shù)對(duì)應(yīng)的依賴數(shù)組
let effectIndex = 0
function useEffect(callback, depsAry) {
// 先判斷參數(shù)類型是否正確
// 如果callback不是函數(shù)類型,直接報(bào)錯(cuò)
if(Object.prototype.toString.call(callback) !== '[object Function]') throw new Error(`${callback} 必須是一個(gè)函數(shù)類型`)
// 判斷依賴數(shù)組有沒有傳入
if(depsAry === undefined) {
// 沒傳入則每次函數(shù)重新調(diào)用都要執(zhí)行回調(diào)函數(shù)
callback()
} else {
// 判斷depsAry是否是一個(gè)數(shù)組類型违寞,如果不是贞瞒,直接報(bào)錯(cuò)
if(Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error(`${depsAry} 必須是一個(gè)數(shù)組類型`)
// 是數(shù)組類型,則需要獲取上一次的依賴數(shù)組趁曼,逐項(xiàng)對(duì)比是否發(fā)生改變
let prevDeps = prevDepsAry[effectIndex]
// 判斷是否發(fā)生改變军浆,判斷prevDeps是否存在
const hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true
if(hasChanged) {
// 有依賴發(fā)生改變,調(diào)用callback
callback()
}
// 同步本次更改后的依賴數(shù)組
prevDepsAry[effectIndex] = depsAry
effectIndex++
}
}
// 因?yàn)闋顟B(tài)更改要刷新視圖彰阴,因此這里用ReactDom.render方法來模擬更改狀態(tài)后刷新視圖的操作
function render() {
// 每次調(diào)用render都要重置stateIndex瘾敢,否則對(duì)應(yīng)的索引無限遞增將無法正確匹配state和setState之間的關(guān)系
// stateIndex = 0
// 每次調(diào)用render都要重置effectIndex,否則對(duì)應(yīng)的索引無限遞增會(huì)導(dǎo)致callback匹配不到對(duì)應(yīng)的依賴數(shù)組
effectIndex = 0
ReactDom.render(<App />, document.getElementById('root'))
}
上面useEffect的基本功能就已實(shí)現(xiàn)尿这。
下面附上完整的測(cè)試代碼(其中還包含自行封裝的useState函數(shù)簇抵,可將其替換成react自帶的):
import React from 'react'
import ReactDom from 'react-dom'
// 存儲(chǔ)狀態(tài)的數(shù)組
let state = []
// 存儲(chǔ)更改狀態(tài)方法的數(shù)組
let setters = []
// 用來記錄狀態(tài)和更改狀態(tài)方法對(duì)應(yīng)關(guān)系的下標(biāo)
let stateIndex = 0
function createSetter(index) {
return function (newState) {
state[index] = newState
render()
}
}
function useState(initialState) {
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState
// 采用閉包緩存每個(gè)state對(duì)應(yīng)的setState
setters.push(createSetter(stateIndex))
const value = state[stateIndex]
const setter = setters[stateIndex]
// 每創(chuàng)建完一組都要+1,用來作為下一組狀態(tài)的索引
stateIndex++
return [value, setter]
}
// 因?yàn)闋顟B(tài)更改要刷新視圖射众,因此這里用ReactDom.render方法來模擬更改狀態(tài)后刷新視圖的操作
function render() {
// 每次調(diào)用render都要重置stateIndex碟摆,否則對(duì)應(yīng)的索引無限遞增將無法正確匹配state和setState之間的關(guān)系
stateIndex = 0
// 每次調(diào)用render都要重置effectIndex,否則對(duì)應(yīng)的索引無限遞增會(huì)導(dǎo)致callback匹配不到對(duì)應(yīng)的依賴數(shù)組
effectIndex = 0
ReactDom.render(<App />, document.getElementById('root'))
}
// 用來存儲(chǔ)每次調(diào)用useEffect時(shí)傳入的依賴數(shù)組
let prevDepsAry = []
// 用索引記錄每個(gè)回調(diào)函數(shù)對(duì)應(yīng)的依賴數(shù)組
let effectIndex = 0
function useEffect(callback, depsAry) {
// 先判斷參數(shù)類型是否正確
// 如果callback不是函數(shù)類型叨橱,直接報(bào)錯(cuò)
if(Object.prototype.toString.call(callback) !== '[object Function]') throw new Error(`${callback} 必須是一個(gè)函數(shù)類型`)
// 判斷依賴數(shù)組有沒有傳入
if(depsAry === undefined) {
// 沒傳入則每次函數(shù)重新調(diào)用都要執(zhí)行回調(diào)函數(shù)
callback()
} else {
// 判斷depsAry是否是一個(gè)數(shù)組類型典蜕,如果不是,直接報(bào)錯(cuò)
if(Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error(`${depsAry} 必須是一個(gè)數(shù)組類型`)
// 是數(shù)組類型罗洗,則需要獲取上一次的依賴數(shù)組愉舔,逐項(xiàng)對(duì)比是否發(fā)生改變
let prevDeps = prevDepsAry[effectIndex]
// 判斷是否發(fā)生改變,判斷prevDeps是否存在
const hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true
if(hasChanged) {
// 有依賴發(fā)生改變伙菜,調(diào)用callback
callback()
}
// 同步本次更改后的依賴數(shù)組
prevDepsAry[effectIndex] = depsAry
effectIndex++
}
}
// 手動(dòng)實(shí)現(xiàn)useState useEffect
function App() {
const [count, setCount] = useState(0)
const [title, setTitle] = useState('useState')
useEffect(() => {
console.log('useEffect');
}, [])
useEffect(() => {
console.log('useEffect count');
}, [count])
useEffect(() => {
console.log('useEffect every time');
})
return (
<div>
<h1>{title}</h1>
<button onClick={() => setTitle('useState Success')}>修改標(biāo)題</button>
<h1>{count}</h1>
<button onClick={() => setCount(count + 1)}>count++</button>
</div>
)
}
export default App