реагировать-маршрутизатор, чтобы понять

исходный код JavaScript React.js внешний фреймворк

Во время Цинминского фестиваля идет дождь, поэтому кодить лучше дома. Реализуйте реактивный маршрутизатор с нуля и запустите реактивный маршрутизатор.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:{} //匹配到的结果
        }
    }
}
Ссылаться на