likes
comments
collection

React Router v6 官方文档翻译 (七) ---- 内置API

作者站长头像
站长
· 阅读数 12

专栏说明:对于react-router v6版本,有同事反映缺少相对完整的中文文档,查看不方便。为了便于使用,作者对官网文档进行针对性翻译,便于v6版本更广泛的被使用。

createRoutesFromChildren

类型定义:

declare function createRoutesFromChildren(
  children: React.ReactNode
): RouteObject[];

// 返回值
interface RouteObject {
  caseSensitive?: boolean;
  children?: RouteObject[];
  element?: React.ReactNode;
  index?: boolean;
  path?: string;
}

createRoutesFromChildren 通常在 <Route> 子元素中使用,用于生成子 route 的配置项。可以把 <Route> 类型的 react element 对象,变成了普通的 route 对象结构。其内部通过 React.Children.forEach 把 Route 组件给结构化,并且内部调用递归,深度递归 children 结构。

示例:

输入

<Routes> 
    <Route element={<Home />} path="/home" /> 
    <Route element={<List/>} path="/list" /> 
    <Route element={<Layout/>} path="/children" > 
        <Route element={<Child1/>} path="/children/child1" /> 
        <Route element={<Child2/>} path="/children/child2" /> 
    </Route> 
</Routes>

输出

React Router v6 官方文档翻译 (七) ---- 内置API

createSearchParams

类型定义:

declare function createSearchParams(
  init?: URLSearchParamsInit
): URLSearchParams;

createSearchParams 通常与 useSearchParams 配对使用,用于 search 传参时构建参数。

示例:

// 传参方式 1
navigate({
    pathname: "/class",
    search: `?id=${id}&grade=${grade}`
})

// 传参方式 2
const params = { id: '1', grade: '2' };
navigate({
    pathname: "/class",
    search: `?${createSearchParams(params)}`
})

generatePath

类型定义:

declare function generatePath(
  path: string,
  params?: Params
): string;

动态生成可跳转的 path, 可自动匹配并填充动态参数。

示例:

generatePath("/users/:id", { id: "42" }); // "/users/42"
generatePath("/files/:type/*", {
  type: "img",
  "*": "cat.jpg",
}); // "/files/img/cat.jpg"

Location

React Router 中 "location" 的接口定义。

matchPath

类型定义:

declare function matchPath<
  ParamKey extends string = string
>(
  pattern: PathPattern | string,
  pathname: string
): PathMatch<ParamKey> | null;

interface PathMatch<ParamKey extends string = string> {
  params: Params<ParamKey>;
  pathname: string;
  pattern: PathPattern;
}

interface PathPattern {
  path: string;
  caseSensitive?: boolean;
  end?: boolean;
}

比较常用的函数。是hook [useMatch](https://reactrouter.com/en/main/hooks/use-match) 的补充,用于非函数式组件中。

示例:

matchPath('/children/child1/:id', '/children/child1/1') 

React Router v6 官方文档翻译 (七) ---- 内置API

个人觉得没啥用的方法,你都知道传入 pathname 了,还匹配个der。

matchRoutes

类型定义:

declare function matchRoutes(
  routes: RouteObject[],
  location: Partial<Location> | string,
  basename?: string
): RouteMatch[] | null;

interface RouteMatch<ParamKey extends string = string> {
  params: Params<ParamKey>;
  pathname: string;
  route: RouteObject;
}

这个是v6版匹配路由的核心算法,官方暴露出来了,供开发者自定义使用。

示例:

const routes = [
  {
    path: '/',
    component: <Home />
  },
  {
    path: '/list',
    component: <List />
  },
  {
    path: '/children',
    component: <Layout />,
    children: [
      {
        path: 'child1',
        component: <Child1 />
      },
      {
        path: 'child2/:id',
        component: <Child2 />
      },
    ]
  }
]
...

<BrowserRouter>
  <Routes>
      {routes.map((route, index) => <Route key={index} element={route.component} path={route.path} >
        {route.children && <>
          {route.children.map((cRoute, cIndex) => <Route key={cIndex} element={cRoute.component} path={cRoute.path} />)}
        </>}
      </Route> )}
  </Routes>
</BrowserRouter>

matchRoutes(routes, '/children/child1')

React Router v6 官方文档翻译 (七) ---- 内置API

可以看到, matchRoutes 将所有的匹配路径上的路由都按照顺序返回啦。可以通过 route.path 来获取原始路由表中的 path。

renderMatches

类型定义:

declare function renderMatches(
  matches: RouteMatch[] | null
): React.ReactElement | null;

它的作用是把 matchRoutes 匹配到的 component 渲染出来。说的容易,网上的使用案例少之又少,官方文档又太过敷衍,只能去读源码了:

// react-router/packages/lib/hooks.tsx
export function _renderMatches(
  matches: RouteMatch[] | null,
  parentMatches: RouteMatch[] = []
): React.ReactElement | null {
  if (matches == null) return null;

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

可以看到,它是将匹配到的路由数组从右向左摘取,统一放到一个 context 里了。而且,从源码里看到,useRoutes 的返回值就是一个 _renderMatches,我们可以参照源码的调用方式进行使用。

考虑路由:

const routes = [
    {
      path: '/',
      component: <Home />
    },
    {
      path: '/list',
      component: <List />
    },
    {
      path: '/children',
      component: <Layout />,
      children: [
        {
          path: 'child1',
          component: <Child1 />
        },
        {
          path: 'child2/:id',
          component: <Child2 />
        },
      ]
    },
  ]

使用方式:

renderMatches(matchRoutes(routes, '/children/child2/1'))

matchRoutes 返回的值是:

React Router v6 官方文档翻译 (七) ---- 内置API

与 _renderMatches 接受的参数一致。

在 matches.reduceRight 中,会将 match.route.element 通过 context 的 children 的形式挂载。不知道是不是开发者的笔误,children 里使用了 match.route.element,而上图中匹配的数组明明是 route.component,导致直接使用渲染不出来任何数据,我改动一下这个函数就有数据了:

const Context = createContext({});

const matches = matchRoutes(routes, '/children/child2/1');

return matches && matches.reduceRight((outlet, match, index) => {
    return (
      <Context.Provider
        children={
          match.route.component !== undefined ? match.route.component : outlet
        }
        value={{
          outlet,
        }}
      />
    );
}, null as React.ReactElement | null);

但是他传的这个 outlet 参数,并不能被自动识别为 <Outlet /> 标签,所以只能渲染出来 Layout, 子组件仍然渲染不出来。

译者自己写了个Demo,来复现一下他这个算法:

// 主
<Context.Provider
  value={{
    outlet: <Context.Provider>
      <Child2 />
    </Context.Provider>
  }}
>
  <Layout />
</Context.Provider>

// Layout
function Layout () {
    return <Context.Consumer>
      {values => {
        return <>
           'Layout
           {values.outlet ? values.outlet : <Outlet />}
        </>
      }}
    </Context.Consumer>
}

可以正常渲染:

React Router v6 官方文档翻译 (七) ---- 内置API

总结:renderMatches 不要用,坑人。嵌套 Outlet 的思想很好,但终究是给内部使用的内部 API,业务上不要用。

resolvePath

类型定义:

declare function resolvePath(
  to: To,
  fromPathname?: string
): Path;

type To = string | Partial<Path>;

interface Path {
  pathname: string;
  search: string;
  hash: string;
}

通过给定的相对路由 to 来获取一个能够 navigate 的绝对路径 path。是hook useResolvedPath 的实现方法。

resolvePath('children/child2/:id') // /children/child2/:id

完!