Внедрить реактивный маршрутизатор с 0

внешний интерфейс React.js внешний фреймворк

Внедрить реактивный маршрутизатор с 0

react-router претерпел серьезные обновления в нескольких версиях. Здесь мы намерены создать колесо со ссылкой на дизайнерскую идею v4.0: tiny-router.

history api

Современные браузеры предоставляют доступ к операциям с содержимым стека историиapi. Важными из них являются pushState, replaceState.

var stateObj = { foo: "bar" };
history.pushState(stateObj, "page 2", "bar.html");

Это заставит адресную строку браузера выглядеть какhttp://xxxx/bar.html, но не заставляет браузер загружать bar.html или даже проверять существование bar.html.

var stateObj = { foo: "bar" };
history.replaceState(stateObj, "page 2", "bar.html");

Это также приведет к отображению адреса браузераhttp://xxxx/bar.html, тоже не загружается. Браузер также не проверяет существование bar.html.
Разница между pushState и replaceState заключается в том, когда вы выполняете откат. push добавляет запись в стек истории, а repalce заменяет запись.
Оба метода получают 3 параметра, а именно:

  1. Объект состояния — состояние объекта состояния является объектом JavaScript.
  2. заглавие
  3. URL-адрес — этот параметр определяет новую историческую запись URL-адреса. Новый URL-адрес должен иметь то же происхождение, что и текущий URL-адрес.

отслеживать изменения URL

Всякий раз, когда URL-адрес изменяется, вид представления изменяется соответственно, что является основным маршрутом. URL-адрес можно легко изменить с помощью API истории. Теперь нам нужно отслеживать каждое изменение URL для достижения цели изменения представления Нам нужно инкапсулировать объект истории для достижения цели мониторинга. код показывает, как показано ниже:

const his = window.history
class History {
    constructor() {
        this.listeners = []
    }

    push = path => {
        his.pushState({}, "", path);
        this.notifyAll()
    }

    listen = listener => {
        this.listeners.push(listener)
        return () => {
            this.listeners = this.listeners.filter(ele => ele !== listener)
        }
    }

    notifyAll = () => {
        this.listeners.forEach(lis => {
            lis()
        })
    }
}

export default new History()

Объявите класс History для регистрации слушателя.Когда адрес передается, класс History вызывает метод history.push внизу, чтобы изменить маршрут, а затем уведомляет зарегистрированный слушатель. Таким образом, при изменении маршрута наша функция обработки может обработать его в первый раз.

Route

Самым важным компонентом в react-router v4.0 является Route. Этот компонент получает свойство пути, и как только URL-адрес совпадает с путем, отображается компонент, указанный этим маршрутом. На странице одновременно может отображаться несколько компонентов маршрута, если атрибут пути маршрута соответствует URL-адресу.
Наш маршрут такой же. Чтобы достичь этого эффекта, нам нужно определить, совпадают ли новый URL-адрес и путь каждый раз, когда URL-адрес изменяется, и, как только он совпадет, отобразить соответствующий компонент. Это требует, чтобы каждый компонент Route регистрировал слушателя в History при его инициализации, чтобы Route мог своевременно реагировать на изменения URL.
код показывает, как показано ниже:

class Route extends Component {
    constructor(props) {
        super(props)
        this.state = {
            match: matchPath(...)
        }
        this.unlisten = history.listen(this.urlChange)
    }

    componentWillUnmount() {
        this.unlisten()
    }

    urlChange = () => {
        const pathname = location.pathname
        this.setState({
            match: matchPath(...)
        })
    }

    render() {
        const { match } = this.state
        if(!match) return

        // 具体的渲染...
    }
}

matchPath

Путь маршрута может быть строкой или регулярным выражением. Кроме того, у маршрута также есть три атрибута: точный (точное совпадение), строгий (конец/) и чувствительный (регистр). Вместе они определяют регулярное выражение PathReg, которое соответствует URL-адресу (правило сопоставления здесь точно такое же, как и у react-router).

Мы предполагаем, что свойства пути, точного, строгого и чувствительного являются неизменяемыми (на самом деле их не нужно модифицировать). Таким образом, мы можем сгенерировать этот PathReg при инициализации компонента Route.

const compilePath = (pattern = '/', options) => {
    const { exact = false, strict = false, sensitive = false } = options
    const keys = []
    const re = pathToRegexp(pattern, keys, { end: exact, strict, sensitive })
    return { re, keys }
}

class Route extends Component {
    static propTypes = {
        path: PropTypes.string,
        component: PropTypes.func,
        render: PropTypes.func,
        exact: PropTypes.bool,
        strict: PropTypes.bool,
    }

