Подробное объяснение принципа реализации front-end роутинга и react-router

JavaScript

В процессе разработки одностраничных приложений полным ходом все меньше внимания уделялосьВнешняя маршрутизация.

И ххх интервьюеры часто спрашивают, что такое front-end роутинг, каков его принцип, как он реализован, а страница при переходе не обновляется...

Много чего, спроси у тебя голова большая, в это время я достану заветные картинки, зрители, пять сигарет. 🤣

предисловие

Сегодня главный разговор:

  • Нативный js реализует hashRouter
  • Нативный js реализует historyRouter
  • BrowserRouter для реактивного роутера
  • HistoryRouter для реактивного роутера

Принцип реализации четырех видов маршрутов.

Экологические проблемы

Потому что я буду использовать новые h5 позже.pushState()метод, потому что эта игра (диао) значит (мао) слишком лицемерна, не поддерживает местныеФайловый протоколзапустить, иначе будет сообщено о следующей ошибке

только вhttp(s) протоколБегая, эта ямная сволочь тоже давно наступила, наступив на сомнения относительно своего пола.

с момента использованияфайловый протоколНе только использовать егоwebpackУстановите грубую окружающую среду, вы также можете использовать Apache, Tomcat ... ах собака ах кошка и тому подобное.

Этот шлак используетсяwebpackОкружающая среда, также удобно ждать объясненияreact-router-domПринцип двух маршрутов. Конфигурацию окружения я просто выложу, и рассказывать здесь не буду. если ты правwebpackЕсли вам интересно, можете взглянуть на эту мразьэта статья, Хоть и не очень хорошо написано, но для начала достаточно.

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

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'
        })
    ]

}

Добавьте команду в скрипт package.json

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

каталог проекта

бегать

npm run dev

Теперь, когда все готово, можно переходить к теме.

Нативный js реализует hashRouter

html

<ul>
    <li><a href='#/home'>home</a></li>
    <li><a href='#/about'>about</a></li>
    <div id="routeView"></div>
</ul>

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;
    }

}

Собственная 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>

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;
    }

}

Реализовать переход по маршруту истории можно без обновления страницы.В этом есть преимущества от pushState(), replaceState() и других методов, предоставляемых H5.Эти методы также могут изменять состояние маршрута (путь), но не делают переходов по страницам. , Мы можем передать location.pathname для отображения соответствующего представления.

react-router-dom

react-router-dom — это маршрут реакции, он помогает нам реализовывать одностраничные приложения в проекте, он предоставляет нам два маршрута, один из которых основан на хеш-сегментеHashRouter, реализация на основе H5ApiBrowserRouter.

Давайте использовать его кратко.

Если вы используете окружение, предоставленное вам этим шлаком, вам необходимо его настроить.Следующие 👇 эти вещи, если нет, пожалуйста, игнорируйте.

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

webpack.config.js, добавьте предустановку в параметры конфигурации js

Буду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'))

public/index.html

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

Обычно я умею только ими пользоваться, но редко задумываюсь, как это делается, поэтому когда нас спросят, мы будем в замешательстве; если вы сегодня прочтете эту статью, эта мразь обещает вам, что вы больше не только будете использовать react-router, он больше не зависит от вас, но вы можете делать с ним все, что хотите.

BrowserRouter реализация react-router-dom

Во-первых, мы создаем новый файл BrowserRouter.js в index.js и реализуем наш собственный BrowserRouter. Поскольку должен быть реализован BrowserRouter, этот файл должен состоять из трех компонентов: BrowserRouter, Route и Link.

Хорошо, теперь у нас все на месте, давайте их по одному 😂

Компоненты BrowserRouter

Компонент Browberter в основном делает текущий путь к текущему пути и прослушивает событие Popstate, поэтому мы должны общаться с потребителем, поперечными поставщиками. Если вы не понимаете, вы можете посмотреть на шлакВ этой статье этот шлак иллюстрирует все методы связи реакции.

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>
        );
    }
}

Реализация компонента маршрутизатора

Что в основном делает компонент Router, так это сравнивает текущее значение, переданное BrowserRouter, с путем, переданным Route через реквизиты, а затем решает, выполнять ли функцию рендеринга, переданную реквизитами.

export class Route extends React.Component {

    constructor(props) {
        super(props)
    }

    render() {
        let { path, render } = this.props
        return (
            <Consumer>
                {({ currentPath }) => currentPath === path && render()}
            </Consumer>
        )
    }
}

Реализация компонента Link

Что в основном делает компонент Link, так это получает реквизит, передает его, изменяет состояние маршрутизации с помощью PushState(), а затем вручную обновляет представление с помощью onChangeView, переданного из BrowserRouter.

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>
        )

    }

}

полный код

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>
        )

    }

}

использовать

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

Реализация hashRouter из react-router-dom

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} />

    }