YutHelloWorld / Blog

🌎 技术自留地
132 stars 17 forks source link

[译]React Router:从V2/V3迁移到V4 #4

Open YutHelloWorld opened 7 years ago

YutHelloWorld commented 7 years ago

官方文档:Migrating from v2/v3 to v4

React Router V4相对V2/V3几乎完全重写了,遵循 Just Component 的 API 设计理念。

react-router V4 分成了三个包:react-router-dom(for web)、react-router-native(for #native)、react-router(core)。在浏览器中,你只需使用react-router-dom。react-router-dom和react-router详见: ReactTraining/react-router#4648

The Router

在 React Router v3中,有 一个单独的 <Router> 组件. 它会提供一个 history 对象作为prop属性。

同时,可以使用routes prop或者children<Router>配置route

// v3
import routes from './routes'
<Router history={browserHistory} routes={routes} />
// or
<Router history={browserHistory}>
  <Route path='/' component={App}>
    // ...
  </Route>
</Router>

而在V4中,一个重大变化就是提供了数种不同的router组件。每种都会创建一个history对象。 <BrowserRouter> 创建 browser history, <HashRouter> 创建 hash history, and<MemoryRouter>创建memory history

V4取消了集中的路由配置。

//v4
<BrowserRouter>
  <div>
    <Route path='/about' component={About} />
    <Route path='/contact' component={Contact} />
  </div>
</BrowserRouter>

这里要注意的是,router component 只能包裹一个元素。

// yes
<BrowserRouter>
  <div>
    <Route path='/about' component={About} />
    <Route path='/contact' component={Contact} />
  </div>
</BrowserRouter>

// no
<BrowserRouter>
  <Route path='/about' component={About} />
  <Route path='/contact' component={Contact} />
</BrowserRouter>

Routes

在V3中,<Route>不是一个真正的组件,而是作为一个标签用来创建route配置对象。

/// in v3 the element
<Route path='contact' component={Contact} />
// 等同于
{
  path: 'contact',
  component: Contact
}

使用 v4,您可以像常规的 React 程序一样布局您应用中的组件,您要根据位置(特别是其 pathname )呈现内容的任何位置,您将呈现 <Route>

在 v4,<Route> 其实是一个组件,所以无论你在哪里渲染 <Route>,它里面的内容都会被渲染。当 <Route> 的 path 与当前的路径匹配时,它将会渲染 component,render, orchildren 属性中的内容,当 <Route>path 与当前的路径不匹配时,将会渲染 null

路由嵌套

在 v3 中,<Route> 组件是作为其父 <Route> 组件的children 嵌套其中的。

<Route path='parent' component={Parent}>
  <Route path='child' component={Child} />
  <Route path='other' component={Other} />
</Route>

当嵌套的 <Route> 匹配时,react 元素将会使用子 <Route> 和 父 <Route>component 属性去构建,子元素将作为父元素的 children 属性。

<Parent {...routeProps}>
  <Child {...routeProps} />
</Parent>

使用 v4,子 <Route> 会由父<Route> 呈现。

<Route path='parent' component={Parent} />

const Parent = () => (
  <div>
    <Route path='child' component={Child} />
    <Route path='other' component={Other} />
  </div>
)

on* properties

react-router v3 提供 onEnter, onUpdate, and onLeave 方法。这些方法本质上是重写(覆盖)了 react 生命周期方法。

使用 v4,你将会使用生命周期方法 通过 <Route> 渲染的组件,你可以使用 componentDidMountcomponentWillMount 代替 onEnter,你可以使用 componentDidUpdate 或者 componentWillUpdate (更或者 componentWillReceiveProps) 代替 onUpdate,你可以使用 componentWillUnmount代替 onLeave

\<Switch>

在v3中,您可以指定一些子路由,并且只会渲染匹配到的第一个。

// v3
<Route path='/' component={App}>
  <IndexRoute component={Home} />
  <Route path='about' component={About} />
  <Route path='contact' component={Contact} />
</Route>

v4 通过 <Switch> 组件提供了相似的功能,当 <Switch>被渲染时,它仅会渲染与当前路径匹配的第一个子 <Route>

// v4
const App = () => (
  <Switch>
    <Route exact path='/' component={Home} />
    <Route path='/about' component={About} />
    <Route path='/contact' component={Contact} />
  </Switch>
)

\<Redirect>

在V3, 如果你想从一个路径重定向到另一个, 例如从//welcome, 你可以使用<IndexRedirect >.

// v3
<Route path="/" component={App}>
  <IndexRedirect to="/welcome" />
</Route>

在V4,你可以使用<Redirect>达到相同的效果。

// v4
<Route exact path="/" render={() => <Redirect to="/welcome" component={App} />} />

<Switch  >
  <Route exact path="/" component={App} />
  <Route path="/login" component={Login} />
  <Redirect path="*" to="/" />
</Switch>

PatternUtils

matchPattern(pattern, pathname)

在v3中,您可以使用与内部相同的匹配代码来检查路径是否匹配模式。在v4中,已被由path-to-regexp 库驱动的matchPath替代。

formatPattern(pattern, params)

在v3中,您可以使用Pattern Utils.format Pattern从路径模式(可能在常量或中央路由配置中)生成有效的路径以及包含名称参数的对象:

// v3
const THING_PATH = '/thing/:id';

<Link to={PatternUtils.formatPattern(THING_PATH, {id: 1})}>A thing</Link>

Link

在V3中,您可以省略to属性或将其设置为null以创建没有href属性的锚标签。

// v3
<Link to={disabled ? null : `/item/${id}`} className="item">
  // item content
</Link>

在v4中,您应该总是提供to.如果你要设置to,你可以做对其做一层封装。

// v4
import { Link } from 'react-router-dom'

const LinkWrapper = (props) => {
  const Component = props.to ? Link : 'a'
  return (
    <Component {...props}>
      { props.children }
    </Component>
  )
)

<LinkWrapper to={disabled ? null : `/item/${id}`} className="item">
  // item content
</LinkWrapper>
WisestCoder commented 7 years ago

很棒

zdJOJO commented 7 years ago

写的不错! 我可以问一个问题吗? V4 取消了 路由集中配置的概念,写法上,Route是写在了对应的组件中。 但是,一旦需要组件按需加载的话,这样就又回到了V3时代的集中配置路由了。我的理解是,如果按需加载的话,只能最外层的父组件可以实现按需加载, 嵌套组件中子组件要如何实现按需加载呢?

YutHelloWorld commented 7 years ago

v4 按需加载不需要集中配置路由。具体实现代码可以看 #5 代码分割。每个路由组件只有当path匹配时,通过webpack2的impost()异步加载。 @zdJOJO

zdJOJO commented 7 years ago

我看了下, 还是有些问题。
第一, 博主使用的是 webpack2的impost()异步加载 和 V4 官方提供的 webpack的 bundle-loader 有何却别? 第二, 我发现 博主项目的路由不存在 嵌套路由,都是一级父路由组件,不存在二级或者三级子路由组件,所以这边的按需加载都是父组件的按需加载,假如Zen组件中有嵌套了子路由, 那这个自路由需不需要单独打包进行按需加载?还是说和父组件Zen 打包在一起,随Zen的加载一起被加载?

YutHelloWorld commented 7 years ago

这个问题很好 @zdJOJO 。

  1. 看了下官方说明:https://reacttraining.com/react-router/web/guides/code-splitting

    为什么使用bundle, 而不是imort()? 我们已经使用它多年并且仍然使用,同时TC39提出官方的动态进口。最新的提案是import(),我们可以将Bundle组件调整为使用import(): 捆绑装载程序的另一个巨大优点是它第二次同步回调,这防止每次访问代码分割屏幕时闪烁加载屏幕。不管您导入的方式如何,这个想法是一样的:一个在渲染时处理代码加载的组件。现在你所做的就是渲染一个,无论你想要动态地加载代码。

具体差别,还有待研究。

  1. 子路由是否需要按需加载是根据你的业务需求来的。如果不做异步加载,你可以参照Home组件的方式同步加载。如果仍然异步,还是相同的方式使用异步高阶组件AsyncComponent。
zdJOJO commented 7 years ago

thx 我再研究研究

YutHelloWorld commented 7 years ago

看了下bundler-loader

var load = require("bundle-loader?lazy!./file.js");

// The chunk is not requested until you call the load function
load(function(file) {

});

效果和import()类似

Chickenbrother commented 6 years ago
  import React from 'react';
import { BrowserRouter, Route, Switch } from 'dva/router';
import dynamic from 'dva/dynamic';
import MiddleSelectPage from "./routes/MiddleSelectPage";

function RouterConfig({ history, app }) {
  const IndexPage = dynamic({
    app,
    component: () => import('./routes/IndexPage'),
  });
  const HomePage = dynamic({
    app,
    component: () => import('./routes/Home/HomePage'),
  }) ;
  return (
    <BrowserRouter>
      <Switch>
        <Route path="/" exact component={MiddleSelectPage} />
        <Route path="/Home" exact component={HomePage} />
        <Route path="/Login" exact component={IndexPage} />
     </Switch>
    </BrowserRouter>
  );
}

为什么我用了BrowserRouter 浏览器路径中带有#符号,如何设置可以去掉

YutHelloWorld commented 6 years ago

是hashHistory 或者HashRouter 引起的, 从你的代码看不出问题~ @Chickenbrother

ganyanchuan1989 commented 6 years ago

按需加载不需要集中路由的说话不太正确。按需加载是解决文件大的问题,集中路由是解决架构层级问题。特别是大项目协同开发,没有集中路由还是不太好管理,代码的后期维护也会比较麻烦。

hkjpotato commented 6 years ago

@ganxunzou gan那么对于react router v4为了维护架构是否还是要写成集中路由?我觉得如果我们一直写出集中路由的话是不是还是没有真正理由v4里route也是component的概念。

fi3ework commented 6 years ago

@hkjpotato 理论上,v4也是可以写成集中配置的路由的,像以前都写在一起就好了。应该说,更灵活了。

ganyanchuan1989 commented 6 years ago

@fi3ework @hkjpotato 是可以写集中路由。现在也有 react-router-config 。在没有这个的时候,我借鉴ant-design-pro的方式,自定义router配置,导出所有的Component,然后在使用的地方用Switch控制只能显示同时显示一个。

另外项目如果路由都写到页面里面,后续维护是很麻烦的,结构也不清晰。