您好,登錄后才能下訂單哦!
這篇文章給大家介紹ReactHooks實現和由來以及如何解決問題,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
與React類組件相比,React函數式組件究竟有何不同?
一般的回答都是:
類組件比函數式組件多了更多的特性,比如 state,那如果有 Hooks 之后呢?函數組件性能比類組件好,但是在現代瀏覽器中,閉包和類的原始性能只有在極端場景下才會有明顯的差別。
性能主要取決于代碼的作用,而不是選擇函數式還是類組件。盡管優化策略有差別,但性能差異可以忽略不計。
而下面會重點講述:React的函數式組件和類組件之間根本的區別: 在心智模型上。
簡單的案例
函數式組件以來,它一直存在,但是經常被忽略:函數式組件捕獲了渲染所用的值。(Function components capture the rendered values.)
思考這個組件:
function ProfilePage(props) { const showMessage = () => alert('你好 ' + props.user); const handleClick = () => setTimeout(showMessage, 3000); return <button onClick={handleClick}>Follow</button>}
上述組件:如果 props.user是 Dan,它會在三秒后顯示 你好 Dan。
如果是類組件我們怎么寫?一個簡單的重構可能就象這樣:
class ProfilePage extends React.Component { showMessage = () => alert('Followed ' + this.props.user); handleClick = () => setTimeout(this.showMessage, 3000); render() { return <button onClick={this.handleClick}>Follow</button>; }}
通常我們認為,這兩個代碼片段是等效的。人們經常在這兩種模式中自由的重構代碼,但是很少注意到它們的含義:
我們通過 React 應用程序中的一個常見錯誤來說明其中的不同。
我們添加一個父組件,用一個下拉框來更改傳遞給子組件(ProfilePage),的 props.user,實例地址:(https://codesandbox.io/s/pjqnl16lm7)
按步驟完成以下操作:
點擊 其中某一個 Follow 按鈕。在3秒內 切換 選中的賬號。查看 彈出的文本。
這時會得到一個奇怪的結果:
當使用 函數式組件 實現的 ProfilePage, 當前賬號是 Dan 時點擊 Follow 按鈕,然后立馬切換當前賬號到 Sophie,彈出的文本將依舊是 'Followed Dan'。當使用 類組件 實現的 ProfilePage, 彈出的文本將是 'Followed Sophie':
在這個例子中,函數組件是正確的。 如果我關注一個人,然后導航到另一個人的賬號,我的組件不應該混淆我關注了誰。 ,而類組件的實現很明顯是錯誤的。
案例解析
所以為什么我們的例子中類組件會有這樣的表現? 讓我們仔細看看類組件中的 showMessage 方法:
showMessage = () => { alert('Followed ' + this.props.user); };
這個類方法從 this.props.user 中讀取數據。
在 React 中 Props 是 不可變(immutable)的,所以他們永遠不會改變。
而 this 是而且永遠是 可變(mutable)的。**
這也是類組件 this 存在的意義:能在渲染方法以及生命周期方法中得到最新的實例。
所以如果在請求已經發出的情況下我們的組件進行了重新渲染, this.props將會改變。 showMessage方法從一個"過于新"的 props中得到了 user。
從 this 中讀取數據的這種行為,調用一個回調函數讀取 this.props 的 timeout 會讓 showMessage 回調并沒有與任何一個特定的渲染"綁定"在一起,所以它"失去"了正確的 props。。
如何用類組件解決上述BUG?(假設函數式組件不存在)
我們想要以某種方式"修復"擁有正確 props 的渲染與讀取這些 props 的 showMessage回調之間的聯系。在某個地方 props被弄丟了。
方法一:在調用事件之前讀取 this.props,然后顯式地傳遞到timeout回調函數中:
class ProfilePage extends React.Component { showMessage = (user) => alert('Followed ' + user); handleClick = () => { const {user} = this.props; setTimeout(() => this.showMessage(user), 3000); }; render() { return <button onClick={this.handleClick}>Followbutton>; }}
然而,這種方法使得代碼明顯變得更加冗長。如果我們需要的不止是一個props 該怎么辦? 如果我們還需要訪問state 又該怎么辦? 如果 showMessage 調用了另一個方法,然后那個方法中讀取了 this.props.something 或者 this.state.something ,我們又將遇到同樣的問題。然后我們不得不將 this.props和 this.state以函數參數的形式在被 showMessage調用的每個方法中一路傳遞下去。
這樣的做法破壞了類提供的工程學。同時這也很難讓人去記住傳遞的變量或者強制執行,這也是為什么人們總是在解決bugs。
這個問題可以在任何一個將數據放入類似 this 這樣的可變對象中的UI庫中重現它(不僅只存在 React 中)
方法二:如果我們能利用JavaScript閉包的話問題將迎刃而解。*
如果你在一次特定的渲染中捕獲那一次渲染所用的props或者state,你會發現他們總是會保持一致,就如同你的預期那樣:
class ProfilePage extends React.Component { render() { const props = this.props; const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return <button onClick={handleClick}>Follow</button>; }}
你在渲染的時候就已經"捕獲"了props:。這樣,在它內部的任何代碼(包括 showMessage)都保證可以得到這一次特定渲染所使用的props。
Hooks 的由來
但是:如果你在 render方法中定義各種函數,而不是使用class的方法,那么使用類的意義在哪里?
事實上,我們可以通過刪除類的"包裹"來簡化代碼:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> );}
就像上面這樣, props仍舊被捕獲了 —— React將它們作為參數傳遞。 不同于 this , props 對象本身永遠不會被React改變。
當父組件使用不同的props來渲染 ProfilePage時,React會再次調用 ProfilePage函數。但是我們點擊的事件處理函數,"屬于"具有自己的 user值的上一次渲染,并且 showMessage回調函數也能讀取到這個值。它們都保持完好無損。
這就是為什么,在上面那個的函數式版本中,點擊關注賬號1,然后改變選擇為賬號2,仍舊會彈出 'Followed 賬號1':
函數式組件捕獲了渲染所使用的值。
使用Hooks,同樣的原則也適用于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 <> <input value={message} onChange={handleMessageChange} /> <button onClick={handleSendClick}>Send</button> </>;}
如果我發送一條特定的消息,組件不應該對實際發送的是哪條消息感到困惑。這個函數組件的 message變量捕獲了"屬于"返回了被瀏覽器調用的單擊處理函數的那一次渲染。所以當我點擊"發送"時 message被設置為那一刻在input中輸入的內容。
讀取最新的狀態
因此我們知道,在默認情況下React中的函數會捕獲props和state。 但是如果我們想要讀取并不屬于這一次特定渲染的,最新的props和state呢?如果我們想要["從未來讀取他們"]呢?
在類中,你通過讀取 this.props或者 this.state來實現,因為 this本身時可變的。React改變了它。在函數式組件中,你也可以擁有一個在所有的組件渲染幀中共享的可變變量。它被成為"ref":
function MyComponent() { const ref = useRef(null);}
但是,你必須自己管理它。
一個ref與一個實例字段扮演同樣的角色。這是進入可變的命令式的世界的后門。你可能熟悉'DOM refs',但是ref在概念上更為廣泛通用。它只是一個你可以放東西進去的盒子。
甚至在視覺上, this.something就像是 something.current的一個鏡像。他們代表了同樣的概念。
默認情況下,React不會在函數式組件中為最新的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; };
如果我們在 showMessage中讀取 message,我們將得到在我們按下發送按鈕那一刻的信息。但是當我們讀取 latestMessage.current,我們將得到最新的值 —— 即使我們在按下發送按鈕后繼續輸入。
ref是一種"選擇退出"渲染一致性的方法,在某些情況下會十分方便。
通常情況下,你應該避免在渲染期間讀取或者設置refs,因為它們是可變得。我們希望保持渲染的可預測性。 然而,如果我們想要特定props或者state的最新值,那么手動更新ref會有些煩人。我們可以通過使用一個effect來自動化實現它:
function MessageThread() { const [message, setMessage] = useState(''); const latestMessage = useRef(''); useEffect(() => { latestMessage.current = message; }); const showMessage = () => { alert('You said: ' + latestMessage.current); };
我們在一個effect 內部執行賦值操作以便讓ref的值只會在DOM被更新后才會改變。這確保了我們的變量突變不會破壞依賴于可中斷渲染的時間切片和 Suspense 等特性。
通常來說使用這樣的ref并不是非常地必要。 捕獲props和state通常是更好的默認值。 然而,在處理類似于intervals和訂閱這樣的命令式API時,ref會十分便利。你可以像這樣跟蹤 任何值 —— 一個prop,一個state變量,整個props對象,或者甚至一個函數。
這種模式對于優化來說也很方便 —— 例如當 useCallback本身經常改變時。然而,使用一個reducer 通常是一個更好的解決方式
閉包幫我們解決了很難注意到的細微問題。同樣,它們也使得在并發模式下能更輕松地編寫能夠正確運行的代碼。這是可行的,因為組件內部的邏輯在渲染它時捕獲并包含了正確的props和state。
函數捕獲了他們的props和state —— 因此它們的標識也同樣重要。這不是一個bug,而是一個函數式組件的特性。例如,對于 useEffect或者 useCallback來說,函數不應該被排除在"依賴數組"之外。(正確的解決方案通常是使用上面說過的 useReducer或者 useRef )
當我們用函數來編寫大部分的React代碼時,我們需要調整關于優化代碼和什么變量會隨著時間改變的認知與直覺。
到目前為止,我發現的有關于hooks的最好的心里規則是"寫代碼時要認為任何值都可以隨時更改"。React函數總是捕獲他們的值 —— 現在我們也知道這是為什么了。
關于ReactHooks實現和由來以及如何解決問題就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。