предисловие
Внешняя маршрутизация всегда была очень классической темой, и она часто встречается в повседневном использовании или на собеседованиях. В этой статье реализована простая версияreact-router
Приходите и раскройте тайну маршрутизации вместе.
Благодаря этой статье вы можете узнать:
- Что такое сущность передней маршрутизации.
- Некоторые ямы и точки внимания в интерфейсной маршрутизации.
- Разница между маршрутизацией хэшей и маршрутизацией истории.
Измените и следите за URL, чтобы позволить NOM-узлу отобразить соответствующий вид.
Это все. Новичков не следует пугать концепцией маршрутизации.
разница в маршрутизации
Вообще говоря, на стороне браузера есть два типа маршрутов:
- Хэш-маршрутизация, характеризуемая URL-адресом, за которым следует
#
номер, такой какbaidu.com/#foo/bar/baz
. - Нет никакой разницы между историческим маршрутом, 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 мы специально извлекаем небольшой файл, чтобы инкапсулировать его и предоставлять извне:
-
history.push
. -
history.listen
.
Эти два API снижают умственную нагрузку пользователей.
Мы используем观察者模式
Он заключает в себе простойlisten
API, чтобы пользователи могли слушать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);
});
location
state
,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».