<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          React Hooks 實(shí)現(xiàn)和由來(lái)以及解決的問(wèn)題

          共 6341字,需瀏覽 13分鐘

           ·

          2020-12-08 05:31


          與React類組件相比,React函數(shù)式組件究竟有何不同?

          一般的回答都是:

          1. 類組件比函數(shù)式組件多了更多的特性,比如 state,那如果有 Hooks 之后呢?

          2. 函數(shù)組件性能比類組件好,但是在現(xiàn)代瀏覽器中,閉包和類的原始性能只有在極端場(chǎng)景下才會(huì)有明顯的差別。

          3. 性能主要取決于代碼的作用,而不是選擇函數(shù)式還是類組件。盡管優(yōu)化策略有差別,但性能差異可以忽略不計(jì)。

          4. 參考官網(wǎng):(https://zh-hans.reactjs.org/docs/hooks-faq.html#are-hooks-slow-because-of-creating-functions-in-render)

          5. 參考作者github:(https://github.com/ryardley/hooks-perf-issues/pull/2)

          而下面會(huì)重點(diǎn)講述:React的函數(shù)式組件和類組件之間根本的區(qū)別:在心智模型上。

          簡(jiǎn)單的案例

          函數(shù)式組件以經(jīng)常被忽略的點(diǎn):函數(shù)式組件捕獲了渲染所用的值。(Function components capture the rendered values.)

          思考這個(gè)組件:

          function?ProfilePage(props)?{
          ??const?showMessage?=?()?=>?alert('你好?'?+?props.user);

          ??const?handleClick?=?()?=>?setTimeout(showMessage,?3000);

          ??return?<button?onClick={handleClick}>Followbutton>
          }

          上述組件:如果 props.userDan,它會(huì)在三秒后顯示 你好 Dan

          如果是類組件我們?cè)趺磳??一個(gè)簡(jiǎn)單的重構(gòu)可能就象這樣:

          class?ProfilePage?extends?React.Component?{
          ??showMessage?=?()?=>?alert('Followed?'?+?this.props.user);

          ??handleClick?=?()?=>?setTimeout(this.showMessage,?3000);

          ??render()?{
          ????return?<button?onClick={this.handleClick}>Followbutton>;
          ??}
          }

          通常我們認(rèn)為,這兩個(gè)代碼片段是等效的。人們經(jīng)常在這兩種模式中自由的重構(gòu)代碼,但是很少注意到它們的含義:

          我們通過(guò) React 應(yīng)用程序中的一個(gè)常見錯(cuò)誤來(lái)說(shuō)明其中的不同。

          我們添加一個(gè)父組件,用一個(gè)下拉框來(lái)更改傳遞給子組件(ProfilePage),的 props.user,實(shí)例地址:(https://codesandbox.io/s/pjqnl16lm7) 。

          按步驟完成以下操作:

          1. 點(diǎn)擊 其中某一個(gè) Follow 按鈕。

          2. 在3秒內(nèi) 切換 選中的賬號(hào)。

          3. 查看 彈出的文本。

          這時(shí)會(huì)得到一個(gè)奇怪的結(jié)果:

          • 當(dāng)使用 函數(shù)式組件 實(shí)現(xiàn)的 ProfilePage, 當(dāng)前賬號(hào)是 Dan 時(shí)點(diǎn)擊 Follow 按鈕,然后立馬切換當(dāng)前賬號(hào)到 Sophie,彈出的文本將依舊是 'Followed Dan'。

          • 當(dāng)使用 類組件 實(shí)現(xiàn)的 ProfilePage, 彈出的文本將是 'Followed Sophie'

          在這個(gè)例子中,函數(shù)組件是正確的。如果我關(guān)注一個(gè)人,然后導(dǎo)航到另一個(gè)人的賬號(hào),我的組件不應(yīng)該混淆我關(guān)注了誰(shuí)。 ,而類組件的實(shí)現(xiàn)很明顯是錯(cuò)誤的。

          案例解析

          所以為什么我們的例子中類組件會(huì)有這樣的表現(xiàn)?讓我們仔細(xì)看看類組件中的 showMessage 方法:

          ??showMessage?=?()?=>?{
          ????alert('Followed?'?+?this.props.user);
          ??};

          這個(gè)類方法從 this.props.user 中讀取數(shù)據(jù)。

          1. 在 React 中 Props 是 不可變(immutable)的,所以他們永遠(yuǎn)不會(huì)改變。

          2. this 是而且永遠(yuǎn)是 可變(mutable)的。**

          這也是類組件 this 存在的意義:能在渲染方法以及生命周期方法中得到最新的實(shí)例。

          所以如果在請(qǐng)求已經(jīng)發(fā)出的情況下我們的組件進(jìn)行了重新渲染, this.props將會(huì)改變。showMessage方法從一個(gè)"過(guò)于新"的 props中得到了 user。

          從 this 中讀取數(shù)據(jù)的這種行為,調(diào)用一個(gè)回調(diào)函數(shù)讀取 this.props 的 timeout 會(huì)讓 showMessage 回調(diào)并沒(méi)有與任何一個(gè)特定的渲染"綁定"在一起,所以它"失去"了正確的 props。。

          如何用類組件解決上述BUG?(假設(shè)函數(shù)式組件不存在)

          我們想要以某種方式"修復(fù)"擁有正確 props 的渲染與讀取這些 props 的 showMessage回調(diào)之間的聯(lián)系。在某個(gè)地方 props被弄丟了。

          方法一:在調(diào)用事件之前讀取 `this.props`,然后顯式地傳遞到timeout回調(diào)函數(shù)中:
          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>;
          ??}
          }

          然而,這種方法使得代碼明顯變得更加冗長(zhǎng)。如果我們需要的不止是一個(gè)props 該怎么辦?如果我們還需要訪問(wèn)state 又該怎么辦?如果 showMessage 調(diào)用了另一個(gè)方法,然后那個(gè)方法中讀取了 this.props.something 或者 this.state.something ,我們又將遇到同樣的問(wèn)題。然后我們不得不將 this.propsthis.state以函數(shù)參數(shù)的形式在被 showMessage調(diào)用的每個(gè)方法中一路傳遞下去。

          這樣的做法破壞了類提供的工程學(xué)。同時(shí)這也很難讓人去記住傳遞的變量或者強(qiáng)制執(zhí)行,這也是為什么人們總是在解決bugs。

          這個(gè)問(wèn)題可以在任何一個(gè)將數(shù)據(jù)放入類似 this 這樣的可變對(duì)象中的UI庫(kù)中重現(xiàn)它(不僅只存在 React 中)

          方法二:如果我們能利用JavaScript閉包的話問(wèn)題將迎刃而解。*

          如果你在一次特定的渲染中捕獲那一次渲染所用的props或者state,你會(huì)發(fā)現(xiàn)他們總是會(huì)保持一致,就如同你的預(yù)期那樣:

          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}>Followbutton>;
          ??}
          }

          你在渲染的時(shí)候就已經(jīng)"捕獲"了props:。這樣,在它內(nèi)部的任何代碼(包括 showMessage)都保證可以得到這一次特定渲染所使用的props。

          Hooks 的由來(lái)

          但是:如果你在 render方法中定義各種函數(shù),而不是使用class的方法,那么使用類的意義在哪里?

          事實(shí)上,我們可以通過(guò)刪除類的"包裹"來(lái)簡(jiǎn)化代碼:

          function?ProfilePage(props)?{
          ??const?showMessage?=?()?=>?{
          ????alert('Followed?'?+?props.user);
          ??};

          ??const?handleClick?=?()?=>?{
          ????setTimeout(showMessage,?3000);
          ??};

          ??return?(
          ????<button?onClick={handleClick}>Followbutton>
          ??);
          }

          就像上面這樣, props仍舊被捕獲了 —— React將它們作為參數(shù)傳遞。不同于 this , props 對(duì)象本身永遠(yuǎn)不會(huì)被React改變。

          當(dāng)父組件使用不同的props來(lái)渲染 ProfilePage時(shí),React會(huì)再次調(diào)用 ProfilePage函數(shù)。但是我們點(diǎn)擊的事件處理函數(shù),"屬于"具有自己的 user值的上一次渲染,并且 showMessage回調(diào)函數(shù)也能讀取到這個(gè)值。它們都保持完好無(wú)損。

          這就是為什么,在上面那個(gè)的函數(shù)式版本中,點(diǎn)擊關(guān)注賬號(hào)1,然后改變選擇為賬號(hào)2,仍舊會(huì)彈出 'Followed 賬號(hào)1'

          函數(shù)式組件捕獲了渲染所使用的值。

          使用Hooks,同樣的原則也適用于state。 看這個(gè)例子:

          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?<>
          ????
          ????Send
          ??;
          }

          如果我發(fā)送一條特定的消息,組件不應(yīng)該對(duì)實(shí)際發(fā)送的是哪條消息感到困惑。這個(gè)函數(shù)組件的 message變量捕獲了"屬于"返回了被瀏覽器調(diào)用的單擊處理函數(shù)的那一次渲染。所以當(dāng)我點(diǎn)擊"發(fā)送"時(shí) message被設(shè)置為那一刻在input中輸入的內(nèi)容。

          讀取最新的狀態(tài)

          因此我們知道,在默認(rèn)情況下React中的函數(shù)會(huì)捕獲props和state。但是如果我們想要讀取并不屬于這一次特定渲染的,最新的props和state呢?如果我們想要["從未來(lái)讀取他們"]呢?

          在類中,你通過(guò)讀取 this.props或者 this.state來(lái)實(shí)現(xiàn),因?yàn)?this本身時(shí)可變的。React改變了它。在函數(shù)式組件中,你也可以擁有一個(gè)在所有的組件渲染幀中共享的可變變量。它被成為"ref":

          function?MyComponent()?{
          ??const?ref?=?useRef(null);

          }

          但是,你必須自己管理它。

          一個(gè)ref與一個(gè)實(shí)例字段扮演同樣的角色。這是進(jìn)入可變的命令式的世界的后門。你可能熟悉'DOM refs',但是ref在概念上更為廣泛通用。它只是一個(gè)你可以放東西進(jìn)去的盒子。

          甚至在視覺(jué)上, this.something就像是 something.current的一個(gè)鏡像。他們代表了同樣的概念。

          默認(rèn)情況下,React不會(huì)在函數(shù)式組件中為最新的props和state創(chuàng)造refs。在很多情況下,你并不需要它們,并且分配它們將是一種浪費(fèi)。但是,如果你愿意,你可以這樣手動(dòng)地來(lái)追蹤這些值:

          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;??};

          如果我們?cè)?showMessage中讀取 message,我們將得到在我們按下發(fā)送按鈕那一刻的信息。但是當(dāng)我們讀取 latestMessage.current,我們將得到最新的值 —— 即使我們?cè)诎聪掳l(fā)送按鈕后繼續(xù)輸入。

          ref是一種"選擇退出"渲染一致性的方法,在某些情況下會(huì)十分方便。

          通常情況下,你應(yīng)該避免在渲染期間讀取或者設(shè)置refs,因?yàn)樗鼈兪强勺兊?。我們希望保持渲染的可預(yù)測(cè)性。然而,如果我們想要特定props或者state的最新值,那么手動(dòng)更新ref會(huì)有些煩人。我們可以通過(guò)使用一個(gè)effect來(lái)自動(dòng)化實(shí)現(xiàn)它:

          function?MessageThread()?{
          ??const?[message,?setMessage]?=?useState('');

          ??const?latestMessage?=?useRef('');
          ??useEffect(()?=>?{
          ????latestMessage.current?=?message;
          ??});
          ??const?showMessage?=?()?=>?{
          ????alert('You?said:?'?+?latestMessage.current);
          ??};

          我們?cè)谝粋€(gè)effect 內(nèi)部執(zhí)行賦值操作以便讓ref的值只會(huì)在DOM被更新后才會(huì)改變。這確保了我們的變量突變不會(huì)破壞依賴于可中斷渲染的時(shí)間切片和 Suspense 等特性。

          通常來(lái)說(shuō)使用這樣的ref并不是非常地必要。捕獲props和state通常是更好的默認(rèn)值。 然而,在處理類似于intervals和訂閱這樣的命令式API時(shí),ref會(huì)十分便利。你可以像這樣跟蹤 任何值 —— 一個(gè)prop,一個(gè)state變量,整個(gè)props對(duì)象,或者甚至一個(gè)函數(shù)。

          這種模式對(duì)于優(yōu)化來(lái)說(shuō)也很方便 —— 例如當(dāng) useCallback本身經(jīng)常改變時(shí)。然而,使用一個(gè)reducer 通常是一個(gè)更好的解決方式

          閉包幫我們解決了很難注意到的細(xì)微問(wèn)題。同樣,它們也使得在并發(fā)模式下能更輕松地編寫能夠正確運(yùn)行的代碼。這是可行的,因?yàn)榻M件內(nèi)部的邏輯在渲染它時(shí)捕獲并包含了正確的props和state。

          函數(shù)捕獲了他們的props和state —— 因此它們的標(biāo)識(shí)也同樣重要。這不是一個(gè)bug,而是一個(gè)函數(shù)式組件的特性。例如,對(duì)于 useEffect或者 useCallback來(lái)說(shuō),函數(shù)不應(yīng)該被排除在"依賴數(shù)組"之外。(正確的解決方案通常是使用上面說(shuō)過(guò)的 useReducer或者 useRef

          當(dāng)我們用函數(shù)來(lái)編寫大部分的React代碼時(shí),我們需要調(diào)整關(guān)于優(yōu)化代碼和什么變量會(huì)隨著時(shí)間改變的認(rèn)知與直覺(jué)。

          到目前為止,我發(fā)現(xiàn)的有關(guān)于hooks的最好的心里規(guī)則是"寫代碼時(shí)要認(rèn)為任何值都可以隨時(shí)更改"。

          React函數(shù)總是捕獲他們的值 —— 現(xiàn)在我們也知道這是為什么了。

          文章參考:React作者 Dan Abramov 的github



          最后



          如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「qianyu443033099」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。


          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了



          瀏覽 26
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本A片高清 | 亚洲成a人片77777精品 | 成人黄频 | 亚洲精品国产精品乱码不卡√香蕉 亚洲日韩一区二区三区四区丨高清 | 国产高清无码内射视频在线观看 |