今天小編給大家分享一下React Hooks的原理分析的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
創(chuàng)新互聯(lián)專(zhuān)注于定州企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站,商城開(kāi)發(fā)。定州網(wǎng)站建設(shè)公司,為定州等地區(qū)提供建站服務(wù)。全流程定制制作,專(zhuān)業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)專(zhuān)業(yè)和態(tài)度為您提供的服務(wù)
在React中有非常多的Hooks,其中useEffect使用非常頻繁,針對(duì)一些具有副作用的函數(shù)進(jìn)行包裹處理,使用Hook的收益有:增強(qiáng)可復(fù)用性、使函數(shù)組件有狀態(tài)
數(shù)據(jù)獲取、訂閱或手動(dòng)修改DOM都屬于副作用(side effects)。
effect會(huì)在React的每次render之后執(zhí)行,如果是有一些需要同步的副作用代碼,則可以借助useLayoutEffect來(lái)包裹,它的用法和useEffect類(lèi)似
useEffect有兩個(gè)參數(shù),第一個(gè)傳遞一個(gè)函數(shù),第二個(gè)參數(shù)是作為effect是否執(zhí)行第一個(gè)參數(shù)中的函數(shù)是否執(zhí)行的標(biāo)準(zhǔn),換句話(huà)說(shuō),第二個(gè)參數(shù)數(shù)組中的變量是否變化來(lái)決定函數(shù)是否執(zhí)行,函數(shù)是否執(zhí)行依賴(lài)于第二個(gè)參數(shù)的值是否變化。在React中的比較是一個(gè)shallow equal(淺比較),對(duì)于深層次的對(duì)象嵌套,無(wú)法準(zhǔn)確判斷是否發(fā)生變化。
useEffect借助了JS的閉包機(jī)制,可以說(shuō)第一個(gè)參數(shù)就是一個(gè)閉包函數(shù),它處在函數(shù)組件的作用域中,同時(shí)可以訪(fǎng)問(wèn)其中的局部變量和函數(shù)。
多個(gè)useEffect串聯(lián),根據(jù)是否執(zhí)行函數(shù)(依賴(lài)項(xiàng)值是否變化),依次掛載到執(zhí)行鏈上
在類(lèi)組件中,有生命周期的概念,在一些講react hooks的文章中常常會(huì)看到如何借助useEffect來(lái)模擬 componentDidmount和 componentUnmount的例子,其第二個(gè)參數(shù)是一個(gè)空數(shù)組[],這樣effect在組件掛載時(shí)候執(zhí)行一次,卸載的時(shí)候執(zhí)行一下return的函數(shù)。也同樣是閉包的關(guān)系,通過(guò)return一個(gè)函數(shù),來(lái)實(shí)現(xiàn)閉包以及在React下次運(yùn)行effect之前執(zhí)行該return的函數(shù),用于清除副作用。
個(gè)人在一開(kāi)始接觸react hooks的時(shí)候,覺(jué)得代碼的執(zhí)行有點(diǎn)違背常識(shí),在對(duì)react構(gòu)建合理的心智模型花了不少時(shí)間。函數(shù)組件(Functional Component)沒(méi)有生命周期的概念,React控制更新,頻繁的更新但是值有的會(huì)變,有的不變,反而使得程序的可理解性變差了。
不過(guò)在后來(lái)不斷地學(xué)習(xí)以及運(yùn)用之后,我個(gè)人覺(jué)得hooks其實(shí)是一種非常輕量的方式,在項(xiàng)目構(gòu)建中,開(kāi)發(fā)自定義的hooks,然后在應(yīng)用程序中任意地方調(diào)用hook,類(lèi)似于插件化(可插拔)開(kāi)發(fā),降低了代碼的耦合度。但隨之也帶來(lái)了一些麻煩的事情,有的同學(xué)在一個(gè)hook里寫(xiě)了大量的代碼,分離的effect也冗雜在一起,再加上多維度的變量控制,使得其他同學(xué)難以理解這個(gè)hook到底在干嘛。
針對(duì)hook的內(nèi)部代碼冗雜的問(wèn)題,首先得明確當(dāng)前hook的工作,是否可拆分工作,在hook里可以調(diào)用其他的hook,所以是否可以進(jìn)行多個(gè)hook拆分?或者組織(梳理)好代碼的運(yùn)行邏輯?
React中每次渲染都有自己的effect
React中的hooks更新,筆者認(rèn)為可以把其看作是一個(gè)“快照”,每一次更新都是一次“快照”,這個(gè)快照里的變量值是不變的,每個(gè)快照會(huì)因?yàn)閞eact的更新而產(chǎn)生串行(可推導(dǎo)的)差異,而effect中的函數(shù)每一次都是一個(gè)新的函數(shù)。
我對(duì)于hooks的心智模型,簡(jiǎn)單來(lái)講,就是一種插件式、有狀態(tài)、有序的工具函數(shù)。
針對(duì)useEffect,React每一次更新都會(huì)根據(jù)useEffect的第二個(gè)參數(shù)中依賴(lài)項(xiàng)去判斷是否決定執(zhí)行包裹的函數(shù)。
React會(huì)記住我們編寫(xiě)的effect function,effect function每次更新都會(huì)在作用于DOM,并且讓瀏覽器在繪制屏幕,之后還會(huì)調(diào)用effect function。
1.組件被點(diǎn)擊,觸發(fā)更新count為1,通知React,“count值更新為1了”
2.React響應(yīng),向組件索要count為1的UI
3.組件:
a.給count為1時(shí)候的虛擬DOM
b.告知react完成渲染時(shí),記得調(diào)用一下effect中的函數(shù)() => {document.title = 'you click' + 1 + 'times!'}
4.React通知瀏覽器繪制DOM,更新UI
5.瀏覽器告知ReactUI已經(jīng)更新到屏幕
6.React收到屏幕繪制完成的消息后,執(zhí)行effect中的函數(shù),使得網(wǎng)頁(yè)標(biāo)題變成了“you click 1 times!”。
假如已經(jīng)對(duì)上面的思想和流程已經(jīng)爛熟于心,對(duì)于“快照”的概念也十分認(rèn)同。
有時(shí)候,我們想在effect中拿到最新的值,而不是通過(guò)事件捕獲,官方提供了useRef的hook,useRef在“生命周期”階段是一個(gè)“同步”的變量,我們可以將值存放到其current里,以保證其值是最新的。
對(duì)于上面描述,為什么說(shuō)其值是捕獲而不是最新的,可以通過(guò) setState(x => x + 1),來(lái)理解。傳入的x是前一個(gè)值,x+1是新的值,在一些setTimeout異步代碼里,我們想獲取到最新的值,以便于同步最新的狀態(tài),所以用ref來(lái)幫助存儲(chǔ)最新更新的值。
這種打破范式的做法,讓程序有一絲絲的dirty,但確實(shí)解決了很多問(wèn)題,這樣做的好處,也可以表明哪些代碼是脆弱的,是需要依賴(lài)時(shí)間次序的。
而在類(lèi)組件中,通過(guò) this.setState() 的做法每次拿到的也是最新的值
在前面的描述中或多或少涉及到對(duì)于effect的清理,只是為了便于一個(gè)理解,但描述并不完全準(zhǔn)確。
例如下面的例子:
useEffect(() => { ChatAPI.subscribeToFriendStatus(props.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.id, handleStatusChange); }; });
假設(shè)第一次渲染的時(shí)候props是{id: 10},第二次渲染的時(shí)候是{id: 20}。你可能會(huì)認(rèn)為發(fā)生了下面的這些事:
React 清除了 {id: 10}的effect。
React 渲染{id: 20}的UI。
React 運(yùn)行{id: 20}的effect。
但是實(shí)際情況并非如此,如果按照這種心智模型來(lái)理解,那么在清除時(shí)候,獲取的值是之前的舊值,因?yàn)榍宄窃阡秩拘耈I之前完成的。這和之前說(shuō)到的React只會(huì)在瀏覽器繪制之后執(zhí)行effects矛盾。
React這樣做的好處是不會(huì)阻塞瀏覽器的一個(gè)渲染(屏幕更新)。當(dāng)然,按照這個(gè)規(guī)則,effect的清除也被延遲到了瀏覽器繪制UI之后。那么正確的執(zhí)行順序應(yīng)該是:
React渲染了id 20 的UI
React清除了id 10的effect
React運(yùn)行id 20的effect
那么為啥effect里清除的是舊的吶?
組件內(nèi)的每一個(gè)函數(shù)(包括事件處理函數(shù),effects,定時(shí)器或者API調(diào)用等等)會(huì)捕獲定義它們的那次渲染中的props和state。
那么,effect的清除并不會(huì)讀取到“最新”的props,它只能讀取到定義它那次渲染中props的值
人類(lèi)發(fā)展的進(jìn)程中淘汰的永遠(yuǎn)都是不思進(jìn)取的守舊派。React中亦是如此思想,或許激進(jìn),但大多數(shù)人們總期待“新桃換舊符”。
useEffect中的第二個(gè)參數(shù),可以是一個(gè)參數(shù)數(shù)組(依賴(lài)數(shù)組)。React更新DOM的思想,不管過(guò)程怎樣,只將結(jié)果展示給世人。
React在更新組件的時(shí)候,會(huì)對(duì)比props,通過(guò)AST等方式比較,然后僅需更新變化了的DOM。
第二個(gè)參數(shù)相當(dāng)于告訴了useEffect,只要我給你的這些參數(shù)任中之一發(fā)生了改變,你就執(zhí)行effect就好了。如此,便可以減少每次render之后調(diào)用effect的情況,減少了無(wú)意義的性能浪費(fèi)。
那么在開(kāi)發(fā)過(guò)程中,我們會(huì)嘗試在組件載入時(shí)候,通過(guò)api獲取遠(yuǎn)程數(shù)據(jù),并運(yùn)用于組件的數(shù)據(jù)渲染,所以我們使用了如下的一個(gè)簡(jiǎn)單例子:
useEffect(() => { featchData(); }, []);
由于是空數(shù)組,所以只有在組件掛載(mount)時(shí)獲取一遍遠(yuǎn)程數(shù)據(jù),之后將不再執(zhí)行。如果effect中有涉及到局部變量,那么都會(huì)根據(jù)當(dāng)前的狀態(tài)發(fā)生改變,函數(shù)是每次都會(huì)創(chuàng)建(每次都是創(chuàng)建的新的匿名函數(shù))。
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(id); }, []); return <h2>{count}</h2>; }
你可能會(huì)認(rèn)為上面的例子,會(huì)在組件加載后,每秒U(xiǎn)I上count+1,但實(shí)際情況是只會(huì)執(zhí)行一次。為什么吶?是不是覺(jué)得有些違反直覺(jué)了?
因?yàn)椋](méi)有給effect的依賴(lài)項(xiàng)加入count,effect只會(huì)在第一次渲染時(shí)候,創(chuàng)建了一個(gè)匿名函數(shù),盡管通過(guò)了setInterval包裹,每秒去執(zhí)行count + 1,但是count的值始終是為0,所以在UI表現(xiàn)上永遠(yuǎn)渲染的是1。
當(dāng)然,通過(guò)一些規(guī)則,我們可以通過(guò)加上count來(lái)改變其值,或者通過(guò)useRef,或者通過(guò)setState(x => x+1),模式來(lái)實(shí)現(xiàn)獲取最新的值。例如下面的黑科技操作:
// useRef function Example() { const [count, setCount] = useState(0); const countRef = useRef(count); countRef.current = count; // 假如這一行代碼放到effect函數(shù)中會(huì)怎么樣吶?可以思考下! // answer: 在effect中count是effect匿名函數(shù)聲明時(shí)就有了,值就是0,那么拿到的count值自然也是渲染前(本次props中的值)的count(值為0,再次復(fù)盤(pán)理解下快照的概念),但由于依賴(lài)數(shù)組中并不存在任何依賴(lài),所以該匿名函數(shù)不會(huì)二次執(zhí)行。 // 但,由于setInterval的原因,函數(shù)會(huì)不停地setCount,關(guān)鍵是其中的參數(shù)了,countRef.current = count;取到的值是第一次快照時(shí)候的值0,所以其更新的值永遠(yuǎn)為0+1 = 1。這樣的結(jié)果是符合預(yù)期規(guī)則的。 // 那為什么放在外面就好了吶?因?yàn)閏ountRef.current同步了count的最新值,每次render前就拿到了新的count值,并且賦值給countRef.current,由于ref的同步特性(及時(shí)性、統(tǒng)一性),所以循環(huán)中獲取的countRef.current也是最新的值,故而能實(shí)現(xiàn)計(jì)數(shù)效果 useEffect(() => { const id = setInterval(() => { setCount(countRef.current + 1); }, 1000); return () => clearInterval(id); }, []); return <h2>{count}</h2>; } // setState傳入函數(shù) function Example() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { setCount(x => x + 1); // 傳遞參數(shù)為一個(gè)函數(shù)時(shí)候,默認(rèn)傳遞的第一個(gè)參數(shù)是之前的值,這是useState的hook在處理 }, 1000); return () => clearInterval(id); }, []); return <h2>{count}</h2>; } // 使用useReducer function Counter({ step }) { const [count, dispatch] = useReducer(reducer, 0); function reducer(state, action) { if (action.type === 'tick') { return state + step; } else { throw new Error(); } } useEffect(() => { const id = setInterval(() => { dispatch({ type: 'tick' }); }, 1000); return () => clearInterval(id); }, [dispatch]); return <h2>{count}</h2>; }
上面的做法其實(shí)有些自欺欺人了,可以看到如下圖中的log,在setInterval匿名函數(shù)中count變量的值并沒(méi)有發(fā)生改變,這可能會(huì)給我們的業(yè)務(wù)帶來(lái)一些風(fēng)險(xiǎn)。
demo示例
不過(guò)一般情況下,如果不是對(duì)業(yè)務(wù)或程序有充分的了解,我并不建議大家這樣做。
對(duì)于依賴(lài),首先得誠(chéng)實(shí)地寫(xiě)入相關(guān)聯(lián)的參數(shù),其次,可以?xún)?yōu)化effect,考慮是否真的需要某參數(shù),是否可以替換?
依賴(lài)項(xiàng)中dispatch、setState、useRef包裹的值都是不變的,這些參數(shù)都可以在依賴(lài)項(xiàng)中去除。
可以把函數(shù)定義到useEffect中,這樣添加的依賴(lài)變成了函數(shù)的參數(shù),這樣子,useEffect就無(wú)需添加xxx函數(shù)名作為依賴(lài)項(xiàng)了。
另外如果單純把函數(shù)名放到依賴(lài)項(xiàng)中,如果該函數(shù)在多個(gè)effects中復(fù)用,那么在每一次render時(shí),函數(shù)都是重新聲明(新的函數(shù)),那么effects就會(huì)因新的函數(shù)而頻繁執(zhí)行,這與不添加依賴(lài)數(shù)組一樣,并沒(méi)有起到任何的優(yōu)化效果,那么該如何改善吶?
如果該函數(shù)沒(méi)有使用組件內(nèi)的任何值,那么就把該函數(shù)放到組件外去定義,該函數(shù)就不在渲染范圍內(nèi),不受數(shù)據(jù)流影響,所以其永遠(yuǎn)不變
用useCallback hook來(lái)包裝函數(shù),與useEffect類(lèi)似,其第二個(gè)參數(shù)也是作為函數(shù)是否更新的依賴(lài)項(xiàng)
常見(jiàn)于異步請(qǐng)求數(shù)據(jù),先發(fā)后到,后發(fā)先到的問(wèn)題,這就叫做競(jìng)態(tài),如果該異步函數(shù)支持取消,則直接取消即可
那么更簡(jiǎn)單的做法,給異步加上一個(gè)boolean類(lèi)型的標(biāo)記值,就可以實(shí)現(xiàn)取消異步請(qǐng)求
function Article({ id }) { const [article, setArticle] = useState(null); useEffect(() => { let didCancel = false; async function fetchData() { const article = await API.fetchArticle(id); if (!didCancel) { setArticle(article); } } fetchData(); return () => { didCancel = true; }; }, [id]); // ... }
按照之前的規(guī)則,例如id=19,并且獲取數(shù)據(jù)的時(shí)間為30s,變成了id=20,其獲取數(shù)據(jù)的時(shí)間僅需5s,那么執(zhí)行順序應(yīng)該如下:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
id=19組件卸載,didCancle=true,當(dāng)id=19異步請(qǐng)求收到數(shù)據(jù)時(shí)30s后,由于!didCancle === false,則不執(zhí)行數(shù)據(jù)更新
id=20,因id改變,首先設(shè)置了didCancle=false,請(qǐng)求獲取數(shù)據(jù),5s后拿到了數(shù)據(jù),然后更新數(shù)據(jù),最后將更新后數(shù)據(jù)渲染到屏幕
以上就是“React Hooks的原理分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
標(biāo)題名稱(chēng):ReactHooks的原理分析
分享路徑:http://muchs.cn/article20/pjjsjo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供軟件開(kāi)發(fā)、網(wǎng)站改版、響應(yīng)式網(wǎng)站、定制網(wǎng)站、云服務(wù)器、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)