博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
react-router了解一下
阅读量:7077 次
发布时间:2019-06-28

本文共 10005 字,大约阅读时间需要 33 分钟。

清明时节雨纷纷,不如在家撸代码。从零开始实现一个react-router,并跑通react-router-dom里的。

react-router是做SPA(不是你想的SPA)时,控制不同的url渲染不同的组件的js库。用react-router可以方便开发,不需要手动维护url和组件的对应关系。开发时用react-router-dom,react-router-dom里面的组件是对react-router组件的封装。

SPA的原理

单页应用的原理用两种,一种是通过hash的变化,改变页面,另一种是通过url的变化改变页面。

  • hash
    • window.location.hash='xxx' 改变hash
    • window.addEventListener('hashchange',fun) 监听hash的改变
  • url
    • history.pushState(obj,title,'/url') 改变url
    • window.addEventListener('popstate',fun) 当浏览器向前向后时,触发该事件。

React-Router-dom的核心组件

  • Router
    • Router是一个外层,最后render的是它的子组件,不渲染具体业务组件。
    • 分为HashRouter(通过改变hash)、BrowserRouter(通过改变url)、MemoryRouter
    • Router负责选取哪种方式作为单页应用的方案hash或browser或其他的,把HashRouter换成BrowserRouter,代码可以继续运行。
    • Router的props中有一个history的对象,history是对window.history的封装,history的负责管理与浏览器历史记录的交互和哪种方式的单页应用。history会作为childContext里的一个属性传下去。
  • Route
    • 负责渲染具体的业务组件,负责匹配url和对应的组件
    • 有三种渲染的组件的方式:component(对应的组件)、render(是一个函数,函数里渲染组件)、children(无论哪种路由都会渲染)
  • Switch
    • 匹配到一个Route子组件就返回不再继续匹配其他组件。
  • Link
    • 跳转路由时的组件,调用history.push把改变url。

history

  • history是管理与浏览器历史记录的交互,和用哪种方式实现单页应用。这里实现了一个简单的history
  • MyHistory是父类,HashHistory、BrowserHistory是两个子类。
  • HashHistory和BrowserHistory实例的loaction属性是相同的,所以updateLocation是子类方法。location的pathname在HashHistory是hash的#后面的值,在BrowserHistory是window.location.pathname。
  • 两个子类里有一个_push方法,用来改变url,都是用的方法。
