Интенсивное чтение «React Router v6»

React.js

1. Введение

React Router v6вышла альфа, прошла на этой неделеA Sneak Peek at React Router v6В статье анализируются произошедшие изменения.

2 Обзор

Переименовать в

Безболезненное изменение, позволяющее сделать именование API более стандартным.

// v5
import { BrowserRouter, Switch, Route } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/">
          <Home />
        </Route>
        <Route path="/profile">
          <Profile />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

В React Router v6 используйте напрямуюRoutesзаменятьSwitch:

// v6
import { BrowserRouter, Routes, Route } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile/*" element={<Profile />} />
      </Routes>
    </BrowserRouter>
  );
}

Обновить

В версии v5 не интуитивно понятно передавать параметры компонентам, для прохождения нужно использовать RenderPropsrouteProps:

import Profile from './Profile';

// v5
<Route path=":userId" component={Profile} />
<Route
  path=":userId"
  render={routeProps => (
    <Profile {...routeProps} animate={true} />
  )}
/>

// v6
<Route path=":userId" element={<Profile />} />
<Route path=":userId" element={<Profile animate={true} />} />

А в версии v6,renderа такжеcomponentПлан был объединен вelementСхема, которая может легко передавать реквизит без прозрачной передачиrotePropsпараметр.

Более удобная вложенная маршрутизация

В v5 вложенные маршруты должны проходитьuseRouteMatchполучатьmatch, и пройтиmatch.pathСращивание реализует подмаршрутизацию:

// v5
import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useRouteMatch
} from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/profile" component={Profile} />
      </Switch>
    </BrowserRouter>
  );
}

function Profile() {
  let match = useRouteMatch();

  return (
    <div>
      <nav>
        <Link to={`${match.url}/me`}>My Profile</Link>
      </nav>

      <Switch>
        <Route path={`${match.path}/me`}>
          <MyProfile />
        </Route>
        <Route path={`${match.path}/:id`}>
          <OthersProfile />
        </Route>
      </Switch>
    </div>
  );
}

опущен в версии v6useRouteMatchЭтот шаг поддерживает прямое использованиеpathПредставляет относительный путь:

// v6
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";

// Approach #1
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile/*" element={<Profile />} />
      </Routes>
    </BrowserRouter>
  );
}

function Profile() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Routes>
        <Route path="me" element={<MyProfile />} />
        <Route path=":id" element={<OthersProfile />} />
      </Routes>
    </div>
  );
}

// Approach #2
// You can also define all
// <Route> in a single place
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="profile" element={<Profile />}>
          <Route path=":id" element={<MyProfile />} />
          <Route path="me" element={<OthersProfile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function Profile() {
  return (
    <div>
      <nav>
        <Link to="me">My Profile</Link>
      </nav>

      <Outlet />
    </div>
  );
}

УведомлениеOutletэто элемент, который отображает дочерний маршрут.

useNavigate заменяет useHistory

В версии v5 активная джамп-маршрутизация может проходить черезuseHistoryпровестиhistory.pushи так далее:

// v5
import { useHistory } from "react-router-dom";

function MyButton() {
  let history = useHistory();
  function handleClick() {
    history.push("/home");
  }
  return <button onClick={handleClick}>Submit</button>;
}

В версии v6 вы можете пройтиuseNavigateНепосредственно реализуйте эту общую операцию:

// v6
import { useNavigate } from "react-router-dom";

function MyButton() {
  let navigate = useNavigate();
  function handleClick() {
    navigate("/home");
  }
  return <button onClick={handleClick}>Submit</button>;
}

react-router внутренне инкапсулирует историю, если это необходимоhistory.replace, в состоянии пройти{ replace: true }Спецификация параметра:

// v5
history.push("/home");
history.replace("/home");

// v6
navigate("/home");
navigate("/home", { replace: true });

Меньший размер 8kb

Из-за почти рефакторинга кода сжатый размер v6 версии кода был уменьшен с 20kb до 8kb.

3 Интенсивное чтение

В исходном коде react-router v6 есть относительно основная концепция, автор взял ее и поделился с вами, что очень полезно для разработки некоторых фреймворков. мы видимuseRoutesЭтот код выдержки:

export function useRoutes(routes, basename = "", caseSensitive = false) {
  let {
    params: parentParams,
    pathname: parentPathname,
    route: parentRoute
  } = React.useContext(RouteContext);

  if (warnAboutMissingTrailingSplatAt) {
    // ...
  }

  basename = basename ? joinPaths([parentPathname, basename]) : parentPathname;

  let navigate = useNavigate();
  let location = useLocation();
  let matches = React.useMemo(
    () => matchRoutes(routes, location, basename, caseSensitive),
    [routes, location, basename, caseSensitive]
  );

  // ...

  // Otherwise render an element.
  let element = matches.reduceRight((outlet, { params, pathname, route }) => {
    return (
      <RouteContext.Provider
        children={route.element}
        value={{
          outlet,
          params: readOnly({ ...parentParams, ...params }),
          pathname: joinPaths([basename, pathname]),
          route
        }}
      />
    );
  }, null);

  return element;
}

Видно, что с помощьюReact.Context, версия v6 оборачивает слой при отображении каждого элемента маршрута.RouteContext.

В качестве примера возьмем более удобную вложенность маршрутов:

опущен в версии v6useRouteMatchЭтот шаг поддерживает прямое использованиеpathПредставляет относительный путь.

Это достигается с помощью этой схемы, потому что каждый уровень файлов маршрутизации обернут контекстом, поэтому каждый уровень может получить предыдущий уровень.path, поэтому при сплайсинге маршрутов его можно полностью реализовать внутри фреймворка, без необходимости пользователям предварительно сплайсировать его при вызове.

снова сuseNavigateНапример, некоторые считаютnavigateЭта инкапсуляция остается только на формальном уровне, но она также имеет инкапсуляцию в функции.Например, если она передается по относительному пути, она будет переключаться в соответствии с текущим маршрутом.СледующееuseNavigateВыдержка из кода:

export function useNavigate() {
  let { history, pending } = React.useContext(LocationContext);
  let { pathname } = React.useContext(RouteContext);

  let navigate = React.useCallback(
    (to, { replace, state } = {}) => {
      if (typeof to === "number") {
        history.go(to);
      } else {
        let relativeTo = resolveLocation(to, pathname);

        let method = !!replace || pending ? "replace" : "push";
        history[method](relativeTo, state);
      }
    },
    [history, pending, pathname]
  );

  return navigate;
}

Видно, что с помощьюRouteContextполучить токpathname, и согласноresolveLocationправильноtoа такжеpathnameвыполнить сращивание путей, в то время какpathnameчерезRouteContext.Providerкоторый предоставил.

Умелое использование многоуровневых контекстных провайдеров

Много раз мы используем Context, чтобы оставаться вProvider, несколькоuseContextНа уровне контекста это самое основное использование контекста, но я считаю, что после прочтения React Router v6 мы сможем узнать больше об использовании контекста: многоуровневый поставщик контекста.

Хотя Context Provider имеет несколько уровней и будет использовать принцип ближайшего покрытия, это не просто функция, позволяющая избежать ошибок, мы можем использовать эту функцию для достижения улучшений, таких как React Router v6.

Чтобы более подробно объяснить эту функцию, вот еще один конкретный пример: Например, при создании механизма рендеринга у каждого компонента есть идентификатор, но этот идентификатор не отображается в свойствах компонента:

const Input = () => {
  // Input 组件在画布中会自动生成一个 id,但这个 id 组件无法通过 props 拿到
};

На этом этапе, если мы разрешаем создание другого дочернего элемента внутри компонента Input и хотим, чтобы идентификатор этого дочернего элемента был получен из Input, нам может потребоваться, чтобы пользователь сделал следующее:

const Input = ({ id }) => {
  return <ComponentLoader id={id + "1"} />;
};

При этом есть две проблемы:

  1. Воздействие компонента INPUT и нарушает простой дизайн предыдущего дизайна.
  2. Компонент должен собрать идентификатор, что очень хлопотно.

Здесь возникает та же проблема, что и в React Router.Можем ли мы упростить код до следующего, но функция останется прежней?

const Input = () => {
  return <ComponentLoader id="1" />;
};

Ответ заключается в том, что это можно сделать, мы можем использовать Context для достижения этого решения. Ключевым моментом является то, что рендеринг Input, но контейнер компонента должен обернуть Provider:

const ComponentLoader = ({ id, element }) => {
  <Context.Provider value={{ id }}>{element}</Context.Provider>;
};

Затем для внутренних компонентов, вызывающих на разных уровняхuseContextПолученный идентификатор отличается, а это именно то, что нам нужно:

const ComponentLoader = ({id,element}) => {
  const { id: parentId } = useContext(Context)

  <Context.Provider value={{ id: parentId + id }}>
    {element}
  </Context.Provider>
}

по этому мыInputвнутренне называется<ComponentLoader id="1" />Фактический идентификатор, который фактически объединен,01, а это полностью брошено на внешний движок обработки слоя, пользователям не нужно прошивать вручную.

4 Резюме

После полного рефакторинга React Router v6 с помощью хуков не только значительно сократилось количество кода, но и стало проще его использовать, его можно быстро обновить при выходе официальной версии.

Кроме того, из этих оптимизаций, сделанных React Router v6, мы обнаружили более изобретательное использование контекста из исходного кода Я надеюсь, что этот метод поможет вам применить его к другим более сложным проектам.

Адрес обсуждения:Готовый "React Router V6" · Выпуск №241 · DT-Fe / Еженедельник

Если вы хотите принять участие в обсуждении, пожалуйста,кликните сюда, с новыми темами каждую неделю, выходящими по выходным или понедельникам. Интерфейс интенсивного чтения — поможет вам отфильтровать надежный контент.

Сфокусируйся наАккаунт WeChat для интенсивного чтения в интерфейсе

Заявление об авторских правах: Бесплатная перепечатка - некоммерческая - не производная - сохранить авторство (Лицензия Creative Commons 3.0)