持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情
前言
在之前的篇章中我们讲述了react-router的核心history库,而在《 来,手写一个简单的路由(一)》和《 来,手写一个简单的路由(二)》中我们通过事件和setState去动态更新组件。
在本篇章中我们将基于history库以及react-router-dom v5之前的版本,实现路由组件的封。
react-router-dom封装
在react-router-dom里,其主要是对react-router库进一步封装,而react-router的核心则是依赖history库。
我们新建一个react-router-dom,模拟该库的实现,引入之前封装的history,实现自己的BrowserRouter:
// BrowserRouter组件
import React, { Component } from 'react'
import { createBrowserHistory } from "./history"
// 待完成...
import { Router } from "../react-router"
export default class BrowserRouter extends Component {
history = createBrowserHistory(this.props)
render() {
return <Router history={this.history}>
{this.props.children}
</Router>
}
}
此处的代码便是我们在react-router-dom里导出的BrowserRouter代码,这里我们主要是通过创建history实例,将history实例通过props传给react-router导出的Router组件。而createBrowserHistory接收的参数便是我们在配置路由文件时传入的props。
react-router里的Router
Router组件主要的作用是通过上下文往其子组件注入上下文信息,其中上下文数据包括history、location、match信息。
import React, { Component,createContext } from 'react'
import PropTypes from "prop-types";
const ctx = createContext();
export default class Router extends Component {
static propTypes = {
history: PropTypes.object.isRequired,
children: PropTypes.node
}
state = {
location: this.props.history.location
}
ctxValue = {}
componentDidMount() {
this.unListen = this.props.history.listen((location, action) => {
this.props.history.action = action;
this.setState({
location
})
})
}
componentWillUnmount() {
this.unListen();//取消监听
}
render() {
console.log(this.props.children)
this.ctxValue.history = this.props.history;
this.ctxValue.location = this.state.location;
this.ctxValue.match = this.state.location.pathname;
return <ctx.Provider value={this.ctxValue}>
{this.props.children}
</ctx.Provider>
}
}
在上面的代码中,我们主要通过整合props传过来的数据,处理成上下文对象,然后注入到上下文的生产者里,供后代组件使用。
在路由整个声明周期里,我们通过history进行监听的开始与销毁。
为了使得match的处理和路由返回的目标数据结构一致,我们可以封装方法对其进行匹配处理。
在react-router内部,match的处理主要依赖path-to-regexp这个库:
import {pathToRegexp} from "path-to-regexp";
export default function matchPath(path, pathname, options) {
const keys = [];
const regExp = pathToRegexp(path, keys, getOptions(options));
const result = regExp.exec(pathname);
if (!result) {
return null;
}
let groups = Array.from(result);
groups = groups.slice(1);
const params = getParams(groups, keys);
return {
isExact: pathname === result[0],
params,
path,
url: result[0]
};
}
function getOptions(options = {}) {
const defaultOptions = {
exact: false,
sensitive: false,
strict: false
}
const opts = { ...defaultOptions, ...options };
return {
sensitive: opts.sensitive,
strict: opts.strict,
end: opts.exact
}
}
function getParams(groups, keys) {
const obj = {};
for (let i = 0; i < groups.length; i++) {
const value = groups[i];
const name = keys[i].name;
obj[name] = value;
}
return obj;
}
在《关于path-to-regexp的那点事》里,我们讲解了该库的基本使用,而其库正式react-router里上下文对象的match方法实现的核心。
基于封装的match方法,改造Router提供的上下文数据:
import matchPath from "./matchPath"
...
this.ctxValue.match = matchPath("/", this.state.location.pathname);
...
为此我们通过封装react-router-dom和react-router,实现了简单的Router组件,其主要是往后代组件提供上下文数据。核心处理是通过事件监听触发setState改变状态,重新执行组件。
// 与之前通过自定义事件监听触发的setState类似。
componentDidMount() {
this.unListen = this.props.history.listen((location, action) => {
this.props.history.action = action;
this.setState({ location })
})
}
基本使用:
import React from "react";
import { BrowserRouter } from "./react-router-dom"
export default function App() {
return <BrowserRouter>
hello world
</BrowserRouter>
}
小结
本篇章主要通过对react-router和react-router-dom的简单封装,实现Router组件,我们需要认识各个库的依赖关系,以及每个模块的作用。而Router的作用就是给后代组件提供上下文数据。