本文作者:鲍观霞

布景

跟着 React 生态的快速开展,社区依据 React 的状况办理计划层出不穷,这意味着许多计划开发者仍然要做许多选择,没有约好的团队,交流本钱和跨团队协作本钱,以及长期的维护是非常高r | = S O的,这时分一致一套开发模式就显得尤为重要。

本文将介绍怎么从零开端建立一个高可复用的后台结构,让每一个人都能轻松搭出自己的后台,深化了解自己的结构。

亲手实X ; : U 7 q (践一R _ f F _ @ [套项目结构有许多好处:
1、事务可定制性强(比方,你们团队有一套事务定制型强的 UI5 I – u i H 组件库;你们团队有一套自己Y C 3 + I 9 ] 4 *的状况办理最佳实践;7 G s L你们团队有一套杂乱的权限办理流程等等)

PS: 当然你完全能够找个第三方结构改构成自己想要的姿态,但是入手本钱、后续的维护本钱、技能更新本钱都会很高

2、收敛技能栈、屏蔽底层差异6 t # j i W Z、一致开发体验,协助团队下降开发和维护本钱
3、成为结构掌控者,技能 d Q m晋级、底层改造为所欲为L / + a B ]

写在前面

本文拟叙述从零建立 React 后台开发结构的中心技能和建立流程,触及到的技能并B ? ; 3 9 )非仅有可选技能栈,你能够随时用你熟悉的技能栈替Q } R & * B H代它。同时我会尽量下降阅读本文的难度,下降前端开发的门槛,但是仍是有一些需求具备的常识:

- React Hooks
- React-RedV - : b ~ & w Fux
- React Router 5.0
- Ant Design 4.x

该项目根本建立了Y r E一个企业级办理体系的骨架结构,A S S /供给通用功用及扩展需求– D R v ~,不触及事务逻辑开发,不触及数据请求,一切数据均为 mock。

开端建立

根底结构及装备

1、 创立根本项目目录和结构
推荐 Create React App 创立根本项+ } g l +目结构。网上许多相关初始化流程,这儿不再赘述。官方教程在这儿。

Create Rea: t D –ct App 是 React 官方推出的构建 React 单页面运用的脚手架东西。它本身集成了 Webpack,并装备了一系列内置的 loader 和根底的 npm 的脚本,能够很轻松的实现零装备就能够快速开发 React 的运用。

9 c R c许的项目目录结构如下:

