首頁 >web前端 >js教程 >React Router知識的全面解析(程式碼範例)

React Router知識的全面解析(程式碼範例)

不言
不言原創
2018-09-17 14:06:441329瀏覽

這篇文章帶給大家的內容是關於React Router知識的全面解析(程式碼範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

一、前端路由與後端路由

1)後端路由

多頁應用程式中,一個URL對應一個HTML頁面,一個Web應用程式包含許多HTML頁面,在多頁應用程式中,頁面路由控制由伺服器端負責,這種路由方式稱為後端路由。

多頁應用程式中,每次頁面切換都需要向伺服器發送一次請求,頁面使用到的靜態資源也需要重新加載,存在一定的浪費。而且,頁面的整體刷新對使用者體驗也有影響,因為不同頁間往往存在共同的部分,例如導覽列、側邊欄等,頁面整體刷新也會導致共用部分的刷新。

2)前端路由

在單面應用程式中,URL發生並不會向伺服器發送新的請求,所以「邏輯頁面」的路由只能由前端負責,這種路由方式稱為前端路由。

目前,國內的搜尋引擎大多對單頁應用的SEO支援的不好,因此,對於SEO 非常看重的Web
應用(例如,企業官方網站,電商網站等),一般還是會選擇採用多頁面應用程式。 React 也並非只能用於開發單頁應用程式。

二、React Router 安裝

這裡使用的 React Router 的大版本號碼是 v4, 這也是目前最新版本。

React Router 包含3個函式庫, react-router、react-router-dom、和 react-router-native。 react-router 提供最基本的路由功能,實際使用,我們不會直接安裝react-router,而是根據應用運行的環境選擇安裝react-router-dom(在瀏覽器中使用)或react-router-native(在react-native中使用)。 react-router-dom 和 react-router-native 都依賴 react-router,所以在安裝時, react-router 也會自動安裝。
建立 Web應用,使用

npm install react-router-dom

建立 navtive 應用,使用

 npm install react-router-native

三、路由器

React Router 透過 Router 和 Route 兩個元件完成路由功能。 Router 可以理解成路由器,一個應用程式中需要一個 Router 實例,所有跌幅設定元件 Route 都定義為 Router 的子元件。在 Web應用程式中,我們一般會使用對 Router 進行包裝的 BrowserRouter 或 HashRouter  兩個元件 BrowserRouter使用 HTML5 的 history API(pushState、replaceState等)來實現應用程式的 UI 和 URL 的同步。 HashRouter 使用 URL 的 hash 實作應用程式的 UI 和 URL 同步。

BrowserRouter 建立的URL 形式如下:

http://example.com/some/path

HashRouter 建立的URL 形式如下:

 http://example.com/#/some/path

使用BrowserRouter 時,一般也需要對伺服器進行配置,讓伺服器能正確地處理所有可能的URL。例如,當瀏覽器發生http://example.com/some/path 和http://example.com/some/path2 兩個請求時,伺服器需要能傳回正確的HTML 頁面(也就是單頁應用程式中唯一的HTML 頁面)

HashRouter 則不存在這個問題,因為hash 部分的內容會被伺服器自動忽略,真正有效的資訊是hash 前端的部分,而對於單頁應用程式來說,這部分是固定的。

Router 會建立一個 history 對象,history 用來追蹤 URL, 當URL 改變時, Router,的後代元件會重新渲染。 React Router 中提供的其他元件可以透過 context 取得 history 對象,這也隱含說明了 React Router 中其他元件必須作為 Router 元件後代使用。但Router 中只能唯一的一個子元素,例如:

// 正确
ReactDOM.render(
  (
  <BrowserRouter>
    <App />
  </BrowserRouter>),
  document.getElementById('root')
)
//错误,Router 中包含两个子元素
ReactDOM.render(
  (
    <BrowserRouter>
      <App1 />
      <App2 />
    </BrowserRouter>),
  document.getElementById('root')
)

四、路由器

