手写 React-Router(Hook版)

文章目录

  • Hook 实现router
    • 组件思想
    • 手写 BrowserRouter
    • 手写 Link
    • 手写 Route

参考文档:

  • react-router
  • git react-router

Hook 实现router

组件思想

根据运行环境安装react-router-dom,它依赖react-router,安装会自动安装react-router。

react-router 中奉行一切皆组件的思想。

  • 路由,如BrowserRouter、HashRouter
  • 路由匹配,如Route、Switch
  • 路由导航,如Link、NavLink、Redirect

关于router的具体使用和API可以查看相关文档事例。

这里手写的代码都是通过hook来实现的,如果想看基于类组件的实现,可以去看官方的源码

手写 BrowserRouter

react-router的源码中,BrowserRouter的实现,是创建了一个顶层的history、location、match等参数,通过Context.provider将状态向下传递,通过中间层组件Router实现核心功能。

这里将Router的实现一并揉进了BrowserRouter中实现,简化了一些额外的处理。

BrowserRouter主要做了两件事:

  • 创建了一个顶层history,通过Context分发
  • 监听history中location的变化,进行reRender
import React, { useContext, useState, useEffect } from 'react'
import { createBrowserHistory } from 'history'

const RouterContext = React.createContext()

export function BrowserRouter({ children }) {
    // 1. 创建history,location等对象,使用Context向下传递,
    const history = createBrowserHistory()
    const [location, setLocation] = useState(history.location)

    // 2. 监听location的变化,重绘
    useEffect(() => {
        const clearListen = history.listen(location => {
            console.log('location', location)
            setLocation(location)
        })
        return () => {
            if (clearListen) {
                clearListen()
            }
        }
    }, [location])

    return (
        <RouterContext.Provider
            value={{
                history,
                location
            }}>
            {children}
        </RouterContext.Provider>
    )
}

手写 Link

通过元素检查,会发现Link最终返回的是一个a标签

源码中对ref 做了forwardRef()的处理,这里我只做了简单的功能实现

export function Link({ to, children }) {
    const { history } = useContext(RouterContext)
    const handlerClick = (e, history) => {
        // 阻止默认跳转
        e.preventDefault()
        history.push(to)
    }
    return (
        <a
            href={to}
            onClick={e => handlerClick(e, history)}>
            {children}
        </a>
    )
}

手写 Route

Route 主要做路由匹配和渲染,匹配规则是匹配URL的开头,而不是整个URL,这里做了简单处理,细节可以深入去读源码。

Route 渲染规则是children > component > render, 读源码会发现,在未匹配时,如果我们设置了函数类型的children,它是无条件渲染的。

export function Route(props) {
    const context = useContext(RouterContext)
    // 将路由信息当做参数传递给组件
    const propsMap = { ...context }
    // 1. 判断路径是否匹配 ,不匹配,直接返回null
    const isMatch = props.path === context.location.pathname

    // 2. 如果匹配,根据渲染优先级 children > component > render 进行渲染
    function matchedRenderCmp(props) {
        const { component, children, render } = props
        console.log(children)
        return children
            ? typeof children === 'function'
                ? children(propsMap)
                : children
            : component
                ? React.createElement(component, propsMap)
                : render
                    ? render(propsMap)
                    : null

    }
    // 3. 未匹配,如果props中有children并且是一个函数,则render children
    function noMatchRenderCmp(props) {
        const { children } = props
        return (children && typeof children === 'function') ? children(propsMap) : null
    }
    return isMatch ? matchedRenderCmp(props) : noMatchRenderCmp(props)
}

一个简单的路由就写好了,现在上测试代码

import React, { Component } from
// 引入自己写的ZRouterDom
import {
    BrowserRouter as Router,
    Route,
    Link
} from '../ZRouterDom'
import LoginPage from '../pages/LoginPage'
import HomePage from '../pages/HomePage'


export default class MyRouterPage extends Component {
    render() {
        return (
            <Router>
                <h1>MyRouterPage</h1>
                <nav>
                    <Link to="/login"> login </Link>
                    <Link to="/"> home </Link>
                </nav>
                <Route path="/login" 
                render={()=><div>我是render</div>}
                component={LoginPage} 
                children={<div>我是children1</div>}
                 ></Route>
                <Route path="/" component={HomePage} />                
                <Route path="/aaa" children={()=><div>我是未匹配的children</div>}></Route>                

            </Router>
        )
    }
}

我们可以进行下一步,看看import { createBrowserHistory } from 'history'做了哪些事?

你可能感兴趣的:(React高级,react,hooks)