├── package.json
├── public                  # 静态目录
│   ├── favicon.ico
│   ├── index.html          # 最终的html的根底模板【默许是单$ I L g w 7 1 ? 页面应】$ l 6 5 E 3 N v u
│   └── manifest.json
├── src
│   ├── App.css= X 0 E R J 5 Y `             # App根组件的css
│   ├── App.js              # Ap. 2 a Jp组件代码
│   ├── Apr m ! ] x n d m .p.test.js
│   ├── index.css           # 发动文件款式
│   ├* P 4 s * { 2 b── index.js            # 发动的文件(履行进口)
│   ├i ( . 3 p + /── log& y w G v ko.svg
│   └── serviceWorker.js
└── yarn.lock

2、履行命令

npm, s 4 = 9 start
# or
yarn start

翻开 http://localhost:3000 在浏览器中查看它。

至此,一个简易的 React 项目就成了。

项目进阶

React Router

为什么选动态化路由

大多数人D v ] ` J习惯了装 a ! G X | 1 z备式路由的开发办法,包括像 Angular,Expres# f B 0 S & #s, Ember 等,近一点的 包括 Ant Design Pro 和 Umi 结构,都是静态路由装备。React Router V4 之前也沿用了这一办法,但是在 React Router V4 版别做了一次不向前兼容的重构晋级。 那 React Router V3 装备式路由的痛点在哪里?为何要动态化?
我了解这块的 React Router V3 的痛点w + + e _ 0 {有以下几点:

为了便利介绍,React Router V3 以下简称 V3;React Router VD ( 5 , S W ^ [ N4 以下简称 V4;

  • V3 脱离了 React 组件化思维。V3 尽管形式上是 React 组件,但是其实它与 UI 没有任何关系,仅仅供给了一条装备项而已。
    这一点能够从相关源码追溯
const Route = createReactClass({
// 无关@ / m E * J J ~代码V ; . z
/* istanbul ignore next: sanity check */
render() {
invariant(
false,
'<Route> elements are for router configuration only and should not be rendered'
)
}
});

这儿 Route 的 render 办法中,没有做任何 UI 烘托相关的作业,不是一个正宗的组件。

  • V3 路由写法需求满意约好的格式,比方Y z f W ~ X g _不能将 Route 脱离 Router 运用,这与 React 倡导的“E O E C h w C能够声明式灵活性进行组件g R P G /组装”的理念相违背。
  • V3 供给了许多相似生命周期的办法,如:onEnter, onUpdate, onLeave 等用来为处于不同阶段的路由供给钩子办法。但是 React 本身有一V q v 5套完善的生命周期办法。V3 路由办法的问+ D S E i G j $题在于,它在 React 组件思维之外,规划了一套独立的 API,这有侵入性。
  • 集中式路由层层嵌套,在装备中你需求关怀路由所属的祖先层级,页面展现由尖端路由来决定,无法表现动态路由的灵活性。

A K p m u然,V4 版别已经解决了这些问题。在 V4 版别中,扔掉了传统的路由概念,Route 回归组件化。

V4 开端选用P y R $单代码仓库模w – ) X ( N b型结构,每个仓库担任不同的功用场景,他们分t $ +别相互独立e ) ( m c q

  • react-router 路由根底库
  • react-routeb N #r-dom 浏览器中运用的封装
  • rU * * Feact-router-native React native 封装

本文咱们只需求用到 react-router-dom 这个仓库,假d 8 c L 1如你不明白为什么,看这儿;

你需求把握 react-router-dom 这些组件:

  • BrowserRo/ _ `utO ` =er
  • Route
  • Switch
  • Link

你需求把握 react-routere b t ~ , a-d] ? ` f .om 这些目b M # )标及其办法:

  • history
  • loE 6 y D Ucation
  • match

React Router 从 4.0 开端完全移除中心化装备U X H,不再主张集中式路由,让 React 回归组件化开发,它本身仅仅供给了导航! D S m # h功用的组件。
这儿咱们依据推荐的动态化思路规划路由,进口只规划一级菜单,事务办理各自子路由。

篇幅# } E u 4 @ J &问题,这儿只罗列二级路由的状况,多级Y [ &路由同理c r C s ) x ; c U

1、装置依靠

npm install --save react-router-dom
cd5 [ P B j d src
touch router.js  // 结构& } R v I 4 b F s咱们的一级路由

2、结构 src 目录(你能够灵活定制),我期望它是这样的

.
├── src
│ ├── indet 3 Q i : vx.js                      // 进口文件
│ ├── pages
│ │ ├── demo1                     // 一级菜单A
│ │ │ ├── index.js
│ │ │ ├── page1                 // A下面的二级页面a
│ │ │ │ └── ind2 a B ^ w :ex.js
│ │ │ └── paO E j 6 2ge2                 // A下面的二2 M y & q  }级页面b! Y = 9 │ │     └── index.js
│ │ └── demo2                     // 一级菜单B
│ │     ├── index.js
│ │     ├── page1                 // B下面的二级e b ( R 1 & t R页面a
│ │     │ └──x o / x I j r { O index.js
│ │     └── page2                 // B下面的二级页面b
│ │         └── ind@  B & L zex.js
│ └── router.js

3、结构一级路由

router.js

import { Switch, Route } from 'react-router-dom';
// 一级菜单
import demo1 from './pages/demo1';
import demo2 from './pages/demo2';
const router = (
<RR i Y R B Uoute render={() => {
return (
<Switch>
<Route path="/demo1" component={demo1}></Route>
<Route path="/demo2" component={demo2}></Route>F  s;
</Switch>
)
}}></Route>
);

4、让一级路由去办理咱们的二级路由

pages/demo1/index.js(同级页面相似)

import { Switch, Route } from 'react-routeV y r Jr-dom';
import page1 from './page1';
import page2 froC | /m './page2';
const RouteI y ) Ur = ({ match }) =&9 ! 8 # R - I Wgt; (
<Switch>
<Route6 $ 2 c ! z H L path={`${match.path}`} exact component=( G a [ D R P ?{page1} />
<Route path={`${match.path}m l K - : F/page1`} component={page1} />
<Route path={`${match.path}/page2`} component={page2} />
</Switch>
);

Switch 中包括 RouC n Yte,只烘托 1 I L C第一个匹配的路由。因此主路由匹配加上 exact 去精确匹配,不会阻拦后边的匹配。

5、进口文件参加路由

src/index.L u J H djs

import React from 'react';
import ReactDoP l M k lm from 'react-dom';
import { BrowserRouter as Router  } fU O Srom 'react-router-dom';
import routeE ` B oChildren from './router';
ReactDom.render(
<Router>
{routeChildren}
</Router>,
document.getElementById('app')
);

这儿咱们用的是 BrowserRouter 组件,翻开m 0 2 % ! s T BrowserRouter 文件能够看到它声明晰实例特点 history 目标Y } x 1 I B p,history 目标的创立来自 history 包的 createBrowserHistory 办法。

import { createBrowse t , r XrHistory as createHistory } from "M h ) f C n S . #history";g 3 D r 5 a D
class B: @ k L * [ 5 ? ProwserRouter extends React.Component {
history = creatE x S d e ; ZeHistory(this.props);4 t &
rend/  9 G W y ber() {
return <Router history={this.history} children={this.props.children} />;
}
}

history 目标上具有许多的特点和办法,这些将在后边给咱们供给很大的便利,假3 a c d G如你想了解更多关于 history 的访问,看这儿。

6、修正咱们的事务页面

pages/demo1/page1/index.js(同级页面相似)

import React from 'react';
const Page1 =O Q Q history => {Z B /
return (
<div>demo2 pageq Q 0 X R d ]1</div>
);
};
export default Page1;

至此,咱们的路由规划就完结了。

现在,npm run start 跑起来看看~

从零搭建中后台框架的核心流程

项目路由根本装备结束。, 8 u w W v G C

装备式菜单办理

后台项目中,路由和菜单是组织起一个运用的要害骨架。规划完路由,接下来咱们考虑导航菜单办理。
这一步,咱们开端建立结构中心才能: 菜单装备,UI 集成,状况办理,用户登陆,路由鉴权。

导航应集成在 Layout 结构中,和事务逻辑解耦,为了不让开发者菜单耦合到事务逻辑中,e T + q W v这儿选用装备式菜– : x M r 5 I r _单办理,开发者只需求关怀菜单装备。
为了便利了解,UI 组件库选o G f A用 Ant Design。

1、 菜单装备 & UI 集成

已然打B T 当作装备式菜单,那么咱们规划一个菜单装备,依据装备生成菜单。

cd src
touch menuConfig.js

menuConf[ I =ig.js

const menu = [
{
path: 'demo1',
n1 / ] r  1 o T 7ame: '一级菜单A',
children: [{
name: 'subnav1',
path: 'page1'
},
{
name: 'subnav2',
path: 'page2'
}]
},
{
path: 'demo2',
name: '一级菜单Y ^  E J m # R zB'
children: [{
name: '测试',
path: 'page2'
}]
}
];

当然,你能够在装备中e ? 7 l _参加任意元素来丰富你的装备,比方 icon,redirect 等等;

2、生成菜单装备

X z 1 ~ / t = C下来需求依据这份装备,结构咱们的导航,看一下 Ant Design 供给的 Menu 组件需求哪些数据?
官方给的 demo 是:

&lP L x 9 T Kt;Menu
theme="dark"
mode="inline"
defaultSelectedKeys=M & :{['2']}>
<Menu.Item key="1">nav1</Menu.Item>
<Menu.Item key="2">nav2</Menu.Item>
&l_ + @t;/Menu>

为了让咱们的装n g Y 3 r M w V备能很便利的生成 MenuX u 9 . J + ~ 组件,咱们需求写个办法把咱们的菜单转成平铺形式。用 path 作为 key,能够很便利的解析 selecM + , r s 5tKey。
咱们期望咱们的菜单能够依据 path 选中或切换,咱们需求依据 Men– u ; l uConfig 结构这样一份结构:

{
"selectMainMenu": {    // 当时访问一级菜单信息【用于符号一级菜单选中】
"l m ( L B l = Gpath": "demo1",
"name": "一级菜单A"
},
"mainMenu": [          // 当时一切一级菜单信息【用于烘托一级导航】
{
"path": "demo1",
"name": "一级菜单A"
},
{
"pj . e . ? z ?ath": "demo2",
"name": "一级菜单B"
}
],
"subMenu": [           // 当时一级菜单下的一切子菜单【用于烘托子导航】
{
"name") } j k F Z a: "subnav1",
"path": "page1",
{
"name": "subnav2",
"path": "page2"
}
],
"paths": [
{
"name": "一级菜单A",
"path": "/demo1"
}
],
"prePath": "/dem4 } : wo1"   //[ f W E  h 5 R b 一级路由+二级路由作为子菜单仅有 key【标识x @ o K r 6 / y c二级菜单状况】
}

