mini-react-router 实现核心路由跳转功能

1. 实现路由跳转

实现了与react-router-dom5.x版本相同的用法,包括BrowserRouterLinkSwitchRoute等组件,基本用法如下代码所示:

<Router>
    <div>
        <Link to="/a">组件A</Link>
        <Link to="/b">组件B</Link>
        <Link to="/a/c">组件C</Link>
    </div>
    <Switch>
        <Route path="/a" component={A} />
        <Route path="/b" component={B} />
        <Route path="/a/c" component={C} />
    </Switch>
</Router>
复制代码

2. BrowserRouter

BrowserRouter组件需要将全部的组件包起来,利用context提供history对象和location对象。其中,history对象由createBrowserHistory()提供,location对象初始值是window.location

history.listen()用于监听location的变化,当其发生变化时,更新state中的location

import { createContext, useState, useEffect } from 'react';
import { createBrowserHistory } from 'history';

const history = createBrowserHistory();
export const RouterContext = createContext();

const BrowserRouter = ({ children }) => {
    const [location, setLocation] = useState(window.location);
    useEffect(() => {
        // 监听路由变化
        const unlisten = history.listen(({ location: loc }) => {
            setLocation(loc);
        });
        return () => {
            unlisten && unlisten();
        };
    }, []);
    return (
        <RouterContext.Provider value={{ history, location }}>{children}</RouterContext.Provider>
    );
};

export default BrowserRouter;
复制代码

3. Link

Link组件里实际上是a链接。但点击路由时,实际上是不发生跳转的,所以在onClick事件里阻止了a链接跳转的默认行为。并且利用history.push()将当前的location推入history栈中,触发BrowserRouter中的监听器,更新state中的location。此时window.location也将更新,url会发生变化。

import { useContext } from 'react';
import { RouterContext } from './BrowserRouter';

const style = {
    border: '2px solid blue',
    padding: '10px',
    margin: '20px',
    textDecoration: 'none',
};

const Link = ({ to, children }) => {
    const { history } = useContext(RouterContext);
    return (
        <a
            href={to}
            style={style}
            onClick={e => {
                e.preventDefault();
                history.push(to);
            }}
        >
            {children}
        </a>
    );
};

export default Link;
复制代码

4. Switch

Switch组件将一系列Route组件包起来。若包含多个Route组件,则children是数组;若只有一个Routechildren是单个元素,故进行判断,确保转化为数组。

statelocation.pathname与包含的Route组件path进行对比,只渲染匹配上的Route

import { useContext } from 'react';
import { RouterContext } from './BrowserRouter';

const Switch = ({ children }) => {
    const { location } = useContext(RouterContext);
    const routes = Array.isArray(children) ? children : [children];
    return (
        <>
            {routes.map(child => {
                const {
                    props: { path },
                } = child;
                if (location.pathname === path) return child;
                return null;
            })}
        </>
    );
};

export default Switch;
复制代码

5. Route

statelocation.pathname与当前Routepath进行对比,若匹配上,则渲染传入的组件。

import { createElement, useContext } from 'react';
import { RouterContext } from './BrowserRouter';

const Route = ({ path, component }) => {
    const { location } = useContext(RouterContext);
    return <>{location.pathname === path ? createElement(component) : null}</>;
};

export default Route;
复制代码

6. 源码

「GitHub」


以上是本人学习所得之拙见,若有不妥,欢迎指出交流!

猜你喜欢

转载自juejin.im/post/7050443954155683870