写作不易,未经作者容许禁止以任何模式转载!<br/>如果感觉文章不错,欢送关注、点赞和分享!
博客原文链接:浅析前端路由Router

前端路由介绍

什么前端路由

路由这个概念最早呈现在后端,通过⽤户申请的url导航到具体的html⻚⾯。当初的前端路由不同

于传统路由,它不须要服务器解析,⽽是能够通过hash函数或者history API来实现。在前端开发中,我

们能够使⽤路由设置拜访门路,并依据门路与组件的映射关系切换组件的显示,⽽这整个过程都是在同

⼀个⻚⾯中实现的,不波及⻚⾯间的跳转,这也就是咱们常说的单⻚应⽤(spa)。

前端路由带来了什么

相⽐多⻚应⽤(mpa)来说,spa有以下长处:

  • 不波及html⻚⾯跳转,内容扭转不须要从新加载⻚⾯,对服务器压⼒⼩。
  • 只波及组件之间的切换,因而跳转晦涩,⽤户体验好。
  • ⻚⾯成果会⽐较炫酷(⽐如切换⻚⾯内容时的转场动画)。
  • 组件化开发便捷。

然而同时spa也有以下毛病:

  • ⾸屏加载过慢。
  • 不利于seo。
  • ⻚⾯复杂度提⾼很多。

⽤原⽣ js 实现前端路由

什么前端路由

路由这个概念最早呈现在后端,通过⽤户申请的 url 导航到具体的 html ⻚⾯。当初的前端路由不同于

传统路由,它不须要服务器解析,⽽是能够通过 hash 函数或者 h5 history API 来实现。在前端开发

中,咱们能够使⽤路由设置拜访门路,并依据门路与组件的映射关系切换组件的显示,⽽这整个过程都

是在同⼀个⻚⾯中实现的,不波及⻚⾯间的跳转,这也就是咱们常说的单⻚应⽤(spa)。

原⽣ js 实现前端路由

<html lang="en">  <head>    <meta charset="UTF-8" />    <meta name="viewport" content="width=device-width, initial-scale=1.0" />    <meta http-equiv="X-UA-Compatible" content="ie=edge" />    <title>lesson2</title>  </head>  <body>    <ul>      <li><a href="#/home">首页</a></li>      <li><a href="#/user">用户核心</a></li>      <li><a href="#/login">登录</a></li>    </ul>    <div id="view"></div>  </body>  <script>    let view = null;    window.addEventListener("DOMContentLoaded", onLoad);    // 监听hash变动    window.addEventListener("hashchange", onHashChange);    function onLoad() {      view = document.getElementById("view");      onHashChange();    }    function onHashChange() {      switch (location.hash) {        case "#/home":          view.innerHTML = "首页";          break;        case "#/user":          view.innerHTML = "用户核心";          break;        case "#/login":          view.innerHTML = "登录";          break;      }    }  </script></html>

环境配置与 react-router 简介

资源

  1. React 官网
  2. react-router

指标

  1. 把握 cra 环境
  2. 把握 react-router 的根本应用

知识点

疾速开始

npx create-react-app router-nutcd router-nutyarn start

配置 less 与装璜器

yarn add @craco/craco craco-less @babel/plugin-proposal-decorators

根目录下增加 craco.config.js 文件

// * 配置实现后记得重启下const CracoLessPlugin = require("craco-less");module.exports = {  babel: {    //用来反对装璜器    plugins: [["@babel/plugin-proposal-decorators", {legacy: true}]]  },  plugins: [    {      plugin: CracoLessPlugin    }  ]};

批改 package.json

 "scripts": {    "start": "craco start",    "build": "craco build",    "test": "craco test"  },

react-router 简介

react-router 蕴含 3 个库,react-router、react-router-dom 和 react-router-native。react-router 提供最根本的路由性能,理论应用的时候咱们不会间接装置 react-router,而是依据利用运行的环境抉择装置 react-router-dom(在浏览器中应用)或 react-router-native(在 rn 中应用)。react-router-dom 和 react-router-native 都依赖 react-router,所以在装置时,react-router 也会主动装置,创立 web 利用。

装置

yarn add react-router-dom

BrowserRouter 与 HashRouter 比照

  1. HashRouter 最简略,不须要服务器端渲染,靠浏览器的#的来辨别 path 就能够,BrowserRouter 须要服务器端对不同的 URL 返回不同的 HTML,后端配置可参考。
  2. BrowserRouter 应用 HTML5 history API( pushState,replaceState 和 popstate 事件),让页面的 UI 同步与 URL。
  3. HashRouter 不反对 location.key 和 location.state,动静路由跳转须要通过?传递参数。
  4. Hash history 不须要服务器任何配置就能够运行,如果你刚刚入门,那就应用它吧。然而咱们不举荐在理论线上环境中用到它,因为每一个 web 利用都应该渴望应用 browserHistory

