предисловие
В предыдущей статье одноклассник упомянул аутентификацию маршрутизации.Из-за связи времени не писалось.В данной статье речь пойдет об этой фиче.vue
иreact
Чтобы сделать специальное объяснение, я надеюсь, что студенты получат много пользы после прочтения, и это будет полезно для вашего проекта.Эта статья опирается на многих старших братьев.Статья относительно длинная.
задний план
В отдельном проекте предполагается увидеть, есть ли у человека разрешение на вход на текущую страницу в соответствии с человеком, вошедшим в систему. Хотя у сервера есть разрешение на выполнение интерфейса, слишком расточительно запрашивать этот интерфейс каждый раз при загрузке маршрута. Иногда полномочия входа проверяются SESSIONID.
на официальном стартеreact
Перед аутентификацией маршрутизации давайте посмотрим, как работает аутентификация маршрутизации vue:
1. Аутентификация Vue перед каждой маршрутизацией
Как правило, мы соответствующим образом настроим меню роли таблицы маршрутизации на бэкэнде.Когда пользователь напрямую обращается к URL-адресу в неавторизованном диапазоне из адресной строки, не проходя через меню страницы, пользователь будет заблокирован и перенаправлен на домашнюю страницу. .
vue
На начальном этапе соответствующая таблица маршрутизации может быть загружена в соответствии с разрешениями посредством динамической маршрутизации.AddRouter
, но из-за пересечения разрешений таблицу маршрутизации разрешений нужно оценивать и объединять, что довольно хлопотно думать, поэтомуbeforeEach
Об этом напрямую судят по способу нединамической маршрутизации
При использовании Vue фреймворк предоставляет функцию защиты маршрутизации, которая используется для выполнения некоторых действий по проверке перед входом на определенный маршрут.Если проверка не удалась, она перейдет на страницу 404 или страницу входа, например, в Vue.
beforeEnter
функция:
...
router.beforeEach(async(to, from, next) => {
const toPath = to.path;
const fromPath = from.path;
})
...
1. Обзор маршрутизации
// index.js
import Vue from 'vue'
import Router from 'vue-router'
import LabelMarket from './modules/label-market'
import PersonalCenter from './modules/personal-center'
import SystemSetting from './modules/system-setting'
import API from '@/utils/api'
Vue.use(Router)
const routes = [
{
path: '/label',
component: () => import(/* webpackChunkName: "index" */ '@/views/index.vue'),
redirect: { name: 'LabelMarket' },
children: [
{ // 基础公共页面
path: 'label-market',
name: 'LabelMarket',
component: () => import(/* webpackChunkName: "label-market" */ '@/components/page-layout/OneColLayout.vue'),
redirect: { name: 'LabelMarketIndex' },
children: LabelMarket
},
{ // 个人中心
path: 'personal-center',
name: 'PersonalCenter',
redirect: '/label/personal-center/my-apply',
component: () => import(/* webpackChunkName: "personal-center" */ '@/components/page-layout/TwoColLayout.vue'),
children: PersonalCenter
},
{ // 系统设置
path: 'system-setting',
name: 'SystemSetting',
redirect: '/label/system-setting/theme',
component: () => import(/* webpackChunkName: "system-setting" */ '@/components/page-layout/TwoColLayout.vue'),
children: SystemSetting
}]
},
{
path: '*',
redirect: '/label'
}
]
const router = new Router({ mode: 'history', routes })
// personal-center.js
export default [
...
{ // 我的审批
path: 'my-approve',
name: 'PersonalCenterMyApprove',
component: () => import(/* webpackChunkName: "personal-center" */ '@/views/personal-center/index.vue'),
children: [
{ // 数据服务审批
path: 'api',
name: 'PersonalCenterMyApproveApi',
meta: {
requireAuth: true,
authRole: 'dataServiceAdmin'
},
component: () => import(/* webpackChunkName: "personal-center" */ '@/views/personal-center/api-approve/index.vue')
},
...
]
}
]
export default [
...
{ // 数据服务设置
path: 'api',
name: 'SystemSettingApi',
meta: {
requireAuth: true,
authRole: 'dataServiceAdmin'
},
component: () => import(/* webpackChunkName: "system-setting" */ '@/views/system-setting/api/index.vue')
},
{ // 主题设置
path: 'theme',
name: 'SystemSettingTheme',
meta: {
requireAuth: true,
authRole: 'topicAdmin'
},
component: () => import(/* webpackChunkName: "system-setting" */ '@/views/system-setting/theme/index.vue')
},
...
]
2. Суждение об аутентификации
Информация для входа пользователя запрашивает внутренний интерфейс, возвращает общедоступную информацию, такую как меню, разрешения, информацию об авторских правах, и сохраняет ее в vuex. Здесь используются следующие поля разрешений:
_userInfo: {
admin:false, // 是否超级管理员
dataServiceAdmin:true, // 是否数据服务管理员
topicAdmin:false // 是否主题管理员
}
- Определить, требует ли текущий маршрут аутентификацию (является ли значение requireAuth истинным в метаполе в маршрутизаторе), и позволить общедоступной странице пройти напрямую;
- Судя по тому, что роль суперадминистратора, пусть идет прямо;
- (Особая логика этой системы) Судя по тому, что путь перехода является настройкой темы, но роль не является администратором темы, продолжайте судить о том, является ли роль администратором службы данных, перейдите на страницу настройки службы данных или перенаправьте («системные настройки 'menu'/label/system-setting' перенаправляет на '/label/system-setting/theme' по умолчанию, а другие меню, перенаправляемые по умолчанию, являются основными общедоступными страницами, поэтому здесь необходимо аутентифицировать перенаправление. настройки не подлежат администраторам темы Должен быть администратором службы данных, поэтому может это сделать);
- Определите, соблюдаются ли разрешения требований маршрутизации, и, если нет, перенаправьте напрямую.
// index.js
router.beforeEach(async (to, from, next) => {
try {
// get user login info
const _userInfo = await API.get('/common/query/menu', {}, false)
router.app.$store.dispatch('setLoginUser', _userInfo)
if (_userInfo && Object.keys(_userInfo).length > 0 &&
to.matched.some(record => record.meta.requireAuth)) {
if (_userInfo.admin) { // super admin can pass
next()
} else if (to.fullPath === '/label/system-setting/theme' &&
!_userInfo.topicAdmin) {
if (_userInfo.dataServiceAdmin) {
next({ path: '/label/system-setting/api' })
} else {
next({ path: '/label' })
}
} else if (!(_userInfo[to.meta.authRole])) {
next({ path: '/label' })
}
}
} catch (e) {
router.app.$message.error('获取用户登陆信息失败!')
}
next()
})
2. Введение
1. Введение в маршрутизацию
Что делает маршрутизация?
Отображение разного контента или страниц в соответствии с разными URL-адресами.
Самая большая особенность одностраничных приложений заключается в том, что существует только одна веб-страница. Поэтому все переходы по страницам нужно реализовывать через javascript. Когда необходимо отображать разные страницы в соответствии с операциями пользователя, нам нужно использовать js для управления отображаемым содержимым страницы в соответствии с путем доступа.
2. Введение в React-маршрутизатор
React Router — это решение для маршрутизации, разработанное специально для React. Он использует API истории HTML5 для управления историей сеансов браузера.
3. Используйте
React Router разделен на четыре пакета: react-router, react-router-dom, react-router-native и react-router-config. react-router предоставляет основные компоненты и функции маршрутизации. react-router-config используется для настройки статических маршрутов (все еще в разработке), а два других предоставляют определенные компоненты, необходимые для запуска среды (браузер и react-native).
Чтобы создать веб-сайт (который будет работать в среде браузера), мы должны установить react-router-dom. Поскольку react-router-dom уже предоставляет объекты и методы, представленные в react-router, вам нужно только установить и ссылаться на react-router-dom.
4. Связанные компоненты
4-1.
API истории HTML5 (pushState, replaceState и событие popstate) используется для обеспечения соответствия информации в адресной строке интерфейсу.
Основные атрибуты:
basename: установить корневой путь
getUserConfirmation: функция для получения подтверждения пользователя
forceRefresh: обновить ли всю страницу
keyLength: длина location.key
дети: дочерний узел (одиночный)
4-2.
Компоненты, разработанные для старых браузеров, обычно легко используют BrowserRouter.
4-3.
Обеспечивает декларативную и доступную навигацию по проектам.
Основные атрибуты:
to: может быть строкой, представляющей целевой путь, или объектом, содержащим четыре свойства:
pathname: указывает целевой путь, на который указывает
поиск: переданный параметр поиска
hash: хеш-значение пути
состояние: состояние адреса
replace: заменить ли весь стек истории
innerRef: обращается к базовой ссылке виджета
Также поддерживает все атрибуты тегов, такие как className, title и т. д.
4-4,
Самый важный компонент в React-маршрутизаторе, основная обязанность которого заключается в отображении указанного компонента в соответствии с соответствующим путем.
Основные атрибуты:
путь: путь для соответствия
компонент: компонент, который необходимо визуализировать
render: функция, которая визуализирует компонент
Children : функция, которая отображает компонент, обычно используемый в «пустом» состоянии, которое представлено, когда путь не может быть сопоставлен, так называемое состояние отображения по умолчанию.
4-5,
компонент перенаправления
Основные атрибуты: to: путь, на который нужно указать
Вложенные компоненты: отображать только первый дочерний элемент соответствующего пути или
3. Аутентификация маршрутизации react-router-config
введение
В предыдущих версиях React Router также предоставлял аналогичный
onEnter
hook, но в React Router 4.0 этот метод устарел. В React Router 4.0 используются декларативные компоненты, а роутинг — это компонент, и чтобы реализовать функцию роутинг-гарда, мы должны написать ее сами.
1. react-router-config — это небольшой помощник, который помогает нам настроить статические маршруты. Его исходный код представляет собой функцию более высокого порядка, которая использует функцию карты для создания статических маршрутов.
import React from "react";
import Switch from "react-router/Switch";
import Route from "react-router/Route";
const renderRoutes = (routes, extraProps = {}, switchProps = {}) =>
routes ? (
<Switch {...switchProps}>
{routes.map((route, i) => (
<Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={props => (
<route.component {...props} {...extraProps} route={route} />
)}
/>
))}
</Switch>
) : null;
export default renderRoutes;
//router.js Предположим, это массив маршрутов, который мы создали (это очень похоже на vue, не так ли?)
const routes = [
{ path: '/',
exact: true,
component: Home,
},
{
path: '/login',
component: Login,
},
{
path: '/user',
component: User,
},
{
path: '*',
component: NotFound
}
]
//app.js Затем мы можем использовать это в app.js для создания статических маршрутов для меня.
import { renderRoutes } from 'react-router-config'
import routes from './router.js'
const App = () => (
<main>
<Switch>
{renderRoutes(routes)}
</Switch>
</main>
)
export default App
Дети, которые использовали Vue, знают, что router.js Vue добавляетmeta: { requiresAuth: true }
затем используйте导航守卫
router.beforeEach((to, from, next) => {
// 在每次路由进入之前判断requiresAuth的值,如果是true的话呢就先判断是否已登陆
})
2. Исходя из идеи роутинг аутентификации похожей на vue немного трансформируем react-router-config
// utils/renderRoutes.js
import React from 'react'
import { Route, Redirect, Switch } from 'react-router-dom'
const renderRoutes = (routes, authed, authPath = '/login', extraProps = {}, switchProps = {}) => routes ? (
<Switch {...switchProps}>
{routes.map((route, i) => (
<Route
key={route.key || i}
path={route.path}
exact={route.exact}
strict={route.strict}
render={(props) => {
if (!route.requiresAuth || authed || route.path === authPath) {
return <route.component {...props} {...extraProps} route={route} />
}
return <Redirect to={{ pathname: authPath, state: { from: props.location } }} />
}}
/>
))}
</Switch>
) : null
export default renderRoutes
Модифицированный исходный код добавляет два параметра authed , authPath и атрибут route.requiresAuth.
Затем посмотрите на самый важный фрагмент кода.
if (!route.requiresAuth || authed || route.path === authPath) {
return <route.component {...props} {...extraProps} route={route} />
}
return <Redirect to={{ pathname: authPath, state: { from: props.location } }} />
Очень просто, если route.requiresAuth = false или authed = true или route.path === authPath (значение параметра по умолчанию '/login'), то визуализируем нашу страницу, иначе отображаем наш наборauthPathстраницу и запишите, с какой страницы перейти.
Соответствующий router.js также следует немного изменить.
const routes = [
{ path: '/',
exact: true,
component: Home,
requiresAuth: false,
},
{
path: '/login',
component: Login,
requiresAuth: false,
},
{
path: '/user',
component: User,
requiresAuth: true, //需要登陆后才能跳转的页面
},
{
path: '*',
component: NotFound,
requiresAuth: false,
}
]
//app.js
import React from 'react'
import { Switch } from 'react-router-dom'
//import { renderRoutes } from 'react-router-config'
import renderRoutes from './utils/renderRoutes'
import routes from './router.js'
const authed = false // 如果登陆之后可以利用redux修改该值(关于redux不在我们这篇文章的讨论范围之内)
const authPath = '/login' // 默认未登录的时候返回的页面,可以自行设置
const App = () => (
<main>
<Switch>
{renderRoutes(routes, authed, authPath)}
</Switch>
</main>
)
export default App
//登陆之后返回原先要去的页面login函数
login(){
const { from } = this.props.location.state || { from: { pathname: '/' } }
// authed = true // 这部分逻辑自己写吧。。。
this.props.history.push(from.pathname)
}
здесьreact-router-config
мы закончили и получили желаемый эффект
3. Обратите внимание ⚠️
Многие люди обнаружат, что иногда желаемый эффект не может быть достигнут, так что же нам делать, тогда смотреть вниз
1. Разработайте глобальный компонент для управления входом в систему
configLogin.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'
class App extends Component {
static propTypes = {
children: PropTypes.object,
location: PropTypes.object,
isLogin: PropTypes.bool,
history: PropTypes.object
};
componentDidMount () {
if (!this.props.isLogin) {
setTimeout(() => {
this.props.history.push('/login')
}, 300)
}
if (this.props.isLogin && this.props.location.pathname === '/login') {
setTimeout(() => {
this.props.history.push('/')
}, 300)
}
}
componentDidUpdate () {
if (!this.props.isLogin) {
setTimeout(() => {
this.props.history.push('/login')
}, 300)
}
}
render () {
return this.props.children
}
}
export default withRouter(App)
Внедрив в основной модуль маршрутизации index.js
import {
BrowserRouter as Router,
Redirect,
Route,
Switch
} from 'react-router-dom'
<Router
history={ history }
basename="/"
getUserConfirmation={ getConfirmation(history, 'yourCallBack') }
forceRefresh={ !supportsHistory }
>
<App isLogin={ isLogin ? true : false }>
<Switch>
<Route
exact
path="/"
render={ () => <Redirect to="/layout/dashboard" push /> }
/>
<Route path="/login" component={ Login } />
<Route path="/layout" component={ RootLayout } />
<Route component={ NotFound } />
</Switch>
</App>
</Router>
Во многих случаях мы можем добиться этого, прослушивая изменения маршрутизации, такие какgetUserConfirmation
Крючки делают именно это
const getConfirmation = (message, callback) => {
if (!isLogin) {
message.push('/login')
} else {
message.push(message.location.pathname)
}
Далее мы смотрим наreact-acl-router
как это достигается
В-четвертых, механизм управления полномочиями
Справочный код для этого раздела:
Являясь очень важной частью системы управления предприятием, управление разрешениями всегда было серьезной проблемой для разработчиков, поскольку бизнес-сторона часто не может использовать точные термины для описания требований. Здесь мы впервые представляем два общих шаблона проектирования управления разрешениями, а именно управление доступом на основе ролей и список управления доступом.
1. Макет и маршрутизация
Прежде чем обсуждать конкретный дизайн компонентов макета, мы должны сначала решить более простую проблему, а именно, как объединить компоненты макета с маршрутизацией приложений.
Пример нижеreact-router
Пример совмещения бокового меню и роутинга предоставлен официальным, здесь автор сделал некоторые упрощения:
const SidebarExample = () => (
<Router>
<div style={{ display: "flex" }}>
<div
style={{
padding: "10px",
width: "40%",
background: "#f0f0f0"
}}
>
<ul style={{ listStyleType: "none", padding: 0 }}>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/bubblegum">Bubblegum</Link>
</li>
<li>
<Link to="/shoelaces">Shoelaces</Link>
</li>
</ul>
</div>
<div style={{ flex: 1, padding: "10px" }}>
{routes.map((route, index) => (
<Route
key={index}
path={route.path}
exact={route.exact}
component={route.main}
/>
))}
</div>
</div>
</Router>
);
Идея абстракции как макета, написанного простым псевдокодом, такова:
<Router>
<BasicLayout> // with sidebar
{routes.map(route => (
<Route {...route} />
))}
</BasicLayout>
</Router>
Это действительно очень элегантное решение, но оно ограничено тем, что не может поддерживать несколько разных макетов. ограниченоRouter
Может содержать только один дочерний компонент, даже если мы обернем несколько компонентов макета в компонент-контейнер, например:
<Router>
<div>
<BasicLayout> // with sidebar
{routes.map(route => (
<Route {...route} />
)}
</BasicLayout>
<FlexLayout> // with footer
{routes.map(route => (
<Route {...route} />
)}
</FlexLayout>
</div>
</Router>
Маршрут соответствуетFlexLayout
следующая страница,BasicLayout
серединаsidebar
также будет отображаться в то же время, что, очевидно, не является тем результатом, который нам нужен. Другой способ мышления: можем ли мы использовать компоненты макета в качествеchildren
перешел непосредственно на нижний уровеньRoute
Что насчет компонентов? код показывает, как показано ниже:
<Router>
<div>
{basicLayoutRoutes.map(route => (
<Route {...route}>
<BasicLayout component={route.component} />
</Route>
))}
{flexLayoutRoutes.map(route => (
<Route {...route}>
<FlexLayout component={route.component} />
</Route>
))}
</div>
</Router>
Здесь мы рассматриваем различные компоненты макета как компоненты более высокого порядка и соответственно помещаем их в разные компоненты страницы, тем самым реализуя поддержку нескольких различных макетов. Еще одна вещь, которую следует отметить, это то, чтоreact-router
По умолчанию это будетmatch
,location
,history
ждать, пока информация о маршрутизации будет переданаRoute
компоненты следующего уровня, так как в приведенной выше схемеRoute
Компонент следующего уровня — это не настоящий компонент страницы, а компонент макета, поэтому нам нужно вручную передать эту информацию о маршрутизации компоненту страницы в компоненте макета или переписать ее единообразно.Route
изrender
Метод:
<Route
render={props => ( // props contains match, location, history
<BasicLayout {...props}>
<PageComponent {...props} />
</BasicLayout>
)}
/>
Еще одна проблема, с которой можно столкнуться, заключается в том,connected-react-router
не будет иметь большого значения в маршрутизацииmatch
объект (содержащий текущий маршрутparams
данные) синхронизируется с хранилищем избыточности, поэтому мы должны убедиться, что компоненты макета и страницы могут быть получены в части маршрутизации.match
объект, в противном случае будет очень проблематично обрабатывать требования, связанные с текущими параметрами маршрутизации, такими как заголовок страницы.
2. Верхний и нижний колонтитулы
Решает проблему совмещения с маршрутизацией приложений.В частности, внутри компонента макета две самые важные части — это верхний и нижний колонтитулы страницы, а верхний колонтитул можно разделить на две части: верхний колонтитул приложения и верхний колонтитул страницы.
Заголовок приложения относится к заголовку всего уровня приложения, который не имеет ничего общего с конкретной страницей, вообще говоря, он содержит информацию уровня приложения и операции, такие как аватар пользователя, панель уведомлений, окно поиска и переключение между несколькими языками. . Заголовок страницы обычно содержит заголовок страницы, навигационную цепочку, общие операции со страницей и другой контент, относящийся к конкретной странице.
В предыдущих проектах, особенно на ранней стадии проекта, многие разработчики имели тенденцию делать заголовок приложения компонентом отображения и вызывать его непосредственно на разных страницах, потому что у них не было общего понимания самого проекта. В этом, конечно, есть свое удобство, например, опущена связь синхронизации данных между страницей и макетом, и каждая страница может напрямую передавать в шапку свои внутренние данные.
Но с точки зрения идеальной архитектуры проекта этоантишаблон. Поскольку заголовок приложения на самом деле является компонентом уровня приложения, но в соответствии с описанным выше методом он становится компонентом уровня страницы.Псевдокод выглядит следующим образом:
<App>
<BasicLayout>
<PageA>
<AppHeader title="Page A" />
</PageA>
</BasicLayout>
<BasicLayout>
<PageB>
<AppHeader title="Page B" />
</PageB>
</BasicLayout>
</App>
С точки зрения потока данных приложения существует та же проблема, то есть заголовок приложения должен передавать данные на разные страницы, а не получать данные со страницы в обратном порядке. Это приводит к тому, что заголовок приложения теряет возможность контролировать, когда он перерисовывается.Как чисто презентационный компонент, заголовок необходимо перерисовывать после изменения полученных реквизитов.
С другой стороны, в дополнение к общему заголовку приложения существует строгое взаимно однозначное соответствие между заголовками страниц и маршрутизацией страниц, поэтому можем ли мы также настроить конфигурацию части заголовка страницы в конфигурации маршрутизации? достичь новой страницы, нужно толькоconfig/routes.js
Можете ли вы настроить еще один объект маршрутизации, чтобы завершить создание части заголовка страницы? Идеальный псевдокод выглядит следующим образом:
<App>
<BasicLayout> // with app & page header already
<PageA />
</BasicLayout>
<BasicLayout>
<PageB />
</BasicLayout>
</App>
1. Конфигурация лучше кода
В прошлых дискуссиях о библиотеке компонентов мы пришли к выводу, что код лучше, чем конфигурация, то есть часть, которую пользователь должен настроить, должна попытаться вызвать функцию обратного вызова, чтобы пользователь мог использовать код. контролировать индивидуальные требования. Это связано с тем, что компоненты являются очень мелкозернистыми абстракциями, а шаблоны использования, основанные на конфигурации, часто трудно удовлетворить меняющимся потребностям пользователей. Однако в системе управления предприятием, как в решении на уровне приложения, мы должны стараться не позволять пользователям писать код для проблем, которые можно решить с помощью элементов конфигурации.
Элементы конфигурации (файлы конфигурации), естественно, представляют собой режим централизованного управления, который может значительно снизить сложность приложения. Возьмем в качестве примера заголовок. Если мы вызываем компонент заголовка в каждом файле страницы, то при возникновении проблемы с компонентом заголовка нам необходимо изменить весь код страницы, который использует компонент заголовка. За исключением случая отладки, даже если это просто требование изменить заголовок страницы, разработчик должен сначала найти файл, соответствующий этой странице, и поместить его в свою папку.render
модифицируется в функции. Эти скрытые затраты — все, на что нам нужно обратить внимание при разработке решений системы управления предприятием, потому что именно такие мелкие детали приводят к тому, что сложность приложения несложной системы управления предприятием резко возрастает после обслуживания и итерации в течение определенного периода времени. . В идеале отличное решение для системы управления предприятием должно быть в состоянии решить более 80% нефункциональных изменений требований путем изменения файлов конфигурации.
2. Заголовок конфигурации
import { matchRoutes } from 'react-router-config';
// routes config
const routes = [{
path: '/outlets',
exact: true,
permissions: ['admin', 'user'],
component: Outlets,
unauthorized: Unauthorized,
pageTitle: '门店管理',
breadcrumb: ['/outlets'],
}, {
path: '/outlets/:id',
exact: true,
permissions: ['admin', 'user'],
component: OutletDetail,
unauthorized: Unauthorized,
pageTitle: '门店详情',
breadcrumb: ['/outlets', '/outlets/:id'],
}];
// find current route object
const pathname = get(state, 'router.location.pathname', '');
const { route } = head((matchRoutes(routes, pathname)));
Основываясь на такой идее, мы можем использовать компонент общего макета в соответствии с текущей страницей.pathname
использоватьreact-router-config
который предоставилmatchRoutes
метод перехода на текущую страницуroute
Все элементы конфигурации объекта, а значит, мы можем единообразно обрабатывать все эти элементы конфигурации. Это не только приносит удобство для обработки общей логики, но и является ограничением для коллег, которые пишут код страницы, что может сделать код, написанный разными разработчиками, менее личным, что удобно для общего управления кодовой базой.
3. Название страницы
renderPageHeader = () => {
const { prefixCls, route: { pageTitle }, intl } = this.props;
if (isEmpty(pageTitle)) {
return null;
}
const pageTitleStr = intl.formatMessage({ id: pageTitle });
return (
<div className={`${prefixCls}-pageHeader`}>
{this.renderBreadcrumb()}
<div className={`${prefixCls}-pageTitle`}>{pageTitleStr}</div>
</div>
);
}
4. Навигация по цепочкам
renderBreadcrumb = () => {
const { route: { breadcrumb }, intl, prefixCls } = this.props;
const breadcrumbData = generateBreadcrumb(breadcrumb);
return (
<Breadcrumb className={`${prefixCls}-breadcrumb`}>
{map(breadcrumbData, (item, idx) => (
idx === breadcrumbData.length - 1 ?
<Breadcrumb.Item key={item.href}>
{intl.formatMessage({ id: item.text })}
</Breadcrumb.Item>
:
<Breadcrumb.Item key={item.href}>
<Link href={item.href} to={item.href}>
{intl.formatMessage({ id: item.text })}
</Link>
</Breadcrumb.Item>
))}
</Breadcrumb>
);
}
3. Стратегия дизайна
1. Управление доступом на основе ролей
Управление доступом на основе ролей не назначает напрямую различные разрешения системных операций конкретным пользователям, а устанавливает набор ролей между пользователями и разрешениями и назначает разрешения ролям, а затем назначает роли пользователям. Таким образом реализуется централизованное управление разрешениями и ролями и избегаются сложные отношения «многие ко многим» между пользователями и разрешениями.
2. Список контроля доступа
В частности, между ролями и разрешениями список управления доступом относится к списку системных разрешений, которые имеет роль. В традиционной компьютерной науке разрешения обычно относятся к возможности добавлять, удалять, изменять и проверять файловую систему. В веб-приложениях большинству систем необходимо контролировать разрешения только на уровне страницы, то есть определять, имеет ли текущий пользователь право просматривать текущую страницу в соответствии с ролью текущего пользователя.
Давайте последуем этой идее, чтобы реализовать базовую версию маршрутизации приложений, включающую функции управления разрешениями.
4. Актуальный код
1. Контейнер маршрутизации
Прежде чем писать код, связанный с управлением разрешениями, нам нужно найти подходящий контейнер для всех маршрутов страниц, а именноreact-router
серединаSwitch
компоненты. В отличие от нескольких независимых маршрутов, обернутых вSwitch
Маршруты в , каждый раз будут отображать только первый с успешным совпадением пути, а не все маршруты, которые соответствуют условиям сопоставления путей.
<Router>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Router>
<Router>
<Switch>
<Route path="/about" component={About}/>
<Route path="/:user" component={User}/>
<Route component={NoMatch}/>
</Switch>
</Router>
Возьмите два приведенных выше фрагмента кода в качестве примера, если текущий путь к странице/about
, так как<About />
,<User />
и<NoMatch />
Пути этих трех маршрутов все встречаются/about
, поэтому они будут отображаться на текущей странице одновременно. завернуть их вSwitch
После середины,react-router
найти первого подходящего<About />
После роутинга он перестанет искать прямой рендеринг<About />
компоненты.
В системе управления предприятием, поскольку страница и страница обычно являются параллельными и исключительными отношениями, хорошо использоватьSwitch
Эта функция очень полезна для упрощения логики рендеринга страницы.
Также стоит упомянуть, что вreact-router
авторRyan Florenceновая работа@reach/routerсередина,Switch
Эта функция включена по умолчанию, и@reach/router
Маршрут, который лучше всего соответствует текущему пути, будет выбран автоматически. Благодаря этому пользователю больше не нужно беспокоиться о порядке написания маршрута, а заинтересованные друзья могут обратить на это внимание.
2. Управление полномочиями
Теперь, когда наша маршрутизация имеет общую структуру, давайте добавим к ней конкретную логику оценки разрешений.
Для приложения помимо страниц, требующих аутентификации, должны быть страницы, не требующие аутентификации, давайте сначала добавим эти страницы в нашу маршрутизацию, например, страницу входа.
<Router>
<Switch>
<Route path="/login" component={Login}/>
</Switch>
</Router>
Для маршрутов, требующих аутентификации, нам нужно сначала абстрагировать функцию, которая оценивает, есть ли у текущего пользователя разрешение, в качестве основы для суждения, и в соответствии с конкретными потребностями пользователи могут иметь одну или несколько ролей или более сложную функцию аутентификации. Здесь автор приводит самый базовый вариант, то есть мы храним роль пользователя в виде строки в фоновом режиме, например, роль одного пользователя — admin, а роль другого пользователя — user.
import isEmpty from 'lodash/isEmpty';
import isArray from 'lodash/isArray';
import isString from 'lodash/isString';
import isFunction from 'lodash/isFunction';
import indexOf from 'lodash/indexOf';
const checkPermissions = (authorities, permissions) => {
if (isEmpty(permissions)) {
return true;
}
if (isArray(authorities)) {
for (let i = 0; i < authorities.length; i += 1) {
if (indexOf(permissions, authorities[i]) !== -1) {
return true;
}
}
return false;
}
if (isString(authorities)) {
return indexOf(permissions, authorities) !== -1;
}
if (isFunction(authorities)) {
return authorities(permissions);
}
throw new Error('[react-acl-router]: Unsupport type of authorities.');
};
export default checkPermissions;
Мы упомянули файл конфигурации маршрутизации выше, здесь мы добавляем еще один атрибут для каждого маршрута, который требует аутентификации.permissions
, какие роли могут получить доступ к странице.
const routes = [{
path: '/outlets',
exact: true,
permissions: ['admin', 'user'],
component: Outlets,
unauthorized: Unauthorized,
pageTitle: 'Outlet Management',
breadcrumb: ['/outlets'],
}, {
path: '/outlets/:id',
exact: true,
permissions: ['admin'],
component: OutletDetail,
redirect: '/',
pageTitle: 'Outlet Detail',
breadcrumb: ['/outlets', '/outlets/:id'],
}];
В приведенной выше конфигурации и администратор, и пользователь могут получить доступ к странице списка магазинов, но только администратор может получить доступ к странице сведений о магазине.
Вообще говоря, есть два способа справиться с ситуацией, когда у вас нет разрешения на просмотр текущей страницы: один — напрямую перенаправить на другую страницу (например, на домашнюю страницу), а другой — отобразить неавторизованную страницу. подсказывая пользователю, что, поскольку у него нет разрешения на текущую страницу, он не может проверить. Эти два являются эксклюзивными, то есть каждой странице нужно использовать только один из них, поэтому мы можем настроить его по мере необходимости в конфигурации маршрутизации.redirect
илиunauthorized
атрибуты, соответствующиеПеренаправление без разрешенияиНет разрешения на отображение неавторизованной страницыДва метода обработки. Для конкретного кода вы можете обратиться к примеру проектаreact-acl-routerВ реализации вот небольшой отрывок из основной части.
renderRedirectRoute = route => (
<Route
key={route.path}
{...omitRouteRenderProperties(route)}
render={() => <Redirect to={route.redirect} />}
/>
);
renderAuthorizedRoute = (route) => {
const { authorizedLayout: AuthorizedLayout } = this.props;
const { authorities } = this.state;
const {
permissions,
path,
component: RouteComponent,
unauthorized: Unauthorized,
} = route;
const hasPermission = checkPermissions(authorities, permissions);
if (!hasPermission && route.unauthorized) {
return (
<Route
key={path}
{...omitRouteRenderProperties(route)}
render={props => (
<AuthorizedLayout {...props}>
<Unauthorized {...props} />
</AuthorizedLayout>
)}
/>
);
}
if (!hasPermission && route.redirect) {
return this.renderRedirectRoute(route);
}
return (
<Route
key={path}
{...omitRouteRenderProperties(route)}
render={props => (
<AuthorizedLayout {...props}>
<RouteComponent {...props} />
</AuthorizedLayout>
)}
/>
);
}
Поэтому в окончательном маршруте мы будем предпочтительно сопоставлять пути к страницам, не требующим аутентификации, чтобы гарантировать, что все пользователи смогут увидеть страницу, как только они посетят страницу, не требующую аутентификации. Затем сопоставьте пути к страницам, которые необходимо аутентифицировать.Наконец, если все пути не могут быть сопоставлены, отображается страница 404, чтобы сообщить пользователю, что текущий путь к странице не существует.
<Switch>
{map(normalRoutes, route => (
this.renderNormalRoute(route)
))}
{map(authorizedRoutes, route => (
this.renderAuthorizedRoute(route)
))}
{this.renderNotFoundRoute()}
</Switch>
Маршруты, требующие аутентификации, и маршруты, не требующие аутентификации, — это две разные страницы, и, как правило, их макеты страниц также различаются. Например, страница входа использует обычный макет страницы:
Здесь мы можем комбинировать различные макеты страниц с логикой аутентификации, чтобы одновременно добиться эффекта логики аутентификации и базового макета для вновь добавленных страниц, настроив соответствующие свойства в конфигурации маршрутизации. Это значительно повысит эффективность работы разработчиков, особенно для новых членов команды проекта, метод чистой конфигурации является наиболее удобным.
5. Интеграция приложений
На данный момент маршрут приложения, включающий базовое управление разрешениями, завершен. Мы можем абстрагировать его в независимый компонент маршрутизации. При его использовании нам нужно настроить только две части маршрута, требующего аутентификации, и маршрут, не требующий аутентификации.
const authorizedRoutes = [{
path: '/outlets',
exact: true,
permissions: ['admin', 'user'],
component: Outlets,
unauthorized: Unauthorized,
pageTitle: 'pageTitle_outlets',
breadcrumb: ['/outlets'],
}, {
path: '/outlets/:id',
exact: true,
permissions: ['admin', 'user'],
component: OutletDetail,
unauthorized: Unauthorized,
pageTitle: 'pageTitle_outletDetail',
breadcrumb: ['/outlets', '/outlets/:id'],
}, {
path: '/exception/403',
exact: true,
permissions: ['god'],
component: WorkInProgress,
unauthorized: Unauthorized,
}];
const normalRoutes = [{
path: '/',
exact: true,
redirect: '/outlets',
}, {
path: '/login',
exact: true,
component: Login,
}];
const Router = props => (
<ConnectedRouter history={props.history}>
<MultiIntlProvider
defaultLocale={locale}
messageMap={messages}
>
// the router component
<AclRouter
authorities={props.user.authorities}
authorizedRoutes={authorizedRoutes}
authorizedLayout={BasicLayout}
normalRoutes={normalRoutes}
normalLayout={NormalLayout}
notFound={NotFound}
/>
</MultiIntlProvider>
</ConnectedRouter>
);
const mapStateToProps = state => ({
user: state.app.user,
});
Router.propTypes = propTypes;
export default connect(mapStateToProps)(Router);
В реальном проекте мы можем использоватьreact-redux
который предоставилconnect
Компонент направляет приложение для подключения к хранилищу избыточности, чтобы мы могли напрямую читать информацию о роли текущего пользователя. Как только роль вошедшего в систему пользователя изменится, маршрутизация на стороне клиента может принять соответствующие решения и отреагировать.
6. Комбинированная разработка: управление правами
Для управления правами на уровне страницы логика части управления правами не зависит от страницы и не имеет ничего общего с конкретным содержимым страницы. То есть код в части управления разрешениями не должен быть частью страницы, но должен заменить страницу без разрешения на страницу с перенаправлением или без разрешения, когда маршрут приложения создается после получения разрешения пользователя.
Таким образом, код части страницы может быть полностью отделен от логики управления разрешениями, так что, если уровень управления разрешениями удален, страница становится страницей без оценки разрешений и по-прежнему может работать независимо. Код управления разрешениями в общей части также может обслуживать больше проектов после тонкой настройки в соответствии с потребностями бизнеса.
7. Резюме
В этой статье мы начнем с основных идей проектирования управления разрешениями, реализуем ролевую систему управления разрешениями приложений на уровне страниц и обсудим методы обработки несанкционированного перенаправления и несанкционированного отображения страниц без разрешения.
Далее давайте посмотрим, как реализовано многоуровневое меню.
5. Логика сопоставления меню
Справочный код для этого раздела:
В большинстве систем управления предприятием базовая компоновка страницы обычно представляет собой организационную форму, такую как боковое меню и содержимое страницы. При поддержке зрелой библиотеки компонентов нетрудно сделать красивое боковое меню на уровне пользовательского интерфейса, но поскольку меню также берет на себя функцию навигации по страницам в системе управления предприятием, это приводит к двум основным проблемам. как быть с многоуровневыми меню, а второе - как выделить подстраницы пунктов меню (например, страница сведений о магазине, заходимая по клику на магазин в управлении магазином, не имеет соответствующего пункта меню в меню ), чтобы выделить родительское меню, которому он принадлежит.
1. Многоуровневое меню
Чтобы повысить масштабируемость системы, меню в системе управления предприятием, как правило, должны обеспечивать многоуровневую поддержку, и соответствующая структура данных заключается в том, что каждый элемент меню должен иметь дочерний атрибут для настройки элемента меню следующего уровня.
const menuData = [{
name: '仪表盘',
icon: 'dashboard',
path: 'dashboard',
children: [{
name: '分析页',
path: 'analysis',
children: [{
name: '实时数据',
path: 'realtime',
}, {
name: '离线数据',
path: 'offline',
}],
}],
}];
Рекурсивно визуализировать родительские меню и подменю
Для поддержки многоуровневых меню в первую очередь необходимо решить проблему унификации взаимодействия пунктов меню на разных уровнях.
В большинстве случаев каждый пункт меню представляет отдельный путь к странице. После нажатия он вызовет изменение URL-адреса и переход на соответствующую страницу, которая является полем пути в приведенной выше конфигурации.
Но для родительского меню щелчок также означает открытие или закрытие соответствующего подменю, что противоречит щелчку для перехода на страницу. Чтобы упростить эту проблему, мы сначала унифицируем взаимодействие с меню, щелкнув родительское меню (пункт меню, содержащий дочерний атрибут), чтобы открыть или закрыть подменю, и щелкнув подменю (пункт меню, не содержащий дочерний атрибут), чтобы перейти на соответствующую страницу.
Во-первых, для успешного рендеринга многоуровневого меню функция рендеринга меню должна поддерживать рекурсию, то есть, если текущий пункт меню содержит дочерний атрибут, он будет рендериться как родительское меню и подменю под дочерним полем будет отображаться первым, что алгоритмически вызываетсяобход в глубину.
renderMenu = data => (
map(data, (item) => {
if (item.children) {
return (
<SubMenu
key={item.path}
title={
<span>
<Icon type={item.icon} />
<span>{item.name}</span>
</span>
}
>
{this.renderMenu(item.children)}
</SubMenu>
);
}
return (
<Menu.Item key={item.path}>
<Link to={item.path} href={item.path}>
<Icon type={item.icon} />
<span>{item.name}</span>
</Link>
</Menu.Item>
);
})
)
Таким образом, у нас есть меню боковой панели, которое поддерживает многоуровневое расширение и подменю, соответствующие маршрутизации страниц. Внимательные друзья могут также обнаружить, что хотя родительское меню не соответствует конкретному маршруту, в элементе конфигурации все же есть атрибут пути.Почему?
2. Подсветка меню процесса
В традиционной системе управления предприятием очень болезненно настраивать пути страниц для разных страниц.Для путей страниц единственное требование многих разработчиков - не повторять их.Например, в приведенном выше примере мы настраиваем данные меню , Это также возможно сделать.
const menuData = [{
name: '仪表盘',
icon: 'dashboard',
children: [{
name: '分析页',
children: [{
name: '实时数据',
path: '/realtime',
}, {
name: '离线数据',
path: '/offline',
}],
}],
}];
<Router>
<Route path="/realtime" render={() => <div />}
<Route path="/offline" render={() => <div />}
</Router>
Пользователь также может правильно перейти на соответствующую страницу при нажатии на пункт меню. Но фатальный недостаток в том, что/realtime
Такой маршрут, если только исходя из текущегоpathname
перейти к пункту меню соответствияpath
Как я могу одновременно сопоставить «Страницу анализа» и «Панель инструментов»? Потому что, если совпадения нет, «Страница анализа» и «Панель инструментов» не будут выделены. Можем ли мы напрямую отразить отношения наследования между пунктами меню в пути страницы? Взгляните на следующую служебную функцию.
import map from 'lodash/map';
const formatMenuPath = (data, parentPath = '/') => (
map(data, (item) => {
const result = {
...item,
path: `${parentPath}${item.path}`,
};
if (item.children) {
result.children = formatMenuPath(item.children, `${parentPath}${item.path}/`);
}
return result;
})
);
Эта служебная функция преобразует возможные пункты меню вchildren
Поля учитываются, и следующие полные данные меню могут быть получены путем передачи исходных данных меню.
[{
name: '仪表盘',
icon: 'dashboard',
path: '/dashboard', // before is 'dashboard'
children: [{
name: '分析页',
path: '/dashboard/analysis', // before is 'analysis'
children: [{
name: '实时数据',
path: '/dashboard/analysis/realtime', // before is 'realtime'
}, {
name: '离线数据',
path: '/dashboard/analysis/offline', // before is 'offline'
}],
}],
}];
Затем сделаем обратный вывод маршрута текущей страницы, то есть предположим, что маршрут текущей страницы/dashboard/analysis/realtime
, мы надеемся соответствовать обоим['/dashboard', '/dashboard/analysis', '/dashboard/analysis/realtime']
, Методы, как показано ниже:
import map from 'lodash/map';
const urlToList = (url) => {
if (url) {
const urlList = url.split('/').filter(i => i);
return map(urlList, (urlItem, index) => `/${urlList.slice(0, index + 1).join('/')}`);
}
return [];
};
Приведенный выше массив представляет пункты меню на разных уровнях, и эти три значения соответственно связаны с пунктами меню в данных меню.path
Сопоставление атрибутов может сопоставлять все элементы меню, которые должны быть выделены на текущей странице одновременно.
Здесь следует отметить, что хотя пункт менюpath
Как правило, это обычные строки, но некоторые специальные маршруты также могут иметь обычную форму, например/outlets/:id
. Поэтому, когда мы сопоставляем два, нам также нужно ввестиpath-to-regexp
Эта библиотека для обработки чего-то вроде/outlets/1
и/outlets/:id
такой путь. И поскольку данные меню в начале находятся в древовидной структуре, это не способствуетpath
сопоставление атрибутов, поэтому нам также нужно сначала сгладить данные меню древовидной структуры, а затем передатьgetMeunMatchKeys
середина.
import pathToRegexp from 'path-to-regexp';
import reduce from 'lodash/reduce';
import filter from 'lodash/filter';
const getFlatMenuKeys = menuData => (
reduce(menuData, (keys, item) => {
keys.push(item.path);
if (item.children) {
return keys.concat(getFlatMenuKeys(item.children));
}
return keys;
}, [])
);
const getMeunMatchKeys = (flatMenuKeys, paths) =>
reduce(paths, (matchKeys, path) => (
matchKeys.concat(filter(flatMenuKeys, item => pathToRegexp(item).test(path)))
), []);
С помощью этих служебных функций выделение многоуровневых меню больше не является проблемой.
3. Точка знаний: мемоизация
В боковом меню есть два важных состояния: одноselectedKeys
, который является текущим выбранным пунктом меню; другойopenKeys
, то есть открытое состояние нескольких многоуровневых меню. Значения этих двух различны, потому что вselectedKeys
В той же ситуации, после открытия или закрытия пользователем других многоуровневых меню,openKeys
изменится, как показано на втором рисунке ниже,selectedKeys
то же самое, ноopenKeys
разные.
заselectedKeys
Например, поскольку он определяется путем к странице (pathname
) определяется, поэтому каждый разpathname
Изменения требуют пересчетаselectedKeys
значение . потому что черезpathname
и самые основные данные менюmenuData
вычислятьselectedKeys
Это очень дорогая вещь (обрабатывать много форматов данных и вычислять), есть ли способ оптимизировать этот процесс?
MemoizationОбычным функциям можно дать функцию запоминания результатов вывода, она будет проверять, точно ли входящие параметры совпадают с параметрами, которые были выполнены перед каждым вызовом функции, если они совпадают, она будет напрямую возвращать последний вычисленный результат, точно так же, как обычно используемый То же, что и кеш.
import memoize from 'memoize-one';
constructor(props) {
super(props);
this.fullPathMenuData = memoize(menuData => formatMenuPath(menuData));
this.selectedKeys = memoize((pathname, fullPathMenu) => (
getMeunMatchKeys(getFlatMenuKeys(fullPathMenu), urlToList(pathname))
));
const { pathname, menuData } = props;
this.state = {
openKeys: this.selectedKeys(pathname, this.fullPathMenuData(menuData)),
};
}
В конструкторе компонента мы можем передать текущий реквизитpathname
иmenuData
рассчитать текущийselectedKeys
и относиться к нему как кopenKeys
Начальное значение инициализирует внутреннее состояние компонента. так какopenKeys
контролируется пользователем, поэтому для последующихopenKeys
Чтобы обновить значение, нам нужно только настроить соответствующий обратный вызов, чтобы передать его вMenu
компонентный контроль.
import Menu from 'antd/lib/menu';
handleOpenChange = (openKeys) => {
this.setState({
openKeys,
});
};
<Menu
style={{ padding: '16px 0', width: '100%' }}
mode="inline"
theme="dark"
openKeys={openKeys}
selectedKeys={this.selectedKeys(pathname, this.fullPathMenuData(menuData))}
onOpenChange={this.handleOpenChange}
>
{this.renderMenu(this.fullPathMenuData(menuData))}
</Menu>
Таким образом, мы достигаем дляselectedKeys
иopenKeys
отдельное управление, разработчикам нужно только синхронизировать текущий путь страницы приложения с компонентом боковой панели при использовании компонента боковой панелиpathname
свойства, компонент боковой панели будет автоматически обрабатывать соответствующую подсветку меню (selectedKeys
) и открытие и закрытие многоуровневых меню (openKeys
).
4. Точка знаний: правильно отличать свойство от состояния
Приведенный выше сценарий также является очень классическим примером того, как правильно отличать свойство от состояния.
selectedKeys
входящийpathname
решение, поэтому мы можемselectedKeys
иpathname
Отношения преобразования между ними инкапсулированы в компоненте, и пользователю нужно только передать правильныйpathname
для получения соответствующегоselectedKeys
И не нужно заботиться о том, как выполняется преобразование между ними. иpathname
Поскольку базовые данные, необходимые для рендеринга компонента, компонент не может быть получен сам по себе, поэтому пользователю необходимо передать его через пропсы.
с другой стороны,openKeys
В качестве состояния внутри компонента начальное значение может быть задано какpathname
Он вычисляется, и последующие обновления не имеют ничего общего с данными вне компонента, а будут выполняться внутри компонента в соответствии с операцией пользователя, тогда это состояние, и вся связанная с ним логика может быть полностью инкапсулирована внутри компонента не раскрывая пользователю.
Короче говоря, если данные хотят быть реквизитом, они должны быть недоступны внутри компонента, и после того, как они станут реквизитом, все данные, которые можно вывести из его значения, больше не должны быть другими реквизитами, иначе они будут нарушать принцип единого источника правды для React. То же самое верно и для состояния.Если кусок данных хочет стать состоянием, то он больше не должен изменяться значениями вне компонента, иначе это нарушит принцип единого источника данных, что приведет к непредсказуемой работе компонента. компонента, что приводит к непонятной ошибке.
5. Комбинированная разработка: меню приложения
Строго говоря, идея части меню приложения, обсуждаемая в этом разделе, не относится к категории комбинированных идей разработки, а больше о том, как написать компонент меню, который поддерживает бесконечные подменю и автоматически соответствует текущему маршруту. Компоненты, конечно, подключаются по желанию, но только в том случае, если родительская часть приложения не зависит от информации, предоставляемой компонентом. Это также спецификация, которой мы должны руководствоваться при написании компонентов, то есть компоненты могут получать информацию из внешнего мира и на этой основе делать логические суждения внутри компонента. Но когда компонент выбрасывает информацию во внешний мир, это должно быть чаще в форме обратного вызова для активного срабатывания вызывающей стороны, а затем обновления внешних данных, а затем передачи их компоненту в виде реквизита для достижения цели. целью обновления компонента, а не обязательно настроить функцию получения обратного вызова извне, чтобы напрямую изменить внутреннее состояние компонента.
С этой точки зрения комбинированная разработка и инкапсуляция компонентов на самом деле похожи, ключ заключается в строгом контроле внутреннего состояния. Независимо от того, сколько интерфейсов необходимо раскрыть модулю или компоненту, он должен решать внутри себя одну или несколько конкретных задач. Точно так же, как ссылка в производственной линии заводского продукта, после прохождения по этой ссылке продукт должен отличаться от того, что был до входа.Будь то добавление некоторых функций или маркировка некоторыми этикетками, продукт обязательно станет более выгодным. для нижестоящих партнеров. Более идеальная ситуация заключается в том, что даже если эта ссылка будет удалена, восходящий и нисходящий потоки исходной ссылки все еще могут быть беспрепятственно соединены для продолжения работы.Это то, что мы называем подключаемостью модулей или компонентов.
В-шестых, значение внутренней службы маршрутизации
В контексте архитектуры разделения клиентской и серверной частей клиентская часть постепенно заменяет внутреннюю часть, чтобы взять на себя оценку и обработку всех фиксированных маршрутов.Однако в таком сценарии, как динамическая маршрутизация, мы обнаруживают, что гибкости простой внешней службы маршрутизации далеко не достаточно. После того, как пользователь достигает определенной страницы, единственным основанием для следующего логического суждения является URL-адрес текущей страницы, и служба маршрутизации в конце URL-адреса может возвращать очень подробные данные.
Типичным примером является тип страницы. Предполагая, что логика рендеринга маркетинговой страницы и интерактивной страницы в приложении различается, то кроме DSL-данных страницы нам также необходимо получить тип страницы для соответствующего рендеринга. Другой пример — SEO-данные страницы, время создания и обновления и т. д. Эти данные очень помогают приложению гибко отображать страницу на фронтенде и обрабатывать бизнес-логику.
Мы можем даже обобщить его, полностью отказаться от front-end сервисов маршрутизации, предоставляемых react-router и т.д., а вместо этого написать свой дистрибутив маршрутизации, то есть вызывать разные сервисы рендеринга страниц по разным типам страниц. сформировать законченное фронтенд-приложение.
Семь, комбинированное развитие
Чтобы решить проблему, заключающуюся в том, что крупное комплексное решение на практике недостаточно гибко, можем ли мы отделить содержащиеся в нем различные модули и выпустить его независимо, чтобы разработчики могли использовать его по требованию? Давайте сначала посмотрим на псевдокод идеальной полной части архитектуры приложения системы управления предприятием:
const App = props => (
<Provider> // react-redux bind
<ConnectedRouter> // react-router-redux bind
<MultiIntlProvider> // intl support
<AclRouter> // router with access control list
<Route path="/login"> // route that doesn't need authentication
<NormalLayout> // layout component
<View /> // page content (view component)
</NormalLayout>
<Route path="/login">
... // more routes that don't need authentication
<Route path="/analysis"> // route that needs authentication
<LoginChecker> // hoc for user login check
<BasicLayout> // layout component
<SiderMenu /> // sider menu
<Content>
<PageHeader /> // page header
<View /> // page content (view component)
<PageFooter /> // page footer
</Content>
</BasicLayout>
</LoginChecker>
</Route>
... // more routes that need authentication
<Route render={() => <div>404</div>} /> // 404 page
</AclRouter>
</MultiIntlProvider>
</ConnectedRouter>
</Provider>
);
В приведенном выше псевдокоде мы абстрагировали несколько независимых модулей, таких как многоязычная поддержка, управление правами на основе маршрутизации, аутентификация при входе в систему, базовый макет, боковое меню и т. д., и любой модуль может быть добавлен или удален по мере необходимости. добавление или удаление любого модуля не будет иметь неприемлемых побочных эффектов для остальной части приложения. Это дает нам общее понимание того, что делать дальше, но на конкретной практике, например, как передавать реквизиты, как обмениваться данными между модулями, как гибко разрешить пользователям настраивать какую-то специальную логику и т. д., возникают огромные проблемы. Мы всегда должны обращать внимание на то, какие части должны быть помещены в независимый модуль при решении конкретной проблемы, какие части должны предоставлять интерфейсы для настройки пользователями и как добиться нулевой связи между модулями, чтобы пользователи могли подключать и отключать модули. любой модуль по желанию для удовлетворения потребностей текущего проекта.
Восемь, учебный маршрут
От конкретного внешнего приложения непосредственно к объяснению навыков и концепций разработки, поэтому для друзей, которые только что начали React, может не хватать базовых знаний.Вот более подробный маршрут обучения для разработчиков React.Рисунок, я надеюсь чтобы предоставить стандартизированный и удобный путь обучения для друзей, которые только начали использовать React.
Суммировать
На данный момент сопоставление аутентификации маршрутизации реакции было решено.Приглашаем всех пересылать, обмениваться и делиться.Пожалуйста, укажите источник, с кодом недавнего связанного проекта, чтобы дать вам представление:
[react-router-config](https://github.com/leishihong/react-router-config)
в то же время, приглашаем друзей присоединиться к группе WeChat, чтобы обсудить:
Мы общаемся по номеру