1. 程式人生 > >React Router 從v3升級到v4的踩坑之旅

React Router 從v3升級到v4的踩坑之旅

React 應用很少不用react-router這個包的。marknoteapp.com之前一直用v3,看到v4出來後一直心癢。最近,抱著 用新不用舊 的理念,手賤升了一下級。這一升級,差不多2天功夫花掉啦。

概述

和 Angular 那改朝換代般的升級相比,React技術棧一直以其穩定的 API 而備受好評。不過,這次 react-router 從v3到v4的升級,簡直是砸 React 的好名聲的。

如果你以為這個升級只是在 package.json 中簡單的改一下版本號就好了,那你就大錯特錯了。我相信,你改完版本號之後,會像我當初一樣面對成堆的問題而驚奇不已。

事實上,不少開發者對v4的的變化感到驚詫,比如下面的這位就抱怨v3升v4花了10天。

圖片描述

總之,v3到v4不是一件幾分鐘就可以搞定的事。一箇中型的專案,花2-3天在升級上很正常。

重要的事情說三遍:升級之前先備份!升級之前先備份!!升級之前先備份!!!

備份完之後,就和我一起來踩坑吧!

坑一: 包的選擇

react router v4 是對v3的重寫。現在分為三個包:

  • react-router:只提供核心的路由和函式。一般的應用不會直接使用;
  • react-router-dom:供瀏覽器/Web應用使用的API。依賴於react-router, 同時將react-router的API重新暴露(export)出來;
  • react-router-native:供 React Native 應用使用的API。同時將react-router的API重新暴露(export)出來;

對於一般的web應用,直接引入第二個包即可,安裝之前先刪除老版本react-router:

npm uninstall react-router --save
npm install --save react-router-dom

或者你像我一樣,更親睞yarn的話:

yarn remove react-router
yarn add react-router-dom

引入完之後,要做的第一件事就是將所有原先引入react-router的地方都換成react-router-dom。

// 原先
import { Route, Redirect } from 'react-router';
// 現在
import { Route, Redirect } from
'react-router-dom';

坑二:不再用,要用具體的實現

在V3中,一般使用,只是在其history屬性中指定是哪種實現: hashHistory或者browserHistory之類的。

// v3
import { Router, hashHistory } from 'react-router';
const MyApp = () => (
     <Router history={hashHistory}>
       ...
    </Router>
);

而在v4中,你需要直接用具體的實現,比如HashRouter或者BrowserRouter:

// v4
import { HashRouter } from 'react-router-dom';
const MyApp = () => (
    <HashRouter>
        ...
    </HashRouter>
);

坑三: 對於redux應用,你需要用react-router-redux

如果你像我一樣,也用了redux的話,那麼恭喜你,你還得用react-router-redux。

而且,這個包的正式版4.x不支援react-router v4。你需要用 alpha 版 的react-router-redux。在package.json 里加入react-router-redux~5.0.0或者用yarn:

yarn add react-router-redux@5.0.0

雖然我用的是alpha 6,貌似也沒啥毛病。

而且,對於redux應用,你不可以用BrowserRouter,或者HashRouter,而應該使用ConnectedRouter。

比如,我的程式碼:

import { Route } from 'react-router-dom';
import history from 'history/createBrowserHistory';
import { ConnectedRouter, routerReducer, routerMiddleware } from 'react-router-redux';
import App from './components/App';
ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history()}>
      <Route path="/" component={App} />
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);

坑四:巢狀 Route和Index Route 全失效啦

在V3中,巢狀Route和Index Route的使用是很常見的。比如marknoteapp.com中,部落格部分的路由定義如下:

<Route path="/blog/:bloggerId" component={BlogLayout}>
          <IndexRoute component={BlogList}/>
          <Route path="/blog/:bloggerId/settings" component={BlogConfig}/>
          <Route path="/blog/:bloggerId/about" component={BlogAbout}/>
          <Route path="/blog/:bloggerId/dashboard" component={BlogDashboard}/>
         <Route path="/blog/:bloggerId/:postId" component={BlogPost}/>
</Route>

