• 经常被问到的react-router实现原理详解


    单页面应用如日中天发展的过程中,备受关注的少了前端路由

    而且还经常会被xxx面试官问到,什么是前端路由,它的原理的是什么,它是怎么实现,跳转不刷新页面的…

    一大堆为什么,问你头都大

    前言

    今天主要讲的是:

    • 原生js实现hashRouter
    • 原生js实现historyRouter
    • react-router-dom的BrowserRouter
    • react-router-dom的HistoryRouter

    四种路由的实现原理。

    环境问题

    因为等一下要用到h5新增的pushState() 方法,因为这玩(diao)意(mao)太矫情了,不支持在本地的file协议运行,不然就会报以下错误

    在这里插入图片描述

    只可以在http(s)协议 运行,这个坑本渣也是踩了很久,踩怀疑自己的性别。

    既然用file协议 不行那就只能用webpack搭个简陋坏境了,你也可以用阿帕奇,tomcat…啊狗啊猫之类的东西代理。

    本渣用的是webpack环境,也方便等下讲解react-router-dom的两个路由的原理。环境的配置,我简单的贴一下,这里不讲。

    npm i webpack webpack-cli babel-loader @babel-core @babel/preset-env html-webpack-plugin webpack-dev-server -D
    
    • 1

    webpack.config.js

    const path = require('path')
    
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    
    module.exports = {
    
        entry:path.resolve(__dirname,'./index.js'),
    
        output:{
    
            filename:'[name].[hash:6].js',
    
            path:path.resolve(__dirname,'../dist')
    
        },
    
        module:{
    
            rules:[
                {
                    test:/\.js$/,
    
                    exclude:/node_module/,
    
                    use:[
                        {
                            loader:'babel-loader',
                            options:{
                                presets:['@babel/preset-env']
                            }
                        }
                    ]
                }
            ]
    
        },
    
        plugins:[
            new HtmlWebpackPlugin({
                template:path.resolve(__dirname,'./public/index.html'),
                filename:'index.html'
            })
        ]
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    package.json的script添加一条命令

        "dev":"webpack-dev-server --config ./src/webpack.config.js --open"
    
    • 1

    项目目录
    在这里插入图片描述

    运行

    npm run dev
    
    • 1

    现在所有东西都准备好了,我们可以进入主题了。

    原生js实现hashRouter

    html

    <ul>
        <li><a href='#/home'>home</a></li>
        <li><a href='#/about'>about</a></li>
        <div id="routeView"></div>
    </ul>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    js

    window.addEventListener('DOMContentLoaded', onLoad)
    
    window.addEventListener('hashchange', changeView)
    
    let routeView = ''
    
    function onLoad() {
    
        routeView = document.getElementById('routeView')
    
        changeView()
    
    }
    
    function changeView() {
        switch (location.hash) {
            case '#/home':
                routeView.innerHTML = 'home'
                break;
            case '#/about':
                routeView.innerHTML = 'about'
                break;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    原生js实现hashRouter主要是监听它的hashchange事件的变化,然后拿到对应的location.hash更新对应的视图

    原生js实现historyRouter

    html

    <ul>
        <li><a href='/home'>home</a></li>
        <li><a href='/about'>about</a></li>
        <div id="routeView"></div>
    </ul>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    historyRoute

    window.addEventListener('DOMContentLoaded', onLoad)
    
    window.addEventListener('popstate', changeView)
    
    let routeView = ''
    
    function onLoad() {
    
        routeView = document.getElementById('routeView')
    
        changeView()
    
        let event = document.getElementsByTagName('ul')[0]
    
        event.addEventListener('click', (e) => {
    
            if(e.target.nodeName === 'A'){
                e.preventDefault()
    
                history.pushState(null, "", e.target.getAttribute('href'))
    
                changeView()
            }
    
        })
    }
    
    function changeView() {
        switch (location.pathname) {
            case '/home':
                routeView.innerHTML = 'home'
                break;
            case '/about':
                routeView.innerHTML = 'about'
                break;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    能够实现history路由跳转不刷新页面得益与H5提供的pushState(),replaceState()等方法,这些方法都是也可以改变路由状态(路径),但不作页面跳转,我们可以通过location.pathname来显示对应的视图

    react-router-dom

    react-router-dom是react的路由,它帮助我们在项目中实现单页面应用,它提供给我们两种路由一种基于hash段实现的HashRouter,一种基于H5Api实现的BrowserRouter

    下面我们来简单用一下。

    如果你在用本渣以上提供给你的环境,还要配置一下,下面👇这些东西,如果不是,请忽略。

    npm i react react-dom react-router-dom @babel/preset-react -D
    
    • 1

    webpack.config.js,在js的options配置加一个preset

    在这里插入图片描述

    index.js改成这样。

    import React from 'react'
    
    import ReactDom from 'react-dom'
    
    import { BrowserRouter, Route, Link } from 'react-router-dom'
    
    function App() {
    
        return (
    
            <BrowserRouter>
                <Link to='/home'>home</Link>
                <Link to='/about'>about</Link>
                <Route path='/home' render={()=><div>home</div>}></Route>
                <Route path='/about' render={()=><div>about</div>}></Route>
            </BrowserRouter>
    
        )
    }
    
    ReactDom.render(<App></App>,document.getElementById('root'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    public/index.html

    <div id="root"></div>
    
    • 1

    平时我么只知道去使用它们,但却很少去考虑它是怎么做到,所以导致我们一被问到,就会懵逼;今日如果你看完这篇文章,本渣promiss你不再只会用react-router,不再是它骑在你身上,而是你可以对它为所欲为。

    react-router-dom的BrowserRouter实现

    首先我们在index.js新建一个BrowserRouter.js文件,我们来实现自己BrowserRouter。 既然要实现BrowserRouter,那这个文件就得有三个组件BrowserRouter,Route,Link。

    在这里插入图片描述

    好,现在我们把它壳定好来,让我们来一个一个的弄*它们😂

    BrowserRouter组件

    BrowserRouter组件主要做的是将当前的路径往下传,并监听popstate事件,所以我们要用Consumer, Provider跨组件通信,参考React实战视频讲解:进入学习

    const { Consumer, Provider } = React.createContext()
    
    export class BrowserRouter extends React.Component {
    
        constructor(props) {
            super(props)
            this.state = {
                currentPath: this.getParams.bind(this)(window.location.pathname)
            }
        }
    
    
        onChangeView() {
            const currentPath = this.getParams.bind(this)(window.location.pathname)
            this.setState({ currentPath });
        };
    
        getParams(url) {
            return url
        }
    
    
        componentDidMount() {
            window.addEventListener("popstate", this.onChangeView.bind(this));
        }
    
        componentWillUnmount() {
            window.removeEventListener("popstate", this.onChangeView.bind(this));
        }
    
        render() {
            return (
                <Provider value={{ currentPath: this.state.currentPath, onChangeView: this.onChangeView.bind(this) }}>
                     <div>
                        {
                            React.Children.map(this.props.children, function (child) {
    
                                return child
    
                            })
                        }
                    </div>
                </Provider>
            );
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    Rouer组件的实现

    Router组件主要做的是通过BrowserRouter传过来的当前值,与Route通过props传进来的path对比,然后决定是否执行props传进来的render函数,

    export class Route extends React.Component {
    
        constructor(props) {
            super(props)
        }
    
        render() {
            let { path, render } = this.props
            return (
                <Consumer>
                    {({ currentPath }) => currentPath === path && render()}
                </Consumer>
            )
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Link组件的实现

    Link组件主要做的是,拿到prop,传进来的to,通过PushState()改变路由状态,然后拿到BrowserRouter传过来的onChangeView手动刷新视图

    export class Link extends React.Component {
    
        constructor(props){
            super(props)
        }
    
        render() {
            let { to, ...props } = this.props
            return (
                <Consumer>
                    {({ onChangeView }) => (
                        <a
                            {...props}
                            onClick={e => {
                                e.preventDefault();
                                window.history.pushState(null, "", to);
                                onChangeView();
                            }}
                        />
                    )}
                </Consumer>
            )
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    完整代码

    import React from 'react'
    
    const { Consumer, Provider } = React.createContext()
    
    export class BrowserRouter extends React.Component {
    
        constructor(props) {
            super(props)
            this.state = {
                currentPath: this.getParams.bind(this)(window.location.pathname)
            }
        }
    
    
        onChangeView() {
            const currentPath = this.getParams.bind(this)(window.location.pathname)
            this.setState({ currentPath });
        };
    
        getParams(url) {
            return url
        }
    
    
        componentDidMount() {
            window.addEventListener("popstate", this.onChangeView.bind(this));
        }
    
        componentWillUnmount() {
            window.removeEventListener("popstate", this.onChangeView.bind(this));
        }
    
        render() {
            return (
                <Provider value={{ currentPath: this.state.currentPath, onChangeView: this.onChangeView.bind(this) }}>
                    <div>
                        {
                            React.Children.map(this.props.children, function (child) {
    
                                return child
    
                            })
                        }
                    </div>
                </Provider>
            );
        }
    }
    
    export class Route extends React.Component {
    
        constructor(props) {
            super(props)
        }
    
        render() {
            let { path, render } = this.props
            return (
                <Consumer>
                    {({ currentPath }) => currentPath === path && render()}
                </Consumer>
            )
        }
    }
    
    export class Link extends React.Component {
    
        constructor(props){
            super(props)
        }
    
        render() {
            let { to, ...props } = this.props
            return (
                <Consumer>
                    {({ onChangeView }) => (
                        <a
                            {...props}
                            onClick={e => {
                                e.preventDefault();
                                window.history.pushState(null, "", to);
                                onChangeView();
                            }}
                        />
                    )}
                </Consumer>
            )
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    使用

    把刚才在index.js使用的react-router-dom换成这个文件路径就OK。

    react-router-dom的hashRouter的实现

    hashRouter就不一个一个组件的说了,跟BrowserRouter大同小异,直接贴完整代码了

    import React from 'react'
    
    let { Provider, Consumer } = React.createContext()
    
    export class HashRouter extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                currentPath: this.getCurrentPath.bind(this)(window.location.href)
            }
        }
    
        componentDidMount() {
            window.addEventListener('hashchange', this.onChangeView.bind(this))
        }
    
        componentWillUnmount() {
            window.removeEventListener('hashchange')
        }
    
        onChangeView(e) {
            let currentPath = this.getCurrentPath.bind(this)(window.location.href)
            this.setState({ currentPath })
        }
    
        getCurrentPath(url) {
    
            let hashRoute = url.split('#')
    
            return hashRoute[1]
        }
    
        render() {
    
            return (
    
                <Provider value={{ currentPath: this.state.currentPath }}>
                    <div>
                        {
                            React.Children.map(this.props.children, function (child) {
    
                                return child
    
                            })
                        }
                    </div>
                </Provider>
    
            )
    
        }
    
    
    }
    
    export class Route extends React.Component {
    
        constructor(props) {
            super(props)
        }
    
        render() {
    
            let { path, render } = this.props
    
            return (
                <Consumer>
                    {
                        (value) => {
                            console.log(value)
                            return (
                                value.currentPath === path && render()
                            )
                        }
                    }
                </Consumer>
            )
    
        }
    
    }
    
    export class Link extends React.Component {
    
        constructor(props) {
            super(props)
        }
    
        render() {
    
            let { to, ...props } = this.props
    
            return <a href={'#' + to} {...props} />
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
  • 相关阅读:
    在树莓派中,Ubuntu系统下使用vim编辑器
    Python Opencv实践 - 轮廓特征(最小外接圆,椭圆拟合)
    LRU最近最少使用算法
    实战文档:彻底搞懂JVM+Linux+MySQL+Netty+Tomcat+并发编程
    dup和dup2函数
    GIT开发学习——git reset current branch to here
    如何看待服装订单外流现象?
    LeetCode算法题解(回溯,难点)|LeetCode37. 解数独
    使用 SAP UI5 ABAP Repository 部署本地 SAP UI5 应用到 ABAP 服务器的单步调试
    Unix运维_CMake教程_CMake中的link_libraries指令
  • 原文地址:https://blog.csdn.net/grooyo/article/details/127645173