生成的 HeadMenu 组件:

<Menu themq q ` Ie="dark"
mode="horizontal"
selectedKey] } k L (s={[selectMainMenu.x S 9 j ypath]} >
{
mainMenu.map(item => {
<Menu.Item key={item.path}>
<LinM | : & Ck to={item.path === 'T = s/' ? '/' : `/${4 y p ?item.path}`}>{item.name}</Link>
<| t k/Menu.Item>
})
}
</Menu>

生成的 SideMenu 组件:

<Menu theme="d- L 0 ? ( b ark"
mode="horizontal"
seleG = h i I g }ctedKeys={[cuw { d E D & p c srrentPath]} >
{
sm % @ T k u P 1 mubMenu.m{ r Eap(item => {
<Menu.Item key={`${prePath}/${item.path}`}>
<Link to={item.path# V = C F y ; D === '/' ? '/' : `${prePV  * E C R y Zath}/${item.pat_ i N R % O Rh}`}>
<span>{item.name}<v b y M;/span@ C r>
</Link>
</Menu.Item>
})
}
</Menu>

这一步转化并不杂乱,自行实现。主要供给依据路由 path 符号菜单状i 2 f Y * + y况的思( @ 7 V n路。

3、Layout 集成 Menu 组件

const BaseLayout = ({ locat9 @ 4 ` 8ion, children }) => {
const { pathname } = location;
const [menuInfo, setMenuInfo] = useState({});
// 菜单信息跟着途径变化
useEffect(() => {
const newInfl ` a `o = pathChange(pathname, menuConfig)( q c;
setMenuInfo(newI)  1 Q l y z 6nfo);
}, [pathname]);
return (
<Layout>
<Header className="header" >
<div className="logo" />` p  7 & A R;
<HeW l S ,  #adMenu menuInfo={menuInfo}></HeadMenu>
</Header>
<Content>
<Layout>
<Sider width={200}>
<SideMenuO ? 7 u ~ = f # menuInfo={menuInfo}></SideMenu>
</Sider&g/ h 7 | C 3 a m xt;
<Content>{childR O I _ 0 v xren}</Content>
</Layout>
</ConteC t . n ) H t s _nt> ) n * j 8 s;
</Layout>
)
}

4、将 Layout 运用于一切路由

改造一下咱们的路由进口(加上 Layout 布局结构):

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import BaseLayout from './layouts';
// 各个一级路由
import demo1 from './page4 H B qs/demo1';
import do C semo2 fro` % & f vm3 @ W './pages/demo2';
const router = (
<Route render={(props) => {
return (
<BaseLayo u / U =ut {...props}>
<Switch>
<Route path="/demo1" component={demo1}></Route>
<Route path="/dem9 W b k N Y F 8 @o2" como T , [ponentp ( 3 X n W @={demo2}></Route>
</Switch>
</BaseLayout>
)
}}></Route>
);f U q 3 C
export default router;

咱们的装备式菜单就完结了,它看起来是这样的:

从零搭建中后台框架的核心流程

路由鉴权

toB5 ! } P 项目最大不同于 toV * z k @ % { {C 的逻辑就在于权限操控,这也几乎是后台结构集成最杂乱的部分。

在一个大型体系中,一个误操作发作的后果可能是非常严峻的,权限办理是不可或缺的一个` [ F !环节。

权限体系的存在最大程度上避免了这类问题 — 只要是界面上出现的功用,都是能] ] L ; 够操作或不会发P 6 m z K Q作严峻后果的。
每个帐号登陆后只能看到和自己有关的信息H n s 3 [ 4 D p X,能够更快速地了解自己作业范围内的事务。

后台权限的根本构成J Z y _ W a Q l

权限规划主要由三个要素构成:帐号,人物,$ M % w权限。

- 帐号:登录体系的仅有身份识别,一个账g ] / 5号代表一个用户;
- 人物:为账号批量分配权限。在一个体系中,不可能为每个帐号订制权限,所以给同一类帐号赋予一个“人物”,以到达批量分配权限的意图;
- 权限:关于前端来说,权限又分为页面权限和操作权限;其间页面权限分为菜单权限和路由权限;

规划根本思路为:

1、登陆实现

login.js

import { connect } from 'react-reduxb E A';
cons3 e P [ w Lt Login = ({
loginStatus,
location,
setLoginInfo,
history
}) => {
let { redirectUrl = '/' } = location.state || {};
// 获取登录信息伪代码
cons7 W a T / #t onFinish = valu; ] o * # / )es => {0 M ; M
/**** 此处去获取登录信! Q M 4 R c息并存放在大局 Store ****/
setLoginInfo({
userna* r D | kme: '小A',
role: 1
});
history.push(redirectUrl);
};
return (
<div claQ q p I { Y u I PssName="login layer">
<Form
name="basic"
onFinish={onFinish} >
<Form.Item
label="用户名"
name="username"
rules={[{ required: true, message: '输入用户名' }]}Y | N 3 S >
<Input />
</Form.Item>
<Form.Item6 ; / ,  P S U
label="暗码"
name="password"
rules={[{ required: true, message: '输入暗码' }]} >
<Input.Password />
</Form.Item&gs = T . ! d it;
<Form.Item>
<Button type="primary" htmlType=L L c Y ^"submit">登陆</Button>
</Form.Item>
</Form>
</div>
);
};
const mapStateToProps = state => ({
loginStatus: state.login.loginStatus
});
const mapDispatchToProps = dispatch => ({
setLoginInfo: (...args) => dispatch(setLoginInfo(...ar3 D u ) ^ % Q 0gs))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Login);

connq I 1 & Ject() 的) K u /作用是将 Store 和 Component 连接起来。connect担任从 Redux state 树中读取部分数据,并通过 Props 来把这些数据供给给要烘托的组件。也传递 action 函数到 Props。
connect 函数接收两个参数,一个 mapStateToProps,把 Redux 的 state,转为组件的 Props;还有一个参数是 mapDispatchToprops,
把发射 actions 的办法,转为 Props 特点函数。

2、用户状况办理
store/login.js存储B 5 g I

// 设置sty n a N j a Z #ate初始值
const initState = {
loginStatus: false,F s e m v 9 k
userInfo: {
username: '',
role: -1  // 用户权限标识
}
};
c} k J E / h ionst SET_LOGIN = 'SET_LOGIN';
// action3 . ` ! h w Q Z {
export const setLoginInfo = (payload) => {
return {
payload,
type: SET_Le Z + { u 0 B P sOGIN
};
};
// rea h nducer
export const loginReducer = (state = initState, action) =>+ l H 3 ~ (  =; {
switch (action.type) {
ca` v + 2 { s ~ Bse SET_LOGIK : 1 4 ]N:
return {
...s_ y I 7 j U ? o `tate,
loginStatu2 p # q r v ! ls: true,
userInfo: action.par h $ F : C yload
};
default:
return state;
}
};

store/index.js

import { createStore, combineReducers }) E e _ # } } 5 $ fr/ C + k = X 9 Fom 'redux';
import { loginReducer } from './lo& e Ngin';
const allReducers = {
login: loginReducer= T e d r V A E i
};
const reducers = combineReducers(allReducers);
const store = createt t VStore(reducers);
export defau# 6 , 3 bltM 1 q & 1 store;

进口 index.js 添加 Provider 下发J l 7 o z T F J Store

import React from 'react';
import ReactDom frx O x g om 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import store6 q L q - i ( T U from '.] ] @ C E/redux';
import routeChildren from './router';
ReactDom.. a 1 h d , )render(
<Provider store={store}>
<Rous d 2 ~ : Uter>
{routeChildren}
</Router>
</Provider>,
document.getEle8 + R p 9 i u ? 9mentById('app') I _ ] 9)
);

Provider 的作用是让 Store 在整个 Ap{ a t K = ap 中可用。

3、登陆校验

咱们需求在一切页面访问之前,校验用户登录状况,以免发作重复登陆;
咱们的 Layout 办理着一切页面进口,需求改z 9 V u T造 layout.js

layout.js 添加如下逻辑:

const loginPath = '/login'N 8 W;
const { pathname } = location;
const redirectUrl = pt g 1 u L 8 m e ,athname === loginPath ? '/' : pathname;
useEffect(() => {
<!--校验是否登陆-->
if (loginStatus) {
history.push(redirectUrl);
} else {
history.push('/login', {
redi/ ; k  orectUrl
});
}
}, []);
if (pathname === '/login') {
return <div>{children}</div>;
}

这一步需求把当时页面作为 redirectUrP 2 0 v 9 3 ]l 带到登陆页,登陆后需回来Z 7 4 b 0原途径。

为了看演示作用,咱们需求略i V x # i u 1 P微调整咱们的款式,款式作用自行添加。

3、用@ w 0 V E户鉴权

后台体系鉴权是个杂乱且差异化很大y L Z的话题,本文只做抛砖引玉,为了便利了解思路,只介绍一种简略的权限计划。

咱们设定,权限标识越小,具有的权限越高,逐级之间为包括关系。
结构权限N [ a f % Z | E ^思路如下:

依据这份权限计划,menuConfig.js 需求添加权限标识:

con : jst menu = [
{
path: 'demo1',
name: '一级菜单A'x ] p g A,
role: [r 2 X4],   // demo1 权限标识
chi) 5 2 t s m Uldren: [{
name: 'subnav1',
path: 'page1',
role: [2]     // demo1/page1 权限标识
},
{
name: 'subnav2',
path: 'page2',
role: [2]    // demo1/page2 权限标识
},
{
name: 'subnav3',
par + D / N )th: 'page3',
role: [3]     // demo1/page3 权限标识
},
{
name: 'subnav4',
path: 'page4',
role: [4]      // demo1/page4 权限标识
}]
},
{
path: 'demo2',
name:; S [ B ? ' & L . w { I /一级菜单B', * [ * y v e d
role: [4],          // demo2 权限标识
childf i {ren: [{
name: '测试',
patb l X z vh: 'page2',
role: [3]       // demo1/page2 权限标识
}]
}
];

layout.js添加鉴权阻拦,其余逻辑不变:

let authChildren = children;
const { role = -1 } = userInfo;
const [menuInfo, setMenuInfo] = useState({});
// 用户人物装备,预留
const fk Q D u s 4 9 vilterMenu = menus => menus
.filter(item => ([ J (role !== -1 && item.role >= role))
.map((item) => {
if (item.children && item.children.length > 0) {
return { ...item, ch, T Cildrf R - a - -en: filterMenu(item.children) };
}
return item;
});
useEffect(() => {
// 过滤菜单权限
constm b 1 m 1 F & F v newMenuInfo = filterMenu(menuConfig);
const curMenuInfo = onPathChange(pathname, newMenuInfo);
setMenuInfo(curMenuInfo);
}, [pathname]);
// 过滤路由C D -权限
const curPathAuth =w  , ; I  Q menu0 V u q 2Info.paths
? menuInfo.paths.find(item => item.path === pathname)P 7 0 4  : {};
// 路由权限阻拦
if (JSON.stringify(curPathAuthM t 5 : /) ==] s L & t / S= '{}') {
authChildren = (
<div className="n-privileges"I Q H 5 a Z N b U>5 ? ? d c l + .
<p>对不住你没有访问该页面的权限</p>
</div>
);
}

为了演示权限作用,咱们添U & q ) i a | X加用户权限切换。

结构结构根本构成。

后续

当然,体系还需更多细节的完善,咱们仅仅完结了中心流程。
多人合作的体系开展到后期的时分,咱们需求考虑功能问题、跨域装备、数据 mock、eslint 等等。不属于中心流程的内容,在这儿仅作评论。

1e e z w O、按需加载
单页运用的首屏烘托一向都是个大问题。优化资源加载a J ` D,咱们能够参阅 React 16.3.0 新G | 7 b增的 SuspeN ? | $ V nse 和 lazy 特性。
React.lazy 供给了按需加载组件的办法,而且办法内部有必要用到 import() 语法导入组件f / b { r l p –,合作 webpack 特性:I b T }遇到 import…from 语法会将依靠的包,合并到 bundle.js 中。能够如此实现:

const page1 = React.l^ D -azy(() => import(/* webpackChun% r h  w ] /kName: "p] } L 9 Qage1" */'./page1'));

即可将 page1 打包为名为 page1.Y c d r v & 8 cjs 的文件。
合作W = I W y z n W React.Suspense 能够很便利i { X )的实现懒加载过渡动画。

2、通用 NotFound
咱们的路a j V 9 T由规划使得咱们能很便利的处理 Not Found 的状况。
在每一级 Switch 最终加上 p` ] j ( K E ; yath=”*” 能够阻拦一切未匹配路由。

<Switch>
<2 { K JRoute path={Z p H J J W E D 7`${match.path}`} exact component={Home} />
<Route path={`${match.path}/page1`} component={page1} />
<Route path={`${match.path}/page2`} component={page2} />
<Route pw M S 0 3 6ath="*" component={NotFound} />
</Switch>

3、跨域装备
当咱们本地开发做服务署理的时分,一般会选择在 dev_server 处进行署理。

devServer: {
proxy: {
'/api': {
target: 'http://www.ba: } ; |idu.com/',
changeOrq { Bigin: true,
secure: false,
},
'/apg G ( ni2': {
.....
}
}
}

但这种办法在 create-react-app 生成的运用中无效,关于这种状况,create-react-app 的版别在低于 2.0 的时分– ( o v I :能够在 package.json 添加 proxy 装备, 装备如下:

"proxy": {
'/api': {
target: 'http://www.baidu.com/',
changeOrigin: true,
secure: false,
},
}

create-react-app 的版别高于 2.0 版别的时分在 package.json 只能装备 string 类型,能够考虑用 http-proxy-middleware 替代。

sr? z } U e Lc/setupJ 6 XPro3 8 H V # i o V sxy.js

const { createProxyMiddleware } = require('hm 1 M : X m h 2 gttp-proxy-middl% q leware');
module.exports = function(app) {
ap. b z Ap.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
);
};

当然,你能够也履行 npm run eject 命令,暴露 webpack 等装备,去修正 devServer。

4、数据 moc+ 0 z s 0 #k 才能
项目开发中,前端工程师需求依7 , h ) @ W – L靠后端工程师的数据D f h R V E接口以及后端联调环境。但是其实咱们也能够依据后端接口文档在接口没有开? H + ? G S发完结之[ P I w _ s前自己 mock 数据进行调试,让接口顾B C , U Z (( 1 $ Y ~ i M W {脱离接口生产者进行开发。

mock 数据常见的解决计划有:

  • 在代码层, + 5硬编码
  • 在前端JS中阻拦
  • 署理r | D软件 (Fiddler、Charles)
  • mock server

这些计划要么对代码有侵入性,要么数据无法溯源,要么本钱较高。
? ` m l B音乐已开源一款 mo{ K @ L – ^ck 平台,能协助开发者办理接口。欢迎入坑:NEI

本文仅以个人经历产出,如对本文有任何定见I } _和主张,欢迎评论。

本文发布自 网易云音乐前端团队,文章未经授权制止任何形式的转载。咱们一向在招人,假如你刚好准备换作业,又刚好喜爱云音乐,那就 参加咱们!