Внедрить реактивный маршрутизатор с 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 параметра, а именно:
- Объект состояния — состояние объекта состояния является объектом JavaScript.
- заглавие
- 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.
Главная Навигация Нажмите, чтобы переключиться в верхней части страницы
Страница личной информации Переключатель на левой панели навигации
Github & npm
- адрес гитхаба:GitHub.com/Увидев Форонгер Блю. …. Чувствуй себя хорошо, нажми звезду
- Установить
npm install tinyy-router
(обратите внимание на два y для крошечного, потому что крошечный маршрутизатор уже используется)