react深入3 - react-router

2022-02-12  本文已影响0人  申_9a33

1. react-router 使用

1.1 安装 npm i react-router-dom

1.2 基本使用

// src\main.tsx
import React from 'react';
import { render } from 'react-dom';
import {
  BrowserRouter, Routes, Route, Link,
} from 'react-router-dom';
import HomePage from './view/Home';
import Login from './view/Login';
import Product from './view/Product';
import Details from './view/Details';
import Error from './view/Error';

function App() {
  return (
    <div>
      <BrowserRouter>
        <div>
          <Link to="/"> home </Link>
          |
          <Link to="/login"> login </Link>
          |
          <Link to="/product"> product </Link>
          |
          <Link to="/product/123"> details </Link>
          |
        </div>

        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/login" element={<Login />} />
          <Route path="/product" element={<Product />}>
            <Route path="/product/:id" element={<Details />} />
          </Route>
          <Route path="*" element={<Error />} />
        </Routes>

      </BrowserRouter>
    </div>
  );
}

render(<App />, document.getElementById('root'));

1.3 history 模式webpack 需要开启 historyApiFallback: true

1.4 嵌套路由 使用 Outlet

// src\view\Product.tsx

import React from 'react';
import { Outlet } from 'react-router-dom';

export default function Product() {
  return (
    <div>
      product
      <Outlet />
    </div>
  );
}

2.react-route实现(以history模式来实现)

2.1 BrowserRouter 组件

import * as React from 'react';
import { createBrowserHistory, BrowserHistory } from 'history';
import Router from './Router';

export interface BrowserRouterProps {
  children?: React.ReactNode;
  window?: Window;
}
export default function BrowserRouter({
  children,
  window,
}:BrowserRouterProps) {
  const historyRef = React.useRef<BrowserHistory>();
  if (!historyRef.current) {
    historyRef.current = createBrowserHistory({ window });
  }

  const history = historyRef.current;
  const [state, setState] = React.useState({
    action: history.action,
    location: history.location,
  });

  React.useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      location={state.location}
      navigationType={state.action}
      navigator={history}
    >
      {children}
    </Router>
  );
}

2.2 Router 组件

export default function Router({
  children = null,
  location: locationProp,
  navigationType = NavigationType.Pop,
  navigator,
}: IRouterProps):React.ReactElement | null {
  const navigationContext = React.useMemo(
    () => ({ navigator }),
    [navigator],
  );

  if (typeof locationProp === 'string') {
    // eslint-disable-next-line no-param-reassign
    locationProp = parsePath(locationProp);
  }

  const {
    pathname = '/',
    search = '',
    hash = '',
    state = '',
    key = 'default',
  } = locationProp;
  const location = React.useMemo(() => ({
    pathname,
    search,
    hash,
    state,
    key,
  }), [hash, key, pathname, search, state]);

  return (
    <NavigationContext.Provider value={navigationContext}>
      <LocationContext.Provider
        value={{ location, navigationType }}
      >
        {children}
      </LocationContext.Provider>
    </NavigationContext.Provider>
  );
}

2.3 Routes 组件

2.3.1 根据 children 里面的Route 得到路由配置,内部包含路由地址和React组件,类似于

[
  {
    path:'/product',
    element=ReactComponents,
    children:[
      {
          path:'/product/:id',
          element=ReactComponents,
      }
      // ...
    ]
  }

  // ... 
]
export function createRoutesFromChildren(
  children:React.ReactNode,
):RouteObject[] {
  const routes:RouteObject[] = [];

  React.Children.forEach(children, (element) => {
    if (!React.isValidElement(element)) {
      return;
    }

    if (element.type === React.Fragment) {
      routes.push(
        ...createRoutesFromChildren(element.props.children),
      );
      return;
    }

    const route:RouteObject = {
      element: element.props.element,
      path: element.props.path,
    };

    if (element.props.children) {
      route.children = createRoutesFromChildren(element.props.children);
    }

    routes.push(route);
  });

  return routes;
}

2.3.2 根据路由配置和当前路由,匹配出需要渲染的组件,得到类似于

[
  {
    params:{},
    pathname:'/product',
    route:{
          path:'/product',
          element=ReactComponents,
    }
  }
  //...
]

2.3.3 renderMatches 渲染匹配出来的组件

<Context.Provider value={
  <Context.Provider value={null}>
    {childComponent}
  </Context.Provider>
}>
    {parentComponent}
</Context.Provider>
export function renderMatches(
  matches:RouteMatch[] | null,
  parentMatches:RouteMatch[] = [],
):React.ReactElement | null{
  if (matches === null) return null;

  return matches.reduceRight((outlet, match, index) => (
    <RouteContext.Provider
      value={{
        outlet,
        matches: parentMatches.concat(matches.slice(0, index + 1)),
      }}
    >
      {
           match.route.element !== undefined ? match.route.element : <Outlet />
      }
    </RouteContext.Provider>
  ), null as React.ReactElement | null);
}

2.4 Route 组件

export default function Route(
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  _props: PathRouteProps | LayoutRouteProps | IndexRouteProps,
): React.ReactElement | null {
  throw new Error('A <Route> is only ever to be used as the child of <Routes> element, '
  + 'never rendered directly. Please wrap your <Route> in a <Routes>.');
}

2.5 Outlet 组件

import * as React from 'react';
import { OutletContext, RouteContext } from './context';

export function useOutlet(context?: unknown): React.ReactElement | null {
  const { outlet } = React.useContext(RouteContext);
  if (outlet) {
    return (
      <OutletContext.Provider value={context}>{outlet}</OutletContext.Provider>
    );
  }
  return outlet;
}

export interface OutletProps {
  context?: unknown;
}
export default function Outlet(props: OutletProps): React.ReactElement | null {
  return useOutlet(props.context);
}

2.7 Link组件

import React, { MouseEvent, useContext } from 'react';
import { NavigationContext } from './context';

export default function Link({ to, children }:any) {
  const navigation = useContext(NavigationContext);

  const handle = (e:MouseEvent<HTMLAnchorElement>) => {
    e.preventDefault();

    navigation.navigator.push(to);
  };
  return <a href={to} onClick={handle}>{children}</a>;
}

源码

上一篇下一篇

猜你喜欢

热点阅读