Углубленное изучение интерфейсной маршрутизации, рукописный реактивный мини-маршрутизатор.

TypeScript React.js
Углубленное изучение интерфейсной маршрутизации, рукописный реактивный мини-маршрутизатор.

предисловие

Внешняя маршрутизация всегда была очень классической темой, и она часто встречается в повседневном использовании или на собеседованиях. В этой статье реализована простая версияreact-routerПриходите и раскройте тайну маршрутизации вместе.

Благодаря этой статье вы можете узнать:

  • Что такое сущность передней маршрутизации.
  • Некоторые ямы и точки внимания в интерфейсной маршрутизации.
  • Разница между маршрутизацией хэшей и маршрутизацией истории.

Измените и следите за URL, чтобы позволить NOM-узлу отобразить соответствующий вид.

Это все. Новичков не следует пугать концепцией маршрутизации.

разница в маршрутизации

Вообще говоря, на стороне браузера есть два типа маршрутов:

  1. Хэш-маршрутизация, характеризуемая URL-адресом, за которым следует#номер, такой какbaidu.com/#foo/bar/baz.
  2. Нет никакой разницы между историческим маршрутом, URL-адресом и обычным маршрутом. Такие какbaidu.com/foo/bar/baz.

Мы уже говорили о сути маршрутизации, так что по сути нам нужно только разобраться, как устроены два маршрута.ИзменятьИ как компонентыСлушайте и полный вид на шоу, все раскрывается.

Чтобы не продаваться, давайте поговорим о том, какой API используют два маршрута для обеспечения внешней маршрутизации:

hash

пройти черезlocation.hash = 'foo'такой синтаксисИзменять, путь будетbaidu.comИзменятьbaidu.com/#foo.

пройти черезwindow.addEventListener('hashchange')Это событие можетмониторприбытьhash

history

history.pushStateСинтаксис APIИзменятьЕго синтаксис на первый взгляд странный, загляните в документ mdn в его определении:

history.pushState(state, title[, url])

вstateОт имени состояния объекта, что позволяет нам дать каждой записи маршрута создать свое собственное состояние, и оно будет после сериализации храниться на диске пользователя, после перезапуска браузера, чтобы пользователи могли его восстановить.

titleВ настоящее время бесполезен.

urlВместо этого наиболее важным параметром URL в маршрутизации является необязательный параметр, который размещается последним.

пройти черезhistory.pushState({}, '', 'foo'), может позволитьbaidu.comизменить наbaidu.com/foo.

Почему страница браузера не перезагружается после обновления пути?

Здесь нам нужно подумать о проблеме, обычноlocation.href = 'baidu.com/foo'Прыжок таким образом заставит браузер перезагрузить страницу и запросить сервер, ноhistory.pushStateМагия заключается в том, что он позволяет изменять URL-адрес, но без перезагрузки страницы пользователь сам решает, что делать с изменением URL-адреса.

Следовательно, передняя маршрутизация таким образом должна поддерживатьсяhistroyПрежде чем вы сможете использовать браузер API.

Почему он получает 404 после обновления?

По сути, потому что будущее обновляется с помощьюbaidu.com/fooЗапрос страницы к ресурсам сервера, но сервер не выполнил ни одного из путей процесса сопоставления, конечно, вернет 404, подход заключается в том, чтобы позволить серверу для страницы «не знаю», вернутьсяindex.html, так что это включает в себя интерфейсную маршрутизацию, связанную сjsДомашняя страница кода загрузит вашу таблицу конфигурации внешней маршрутизации, и хотя файл, предоставленный вам сервером, является файлом домашней страницы, ваш URL-адресbaidu.com/foo, интерфейсный маршрут будет загружен/fooПредставление, соответствующее этому пути, отлично решает проблему 404.

historyМаршрутизациямониторЭто также немного без косточек, браузер обеспечиваетwindow.addEventListener('popstate')события, но он может только прослушивать изменения маршрутизации, генерируемые браузером назад и вперед, для активныхpushStateНо не могу контролировать. Решение, конечно, следующая реализацияreact-routerЯ расскажу об этом позже~

Внедрить реактивный мини-маршрутизатор

реализовано в данной работеreact-routerна основеhistoryверсии основная функция маршрутизации восстанавливается с минимальным кодом, поэтому не будет функций более высокого порядка, таких как регулярное сопоставление или вложенная подмаршрутизация, возврат к ядру и реализация максимально упрощенной версии от нуля до единицы.

реализовать историю

дляhistoryДля сложного в использовании официального API мы специально извлекаем небольшой файл, чтобы инкапсулировать его и предоставлять извне:

  1. history.push.
  2. history.listen.

Эти два API снижают умственную нагрузку пользователей.

Мы используем观察者模式Он заключает в себе простойlistenAPI, чтобы пользователи могли слушатьhistory.pushРезультирующий путь меняется.