    constructor(props) {
        super(props)

        this.pathReAndKeys = compilePath(props.path, {
            exact: props.exact,
            strict: props.strict,
            sensitive: props.sensitive
        })
        this.state = {
            match: matchPath(...)
        }
        this.unlisten = history.listen(this.urlChange)
    }
 }

Мы предварительно сгенерировали pathReAndKeys в конструкторе компонента. Затем каждый раз, когда вы выполняете matchPath, используйте эту закономерность напрямую. Код для mathPath выглядит следующим образом:

const matchPath = (pathname, props, pathReAndKeys) => {
    const { path = '/', exact = false } = props
    const { re, keys } = pathReAndKeys
    const match = re.exec(pathname)

    if (!match)
        return null

    const [ url, ...values ] = match
    const isExact = pathname === url

    if (exact && !isExact)
        return null

    return {
        path, // the path pattern used to match
        url: path === '/' && url === '' ? '/' : url, // the matched portion of the URL
        isExact, // whether or not we matched exactly
        params: keys.reduce((memo, key, index) => {
            memo[key.name] = values[index]
            return memo
        }, {})
    }
}

Если маршрут не совпадает, возвращайте null напрямую. В противном случае возвращает объект с соответствующей информацией. Например:

url path match.params
/user /user {}
/user/12 /user/:id {id: '12'}
/user/12/update /user/:id/:op {id: 12, op: 'update'}

Здесь используется путь обработкиpath-to-regexpэта библиотека.

оказывать

Сопоставление пути — это просто процесс, а отрисовка соответствующего представления — конечная цель! Компонент Route предоставляет 3 свойства: component, render, children. Конкретное использование заключается в следующем:

// one
class A extends Component {
    render() {
        return <div>A</div>
    }
}

// Route默认会把匹配的信息注入到组件A的props
<Route path='/a' component={A} /> 

// two
<Route path='/a' render={({match}) => (<div>A</div>)}

// three Route默认会把匹配的信息注入到组件A的props
<Route>
    <A>
</Route>

Полный код маршрута выглядит следующим образом:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { compilePath, matchPath } from './util'
import history from './history'

class Route extends Component {
    static propTypes = {
        path: PropTypes.string,
        component: PropTypes.func,
        render: PropTypes.func,
        exact: PropTypes.bool,
        strict: PropTypes.bool,
    }

    constructor(props) {
        super(props)

        this.pathReAndKeys = compilePath(props.path, {
            exact: props.exact,
            strict: props.strict,
            sensitive: props.sensitive
        })
        this.state = {
            match: matchPath(location.pathname, props, this.pathReAndKeys)
        }
        this.unlisten = history.listen(this.urlChange)
    }

    componentWillReceiveProps(nextProps) {
        const {path, exact, strict} = this.props
        if (nextProps.path !== path || nextProps.exact !== exact || nextProps.strict !== strict) {
            console.warn("you should not change path, exact, strict props")
        }
    }

    componentWillUnmount() {
        this.unlisten()
    }

    urlChange = () => {
        const pathname = location.pathname
        this.setState({
            match: matchPath(pathname, this.props, this.pathReAndKeys)
        })
    }

    render() {
        const { match } = this.state
        if(!match) return

        const { children, component, render } = this.props

        if (component) {
            const Comp = component
            return <Comp match={match}/>
        }
        if (render) {
            return render({ match })
        }

        return React.cloneElement(React.Children.only(children), { match })
    }
}

export default Route

Link

То же, что и реактивный маршрутизатор. Мы также предоставляем компонент Link для реализации «декларативных» маршрутных переходов. Ссылка по сути является нативным тегом, и при нажатии на нее history.push переходит по адресу, указанному в атрибуте to.


import React, { Component } from 'react'
import history from './history'

export default class Link extends Component {

    handleClick = e => {
        const { onClick, to } = this.props
        if (onClick){
            onClick(e)
        }

        e.preventDefault()
        history.push(to)
    }

    render() {
        return (
            <a  {...this.props} onClick={this.handleClick}/>
        )

    }
}

«Сложный» пример

Вот веб-сайт, который имитирует скачки маршрутизации в обычных бизнес-сценариях. Маршрутизацией управляет tiny-router.

Главная Навигация Нажмите, чтобы переключиться в верхней части страницы

Страница личной информации Переключатель на левой панели навигации

live demo

Github & npm

  1. адрес гитхаба:GitHub.com/Увидев Форонгер Блю. …. Чувствуй себя хорошо, нажми звезду
  2. Установитьnpm install tinyy-router(обратите внимание на два y для крошечного, потому что крошечный маршрутизатор уже используется)

Статьи по Теме

  1. Реализовать крошечную реакцию с 0 (1)
  2. Реализовать tinyredux с 0
  3. Реализовать крошечный реактивный редукс с 0
  4. Почему нам нужно повторно выбрать