<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-Router 帶來的‘血案’

          共 1856字,需瀏覽 4分鐘

           ·

          2021-10-28 12:59

          一 前言

          在前端開發(fā)過程中,有一種風險開發(fā)者值得警惕,就是正常情況下沒有問題,但是因為一次小上線,或者一次服務(wù)器部署,造成的線上 bug 的情況,更有甚者線上的 bug 和上線的內(nèi)容毫不相干,那么今天筆者就給大家分享一個真實案例。

          本次案例覆蓋的知識點如下:

          • 1 項目中安裝依賴包的規(guī)范。
          • 2 context 的消費訂閱。
          • 3 react-router v5.2.0 版本變化。
          • 4 本地和線上事故排查。

          二 問題背景

          接下來介紹一下具體問題,最近有同學(化名小明)在開發(fā)中遇到了一個問題,就是使用 React-Router 帶來的線上事故。事故的發(fā)生源頭就是因為一個全局組件內(nèi)使用了 React-Router 中的自定義 hooks —— useHistory,具體細節(jié)是這樣的。

          import?{?useHistory?}?from?'react-router'
          function?Index(){
          ????/*?獲取?histroy?對象?*/
          ????const?history?=?useHistory()
          ????console.log(history)
          ????return?<div>
          ????????{/*?展示?history?里面信息,期望當?history?中?location?信息變化的時候,組件也能更新?*/}
          ????div>

          }

          小明用 React-Router 中的 useHistory 來獲取 history 對象里面的狀態(tài)。并期望:

          • 展示 location 里面的字段。
          • 當路由跳轉(zhuǎn),history 發(fā)生變化,期望組件 Index 也重新渲染,更新展示內(nèi)容。

          這個功能在項目中是一直沒有問題的。但是最近小明開發(fā)了一個和當前組件毫無關(guān)系的新功能,并上了線。

          結(jié)果在線上就出現(xiàn)了事故:當路由改變的時候,Index組件不再像原來一樣更新了。

          更讓人匪夷所思的是,小明在本地環(huán)境下,不會出現(xiàn)問題。所以這個問題也就伴隨著上了線。也就是說這個問題只出現(xiàn)在線上。

          這個突如其來的問題,讓小明一臉懵逼,頓時慌了手腳。那么是什么原因造成的呢?

          7.jpg

          三 解決問題

          本地和線上不一樣

          接下來我們來幫助小明解決這個問題。那么首先??思考一下:為什么會出現(xiàn)本地和線上不一致的情況發(fā)生?

          線上和本地不一致,那么這種情況下,第一個應(yīng)該想到的就是,是不是線上的依賴包和本地的有區(qū)別。那么驗證也很簡單,就是升級本地的所有包,因為線上部署的包,一般都是 install 一個的新的包。那么可以通過如下方式驗證一下:

          • 下載本地 node_modules;
          • 重新安裝 npm install

          經(jīng)過上述方案折騰之后,發(fā)現(xiàn)本地現(xiàn)象和線上的一樣了。那么又引出了一個新的問題,小明壓根兒沒有更新過項目依賴,那么為什么會造成依賴包的差別呢?

          這個本質(zhì)上和 npm 包安裝機制有關(guān)系,也就是比如你的項目依賴了 x.x.x 版本的 a 模塊,那么部署上線后項目中就一定安裝 x.x.x 版本的 a 嗎?答案是否定的,具體 npm 怎樣處理,一會會重點介紹。通過上述情況,首先分析出,問題出現(xiàn)在 React-Router 庫上,于是看一下小明項目中 package.json

          "react-router":?"^5.1.2",
          • 如上可以看到,小明項目中用的 react-router5.1.2 版本的,那么問題就在 ^ 上。

          npm 版本安裝機制

          ^ 在package.json中代表什么意思,原來在 package.json^ 會匹配最新的大版本依賴包。打個比方:

          • 如果依賴版本這么寫^1.2.3,表示安裝1.x.x的最新版本(不低于1.2.3,包括1.3.0),但是不安裝2.x.x,也就是說安裝時不改變大版本號。

          那么小明在項目中 ^5.1.2 這么寫,那么如果有更高版本的 react-router 比如 5.2.x5.3.x,那么會下載最新安裝包,一直到 6.0.0 為止(不會安裝 6.0.0 )。

          需要注意的是,如果大版本號為0,則插入號的行為與波浪號相同,這是因為此時處于開發(fā)階段,即使是次要版本號變動,也可能帶來程序的不兼容。(主版本)

          比如 ^0.2.3 那么代表安裝的版本范圍是 >=0.2.3 <0.3.0

          依賴版本對應(yīng)關(guān)系

          符號例子范圍說明
          ^會匹配最新的大版本依賴包^1.2.3>=1.2.3 <2.0.0、表示安裝1.x.x的最新版本(不低于1.2.3,包括1.3.0),但是不安裝2.x.x,也就是說安裝時不改變大版本號。
          ~會匹配最近的小版本依賴包~1.2.3>=1.2.3 <1.3.0表示安裝1.2.x的最新版本(不低于1.2.3),但是不安裝1.3.x,也就是說安裝時不改變大版本號和次要版本號。
          >=>=2.1.0>=2.1.0大于等于2.1.0
          <=<=2.0.0<=2.0.0小于等于2.0.0
          laster----安裝最新的版本
          *----任何版本
          -1.2.3 - 2.3.4>=1.2.3 <=2.3.4兩個版本之間

          那么我們回到小明遇到的問題的上,既然知道了原因是自動升級了,那么如果解決這個問題呢?

          現(xiàn)在到了解決問題的時候了,如果出現(xiàn)線上和本地版本差異帶來的 bug ,那么最直接快速的方式就是固定版本。

          "react-router":?"5.1.2",

          版本號前面不加任何符號,固定版本 5.1.2,最根本有效的解決了問題。

          顯然這個不是最佳答案,首先我們應(yīng)該從問題的本質(zhì)入手,為什么 react-router 不能通過 useHistory 訂閱路由信息了。那么本質(zhì)上到底改了些什么呢?我們找到 react-routerV5.1.2源碼,

          export?function?useHistory()?{
          ??return?useContext(Context).history;
          }
          • 如上可以看到 useHistory 本質(zhì)上調(diào)用了 useContext ,使用了整個路由庫中 Contexthistory 對象。
          • Context 上保存了整個路由狀態(tài)信息,每次路由改變,就是通過 Context 變化來通知路由組件渲染對應(yīng)視圖的。對于 React Router 還不熟悉的同學,可以看一下筆者的另外一篇文章:「源碼解析 」這一次徹底弄懂react-router路由原理

          如果對 context 的訂閱消費機制不熟悉的話,請往下??看。

          context 消費機制

          useHistory 本質(zhì)上用的是 useContext , useContext 本質(zhì)上是訂閱了新版本的 React Context 對象。這里有必要介紹一下 React Context 訂閱更新機制。

          新版本的 Context 對象包括提供者 Provider 和訂閱者 Consumer

          • Provider : 傳遞 context value 值。
          • Consumer: 消費 Provider 提供的 value 值。
          • 類組件 contextType 和函數(shù)組件的 useContext也可以訂閱消費 context value ?,并且 context value 改變的時候,它們會重新渲染,而且不受到 PureComponentmemoshouldComponentUpdate 優(yōu)化策略的影響。

          我們回到小明遇到的問題,之前小明用 useHistory 來訂閱路由變化,當路由更新,那么使用 useHistory 的組件會重新渲染,因為之前的邏輯是,路由更新就會更新 history 對象 。我們來模擬一下流程。

          const?Context?=?React.createContext()

          function?useName?(){
          ????return?React.useContext(Context).name
          }

          const?Child?=?()=>{
          ????const?name?=?useName()
          ????return?<div>
          ????????{name}
          ????div>

          }

          const?Index?=?memo(function(){
          ????return?<div>
          ????????<p>root?組件?p>

          ????????<Child/>
          ????div>
          })

          export?default?function?App(){
          ????const?[?value?,?changeValue???]?=?React.useState({?name:'列表'?,?path:'/list'??})
          ????return?<div>
          ????????<Context.Provider?value={value}?>
          ????????????<Index?/>
          ????????Context.Provider>

          ????????<button?onClick={()=>?changeValue({?name:'首頁',path:'/detail'?})}?>改變?value?button>
          ????div>
          }

          效果:

          10.gif
          • 切換路由相當于調(diào)用 changeValue,改變了 Provider 中的 value
          • 小明使用的組件就是 Child ,而使用的 useHistory 類似于 useName
          • 當點擊按鈕改變 value 。Child 更新視圖。

          react-router改版

          上面知道了 context 的訂閱更新機制,那么為什么現(xiàn)在的 useHistory ,那么新版本的 react-router 改動了些什么呢?后來查看更新日志發(fā)現(xiàn),在 react-router v5.2.0 的時候,已經(jīng)把 history 的 Context 中抽離出來,而且已經(jīng)有了自己的 Context 。

          這個是 Releases 記錄:

          9.jpg

          然后我們又去看了下源碼:


          export?function?useHistory()?{
          ??return?useContext(HistoryContext);
          }

          export?function?useLocation()?{
          ??return?useContext(Context).location;
          }

          通過上面可以看到:

          • useHistory 已經(jīng)不再訂閱 Context ,而是 HistoryContext
          • useLocation 依舊訂閱 Context
          • 當我們改變路由的時候,本質(zhì)上改變的是 Context,所以使用 useLocation 的組件會更新,使用 useHistory 的組件不會更新。

          到這里恍然大悟,真相終于浮出了水面。

          8.jpg

          四 總結(jié)

          通過本文的學習,可以收獲如下內(nèi)容:

          • 線上和本地不一致問題排查。
          • package.json版本號問題。
          • useHistory 原理。
          • context 訂閱更新流程。

          覺得有收獲的同學可以給筆者 點贊 + 關(guān)注,持續(xù)分享前端好文。

          參考資源

          • package.json詳解
          • 「源碼解析 」這一次徹底弄懂react-router路由原理


          瀏覽 75
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  影音先锋激情在线 | 中文黄色电影 | 久久色情视频 | 精品国产久久久久 | 操女人黄片 |