let confirm;export default class MyHistory {    constructor() {        this.updateLocation();//改变实例上的location变量,子类实现    }    go() {    //跳到第几页    }    goBack() {    //返回    }    goForward() {    //向前跳    }    push() {    //触发url改变        if (this.prompt(...arguments)) {            this._push(...arguments);//由子类实现            this.updateLocation();              this._listen();            confirm = null;     //页面跳转后把confirm清空        }    }    listen(fun) {    //url改变后监听函数        this._listen = fun;    }    createHref(path) {    // Link组件里的a标签的href        if (typeof path === 'string') return path;        return path.pathname;    }    block(message) {    //window.confirm的内容可能是传入的字符串,可能是传入的函数返回的字符串        confirm = message;    }    prompt(pathname) {    //实现window.confirm,确定后跳转,否则不跳转        if (!confirm) return true;        const location = Object.assign(this.location,{pathname});        const result = typeof confirm === 'function' ? confirm(location) : confirm;        return window.confirm(result);    }}复制代码
import MyHistory from './MyHistory';class HashHistory extends MyHistory {    _push(hash) {    //改变hash        history.pushState({},'','/#'+hash);    }    updateLocation() {    //获取location        this.location = {            pathname: window.location.hash.slice(1) || '/',            search: window.location.search        }    }}export default function createHashHistory() {//创建HashHistory    const history = new HashHistory();    //监听前进后退事件    window.addEventListener('popstate', () => {        history.updateLocation();        history._listen();    });    return history;};复制代码
import MyHistory from './MyHistory';class BrowserHistory extends MyHistory{    _push(path){    //改变url        history.pushState({},'',path);    }    updateLocation(){        this.location = {            pathname:window.location.pathname,            search:window.location.search        };    }}export default function createHashHistory(){//创建BrowserHistory    const history = new BrowserHistory();    window.addEventListener('popstate',()=>{        history.updateLocation();        history._listen();    });    return history;};复制代码

Router

  • HashRouter和BrowserRouter是对Router的封装,传入Router的history对象不同
  • Router中要创建childContext,history是props的history,location是history里的location,match是Route组件里匹配url后的结果
  • history的listen传入函数,url改变后重新渲染
import PropTypes from 'prop-types';//类型检查export default class HashRouter extends Component {    static propTypes = {        history: PropTypes.object.isRequired,        children: PropTypes.node    }    static childContextTypes = {        history: PropTypes.object,        location: PropTypes.object,        match:PropTypes.object    }    getChildContext() {        return {            history: this.props.history,            location: this.props.history.location,            match:{                path: '/',                url: '/',                params: {}            }        }    }    componentDidMount() {        this.props.history.listen(() => {            this.setState({})        });    }    render() {        return this.props.children;    }}复制代码
import React,{Component} from 'react';import PropTypes from 'prop-types';import {createHashHistory as createHistory} from './libs/history';import Router from './Router';export default class HashRouter extends Component{    static propTypes = {        children:PropTypes.node    }    history = createHistory()    render(){        return 
; }}复制代码
import {createBrowserHistory as createHistory} from './libs/history';export default class BrowserRouter extends Component{    static propTypes = {        children:PropTypes.node    }    history = createHistory()    render(){        return 
; }}复制代码

Route

  • Route通过props里的path和url进行匹配,匹配到了,渲染组件,继续匹配下一个。
  • Route里用到匹配路径,获取匹配到的params
  • Route有三种渲染组件的方法,要分别处理
  • Route的props有一个exact属性。如果是true,匹配时到path结束,要和location.pathname准确匹配。
  • Route的Context是Router创建的location、history、match,每次匹配完成,要改变Route里的match。
import pathToRegexp from 'path-to-regexp';export default class Route extends Component {    static contextTypes = {        location: PropTypes.object,        history: PropTypes.object,        match:PropTypes.object    }    static propTypes = {        component: PropTypes.func,        render: PropTypes.func,        children: PropTypes.func,        path: PropTypes.string,        exact: PropTypes.bool    }    static childContextTypes = {        history:PropTypes.object    }    getChildContext(){        return {            history:this.context.history        }    }    computeMatched() {        const {path, exact = false} = this.props;        if(!path) return this.context.match;        const {location: {pathname}} = this.context;        const keys = [];        const reg = pathToRegexp(path, keys, {end: exact});        const result = pathname.match(reg);        if (result) {            return {                path: path,                url: result[0],                params: keys.reduce((memo, key, index) => {                    memo[key.name] = result[index + 1];                    return memo                }, {})            };        }        return false;    }    render() {        let props = {            location: this.context.location,            history: this.context.history        };        const { component: Component, render,children} = this.props;        const match = this.computeMatched();        if(match){            props.match = match;            if (Component) return 
; if (render) return render(props); } if(children) return children(props); return null; }}复制代码

Switch

  • Switch可以套在Route的外面,匹配到了一个Route,就不再往下匹配。
  • Switch也用到了路径匹配,和Route里的方法类似,可以提取出来,在react-router-dom里的example用到Switch的地方不多,所以没有提取。
import pathToRegexp from 'path-to-regexp';export default class Switch extends Component{    static contextTypes = {        location:PropTypes.object    }    constructor(props){        super(props);        this.path = props.path;        this.keys = [];    }    match(pathname,path,exact){        return pathToRegexp(path,[],{end:exact}).test(pathname);    }    render(){        const {location:{pathname}} = this.context;        const children = this.props.children;        for(let i = 0,l=children.length;i

Link

  • Link的属性to是字符串或对象。
  • Link渲染a标签,在a标签上绑定事件,进行跳转。
  • Link的跳转是用history.push完成的,a的href属性是history.createHref的返回值。
export default class Link extends Component{    static propsTypes = {        to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired    }    static contextTypes = {        history:PropTypes.object    }    onClickHandle=(e)=>{        e.preventDefault();        this.context.history.push(this.href);    }    render(){        const {to} = this.props;        this.href = this.context.history.createHref(to);        return (            {this.props.children}        );    }}复制代码

Redirect

  • Redirect跳转到某个路由,不渲染组件
  • 通过history.createHref获得path,history.push跳转过去
export default class Redirect extends Component{    static propTypes = {        to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired    }    static contextTypes = {        history: PropTypes.object    }    componentDidMount(){        const href = this.context.history.createHref(this.props.to);        this.context.history.push(href);    }    render(){        return null;    }};复制代码

Prompt

  • 相当于window.confirm,点击确定后跳转到想要的链接,点击取消不做操作
  • Prompt的属性when为true是才触发confirm
export default class Prompt extends Component {    static propTypes = {        when: PropTypes.bool,        message: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired    }    static contextTypes = {        history: PropTypes.object    }    componentWillMount() {        this.prompt();    }    prompt() {        const {when,message} = this.props;        if (when){            this.context.history.block(message);        }else {            this.context.history.block(null);        }    }    componentWillReceiveProps(nextProps) {        this.prompt();    }    render() {        return null;    }};复制代码

withRouter

  • withRouter实际是一个高阶组件,即一个函数返回一个组件。返回的组件外层是Route,Route的children属性里渲染接收到的组件。
import React from 'react';import Route from './Route';const withRouter = Component => {    const C = (props)=>{        return (            
{ return (
) }}/> ) }; return C;};export default withRouter复制代码
总结

react-router、react-router-dom的api还有很多,像Redirect和withRouter还有的许多api。本文的组件只能跑通react-router-dom里的example。源码要复杂的多,通过学习源码,并自己实现相应的功能,可以对react及react-router有更深的理解,学到许多编程思想,数据结构很重要,像源码中Router里的ChildContext的数据解构,子组件多次用到里面的方法或属性,方便复用。

//ChildContext的数据解构{    router:{        history,    //某种 history        route:{            location:history.location,            match:{} //匹配到的结果        }    }}复制代码
参考

转载地址:http://gscml.baihongyu.com/

你可能感兴趣的文章
如何成为一名成功的程序员
查看>>
华为nova 2系列自拍狂欢季即将火爆开启
查看>>
观点解读:Hadoop可能衰落,但核心组件的生命力依旧旺盛!
查看>>
28000名开发者,100个问题,这份JavaScript生态报告值得收藏!
查看>>
湖北襄阳女警“跪拍”照感动网友
查看>>
统计局解读1月制造业采购经理指数:服务业回升明显
查看>>
警方通报“济南1家6口死亡”案:男子杀害亲人后放火跳楼
查看>>
揭秘爆款《啥是佩奇》:差点被毙 主演非专业(图)
查看>>
我国高原柴油车治污迎来新机遇
查看>>
刷脸进门、病猫识别,程序员撸猫太高端!网友:看完服了!
查看>>
七牛云:基于Go开发的大数据平台
查看>>
ES 5 新增特性汇总
查看>>
Unity 游戏框架搭建 (八) 减少加班利器-QLog
查看>>
Array · 数组去重
查看>>
前端读取excel文件
查看>>
JavaScript 计时器
查看>>
Android依赖导入全攻略
查看>>
如何编写一个可升级的智能合约
查看>>
Canvas&Paint 知识梳理(5) Paint#setShader
查看>>
我的《机器学习实战》读书笔记(1)
查看>>