#Route 是React Router中用來設定路由資訊的元件,也是React Router 中使用頻率最高的組件。每當有一個元件需要根據 URL 決定是否渲染時,就需要建立一個 Route。

1) path

每個Route 都需要定義一個path 屬性,當使用BrowserRouter 時,path 用來描述這個Router符合的URL 的pathname;使用HashRouter時,path 用來描述這個Route 相符的URL 的hash。例如,使用 BrowserRouter 時, 會符合一個 pathname 以 foo 開始的 URL (如: http://example.com/foo)。當 URL 符合一個 Route 時,這個 Route 中定義的元件就會被渲染出來。

2)match

當 URL 和 Route匹配時,Route 會建立一個 match 物件作為 props 中的一個 屬性傳遞給被渲染的元件。這個物件包含以下4個屬性。

(1)params: Route的 path 可以包含參數,例如

(2)isExact: 是一個布林值,當URL 完全匹時,值為true; 當URL 部分匹配時,值為false.例如,當path='/foo'、URL="http ://example.com/foo" 時,是完全匹配; 當URL="http://example.com/foo/1" 時,是部分匹配。

(3)path: Route 的 path 属性,构建嵌套路由时会使用到。

(4)url: URL 的匹配的方式

3)Route 渲染组件的方式

(1)component

component 的值是一个组件,当 URL 和 Route 匹配时,Component属性定义的组件就会被渲染。例如:

<Route path=&#39;/foo&#39; component={Foo} ></p>
<p>当 URL = "http://example.com/foo" 时,Foo组件会被渲染。</p>
<p>(2) render<br>render 的值是一个函数,这个函数返回一个 React 元素。这种方式方便地为待渲染的组件传递额外的属性。例如:</p>
<pre class="brush:php;toolbar:false"><Route path=&#39;/foo&#39; render={(props) => {
  <Foo {...props} data={extraProps} />
}}>
</Route>

Foo 组件接收了一个额外的 data 属性。

(3)children
children 的值也是一个函数,函数返回要渲染的 React 元素。 与前两种方式不同之处是,无论是否匹配成功, children 返回的组件都会被渲染。但是,当匹配不成功时,match 属性为 null。例如:

<Route path=&#39;/foo&#39; render={(props) => {
  <p className={props.match ? &#39;active&#39;: &#39;&#39;}>
    <Foo {...props} data={extraProps} />
  </p>
}}>
</Route>

如果 Route 匹配当前 URL,待渲染元素的根节点 p 的 class 将设置成 active.

4)Switch 和 exact

当URL 和多个 Route 匹配时,这些 Route 都会执行渲染操作。如果只想让第一个匹配的 Route 沉浸,那么可以把这些 Route 包到一个 Switch 组件中。如果想让 URL 和 Route 完全匹配时,Route才渲染,那么可以使用 Route 的 exact 属性。Switch 和 exact 常常联合使用,用于应用首页的导航。例如:

<Router>
 <Switch>
    <Route exact path=&#39;/&#39; component={Home}/>
    <Route exact path=&#39;/posts&#39; component={Posts} />
    <Route exact path=&#39;/:user&#39; component={User} />
  </Switch>
</Router>

如果不使用 Switch,当 URL 的 pathname 为 "/posts" 时, 都会被匹配,但显然我们并不希望 被匹配,实际上也没有用户名为 posts 的用户。如果不使用 exact, "/" "/posts" "/user1"等几乎所有 URL 都会匹配第一个 Route,又因为Switch 的存在,后面的两个 Route永远不会被匹配。使用 exact,保证 只有当 URL 的 pathname 为 '/'时,第一个Route才会匹配。

5)嵌套路由

嵌套路由是指在Route 渲染的组件内部定义新的 Route。例如,在上一个例子中,在 Posts 组件内再定义两个 Route:

const Posts = ({match}) => {
  return (
    <p>
      {/* 这里 match.url 等于 /posts */}
      <Route path={`${match.url}/:id`} component={PostDetail} />
      <Route exact path={match.url} component={PostList} />
    </p>
  )
}