MemoryRouter

把 URL 的历史记录保留在内存中的 <Router>(不读取、不写入地址栏)。在测试和非浏览器环境中很有用,如 React Native。

根本应用

react-router 中奉行所有皆组件的思维,路由器-Router、链接-Link、路由-Route、独占-Switch、重定向-Redirect都以组件模式存在

import {BrowserRouter as Router, Route, Link} from "react-router-dom";import HomePage from "./pages/HomePage";import UserPage from "./pages/UserPage";import LoginPage from "./pages/LoginPage";function App() {  return (    <div className="App">      <Router>        <Link to="/">首页</Link>        <Link to="/user">用户核心</Link>        <Link to="/login">登录</Link>        {/* 根路由要增加exact,实现准确匹配 */}        <Route exact path="/" component={HomePage} />        <Route path="/user" component={UserPage} />        <Route path="/login" component={LoginPage} />      </Router>    </div>  );}export default App;

Route 渲染内容的三种形式

资源

  1. React 官网
  2. react-router

指标

  1. 把握 Route 渲染内容的三种形式
  2. 把握 404 路由

知识点

Route 渲染优先级:children>component>render。

三者能接管到同样的[route props],包含 match, location and history,然而当不匹配的时候,children 的 match 为 null。

这三种形式互斥,你只能用一种。

import React, {useState} from "react";import {BrowserRouter as Router, Route, Link, Switch} from "react-router-dom";import HomePage from "./pages/HomePage";import UserPage from "./pages/UserPage";import LoginPage from "./pages/LoginPage";import _404Page from "./pages/_404Page";function App() {  const [count, setCount] = useState(0);  return (    <div className="App">      <button        onClick={() => {          setCount(count + 1);        }}>        add: {count}      </button>      <Router>        <Link to="/">首页</Link>        <Link to="/user">用户核心</Link>        <Link to="/login">登录</Link>        {/* 独占路由 */}        <Switch>          <Route            path="/"            exact            //children={children}            component={HomePage}            // ! 渲染component的时候会调用React.createElement,如果应用上面这种匿名函数的模式,每次都会生成一个新的匿名的函数,            // ! 导致生成的组件的type总是不雷同,这个时候会产生反复的卸载和挂载            //component={() => <HomePage />}            // render={render}          />          <Route path="/user" component={UserPage} />          <Route path="/login" component={LoginPage} />          <Route component={_404Page} />        </Switch>      </Router>    </div>  );}export default App;function children(props) {  console.log("children props", props); //sy-log  return <div>children</div>;}function render(props) {  console.log("props props", props); //sy-log  return <div>render</div>;}

children:func

有时候,不论 location 是否匹配,你都须要渲染一些内容,这时候你能够用 children。

除了不论 location 是否匹配都会被渲染之外,其它工作办法与 render 齐全一样。

import React, {Component} from "react";import ReactDOM from "react-dom";import {BrowserRouter as Router, Link, Route} from "react-router-dom";function ListItemLink({to, name, ...rest}) {  return (    <Route      path={to}      children={({match}) => (        <li className={match ? "active" : ""}>          <Link to={to} {...rest}>            {name}          </Link>        </li>      )}    />  );}export default class RouteChildren extends Component {  render() {    return (      <div>        <h3>RouteChildren</h3>        <Router>          <ul>            <ListItemLink to="/somewhere" name="链接1" />            <ListItemLink to="/somewhere-else" name="链接2" />          </ul>        </Router>      </div>    );  }}

render:func

然而当你用 render 的时候,你调用的只是个函数。然而它和 component 一样,能拜访到所有的[route props]。

import React from "react";import ReactDOM from "react-dom";import {BrowserRouter as Router, Route} from "react-router-dom";// 不便的内联渲染ReactDOM.render(  <Router>    <Route path="/home" render={() => <div>Home</div>} />  </Router>,  node);// wrapping/composing//把route参数传递给你的组件function FadingRoute({component: Component, ...rest}) {  return (    <Route {...rest} render={routeProps => <Component {...routeProps} />} />  );}ReactDOM.render(  <Router>    <FadingRoute path="/cool" component={Something} />  </Router>,  node);

component: component

只在当 location 匹配的时候渲染。

留神

当你用component的时候,Route 会用你指定的组件和 React.createElement 创立一个新的[React element]。这意味着当你提供的是一个内联函数的时候,每次 render 都会创立一个新的组件。这会导致不再更新曾经现有组件,而是间接卸载而后再去挂载一个新的组件。因而,当用到内联函数的内联渲染时,请应用 render 或者 children。

Route 外围渲染代码如下:

404 页面

设定一个没有 path 的路由在路由列表最初面,示意肯定匹配

<Switch>  <Route path="/" exact component={HomePage} />  <Route path="/user" component={UserPage} />  <Route path="/login" component={LoginPage} />  <Route component={_404Page} /></Switch>

动静路由

资源

  1. React 官网
  2. react-router

指标

  1. 动静路由

知识点

动静路由

应用:id的模式定义动静路由

定义路由:

<Route path="/product/:id" component={Product} />

增加导航链接:

<Link to={"/product/123"}>搜寻</Link>

创立Search组件并获取参数:

function Product({location, match}) {  console.log("match", match); //sy-log  const {id} = match.params;  return <h1>Product-{id}</h1>;}

嵌套路由

资源

  1. React 官网
  2. react-router

⽬标

  • 把握嵌套路由

知识点

嵌套路由

Route组件嵌套在其余⻚⾯组件中就产⽣了嵌套关系

批改Product,增加新增和详情

<Route path={url + "/detail"} component={Detail} />
function Product({match}) {    console.log("match", match); //sy-log    const {params, url} = match;    const {id} = params;    return (        <div>             <h1>Search-{id}</h1>             <Link to={url + "/detail"}>详情</Link>             <Route path={url + "/detail"} component={Detail} />        </div> );}

⼿写实现BrowserRouterRouteLink

资源

  1. React 官网
  2. react-router

⽬标

  • react-router初步实现

知识点

跨层级传输数据 Context

import React from "react";// 应用Context做数据跨层级传递// step1: 创立context对象export const RouterContext = React.createContext();// step2: 应用context对象的Provider传递value// step3: 子组件生产value: Consumer、useContext、contextType

实现Router

import React, {Component} from "react";import {RouterContext} from "./Context";export default class Router extends Component {    static computeRootMatch(pathname) {        return {path: "/", url: "/", params: {}, isExact: pathname === "/"};    }    constructor(props) {        super(props);        this.state = {            location: props.history.location        };        this.unlisten = props.history.listen(location => {            this.setState({                location            });        });    }    componentWillUnmount() {        if (this.unlisten) {            this.unlisten();        }    }    render() {        return (            <RouterContext.Provider                value={{                    history: this.props.history,                    location: this.state.location,                    match: Router.computeRootMatch(this.state.location.pathname)                }}>                {this.props.children}            </RouterContext.Provider>        );    }}

实现BrowserRouter

BrowserRouter:历史记录治理对象history初始化及向下传递,location变更监听

import React, {Component} from "react";import {createBrowserHistory} from "history";import Router from "./Router";export default class BrowserRouter extends Component {    constructor(props) {        super(props);        this.history = createBrowserHistory();    }    render() {        return <Router history={this.history}>{this.props.children}</Router>;    }}

实现Route

路由配置,匹配检测,内容渲染

// match 依照互斥规定 优先渲染程序为children component render null,children如果是function执⾏function,是节点间接渲染// 不match children 或者null (只渲染function)export default class Route extends Component {    render() {        return (            <RouterContext.Consumer>                {context => {                    // 优先从props中取值                    const location = this.props.location || context.location;                    // 优先从props中取值计算                    const match = this.props.computedMatch                        ? this.props.computedMatch                        : this.props.path                            ? matchPath(location.pathname, this.props)                            : context.match;                    const props = {                        ...context,                        location,                        match                    };                    let {path, children, component, render} = this.props;                    // match 渲染这三者之⼀:children component render或者null                    // 不match,渲染children 或者 null                    return (                        <RouterContext.Provider value={props}>                            {match                                ? children                                    ? typeof children === "function"                                        ? children(props)                                        : children                                    : component                                        ? React.createElement(component, props)                                        : render                                            ? render(props)                                            : null                                : typeof children === "function"                                    ? children(props)                                    : null}                        </RouterContext.Provider>                    );                }}            </RouterContext.Consumer>);    }}

实现Link

Link.js: 跳转链接,解决点击事件

import React from "react";import {RouterContext} from "./RouterContext";export default function Link({to, children, ...restProps}) {  const context = React.useContext(RouterContext);  const handleClick = e => {    e.preventDefault();    context.history.push(to);  };  return (    <a href={to} {...restProps} onClick={handleClick}>      {children}    </a>  );}

实现Switch

渲染与该地址匹配的第⼀个⼦节点 <Route> 或者 <Redirect> 。

import React, {Component, isValidElement} from "react";import {RouterContext} from "./Context";import matchPath from "./matchPath";export default class Switch extends Component {    render() {        return (            <RouterContext.Consumer>                {context => {                    const {location} = context;                    let match, element;                    // children element | array                    React.Children.forEach(this.props.children, child => {                        if (match == null && React.isValidElement(child)) {                            element = child;                            const {path} = child.props;                            match = path                                ? matchPath(location.pathname, child.props)                                : context.match;                        }                    });                    return match                        ? React.cloneElement(element, {computedMatch: match})                        : null;                }}            </RouterContext.Consumer>        );    }}

原文链接:浅析前端路由Router

掘金:前端LeBron

知乎:前端LeBron

继续分享技术博文,关注微信公众号