摹寫react-router-dom庫,簡單實現!

這一輯我決定摹寫一個基於Hash實現的react路由庫,可是鑑於browserRouter很簡單也就順便一塊兒摹寫了。

  • 一、我先將要實現的文件歸類導出一下,原諒我默默地裝一下大(年齡大)佬(不用裝就是老)。
  • react-router-dom/index.js
import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
Import Switch from './Switch';
import Redirect from './Redirect';
import NavLink from './NavLink';
import WithRouter from './WithRouter';
import Prompt from './Prompt'import BrowserRouter from './BrowserRouter';

export {
    HashRouter,
    Route,
    Link,
    Swicth,
    Redirect,
    NavLink,
    WithRouter,
    Prompt,
    BrowserRouter
}
複製代碼
  • 第一步咱們先來實現一下HashRouter

事實上HashRouter只是個容器,並不具備DOM結構,他的職責就是渲染他的子組件,和想下層組件傳遞數據 exp: locationcss

  • react-router-dom/HashRouter.js
<!--建立並導出一個HashRouter文件-->
import React from 'react';
import RouterContext from './RouterContext';
export default class HashRouter extends React.Component {
    state = {
        location : {
            // 去掉#號,hash是帶有#
            pathname: window.location.hash.slice(1) 
        }
    }
    componentDidMount(){
        // 監聽haschange事件,當以觸發hashchange事件就是去改變當前的狀態以後再同步hash的值
        window.addEventListener('hashchange', () = > {
            this.setState({
                ...this.state.location,
                pathname: window.location.hash.slice(1) || '/',
                state: this.locationState
            })
        });
        window.location.hash = window.location.hash || '/';
    }
    render() {
        let self = this;
        let history = {
            location: this.state.location,
            push(to) {
                if(typeof to === 'object') { // 有可能用戶傳遞的是個包含路徑和狀態的對象
                    let {pathname, state} = to;
                    that.locationState = state;
                    window.location.hash = pathname;
                } else { //傳遞的字符串
                    window.location.hash = to
                }
            }
            block(prompt){
                history.prompt = prompt
            }
            unblock() {
                history.prompt = null;
            }
        }
        let routerValue = {
            location: that.state.location,
            history
        }
        return (
            <RouterContext.Provider value= {routerValue}> {this.props.chilren} </RouterContext.Provider> ) } } 複製代碼

這裏咱們會實現Route路由組件,他不少時候表明的是一條路由規則。hashRouter中的state、location、pathname是經過上下文傳遞出去的。vue

  • react-router-dom/Route.js
import React from 'react';
import RouterContext from './RouterContext';
import pathToRegexp from ' path-to-regexp';
export default class Route extends React.Component {
    static contextType = RouterContext; // this.context.location.pathname
    // component是route組件裏面傳過來的那個component屬性值也就是該條路由所對應的組件,這裏我將它重命名爲RouteComponent。
    // path是該路由組件上傳過來的path路徑用來作匹配的
    // excat 表明的是該條路由上的路徑是否是精確匹配和`path-to-regexp`的參數對應。
    // 組件的渲染有三種方式分別是傳入一個render函數進行渲染、傳入一個children並且改children屬性是一個函數執行便可實現渲染、最後就是直接按照匹配的組件進行渲染。
    render() {
        let { path='/', component: RouteComponent, excat= false, render, children } = this.props;
        let path = typeof path === 'object' ? path.pathname : path;
        let pathname = this.context.location.pathname;
        let paramNames = [];
        let regexp = pathToRegexp(path, paramNames, {end: exact});
        paramNames = paramNames.map(p=>p.name);
        let matched = pathname.match(regexp);
        let routeProps = {
            location: this.context.location,
            history: this.context.history
        };
        if(matched) {
            let [url, ...values] = matched;
            let params = values.reduce((memo, cur, index) => {
                memo[paramNames[index]] = cur;
                return memo;
            }, {})
            let match - {
                url,
                path,
                isExact: pathname === url,
                params
            }
            routerProps.match = match;
            if(RouteComponent) {
                return <RouteCompoponent {...routerProps}/> } else if(render) { return render(routerProps); } else if(children) { children(routerProps); } else { return null; } } else { if(children) { return children(routerProps); } else { return null; } } } } // match 屬性的存在與否很大程度上和這個組件是不是路由渲染出來的正相關 // isExact 表示的是是否爲精確匹配,同時也是表明了pathname和url是否徹底相同 複製代碼
  • react-router-dom/RouterContext.js
// 不少人會問上面那個爲何有個文件不知道在哪,那麼接下來我就來實現一下RouterContext文件
// 這個其實賊簡單,不信你看看就知道了
import React from 'react';
export default const context = React.createContext();
// 要不要太簡單了一點
複製代碼
  • 接下來我來實現一下Link,其實細細分析一下來看,其實這個東西只不過是個標籤而已,也沒有那麼神奇。和vue的router-link有殊途同歸之妙(業界不少人都說vue臨摹借鑑的。我以爲好用就行,技術嘛相互促進。)
  • react-router-dom/Link.js