這樣/blog/123 會觸發BlogLayout,和BlogList,而/blog/123/456則會觸發BlogLayout和BlogPos。很方便。

在 v4 中 IndexRoute沒了,然後不允許Route巢狀。

要實現類似的功能,需要兩步:

  • 在父元件中將路由給”/blog/:blggerId”給”BlogLayout”,程式碼如下:
<Switch>
           <Route path="/blog/:bloggerId" component={BlogLayout} />
</Switch>
  • 然後在BlogLayout中定義Blog這部分的路由:
<div className='content' id="blog-content">
     <Switch>
          <Route exact path="/blog/:bloggerId/settings" component={BlogConfig} />
          <Route exact path="/blog/:bloggerId/about" component={BlogAbout} />
         <Route exact path="/blog/:bloggerId/dashboard" component={BlogDashboard} />
         <Route exact path="/blog/:bloggerId/:postId" component={BlogPost} />
         <Route exact path="/blog/:bloggerId" component={BlogList} />
     </Switch>
</div>

這裡有幾點需要注意:

  • 在 v3 中,Router 的定義一般是全域性的,所有的路由都在一個檔案中定義;而在 v4 中Router可以出現在任何元件中。
  • 在 v4 中,Router 在UI元件中載入對應的子 UI 元件。比如,我的上面的程式碼中,路由會根據URL 匹配來在BlogConfig/BlogAbout/BlogDashboard/BlogPost/BlogList選擇一個,載入進來,並顯示在id為 blog-content 的 div 中。
  • Switch 用來從多個Route中選出一個。否則會觸發多個元件。
  • exact 這個屬性來表示精確匹配,否則,URL /blog/123/456 也可以觸發BlogList。
  • 由於v4中沒有了IndexRoute,將BlogList 的 path宣告得和它的父元件一樣就可以了。
  • 在v4中,path屬性永遠是絕對路徑。如果你不想重複當前級別的路徑,可以使用match這個屬性。

比如在我的程式碼中, 在BlogLayout中,我可以 match.url 來代替“/blog/:bloggerId”,程式碼如下:

<div className='content' id="blog-content">
      <Switch>
            <Route exact path={"${match.url}/settings"}} component={BlogConfig} />
            <Route exact path={"${match.url}/about"} component={BlogAbout} />
            <Route exact path={"${match.url}/dashboard"} component={BlogDashboard} />
            <Route exact path={"${match.url}/:postId"} component={BlogPost} />

            <Route exact path={"${match.url}}component={BlogList} />

      </Switch>
</div>

坑五:不再可以從params中取URL引數了

在 v3 中,你可以從params這個屬性中取到URL 中傳遞過來的引數。比如我在 BlogLayout中獲取bloggerID,v3的程式碼如下:

//v3
const bloggerId = this.props.params.bloggerId;

這個屬性在v4中不再被自動注入了,需要從match屬性中獲取。程式碼如下:

//v4
const bloggerId = this.props.match.params.bloggerId;

這裡還有一個小小的差異。在v3中,引數會自動解碼,而在v4中不會。所以如果你的URL引數中有特殊字元的話,你可能需要自己調decodeURI之類的方法解碼。

坑六:Route的onEnter, onUpdate和onLeave之類的事件沒有了

在v3中,我可以使用使用 Route的 onEnter, onUpdate和 onLeave事件來做一些事情。

比如,我可以在onUpdate中觸發google analytics來跟蹤使用者行為:

//v3
<Router history={browserHistory} onUpdate={doLogPageView} >

這裡doLogPageView實現如下:

function doLogPageView(){
  const sUrl = window.location.pathname + window.location.search;

  ReactGA.set({ page: sUrl });
  ReactGA.pageview(sUrl);
}

在v4中,Route的事件沒了。我查了很多資料,貌似有兩種解決辦法:

  • 將程式碼移到每個元件的componentWillMount 方法中去。這意味著每個元件都要做這樣的事情,程式碼冗餘;
  • 使用withRouter 這個HOC (高階元件);

我用的後一種方法,程式碼如下:

import { withRouter } from 'react-router-dom';
import ReactGA from 'react-ga';
class App extends Component {

  componentWillMount() {
    this.setState(
      {
         height: window.innerHeight,
        sidebarOpen: false,
        docked: true,
      }
    );
    this.onSetSidebarOpen = this.onSetSidebarOpen.bind(this);

    this.props.history.listen((location, action) => {
      const url = location.pathname + location.search + location.hash;
      ReactGA.set({ page: url });
      ReactGA.pageview(url);
      console.log(`current URL:${url}`);

     });
  }
}
export default withRouter(App);

總結

簡單的說,react-router v4 簡直不像是v3的升級,而更像一次天馬行空的重寫。其理念是完全不一樣的。v3 更像是AOP (面向切面程式設計)的樣子:路由全域性配置,可以通過 事件進行一些共通的處理;v4 用react-router 團隊的話說是 real component,可以嵌入任何元件中。

據說 facebook 的react 團隊對 v3之前的react-router 一直沒有好感,而v4之後則頗多溢美之詞。

但是客觀的講,從v3升級到 v4遇到的問題還是比較多的。尤其是官方居然沒有一個全面的指南。雖然在 這裡 有一個文件,但是很多實際升級時會遇到的問題都沒有涉及。

你的專案用的v3 還是 v4?需要升級嗎?升級遇到什麼問題,可以在下面留言,一起探討。

參考

感謝作者marknote授權釋出。
作者簡介: marknote,個人開發者,專注iOS和web相關技術,喜好嘗試新技術,熱愛分享。 個人郵箱:[email protected],點選檢視: 部落格地址簡書個人主頁

相關推薦

React Router v3升級v4

React 應用很少不用react-router這個包的。marknoteapp.com之前一直用v3,看到v4出來後一直心癢。最近,抱著 用新不用舊 的理念,手賤升了一下級。這一升級,差不多2天功夫花掉啦。概述和 Angular 那改朝換代般的升級相比,Rea

react-router v3 版本升到 v4 版本,升級小記

必須 寫作 his 瀏覽器 down red isp 跳轉 mark react-router v4 跟 react 一樣拆成了兩部分,核心的 react-router 和依運行環境而定的 react-router-dom 或 react-router-native(跟

Vue-router(2)

報錯:vue.esm.js?efeb:591 [Vue warn]: Do not use built-in or reserved HTML elements as component id: head原因:元件內部設定的name值是vue內的保留值。解決:修改元件內nam

webpack

image cnp conf 項目 style win src 丟失 文件 1、安裝webpack失敗問題 錯誤原因: 這主要是我以普通用戶的身份進行webpack的全局安裝,權限不夠。 【普通用戶】 說白了就是通過運行window+r+cmd進入的命令行 解決方式:

Ubuntu搭建Hadoop的(三)

namenode 結束 ctu mapreduce 分布 使用 framework 2.6 start 之前的兩篇文章介紹了如何從0開始到搭建好帶有JDK的Ubuntu的過程,本來這篇文章是打算介紹搭建偽分布式集群的。但是後來想想反正偽分布式和完全分布式差不多,所幸直接介紹

一次痛苦又甜蜜的微信支付

call utf-8 客戶 打開 區分 AD times jpg 運算 凡是和錢打交道的事,沒有一樣是容易的。這是我第一次接觸微信支付,發現網上還是有很多同學在求助,XXX了怎麽辦?XXX是什麽情況?為了幫助更多的小夥伴脫離“苦海”,我決定寫下這次的踩坑之旅,給更多的人幫助

python 3.6.1 安裝scrapy

ext href sta 版本 deb targe IE src pyw 系統環境:win10 64位系統安裝 python基礎環境配置不做過多的介紹 window環境安裝scrapy需要依賴pywin32,下載對應python版本的exe文件執行安裝,下載的pywin

快應用開發的

校驗 未來 bug 失敗 作用 定義 無需 功能 com 前言 嘗試一款新的開發框架的時候勢必會遇見各種各樣的問題。可能因為一開始不熟悉文檔,導致配置錯誤,或是api使用錯誤。當然開發的時候我們也不能確認框架沒有問題,是否存在bug。所以在某些出錯的情況下,我們也許會不斷懷

記一次修改php.ini不生效的

前言 想給公司的測試環境裝一個xdebug,按照以往的方式(之前已經裝過很多次了),編譯安裝了xdebug,然後修改php.ini,將xdebug擴充套件加進去,可是,不論怎麼改,都不生效,xdebug就是沒有。 首先,我想到的是xdebug版本不對,由於之前有過這種經驗,xdebug安裝了

小程式wepy(五)----- 購物車的實現

首先大家可以看下演示效果 我先把封裝的幾個元件程式碼放到前面。 1.購物車數量加減cart-count.wpy元件 <template> <view class="cart-count"> <vi

小程式wepy(四)----- 簡單的動畫

大家可以先看下官網小程式apianimation:https://developers.weixin.qq.com/miniprogram/dev/api/api-animation.html,看完之後推薦看一下http://www.jb51.net/article/102263

小程式wepy(三)----- 微信小程式wepy左滑刪除特效原始碼

我寫在了shop_cart.wepy裡,原始碼就在下面註釋很詳細,直接拷貝到新建的.wpy就可以使用 <template> <view class="item-box"> <view class="items">

小程式wepy(一)---- thirdScriptError sdk uncaught third Error module "npm/lodash/_nodeUtil.js

     近期一直在學小程式,作為新手,比較了下mpvue和wepy兩個小程式框架,mpvue作為美團剛出來的vuejs開發看起來很不錯,學習成本很低,但是對於在實際專案開發中,mpvue剛出來,很多資料,比如踩坑,比較少,而we

Flutter接入現有Android工程

把Flutter作為一個模組接入到現有的Android工程,Flutter有官方推薦方案 Add Flutter to existing apps,通過這樣的工程配置,可以在debug支援HotReload,也可以輸出Release包供釋出。不過在使用過程中有一些需要調整的地方,特此記錄希望對大家能有借鑑意義

快應用開發

前言 嘗試一款新的開發框架的時候勢必會遇見各種各樣的問題。可能因為一開始不熟悉文件,導致配置錯誤,或是api使用錯誤。當然開發的時候我們也不能確認框架沒有問題,是否存在bug。所以在某些出錯的情況下,我們也許會不斷懷疑自己,懷疑框架,最終懷疑人生。這時候就需要開

AIDL

AIDL 踩坑之旅 ---- 通過掃雷遊戲學習 AIDL 跨程序在兩個APP之間進行通訊   某天下班在地鐵上玩半年前寫的掃雷小遊戲的時候, 突然產生了一個想法, 能不能設計一套演算法讓遊戲自動進行呢? 由程式來尋找最優解, 瞬間完成掃雷, 但是由於我對演算

新手學習VUE---methods裡面使用箭頭函式要注意this

VUE的methods物件裡面如果函式使用箭頭函式會導致this指向的不是vue例項$vm 例子:想寫一個點選事件:點選輸入框的“x”,即可清空文字框的內容 首先為輸入框增加一個ref屬性(ref=“inputUser”),然後為“x”加一個點選事件(@click=“deleteInp”)

大疆無人機Android版SDK開發(一)----前言

  最近一段時間一直在做大疆無人機安卓版開發,這水也是挺深的,不仔細看官網SDK的介紹就會遇到各種各樣的坑,簡單記錄一下,希望可以讓其他人少走一些彎路。   安卓端用到的SDK大概有兩種:Android SDK和Android UX SDK   Android SDK(官網介紹):   開發人員可以通過SDK

vue

1.vue專案外部js(es5,es6),css檔案的引入;   方法一:       在index.html通過script ,link標籤引入,要將檔案先放入static資料夾(靜態資源)下, 注意不能放src下   方法二:       如果是es6,參照export和

cnpm私有倉庫

為了方便團隊內部成員程式碼的共用,不寫重複而有無意義的程式碼,打算搭建團隊內部私有的cnpm倉庫。 Start 從cnpm.org clone 整個專案。 git clone https://github.com/cnpm/cnpmjs.o