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