<!--這裏面我以爲仍是用函數組件來實現比較好,畢竟都已經functional programming-->
import React from 'react';
import RouterContext from 'RouterContext';
//注意千萬不要寫href而且賦值爲具體路徑,由於他會繞過路由的攔截走a標籤的屬性
export default function Link(props) {
    return (
        <RouterContext.Consumer> { routerValue => { <a {...props} onClick ={ () => { routerValue.history.push(props.to) } } > { prpos.children } </a> } } </RouterContext.Consumer> ) } 複製代碼
  • 接下來我來實現一下Switch。他的職責很簡單就是負責進行子組件的匹配,只會渲染第一個匹配上的子組件。和vuerouter-view很像。
  • react-router-dom/Switch.js
import React, {useContext} from 'react'import RouterContext from 'RouterContext';
import pathToRegexp from 'path-to-regexp'// 這裏我又引入了一個新東西useContext是的他就是傳說中的hooks之一了。
// useContext是獲取上下文對象的第三種方式
// static contextType (類中使用)
// Consumer(函數中使用)
// 還能夠ReactHooks useContext獲取上下文對像

export default function (props) {
    let routerContext = useContext(RouterContext);
    let children = props.children;
    children = Array.isArray(children) ? children : [children];
    let pathname = routerContext.location.pathname;// 從上下文中取出當前的路徑
    for(let i = 0; i < children.length; i++) {
    // 千萬記住這裏的child是React元素或者虛擬DOM並非什麼組件
    // 至關因而`React.createElement(Route,{exact,path,component})`的結果,
    // `{type:Route,props:{exact,path,component}}`
        let child = children[i];
        let { path = '/', component, exact = false} = child.props;
        <!--這裏面就用到了路由處理的正則庫不明白的能夠看看我以前的一篇介紹path-to-regexp的文章--> let regexp = pathToRegexp(path, [], {end: exact}); <!--用當前的路徑去匹配--> let matched = pathname.match(regexp); <!--匹配到了直接渲染子組件--> if(matched) { return child; } } <!--沒有匹配的子組件則return null--> return null } 複製代碼
  • 重定向組件,實現的是在經過url未匹配到對應組件時自動進行重定向操做。
  • react-router-dom/Redirect.js
import React, {useContext} from 'react';
import RouterContext from './RouterContext';
export default function (props) {
    let routerContext = useContext(RouterContext);
    if(!props.from || props.from === routerContext.location.pathname) {
        routerContext.history.push(props.to);
    }
    return null;
}
複製代碼
  • 導航條上面實現高亮的組件。
  • react-router-dom/NavLink.js
  • react-router-dom/NavLink.css
.acitve {
    color: #425658;
    background: #eeeddd;
}
複製代碼
import React from 'react';
import './NavLink.css'
import { Route, Redirect, Link } from './index';
export default function (props) {
    let { to, exact, children } = props;
    return (
        <Route path={to} exact={exacr} children={ routerProps => { <Link className={ routerProps.match? 'active' : '' to={to} }> {children} </Link> } }> </Route>
    )
}


複製代碼
  • Prompt阻止跳轉的組件,沒有DOM結構
  • react-router-dom/Prompt.js
import React, {useContext} from 'react';
import RouterContext from './RouterContext';
export default function (props) {
    let routerContext = useContext(RouterContext);
    let {bool, msg} = props;
    if(bool) {
        routerContext.history.block(msg);
    } else {
        routerContext.history.unblock()
    }
    return null
}
複製代碼
  • 寫兩個高級組件
<!--1、withContext.js-->
import React from 'react';
import { Route } './index';
export default function (OldComponent) {
    reutnr props => {
        <Route>
            render={
                routerProps =><OldComponent {...props} {...routerProps}/> } </Route>
    }
}

export default function (OldComponent) {
    return (
        <RouterContext.Consumer> { contextValue => ( <div> <OldComponent/> </div> ) } </RouterContext.Consumer> ) } 複製代碼
  • 順便把BrowserRouter也實現一下
  • react-router-dom/BrowserRouter
import React, { useState, useEffect } from 'react';
import RouterContext from "./RouterContext.js";
export default function BrowserRouter(props) {
    let [currentState, setCurrentState] = useState({ location: { pathname: window.location.pathname } });
    useEffect(() => {
        window.onpushstate = (state, pathname) => {
            setCurrentState({
                location: {
                    ...currentState.location,
                    pathname,
                    state
                }
            });
        }
        window.onpopstate = (event) => {
            setCurrentState({
                location: {
                    ...currentState.location,
                    pathname: window.location.pathname,
                    state: event.state
                }
            });
        }
    }, []);
    const globalHistory = window.history;
    let history = {
        location: currentState.location,
        push(to) {
            if (history.prompt) {
                let target = typeof to === 'string' ? { pathname: to } : to;
                let yes = window.confirm(history.prompt(target));
                if (!yes) return;
            }
            if (typeof to === 'object') {//傳的是一個對象 {pathname,state}
                let { pathname, state } = to;
                globalHistory.pushState(state, null, pathname);
            } else {//就是個字符串
                globalHistory.pushState(null, null, to);
            }
        },
        block(prompt) {
            history.prompt = prompt;
        },
        unblock() {
            history.prompt = null;
        }
    }
    let routerValue = {
        location: currentState.location,
        history
    }
    return (
        <RouterContext.Provider value={routerValue}> {props.children} </RouterContext.Provider> ) } 複製代碼
相關文章
相關標籤/搜索