五、链接

Link 是 React Router提供的链接组件,一个 Link 组件定义了当点击该 Link 时,页面应该如何路由。例如:

const Navigation = () => {
  <header>
    <nav>
      <ul>
        <li><Link to=&#39;/&#39;>Home</Link></li>
        <li><Link to=&#39;/posts&#39;>Posts</Link></li>
      </ul>
    </nav>
  </header>
}

Link 使用 to 属性声明要导航到的URL地址。to 可以是 string 或 object 类型,当 to 为 object 类型时,可以包含 pathname、search、hash、state 四个属性,例如:

<Link to={{
  pathname: &#39;/posts&#39;,
  search: &#39;?sort=name&#39;,
  hash:&#39;#the-hash&#39;,
  state: { fromHome: true}
}}>
</Link>

除了使用Link外,我们还可以使用 history 对象手动实现导航。history 中最常用的两个方法是 push(path,[state]) 和 replace(path,[state]),push会向浏览器记录中新增一条记录,replace 会用新记录替换记录。例如:

history.push('/posts');
history.replace('/posts');

六、路由设计

路由设计的过程可以分为两步:

  1. 为每一个页面定义有语义的路由名称(path)

  2. 组织 Route 结构层次

1)定义路由名称

我们有三个页面,按照页面功能不难定义出如下的路由名称:

  • 登录页: /login

  • 帖子列表页: /posts

  • 帖子详情页: /posts/:id(id代表帖子的ID)

但是这些还不够,还需要考虑打开应用时的默认页面,也就是根路径"/"对应的页面。结合业务场景,帖子列表作为应用的默认页面为合适,因此,帖子列表对应两个路由名称: '/posts'和 '/'

2)组织 Route 结构层次

React Router 4并不需要在一个地方集中声明应用需要的所有 Route, Route实际上也是一个普通的 React 组件,可以在任意地方使用它(前提是,Route必须是 Router 的子节点)。当然,这样的灵活性也一定程度上增加了组织 Route 结构层次的难度。
我们先考虑第一层级的路由。登录页和帖子列表页(首页)应该属于第一层级:

<Router>
  <Switch>
    <Route exact path="/" component={Home}></Route>
    <Route exact path="/login" component={Login}></Route>
    <Route exact path="/posts" component={Home}></Route>
  </Switch>
</Router>

第一个Route 使用了 exact 属性,保证只有当访问根路径时,第一个 Route 才会匹配成功。Home 是首页对应组件,可以通过 "/posts" 和 “/” 两个路径访问首页。注意,这里并没有直接渲染帖子列表组件,真正渲染帖子列表组件的地方在 Home 组件内,通过第二层级的路由处理帖子列表组件和帖子详情组件渲染,components/Home.js 的主要代码如下:

class Home extends Component {
  /**省略其余代码 */
  render() {
    const {match, location } = this.props;
    const { username } = this.state;
    return(
      <p>
        <Header
          username = {username}
          onLogout={this.handleLogout}
          location = {location}
        >
        </Header>
        {/* 帖子列表路由配置 */}
        <Route
          path = {match.url}
          exact
          render={props => <PostList username={username} {...this.props}></PostList>}
        ></Route>
      </p>
    )
  }
}

Home的render内定义了两个 Route,分别用于渲染帖子列表和帖子详情。PostList 是帖子列表组件,Post是帖子详情组件,代码使用Router 的render属性渲染这两个组件,因为它们需要接收额外的 username 属性。另外,无论访问是帖子列表页面还是帖子详情页面,都会共用相同 Header 组件。

七、代码分片

默认情况下,当在项目根路径下执行 npm run build 时 ,create-react-app内部使用 webpack将 src路径下的所有代码打包成一个 JS 文件和一个 Css 文件。

当项目代码量不多时,把所有代码打包到一个文件的做法并不会有什么影响。但是,对于一个大型应用,如果还把所有的代码都打包到一个文件中,显然就不合适了。