// 存储 history.listen 的回调函数
let listeners: Listener[] = [];
function listen(fn: Listener) {
  listeners.push(fn);
  return function() {
    listeners = listeners.filter(listener => listener !== fn);
  };
}

Таким образом, снаружи может пройти:

history.listen(location => {
  console.log('changed', location);
});

locationstate,pathname,searchи другую ключевую информацию.

Реализовать основной метод изменения путиpushТоже очень просто:

function push(to: string, state?: State) {
  // 解析用户传入的 url
  // 分解成 pathname、search 等信息
  location = getNextLocation(to, state);
  // 调用原生 history 的方法改变路由
  window.history.pushState(state, '', to);
  // 执行用户传入的监听函数
  listeners.forEach(fn => fn(location));
}

существуетhistory.push('foo')по сути звонитwindow.history.pushStateизменить путь и уведомитьlistenСмонтированная функция обратного вызова выполняется.

Конечно, не забудьте перенаправить поведение кнопки возврата пользователя в браузере, также необходимо использоватьpopstateСобытие для прослушивания и выполнения того же процесса:

// 用于处理浏览器前进后退操作
window.addEventListener('popstate', () => {
  location = getLocation();
  listeners.forEach(fn => fn(location));
});

Далее нам нужно реализоватьRouterа такжеRouteкомпонентов, и вы увидите, как они выглядят с помощью этого простогоhistoryБиблиотеки используются в комбинации.

Реализовать маршрутизатор

Основной принцип через маршрутизаторProviderПучокlocationа такжеhistoryПодождите, пока информация о ключе маршрутизации будет передана подкомпонентам, и сообщите подкомпонентам об изменениях в маршрутизации:

import React, { useState, useEffect, ReactNode } from 'react';
import { history, Location } from './history';
interface RouterContextProps {
  history: typeof history;
  location: Location;
}

export const RouterContext = React.createContext<RouterContextProps | null>(
  null,
);

export const Router: React.FC = ({ children }) => {
  const [location, setLocation] = useState(history.location);
  // 初始化的时候 订阅 history 的变化
  // 一旦路由发生改变 就会通知使用了 useContext(RouterContext) 的子组件去重新渲染
  useEffect(() => {
    const unlisten = history.listen(location => {
      setLocation(location);
    });
    return unlisten;
  }, []);

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

Обратите внимание на закомментированную часть, мы используем ее при инициализации компонентаhistory.listenСлушая изменение маршрута, как только маршрут изменится, он позвонитsetLocationПерейти к обновлениюlocationИ поProviderпередается дочерним компонентам.

И этот шаг вызоветProviderизvalueизменения значения, уведомить всех пользователей сuseContextподписалсяhistoryа такжеlocationПодкомпоненты для повторногоrender.

Реализовать маршрут

RouteКомпонент принимаетpathа такжеchildrenдваprop, который по сути определяет, какие компоненты нужно рендерить по определенному пути, и мы можем передатьRouterизProviderпереданlocationИнформация получает текущий путь, поэтому все, что нужно сделать этому компоненту, — это оценить, соответствует ли текущий путь, и отобразить соответствующий компонент.

import { ReactNode } from 'react';
import { useLocation } from './hooks';

interface RouteProps {
  path: string;
  children: ReactNode;
}

export const Route = ({ path, children }: RouteProps) => {
  const { pathname } = useLocation();
  const matched = path === pathname;

  if (matched) {
    return children;
  }
  return null;
};

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

Реализовать useLocation, useHistory

Здесь все очень просто, используйтеuseContextПросто упакуйте один слой и получите егоRouterпереданhistoryа такжеlocationВот и все.

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

export const useHistory = () => {
  return useContext(RouterContext)!.history;
};

export const useLocation = () => {
  return useContext(RouterContext)!.location;
};

Достичь демонстрации сертификации

import React, { useEffect } from 'react';
import { Router, Route, useHistory } from 'react-mini-router';

const Foo = () => 'foo';
const Bar = () => 'bar';

const Links = () => {
  const history = useHistory();

  const go = (path: string) => {
    const state = { name: path };
    history.push(path, state);
  };

  return (
    <div className="demo">
      <button onClick={() => go('foo')}>foo</button>
      <button onClick={() => go('bar')}>bar</button>
    </div>
  );
};

export default () => {
  return (
    <div>
      <Router>
        <Links />
        <Route path="foo">
          <Foo />
        </Route>
        <Route path="bar">
          <Bar />
        </Route>
      </Router>
    </div>
  );
};

Эпилог

vue-routerЭто аналогичный принцип.

Если вы хотите узнать больше о полном исходном коде этой статьи, вы можете следовать официальному аккаунту «Front-End от Advanced в больницу», ответить на «Маршрут», вы можете получить полный исходный код, обратите внимание на первый ИНФОРМАЦИЯХ ДИСКУССТВЕННОЙ ДОСТУПКИ, И вы также можете получить «Расширенное расширенное руководство по внешнему интерфейсу» и «Algorith Algorithm Allgorithm NoL Basic».