新聞中心
作者 |這波能反殺丶

來源 |這波能反殺(ID:keepKilling)
序
當(dāng)了二十多天廢物,今天開始更新,讓大家久等了?;送﹂L(zhǎng)的時(shí)間優(yōu)化文章 UI 細(xì)節(jié),進(jìn)一步提高閱讀體驗(yàn),大家多多感受一下.
useEffect 是一個(gè)難以掌握的知識(shí)點(diǎn)。許多人對(duì)它半知半解,因此他們覺得函數(shù)式組件不受控制。
除了本身難以理解之外,React 還提供了一個(gè)類似的 hook:useLayoutEffect 來增加學(xué)習(xí)難度,對(duì)于新手來說,這可要了老命了。
許多朋友試圖利用 class 語法中的生命周期來類比理解 useEffect,因?yàn)楣俜轿臋n就是這么引導(dǎo)的,那么他們多半會(huì)陷入一些誤區(qū),因此,學(xué)習(xí)之前,大家需要明確的是,生命周期函數(shù)與 useEffect 是不同的。
要充分理解并使用該方法,你需要對(duì)閉包、同步、異步、事件循環(huán)等基礎(chǔ)概念有清晰認(rèn)知。
一、概念
useEffect 可以讓使用者在函數(shù)組件中執(zhí)行副作用操作。
那什么是副作用操作呢?
在 React 中,由 state 的變化導(dǎo)致 UI 發(fā)生變化的過程是正常操作,其他操作行為:例如數(shù)據(jù)請(qǐng)求、直接手動(dòng)修改 DOM 節(jié)點(diǎn)、直接操作頁(yè)面「修改頁(yè)面標(biāo)題等」、記錄日志等都是副作用操作。
副作用操作是相對(duì)于操作 state 而言的。
每一次因?yàn)?state 的改變,都有一次對(duì)應(yīng)副作用函數(shù)的執(zhí)行時(shí)機(jī)。如果 state 多次改變,那么就有多次對(duì)應(yīng)副作用的執(zhí)行時(shí)機(jī)。
例如:我希望記錄點(diǎn)擊的次數(shù)。
該次數(shù)不僅要在頁(yè)面上顯示,也要在頁(yè)面標(biāo)題中顯示。
我們就可以給出如下代碼來實(shí)現(xiàn)需求。
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
You clicked {count} times
);
}在該例子中,修改頁(yè)面標(biāo)題的行為是副作用行為,因此我們可以直接使用 useEffect 來定義它。useEffect 的第一個(gè)參數(shù)為一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)就是我們上面說的副作用函數(shù)「effect」,我們想要執(zhí)行的副作用邏輯都寫在該函數(shù)中。
二、語法
// 中括號(hào)表示參數(shù)可選
useEffect(effct[, deps])
useEffect 是 React 提供的 Hook,它能夠幫助我們定義 effect 函數(shù)。
第一個(gè)參數(shù)就是副作用函數(shù) effect。
第二個(gè)參數(shù)表示依賴項(xiàng),是一個(gè)可選參數(shù)。當(dāng)不傳入該參數(shù)時(shí),每次 UI 渲染 effect 函數(shù)都會(huì)執(zhí)行。
但是大多數(shù)時(shí)候我們并不想任何 state 的變化都一定要執(zhí)行 effect 函數(shù),這個(gè)時(shí)候我們可以傳入依賴項(xiàng)數(shù)組。使用時(shí)請(qǐng)確保依賴項(xiàng)數(shù)組中為 state/props 的值,表示 effect 只會(huì)響應(yīng)依賴項(xiàng)中狀態(tài)的變化。
如果你在 useEffect 中傳入與 state 無關(guān)的數(shù)據(jù),effect 不會(huì)響應(yīng)它們。
只有當(dāng)依賴項(xiàng)中是 state 發(fā)生變化時(shí),effect 才會(huì)與之對(duì)應(yīng)的執(zhí)行。
不同的 state 數(shù)據(jù)變化通常對(duì)應(yīng)不同的副作用操作。因此我們可以在函數(shù)組件中,定義多個(gè) effect。
function Demo() {
const [count, setCount] = useState(0)
const [show, setShow] = useState(false)
useEffect(() => {
// do something
}, [count])
useEffect(() => {
// do other something
}, [show])
...
}除此之外,我們還可以傳入空數(shù)組作為依賴項(xiàng),用于表示依賴項(xiàng)不會(huì)發(fā)生變化。因此,空數(shù)組對(duì)應(yīng)的 effect,就只會(huì)在初始化時(shí)執(zhí)行一次,以后就再也不會(huì)執(zhí)行了。
我們通常利用這個(gè)特性完成一些初始化工作,例如請(qǐng)求頁(yè)面數(shù)據(jù)。
const [list, setList] = useState(0);
// DOM渲染完成之后 effect 函數(shù)執(zhí)行
useEffect(() => {
recordListApi().then(res => {
setList(res.data);
})
}, []);
三、清除副作用
有的時(shí)候,副作用函數(shù) effect 執(zhí)行會(huì)留下一些痕跡,因此 useEffect 提供了一種清除副作用的方式。
effect 與 clear effect 是一一對(duì)應(yīng)的緊密關(guān)系。因此,我們可以定義一個(gè)回調(diào)函數(shù)由 effect 執(zhí)行時(shí)返回,該函數(shù)就是 clear effect 函數(shù)。
useEffect(() => {
// dosomething
// 定義 clear effect 函數(shù)
return () => {
// clear something
}
}, [])
這里一定要注意該函數(shù)與 class 組件中的 componentWillUnmount 的區(qū)別,官方文檔中的案例存在一定的誤導(dǎo)性。如果 deps 傳入空數(shù)據(jù),則兩者是類似的,否則他們完全不一樣,effect 與 clear effect 都有可能執(zhí)行多次。
clear effect 在下次 effect 執(zhí)行之前執(zhí)行,也會(huì)在組件銷毀之前執(zhí)行一次。
我們可以借助該特性實(shí)現(xiàn)一個(gè)防抖的案例。
例如我們要實(shí)現(xiàn)一個(gè)搜索框的功能。文字輸入過程中會(huì)自動(dòng)發(fā)起搜索請(qǐng)求。為了防止請(qǐng)求發(fā)送過于頻繁,在高頻輸入時(shí),不發(fā)送接口請(qǐng)求,如果超過了 500ms 下一次輸入事件還沒有發(fā)生,那么就自動(dòng)請(qǐng)求一次。
實(shí)現(xiàn)代碼如下:
import { useEffect, useState } from 'react'
export default function EffectDemo() {
const [text, setText] = useState('')
useEffect(() => {
let timer = setTimeout(() => {
console.log('發(fā)送搜索請(qǐng)求')
}, 500)
return () => {
console.log('清除定時(shí)器')
clearTimeout(timer)
}
}, [text])
return (
setText(e.target.value)} />
)
}我們?cè)?effect 中定義了定時(shí)器,作為延遲操作:500ms 后執(zhí)行請(qǐng)求邏輯。如果下一次 text 快速發(fā)生變化,clear effect 執(zhí)行會(huì)清除掉上一次定義的定時(shí)器任務(wù),那么請(qǐng)求邏輯就不會(huì)執(zhí)行。
只有下一次 text 的改變超過了 500ms 時(shí),定時(shí)器任務(wù)才會(huì)如期執(zhí)行。
執(zhí)行順序?yàn)椋?/p>
四、案例
在學(xué)習(xí)和理解 effect 的含義時(shí),我們知道 state 的變化引發(fā) UI 重新渲染,UI 渲染完成之后會(huì)執(zhí)行 effect。
然而在真實(shí)實(shí)踐時(shí),我們往往是知道自己要執(zhí)行的副作用邏輯是什么,難的是需要我們自己去設(shè)計(jì)合理的 state。不合理的設(shè)計(jì)會(huì)讓程序變得復(fù)雜。
現(xiàn)在我們要來實(shí)現(xiàn)下面的動(dòng)畫效果:
- 點(diǎn)擊紅色畫布,白色方塊執(zhí)行第一段動(dòng)畫,并顯示執(zhí)行日志。
- 執(zhí)行完后緊接著執(zhí)行第二段動(dòng)畫回到圓點(diǎn),并顯示執(zhí)行日志。
- 在白色方塊執(zhí)行動(dòng)畫的過程中點(diǎn)擊事件無效:點(diǎn)擊不影響動(dòng)畫的執(zhí)行,結(jié)束之后重新生效。
這個(gè)效果的實(shí)現(xiàn),最重要的是對(duì)于幾個(gè)狀態(tài)的設(shè)計(jì)。
首先,我們需要用一個(gè)狀態(tài)來表示第一段動(dòng)畫的執(zhí)行與否 anime01。
其次,我們需要用一個(gè)狀態(tài)來表示第二段動(dòng)畫的執(zhí)行與否 anime02。
最后,我們也可以使用一個(gè)額外的狀態(tài)來判斷整個(gè)過程是否已經(jīng)執(zhí)行完畢 stoped。
重點(diǎn)思考該狀態(tài)的特性,與存在的必要性。
在實(shí)現(xiàn)該邏輯中,我們只需要知道每一個(gè)運(yùn)動(dòng)的結(jié)束時(shí)間點(diǎn),并修改對(duì)應(yīng)的狀態(tài)即可。
例如:第一段動(dòng)畫執(zhí)行結(jié)束,修改 anime02 為true。
完整代碼如下:
import { useState, useRef, useEffect } from 'react';
// @ts-ignore
import anime from 'animejs';
import './style.scss';
export default function AnimateDemo() {
const [anime01, setAnime01] = useState(false);
const [anime02, setAnime02] = useState(false);
const element = useRef(null);
// 是否已經(jīng)停下來了
const stoped = useRef(true)
useEffect(() => {
anime01 && animate01();
anime02 && animate02();
}, [anime01, anime02]);
function animate01() {
anime({
targets: element.current,
translateX: 400,
backgroundColor: '#FF8F42',
borderRadius: ['0%', '50%'],
complete: () => {
setAnime01(false)
setAnime02(true)
}
})
}
function animate02() {
anime({
targets: element.current,
translateX: 0,
backgroundColor: '#FFF',
borderRadius: ['50%', '0%'],
easing: 'easeInOutQuad',
complete: () => {
setAnime02(false);
stoped.current = true
}
})
}
function clickHandler() {
if (stoped.current) {
stoped.current = false
setAnime01(true);
}
}
return (
{anime01 && 第一段動(dòng)畫執(zhí)行中}
{anime02 && 第二段動(dòng)畫執(zhí)行中}
)
} 這個(gè)案例值得我們進(jìn)一步思考,一方面是數(shù)據(jù)為什么需要使用 state 或者 ref. 另一方面是關(guān)于 effect 是否還有另外一個(gè)角度的思考。
網(wǎng)站題目:重點(diǎn)來了,UseEffect如何讓使用者在函數(shù)組件中執(zhí)行副作用操作
分享鏈接:http://www.fisionsoft.com.cn/article/dghhghi.html


咨詢
建站咨詢