create-react-app 支持通过动态 import() 的方式实现代码分片。import()接收一个模块的路径作为参数,然后返回一个 Promise 对象, Promise 对象的值就是待导入的模块对象。例如

// moduleA.js

const moduleA = 'Hello'
export { moduleA };

// App.js

import React, { Component } from 'react';

class App extends Component {
  handleClick = () => {
    // 使用import 动态导入 moduleA.js
    import('./moduleA')
      .then(({moduleA}) => {
        // 使用moduleA
      })
      .catch(err=> {
        //处理错误
      })
  };
  render() {
    return(
      <p>
        <button onClick={this.handleClick}>加载 moduleA</button>
      </p>
    )
  }
}

export default App;

上面代码会将 moduleA.js 和它所有依赖的其他模块单独打包到一个chunk文件中,只有当用户点击加载按钮,才开始加载这个 chunk 文件。
当项目中使用 React Router 是,一般会根据路由信息将项目代码分片,每个路由依赖的代码单独打包成一个chunk文件。我们创建一个函数统一处理这个逻辑:

import React, { Component } from 'react';
// importComponent 是使用 import()的函数
export default function asyncComponent(importComponent) {
  class AsyncComponent extends Component {
    constructor(props) {
      super(props);
      this.state = {
        component:  null //动态加载的组件
      }
    }
    componentDidMount() {
      importComponent().then((mod) => {
        this.setState({
          // 同时兼容 ES6 和 CommonJS 的模块
          component: mod.default ? mod.default : mod;
        });
      })
    }
    render() {
      // 渲染动态加载组件
      const C = this.state.component;
      return C ? <C {...this.props}></C> : null
    }
  }

  return AsyncComponent;
}

asyncComponent接收一个函数参数 importComponent,  importComponent 内通过import()语法动态导入模块。在AsyncComponent被挂载后,importComponent就会阴调用,进而触发动态导入模块的动作。
下面利用 asyncComponent 对上面的例子进行改造,代码如下:

import React, { Component } from 'react';
import { ReactDOM, BrowserRouter as Router, Switch, Route } from 'react-dom';
import asyncComponent from './asyncComponent'
//通过asyncComponent 导入组件,创建代码分片点
const AsyncHome = asyncComponent(() => import("./components/Home"))
const AsyncLogin = asyncComponent(() => import("./components/Login"))

class App extends component {
  render() {
    return(
      <Router>
        <Switch>
          <Route exact path="/" component={AsyncHome}></Route>
          <Route exact path="/login" component={AsyncLogin}></Route>
          <Route exact path="/posts" component={AsyncHome}></Route>
        </Switch>
      </Router>
    )
  }
}

export default App;

这样,只有当路由匹配时,对应的组件才会被导入,实现按需加载的效果。

这里还有一个需要注意的地方,打包后没有单独的CSS文件了。这是因为 CSS样子被打包到各个 chunk 文件中,当 chunk文件被加载执行时,会有动态把 CSS 样式插入页面中。如果希望把 chunk 中的 css打包到一个单独的文件中,就需要修改 webpack 使用的 ExtractTextPlugin 插件的配置,但 create-react-app 并没有直接把 webpack 的配置文件暴露给用户,为了修改相应配置
,需要将 create-react-app 管理的配置文件“弹射”出来,在项目根路径下执行:

npm run eject

项目中会多出两个文件夹:config和 scripts,scrips中包含项目启动、编译和测试的脚本,config 中包含项目使用的配置文件,
webpack配置文件 就在这个路径下,打包 webpack.config.prod.js 找到配置 ExtractTextPlugin 的地方,添加 allChunks:true 这项配置:

new ExtractTextPlugin({
  filename: cssFilename,
  allChunks: true
})

然后重新编译项目,各个chunk 文件 使用的 CSS 样式 又会统一打包到 main.css 中。

以上是React Router知識的全面解析(程式碼範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn