实现简易版 React Route

实现简易版 React Route

我们都知道 React中,一切皆组件。Router的使用就是引入一个个组件,非常方便。常见的路由组件有这些:

  • Link 路由链接
  • Route 基本路由
  • Switch 独占路由
  • Redirect 重定向组件
  • PrivateRoute 导航守卫
  • BrowserRouter 在Route基础上添加了一些API方法

上面组件用法不多介绍了,参考react-router文档 很容易上手。来看看如何实现它们:

Link

组件返回一个 a标签,href路径为props中的 to 属性,为a标签添加一个点击事件,跳转到对应组件

image.png

Switch

独占路由,即拿到Route数组,一旦匹配直接渲染,不往下继续匹配
同时,Switch组件有location属性,可指定path,无论浏览器path如何改变,总会显示指定path对应组件

import React, {Component} from "react";
import {RouterContext} from "./RouterContext";
import matchPath from "./matchPath";

export default class Switch extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          // 找出渲染的,第一个符合匹配的元素,存在element
          // const {location} = context;
          // 优先用props上的location
          const location = this.props.location || context.location;
          let element,
            match = null;
          let {children} = this.props;

      		// 此处直接用for循环亦可,不过要考虑children非数组情况
          React.Children.forEach(children, child => {
            if (match === null && React.isValidElement(child)) {
              element = child;
              const path = child.props.path;
              match = path
                ? matchPath(location.pathname, {
                    ...child.props,
                    path
                  })
                : context.match;
            }
          });

          return match
            ? React.cloneElement(element, {
                location,
                computedMatch: match
              })
            : null;
        }}
      </RouterContext.Consumer>
    );
  }
}

Redirect

重定向组件,跳转自然会用到 history.push

import React, {Component} from "react";
import {RouterContext} from "./RouterContext";

export default class Redirect extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const {history} = context;
          const {to} = this.props;
          // history.push(to)
          return <LifeCycle onMount={() => history.push(to)} />;
        }}
      </RouterContext.Consumer>
    );
  }
}

// 考虑到渲染的生命周期,在Mount周后执行
class LifeCycle extends Component {
  componentDidMount() {
    if (this.props.onMount) {
      this.props.onMount();
    }
  }
  render() {
    return null;
  }
}

PrivateRoute

导航守卫,通常用于权限校验或判断登录。
以判断登录状态为例,结合Redirect组件,已登录,跳转到to对应的path组件,未登录则重定向到登录页

// 此处的connect方法是 react-redux中的方法,在react-redux一节提到过
export default connect(
  // mapStateToProps
  ({user}) => ({isLogin: user.isLogin})
)(
  class PrivateRoute extends Component {
    render() {
      const {isLogin, path, component} = this.props;
      if (isLogin) {
        // 登录
        return <Route path={path} component={component} />;
      } else {
        // 去登录,跳转登录页面
        return <Redirect to={{pathname: "/login", state: {redirect: path}}} />;
      }
    }
  }
);

BrowserRouter

BrowserRouter暴露出三个属性:

  • history 即history库提供的 createBrowserHistory方法产生的对象
  • location 当前路由地址
  • match 是否匹配
import React, {Component} from "react";
import {createBrowserHistory} from "history";
import {RouterContext} from "./RouterContext";

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

Route

Route相比其他几个组件会复杂些,掌握以下要点:

  • props 除了人为添加的 children/component/render, path 之外,还有BrowserRouter传到context中的 match, location, history
  • 渲染的优先级 Children > Component > render
  • return 时先看 match结果,
    • 结果为true,则按优先级渲染;
    • 结果为false,则判断是否有children,有则渲染,没有为null
  • 外面包一层Provide, 将需要传递的属性如: context对象,location以及 match结果传递出去(可能跨层级,所以使用context方式传递)
import React, {Component, Children} from "react";
import {RouterContext} from "./RouterContext";
import matchPath from "./matchPath";

export default class Route extends Component {
  render() {
    return (
      <RouterContext.Consumer>
        {context => {
          const {path, computedMatch, children, component, render} = this.props;
          const location = this.props.location || context.location;
          const match = computedMatch
            ? computedMatch
            : path
            ? matchPath(location.pathname, this.props)
            : context.match;
          const props = {
            ...context,
            location,
            match
          };
    
    			// match 匹配 children是function或者是节点
					// 非match 不匹配  children是function
          return (
            <RouterContext.Provider value={props}>
              {match
                ? children
                  ? typeof children === "function"
                    ? children(props)
                    : children
                  : component
                  ? // ? React.cloneElement(element, props)
                    React.createElement(component, props)
                  : render
                  ? render(props)
                  : null
                : typeof children === "function"
                ? children(props)
                : null}
            </RouterContext.Provider>
          );

          // return match ? React.createElement(component, this.props) : null;
        }}
      </RouterContext.Consumer>
    );
  }
}

为了看的更清楚,当访问首页 / 时传递的props截图如下:

image.png

发布了171 篇原创文章 · 获赞 246 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/weixin_42042680/article/details/104623885