摘要:但是,你可能已經(jīng)注意到,當(dāng)你試圖通過指定依賴數(shù)組來優(yōu)化時,可能會遇到帶有過時閉包的錯誤。這是否意味著閉包是問題所在我不這么認(rèn)為。到目前為止,我所看到的所有情況下,過時的閉包問題都是由于錯誤地假設(shè)函數(shù)不更改或總是相同而發(fā)生的。
原文鏈接:https://overreacted.io/how-ar...
在很長一段時間內(nèi),標(biāo)準(zhǔn)答案是class components提供更多的特性(像state)。但隨著Hooks的出現(xiàn),答案就不再是這樣子了。
或許你聽說過他們中的一個性能可能更好,哪一個?因為各種的判斷標(biāo)準(zhǔn)獲取都存在缺陷,所以我們需要小心仔細(xì)的得出結(jié)論。性能的好壞主要取決于什么?它主要取決于你的代碼在做什么,而不是你使用的是function還是class。在我們的觀察中,盡管優(yōu)化的策略可能會有些許的不同,但性能的差異幾乎可以忽略不及。
無論是哪種情況,我們都不建議你重寫現(xiàn)有的組件,除非你有一些其他的原因或者是想成為Hooks的早期的采用者。Hooks仍然是一個新特性(就像2014年的React一樣),一些最佳實踐還沒有被寫入到教程中。
那我們該怎么辦?function components和class components之間有什么本質(zhì)的區(qū)別嗎?顯然,在構(gòu)思模型中是不同的。在這篇文章中,我們將看到他們之間最大的區(qū)別,自從2015年推出function componetns以來,它就一直存在著,但是卻經(jīng)常被忽視:
function components捕獲渲染值(capture value)
注意: 本文并不是對函數(shù)或者類的值做判斷。我只描述了React中這兩個編程模型之間的區(qū)別。有關(guān)更廣泛地采用函數(shù)的問題,請參閱hooks常見問題解答。
思考下面這樣一個組件:
function ProfilePage(props) { const showMessage = () => { alert("Followed " + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( ); }
這個組件通過setTimeout模擬網(wǎng)絡(luò)請求,在點擊按鈕3秒后彈出props.user的值,如果props.user的值是Dan的話,他將在點擊后3秒彈出“Followed Dan”。(注意,使用箭頭函數(shù)還是函數(shù)聲明的形式并不重要,handleClick函數(shù)的工作方式完全相同)
如果改寫成class形式可能長下面這個樣子:
class ProfilePage extends React.Component { showMessage = () => { alert("Followed " + this.props.user); }; handleClick = () => { setTimeout(this.showMessage, 3000); }; render() { return ; } }
通常認(rèn)為這兩段代碼是等效的。但是大家經(jīng)常在這兩種形式之間來回切換代碼而不去關(guān)注他們的含義。
然而其實這兩段代碼是有細(xì)微的差別的,就我個人而言,我花費了一段時間才看出來。
如果你自己想弄清楚的話,這里有一個在線demo。本文的剩余部分解釋了有什么不同和它的重要性。
再繼續(xù)之前,我要強(qiáng)調(diào),我所描述的差異本身和React Hooks無關(guān),上面的例子甚至都沒有使用Hooks。
這全部都是React中,function和class的差別。如果你想要在React應(yīng)用中去頻繁的使用function components,那么你應(yīng)該去了結(jié)它。
我們將用一個在React應(yīng)用程序中常見的錯誤來說明這一區(qū)別。
打開這個示例,有一個主頁select和兩個主頁,且每一個包含一個Follow按鈕。
嘗試按照下面的順序操作:
點擊一個Follow按鈕
改變select選項然后等待3秒
查看alert的文字
你會發(fā)現(xiàn)一個問題:
在function components中,在Dan的主頁點擊follow然后切換到Sophie,alert仍然會展示“Followed Dan”。
在class components中,alert的卻是“Followed Sophie”。
在這個例子中,第一個行為是正確的。如果我Follow A,然后導(dǎo)航B的主頁,我的組件不應(yīng)該Follow到B。這個class顯然有缺陷。
所以為什么我們class的例子展示出這樣的結(jié)果呢?
讓我們仔細(xì)研究一下class中的showMessage方法:
showMessage = () => { alert("Followed " + this.props.user); };
這個方法從this.props.user取值,在React中,props應(yīng)該是不可變的,但是this卻是可變的。
實際上,在React內(nèi)部會隨著時間的推移改變this,以便可以在render和生命周期中取到最新的版本。
所以如果我們的組件在請求過程中re-render,this.props將會改變,showMessage方法將會從“最新”的props中取到user的值。
這就暴露了一個關(guān)于UI的有趣問題。如果說UI是一個關(guān)于當(dāng)前應(yīng)用state的函數(shù),那么事件處理函數(shù)就是render的一部分,就像是可視化輸出一樣。我們的事件處理函數(shù)“屬于“某一特定state和props的render。
但是在包含超時操作的回調(diào)函數(shù)內(nèi)讀取this.props會破壞這個關(guān)聯(lián)。showMessage沒有“綁定”到任何一個特定的render,因此它“丟失”了正確的props。
我們說function components不會存在這個問題。那么我們該怎么去解決呢?
我們需要去用某種方式“修復(fù)”正確的props到showMessage之間的關(guān)聯(lián)。在執(zhí)行的某個地方,props丟失了。
一個簡單的方式就是在早期我們就拿到這個this.props的值,然后顯示的去將它傳遞到超時處理函數(shù)中:
class ProfilePage extends React.Component { showMessage = (user) => { alert("Followed " + user); }; handleClick = () => { const {user} = this.props; setTimeout(() => this.showMessage(user), 3000); }; render() { return ; } }
這是可行的。然而,這種方法使代碼更加冗長,并且隨著時間的推移更容易出錯。如果我們需要的不僅僅是一個單一的props怎么辦?如果ShowMessage調(diào)用另一個方法,而該方法讀取this.props.something或this.state.something,我們將再次遇到完全相同的問題。所以我們必須通過在ShowMessage調(diào)用的每個方法,將this.props和this.state作為參數(shù)傳遞。
這樣做我們通常會破壞一個class,并且會導(dǎo)致很多bug出現(xiàn)。
同樣,在handleClick中用alert展示也不能暴露出更深的問題。如果我們想要去結(jié)構(gòu)化我們的代碼,將代碼拆分出不同的方法,并且在讀取props和state時也能保持一樣的展示結(jié)果,而且不僅僅在React中,你也可以在任何UI庫中去調(diào)用它。
也許,我們可以在構(gòu)造函數(shù)中綁定這些方法?
class ProfilePage extends React.Component { constructor(props) { super(props); this.showMessage = this.showMessage.bind(this); this.handleClick = this.handleClick.bind(this); } showMessage() { alert("Followed " + this.props.user); } handleClick() { setTimeout(this.showMessage, 3000); } render() { return ; } }
不,這并不能解決任何問題。記住,我們的問題是拿到this.props太晚了,而不是我們使用何種語法。但是,如果我們完全依賴于js的閉包,問題就會得到解決。
閉包通常是被避免的,因為它很難考慮一個隨時間變化的值。但是在React中,props和state應(yīng)該是不可變的。
這意味著,如果去掉某個特定render中的props或state,則始終可以指望它們保持相同:
class ProfilePage extends React.Component { render() { // Capture the props! const props = this.props; // Note: we are *inside render*. // These aren"t class methods. const showMessage = () => { alert("Followed " + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ; } }
已經(jīng)在render時“捕獲”到了props。
這樣,它里面的任何代碼(包括showMessage)都可以保證取到某個特定render中的props了。
然后我們可以添加很多的helper函數(shù),他們都可以捕獲到props和state。閉包救了我們。
上面的例子是正確的,但看起來很奇怪。如果只是在render中定義函數(shù)而不是使用類方法,那么我們使用一個class又有什么意義呢?
實際上我們可以通過移除class來簡化代碼:
function ProfilePage(props) { const showMessage = () => { alert("Followed " + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( ); }
就像上面所說的,props仍然可以被捕獲到,React將它作為一個參數(shù)傳遞。不同的是,props對象本身不會因React而發(fā)生變化了。
在下面中就更明顯了:
function ProfilePage({ user }) { const showMessage = () => { alert("Followed " + user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( ); }
當(dāng)父組件根據(jù)不同的props渲染時,React將會再次調(diào)用function,但是我們點擊的事件處理函數(shù)是上一個包含user值的render,并且showMessage函數(shù)已經(jīng)拿到了user這個值
這就是為什么在這個版本的function demo中在Sophie主頁點擊Follow,然后改變select,將會alert “Followed Sophie”。
現(xiàn)在我們知道了在React中 function 和 class的最大不同。
function components捕獲渲染值(capture value)
對于鉤子,同樣的原理也適用于state??紤]這個例子:
function MessageThread() { const [message, setMessage] = useState(""); const showMessage = () => { alert("You said: " + message); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); }; return ( <> > ); }
這里是在線demo
這個例子說明了相同點:在點擊send按鈕后,再次修改輸入框的值,3秒后的輸出依然是點擊前輸入框的值。這說明function Hooks同樣具有capture value的特性。
所以我們知道了在React中function默認(rèn)情況下會捕獲props和state(capture value)。但是如果我們想要去避免這個capture value呢?
在class中,我們可以通過使用this.props和this.state,因為this本事是可變的,React改變了它,在function components中,還有一個被所有組件所共享的可變值,被叫做ref:
function MyComponent() { const ref = useRef(null); // You can read or write `ref.current`. // ... }
但是,你必須自己管理它。
ref和實例字段有著相同的作用,你也許更為熟悉“dom refs”,但是這個概念更為普遍,它僅僅是一個“放置一些東西的通用容器”。
盡管看起來它像是某一些東西的鏡像,但實際上他們表示著相同的概念。
React默認(rèn)不會為function components創(chuàng)建保存最新props和state的refs。因為很多情況下你是不需要他們的,并且分配他們也很浪費時間。但是需要的時候可以手動的去跟蹤值:
function MessageThread() { const [message, setMessage] = useState(""); const latestMessage = useRef(""); const showMessage = () => { alert("You said: " + latestMessage.current); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); latestMessage.current = e.target.value; };
這時我們發(fā)現(xiàn),在點擊send按鈕后繼續(xù)輸入,3秒后alert的是點擊按鈕后輸入的值而不是點擊按鈕錢輸入的值。
通常,應(yīng)該避免在渲染期間讀取或設(shè)置refs,因為它們是可變的。我們希望保持渲染的可預(yù)測性。但是,如果我們想要獲取特定props或state的最新值,手動更新ref可能會很煩人。我們可以通過使用一種效果來實現(xiàn)自動化(useEffect在每次render都會執(zhí)行):
function MessageThread() { const [message, setMessage] = useState(""); // Keep track of the latest value. const latestMessage = useRef(""); useEffect(() => { latestMessage.current = message; }); const showMessage = () => { alert("You said: " + latestMessage.current); };
這里是demo
我們在effect中進(jìn)行賦值,以便ref的值只在DOM更新后才更改。
像這樣使用ref不是經(jīng)常需要的。通常capture props或state才是默認(rèn)更好的選擇。但是,在處理諸如間隔和訂閱之類的命令式API時,它非常方便。記住,您可以跟蹤任何這樣的值:一個prop、一個state變量、整個props對象,甚至一個函數(shù)。
在本文中,我們研究了class中常見的中斷模式,以及閉包如何幫助我們修復(fù)它。但是,你可能已經(jīng)注意到,當(dāng)你試圖通過指定依賴數(shù)組來優(yōu)化Hooks時,可能會遇到帶有過時閉包的錯誤。這是否意味著閉包是問題所在?我不這么認(rèn)為。
正如我們上面所看到的,閉包實際上幫助我們解決了難以注意到的細(xì)微問題。類似地,它們使在并發(fā)模式下正確地編寫代碼變得更加容易。
到目前為止,我所看到的所有情況下,“過時的閉包”問題都是由于錯誤地假設(shè)“函數(shù)不更改”或“props總是相同”而發(fā)生的。事實并非如此,我希望這篇文章能夠幫助澄清。
function components沒有props和state,因此它們的也同樣重要。這不是bug,而是function components的一個特性。例如,函數(shù)不應(yīng)該從useEffect或useCallback的“依賴項數(shù)組”中被排除。(正確的解決方案通常是上面的useReducer或useRef解決方案。)
當(dāng)我們用函數(shù)編寫大多數(shù)React代碼時,我們需要調(diào)整優(yōu)化代碼的直覺,以及什么值會隨著時間而改變。
正如Fredrik所說:
對于Hooks,我迄今為止發(fā)現(xiàn)的最好的規(guī)則是“代碼就像任何值在任何時候都可以改變”。
React的function總是捕捉它們的值(capture value)—— 現(xiàn)在我們知道為什么了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/102990.html
摘要:這是一個用于構(gòu)建響應(yīng)式應(yīng)用和網(wǎng)站的前端框架。是基于設(shè)計的一套豐富的組件。這是一個對混合式手機(jī)應(yīng)用框架的擴(kuò)展庫。到目前為止它僅大小,而且不依賴于任何第三方的插件,它可以很輕量的被用來創(chuàng)建和應(yīng)用。 _Material design_是Google開發(fā)的,目的是為了統(tǒng)一公司的web端和手機(jī)端的產(chǎn)品風(fēng)格。它是基于很多的原則,比如像合適的動畫,響應(yīng)式,以及顏色和陰影的使用。完整的指南詳情請看這里...
摘要:這是一個用于構(gòu)建響應(yīng)式應(yīng)用和網(wǎng)站的前端框架。是基于設(shè)計的一套豐富的組件。這是一個對混合式手機(jī)應(yīng)用框架的擴(kuò)展庫。到目前為止它僅大小,而且不依賴于任何第三方的插件,它可以很輕量的被用來創(chuàng)建和應(yīng)用。 _Material design_是Google開發(fā)的,目的是為了統(tǒng)一公司的web端和手機(jī)端的產(chǎn)品風(fēng)格。它是基于很多的原則,比如像合適的動畫,響應(yīng)式,以及顏色和陰影的使用。完整的指南詳情請看這里...
摘要:在里面有兩種組件類組件和函數(shù)式組件兩者有明顯的區(qū)別比如是屬于的類是一個函數(shù)它返回一個組件什么是先看一段代碼這是一個函數(shù)式組件它和類組件最關(guān)鍵的區(qū)別就是函數(shù)式組件沒有和一系列的鉤子函數(shù)這也是函數(shù)式組件經(jīng)常被用作無狀態(tài)組件的原因 在React里面有兩種組件, Class components(類組件) 和 Functional components(函數(shù)式組件).兩者有明顯的區(qū)別,比如 ...
摘要:譯者前端小智原文就像人們對更新移動應(yīng)用程序和操作系統(tǒng)感到興奮一樣,開發(fā)人員也應(yīng)該對更新框架感到興奮。錯誤邊界是一種組件。注意將作為值傳遞進(jìn)去并不會導(dǎo)致使用。如果兩者不同,則返回一個用于更新狀態(tài)的對象,否則就返回,表示不需要更新狀態(tài)。 譯者:前端小智 原文:medium.freecodecamp.org/why-react16… 就像人們對更新移動應(yīng)用程序和操作系統(tǒng)感到興奮一樣,開發(fā)人員也應(yīng)...
閱讀 3468·2023-04-26 00:39
閱讀 4077·2021-09-22 10:02
閱讀 2560·2021-08-09 13:46
閱讀 1108·2019-08-29 18:40
閱讀 1457·2019-08-29 18:33
閱讀 784·2019-08-29 17:14
閱讀 1525·2019-08-29 12:40
閱讀 2985·2019-08-28 18:07