Во время Цинминского фестиваля идет дождь, поэтому кодить лучше дома. Реализуйте реактивный маршрутизатор с нуля и запустите реактивный маршрутизатор.example.
react-router — это библиотека js, которая управляет разными URL-адресами для отображения разных компонентов при выполнении SPA (а не SPA, как вы думаете). Использование react-router может облегчить разработку, и нет необходимости вручную поддерживать соответствие между url и компонентами. React-router-dom используется для разработки Компоненты в react-router-dom являются инкапсуляцией компонентов react-router.
Принцип СПА
Существует два принципа одностраничного приложения: один заключается в изменении страницы путем изменения хэша, а другой — в изменении страницы путем изменения URL-адреса.
- hash
- window.location.hash='xxx' Изменить хэш
- window.addEventListener('hashchange',fun) Прослушивание изменений хеша
- url
- history.pushState(obj,title,'/url') изменить URL-адрес
- window.addEventListener('popstate',fun) Это событие запускается, когда браузер перемещается вперед и назад.
Основные компоненты React-Router-dom
- Router
- Маршрутизатор — это внешний уровень, и его подкомпоненты визуализируются окончательно, а конкретные бизнес-компоненты не визуализируются.
- Делится на HashRouter (путем изменения хэша), BrowserRouter (путем изменения URL-адреса), MemoryRouter
- Маршрутизатор отвечает за выбор метода для использования в качестве хэша или браузера или других решений для одностраничного приложения.Замените HashRouter на BrowserRouter, и код может продолжать работать.
- В пропсах роутера есть объект истории.История это инкапсуляция window.history.История отвечает за управление взаимодействием с историей браузера и какое одностраничное приложение. history будет передаваться как свойство в childContext.
- Route
- Отвечает за отрисовку определенных бизнес-компонентов, отвечает за сопоставление URL-адресов и соответствующих компонентов.
- Существует три способа рендеринга компонентов: компонент (соответствующий компонент), рендеринг (функция, которая рендерит компонент в функции), дочерние элементы (в зависимости от того, какой маршрут будет рендериться).
- Switch
- Если он соответствует подкомпоненту маршрута, он возвращается и больше не соответствует другим компонентам.
- Link
- Компонент при переходе по маршруту вызывает history.push для изменения URL-адреса.
history
- history — это управление взаимодействием с историей браузера и способ реализации одностраничного приложения. Здесь реализована простая история
- MyHistory — родительский класс, а HashHistory и BrowserHistory — два подкласса.
- Свойство loaction экземпляров HashHistory и BrowserHistory одинаково, поэтому updateLocation является методом подкласса. Путь к расположению — это значение после хэша # в HashHistory, а в BrowserHistory — window.location.pathname.
- В двух подклассах есть метод _push, который используется для изменения URL-адреса, оба из которых используютсяhistory.pushStateметод.
let confirm;
export default class MyHistory {
constructor() {
this.updateLocation();//改变实例上的location变量,子类实现
}
go() {
//跳到第几页
}
goBack() {
//返回
}
goForward() {
//向前跳
}
push() {
//触发url改变
if (this.prompt(...arguments)) {
this._push(...arguments);//由子类实现
this.updateLocation();
this._listen();
confirm = null; //页面跳转后把confirm清空
}
}
listen(fun) {
//url改变后监听函数
this._listen = fun;
}
createHref(path) {
// Link组件里的a标签的href
if (typeof path === 'string') return path;
return path.pathname;
}
block(message) {
//window.confirm的内容可能是传入的字符串,可能是传入的函数返回的字符串
confirm = message;
}
prompt(pathname) {
//实现window.confirm,确定后跳转,否则不跳转
if (!confirm) return true;
const location = Object.assign(this.location,{pathname});
const result = typeof confirm === 'function' ? confirm(location) : confirm;
return window.confirm(result);
}
}
import MyHistory from './MyHistory';
class HashHistory extends MyHistory {
_push(hash) {
//改变hash
history.pushState({},'','/#'+hash);
}
updateLocation() {
//获取location
this.location = {
pathname: window.location.hash.slice(1) || '/',
search: window.location.search
}
}
}
export default function createHashHistory() {
//创建HashHistory
const history = new HashHistory();
//监听前进后退事件
window.addEventListener('popstate', () => {
history.updateLocation();
history._listen();
});
return history;
};
import MyHistory from './MyHistory';
class BrowserHistory extends MyHistory{
_push(path){
//改变url
history.pushState({},'',path);
}
updateLocation(){
this.location = {
pathname:window.location.pathname,
search:window.location.search
};
}
}
export default function createHashHistory(){
//创建BrowserHistory
const history = new BrowserHistory();
window.addEventListener('popstate',()=>{
history.updateLocation();
history._listen();
});
return history;
};
Router
- HashRouter и BrowserRouter являются инкапсуляцией Router, и объекты истории, передаваемые в Router, различаются.
- Чтобы создать childContext в маршрутизаторе, история — это история реквизитов, местоположение — это местоположение в истории, а совпадение — результат сопоставления URL-адреса в компоненте маршрута.
- Функция прослушивания истории передается, и URL-адрес повторно отображается после изменения URL-адреса.
import PropTypes from 'prop-types';//类型检查
export default class HashRouter extends Component {
static propTypes = {
history: PropTypes.object.isRequired,
children: PropTypes.node
}
static childContextTypes = {
history: PropTypes.object,
location: PropTypes.object,
match:PropTypes.object
}
getChildContext() {
return {
history: this.props.history,
location: this.props.history.location,
match:{
path: '/',
url: '/',
params: {}
}
}
}
componentDidMount() {
this.props.history.listen(() => {
this.setState({})
});
}
render() {
return this.props.children;
}
}
import React,{Component} from 'react';
import PropTypes from 'prop-types';
import {createHashHistory as createHistory} from './libs/history';
import Router from './Router';
export default class HashRouter extends Component{
static propTypes = {
children:PropTypes.node
}
history = createHistory()
render(){
return <Router history={this.history} children={this.props.children}/>;
}
}
import {createBrowserHistory as createHistory} from './libs/history';
export default class BrowserRouter extends Component{
static propTypes = {
children:PropTypes.node
}
history = createHistory()
render(){
return <Router history={this.history} children={this.props.children}/>;
}
}
Route
- Маршрут соответствует пути и URL-адресу в свойствах. Если он совпадает, визуализируйте компонент и продолжайте сопоставлять следующий.
- Используется в маршрутеpath-to-regexpСопоставьте путь, получите согласованные параметры
- Route имеет три метода рендеринга компонентов, которые следует обрабатывать отдельно.
- Реквизиты маршрута имеют точное свойство. Если это правда, когда совпадение заканчивается в конце пути, оно должно точно совпадать с location.pathname.
- Контекст маршрута — это местоположение, история и совпадение, созданное маршрутизатором.Каждый раз, когда соответствие завершено, совпадение в маршруте должно быть изменено.
import pathToRegexp from 'path-to-regexp';
export default class Route extends Component {
static contextTypes = {
location: PropTypes.object,
history: PropTypes.object,
match:PropTypes.object
}
static propTypes = {
component: PropTypes.func,
render: PropTypes.func,
children: PropTypes.func,
path: PropTypes.string,
exact: PropTypes.bool
}
static childContextTypes = {
history:PropTypes.object
}
getChildContext(){
return {
history:this.context.history
}
}
computeMatched() {
const {path, exact = false} = this.props;
if(!path) return this.context.match;
const {location: {pathname}} = this.context;
const keys = [];
const reg = pathToRegexp(path, keys, {end: exact});
const result = pathname.match(reg);
if (result) {
return {
path: path,
url: result[0],
params: keys.reduce((memo, key, index) => {
memo[key.name] = result[index + 1];
return memo
}, {})
};
}
return false;
}
render() {
let props = {
location: this.context.location,
history: this.context.history
};
const { component: Component, render,children} = this.props;
const match = this.computeMatched();
if(match){
props.match = match;
if (Component) return <Component {...props} />;
if (render) return render(props);
}
if(children) return children(props);
return null;
}
}
Switch
- Коммутатор можно разместить за пределами маршрута, и когда он совпадает с маршрутом, он больше не будет совпадать.
- Switch также использует сопоставление путей.Похоже на метод в Route, его можно извлечь.Пример в react-router-dom использует Switch не так много, поэтому он не извлекается.
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component{
static contextTypes = {
location:PropTypes.object
}
constructor(props){
super(props);
this.path = props.path;
this.keys = [];
}
match(pathname,path,exact){
return pathToRegexp(path,[],{end:exact}).test(pathname);
}
render(){
const {location:{pathname}} = this.context;
const children = this.props.children;
for(let i = 0,l=children.length;i<l;i++){
const child = children[i];
const {path,exact} = child.props;
if(this.match(pathname,path,exact)){
return child
}
}
return null;
}
}
Link
- Свойство ссылки to является строкой или объектом.
- Ссылка отображает тег a, привязывает событие к тегу a и переходит.
- Переход Link выполняется с помощью history.push, а атрибут href объекта a является возвращаемым значением history.createHref.
export default class Link extends Component{
static propsTypes = {
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
}
static contextTypes = {
history:PropTypes.object
}
onClickHandle=(e)=>{
e.preventDefault();
this.context.history.push(this.href);
}
render(){
const {to} = this.props;
this.href = this.context.history.createHref(to);
return (
<a onClick={this.onClickHandle} href={this.href}>{this.props.children}</a>
);
}
}
Redirect
- Перенаправление переходит на маршрут без рендеринга компонента
- Получите путь через history.createHref и перейдите к history.push
export default class Redirect extends Component{
static propTypes = {
to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
}
static contextTypes = {
history: PropTypes.object
}
componentDidMount(){
const href = this.context.history.createHref(this.props.to);
this.context.history.push(href);
}
render(){
return null;
}
};
Prompt
- Эквивалентно window.confirm, нажмите «ОК», чтобы перейти к нужной ссылке, нажмите «Отмена», чтобы ничего не делать.
- Когда атрибут Prompt имеет значение true, срабатывает подтверждение
export default class Prompt extends Component {
static propTypes = {
when: PropTypes.bool,
message: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired
}
static contextTypes = {
history: PropTypes.object
}
componentWillMount() {
this.prompt();
}
prompt() {
const {when,message} = this.props;
if (when){
this.context.history.block(message);
}else {
this.context.history.block(null);
}
}
componentWillReceiveProps(nextProps) {
this.prompt();
}
render() {
return null;
}
};
withRouter
- withRouter на самом деле является компонентом более высокого порядка, то есть функция возвращает компонент. Внешний слой возвращенного компонента — это Маршрут, а полученные компоненты отображаются в дочернем свойстве Маршрута.
import React from 'react';
import Route from './Route';
const withRouter = Component => {
const C = (props)=>{
return (
<Route children={props=>{
return (
<Component {...props} />
)
}}/>
)
};
return C;
};
export default withRouter
Суммировать
Существует множество API для react-router и react-router-dom, например Redirect и withRouter. Компоненты в этой статье можно запускать только в примере в react-router-dom. Исходный код намного сложнее. Изучая исходный код и реализуя соответствующие функции, вы можете глубже понять реакцию и реакцию-маршрутизатор, а также изучить многие идеи программирования. Структура данных очень важна, например, деконструкция данных. ChildContext в Router в исходном коде. , подкомпоненты многократно используют содержащиеся в них методы или свойства, что удобно для повторного использования.
//ChildContext的数据解构
{
router:{
history, //某种 history
route:{
location:history.location,
match:{} //匹配到的结果
}
}
}