react分享

 

后台项目应用分享

后台项目应用分享

webpack + react + redux + antd

策略篇

框架选择

兼容性IE9+

  • 组件化:React
  • 状态管理:React Redux
  • 前端路由: React Router
  • Ajax请求: Axios
  • UI库:Ant Design
  • 构建工具:Webpack

组件化开发

组件?组件!

(讨论)

CSS in JS下的样式开发思路

  • 样式跟着组件走
button-group.js
button-group.less
  • 使用工具方法
@import (reference) "~BaseLess";
.username{
display: inline-block;
max-width: 200px;
.text-overflow() //使用单行溢出隐藏方法
}

注:在less文件中引用alias定义或node _modules下的less文件,需要在路径前加~

  • 使用compose实现样式复用
/* components/Button.css */
.base { /* 所有通用的样式 */ }

.normal {
composes: base;
/* normal 其它样式 */
}

.disabled {
composes: base;
/* disabled 其它样式 */
}
imp
  • 重置全局样式
.collapsed {
//anticon类原样输出
:global(.anticon) {
font-size: 16px;
margin-left: 8px;
}
:global(.anticon+span),
:global(.ant-menu-submenu-vertical > .ant-menu-submenu-title:after) {
display: none;
}
}

扩展阅读:CSS Modules 详解及 React 中实践

展示组件 VS 容器组件

我们先来看一下Redux官方文档中的定义:

 展示组件容器组件
作用 描述如何展现(骨架、样式) 描述如何运行(数据获取、状态更新)
直接使用 Redux
数据来源 props 监听 Redux state
数据修改 从props调用回调函数 向 Redux 派发 actions
调用方式 手动 通常由 React Redux 生成

结合官方定义,我们把组件分为三个层次:

  • 应用(App): 整个管理系统是一个应用(单页模式一般只有一个应用,多页模式可能有多个应用)
  • 容器(Container):可以从路由访问得到的组件叫做容器,类似传统开发模式中的后端页面
  • 组件(Component):容器以外的组件都叫组件

Action/Reducer 应该和组件绑定吗

(讨论)

调试Redux

(演示)

构建一个调试工具配置文件devtool.js

/**
* redux调试工具
*/


import React from 'react';
import { createDevTools } from 'redux-devtools';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';

export default createDevTools(
<DockMonitor defaultIsVisible={false}
toggleVisibilityKey="alt-h"
changePositionKey="alt-q">

<LogMonitor />
</DockMonitor>

);

开发环境,在容器root.dev.js中引入调试工具

import React from 'react';
import DevTools from './devtools';
import Layout from 'components/layout';
import style from './style.less'

export default class Root extends React.Component {

render () {
return (
<div className={style.root}>
<Layout>{this.props.children}</Layout>
<DevTools />
</div>

)
}
}

开发环境,在store.dev.js中引入调试工具

import DevTools from 'containers/root/devtools'

export default function configureStore(initialState, reducers) {
const store = createStore(
...
compose(
...
//redux调试工具
DevTools.instrument()
)
);
...
return store
}

规范建议

命名规范

  • 变量名以驼峰方式,如:
const userInfo = {}
  • 类名以大写字母开头,如:
Class UserManager extend from React.Component{
...
}
  • 文件(夹)名一律小写,以下划线_或中杠线-作为分隔符
  • 文件(夹)名以下划线_区分类型,如:common_action.js,表示Action
  • 文件(夹)名以圆点.区分环境,如:root.dev.js,表示开发环境

模块规范

  • 同一目录下的模块之间以相对路径的方式引用
  • 不同目录下的模块之间以绝对路径的方式引用
  • 常用模块以alias的方式引用(推荐alias以大写字母开头,以区分模块路径引用)

思考:这样设计的好处是什么?

目录划分

build                构建工具及配置
dist 目标目录
src 源码目录
mock mock数据
public 开发环境临时目录
node_modules npm包目录
node_shrinkwrap npm离线包目录

延伸阅读:为什么要有npm离线包?

build:构建工具及配置

build/
lib/ 工具库
*.js
shell/ 部署脚本
*.sh
webpack.config.common.js webpack公共配置
webpack.config.dev.js webpack开发环境配置
webpack.config.prod.js webpack生产环境配置
webpack.dll.config.js webpack.dllPlugin配置
config.js 构建配置

config.js示例:

const fs = require('fs');
const path = require('path');

//提取多文件共用配置、项目可定制的配置

const pkg = require('../package.json');
const src = path.resolve(__dirname, '../src');
const dist = path.resolve(__dirname, '../dist/resource')

module.exports = {

/*
* 以下配置在项目中通常不需要变动
*/


//package.json
pkg,
//源文件路径,使用绝对路径
src,
//导出路径,使用绝对路径
dist,
//静态资源目录,使用绝对路径
contentBase: path.resolve(__dirname, '../public'),
//导出资源映射表路径,使用绝对路径
manifest: path.resolve(__dirname, '../dist/manifest'),
//资源映射表名称,如下配置将根据当前日期生成对应的资源映射表
manifestFileName: function() {
return `${new Date().getFullYear()}-${new Date().getMonth()+1}-${new Date().getDate()}.json`
},
//dll生成路径
dllPath: {
development: path.resolve(src, 'vendor'),
production: dist
},
//dll资源映射
dllManifest: {
development: path.resolve(src, 'vendor/dll-manifest.json'),
production: path.resolve(dist, 'dll-manifest.json')
},
//js压缩配置
UglifyJsOptions: {
compress: {
//不输出警告
warnings: false
},
//不输出注释
comments: false
},

/*
* 以下配置在项目中通常需要定制
*/


//生产环境中前端资源路径(需要与nginx配置保持一致),可以为域名url
publicPath: '/Public/',
//模块别名,相对于conf.src路径配置
//- 推荐以大写字母开头,以区分非别名
alias: {
// 起别名:"module" -> "new-module" 和 "module/path/file" -> "new-module/path/file"
// "module": "new-module",
// 起别名 "only-module" -> "new-module",但不匹配 "module/path/file" -> "new-module/path/file"
// "only-module$": "new-module",
// 起别名 "module" -> "./app/third/module.js" 和 "module/file" 会导致错误
// 模块别名相对于当前上下文导入
// "module": "./app/third/module.js"
"Coms": "components/common",
"ActionTypes$": "utils/action_types.js",
"Api$": "utils/api.js",
"Helper$": "utils/helper.js",
"Ajax$": "utils/ajax.js",
"BaseLess$":"utils/baseless/baseless.less"
},
//代理配置
proxy: {
"/": {
target: "http://test-matrix-v2.yileyoo.com",
changeOrigin: true
}
},
//mock配置
mock: {
//mock目录,使用绝对路径
mockPath: path.resolve(__dirname, '../mock'),
//支持设置统一接口后缀,如:.do
apiExt: ''
},
//html模板配置
template: {
all:{
title:'Matrix管理平台'
},
development:{
serverOutput:'<script src="/server/output"></script>'
},
production:{
serverOutput:'<script>window.REDUX_STATE = {!! $jsData !!};</script>'
}
},
theme: path.resolve(src, 'theme/red.less')
}

dist:存放构建结果

dist/
resource
index_xxx.html
app_xxx.js
vendor_xxx.js
app
manifest
2017-x-x.json

src:源码目录

src/
----------------------------------------------------
actions/ Redux Action目录
*_action.js
reducers/ Redux Reducer目录
*_reducer.js
store/ Redux Store目录
index.js
store.dev.js
store.prod.js
----------------------------------------------------
routes/ React路由目录
index.js
----------------------------------------------------
containers/ React容器目录
root/
index.js
root.dev.js
root.prod.js
devtools.js
components/ React组件目录
common/ React公共组件目录(alias:Coms)
page_1/
index.js
*.js
*.less
... React页面组件
page_n/
----------------------------------------------------
vendor/ 第三方库目录
vendor.js
verdor.js.map
dll-manifest.json
static/ 静态资源目录
favicon.ico
utils/ 工具目录
ajax.js ajax工具(alias:Ajax)
api.js api工具(alias:Api)
action_types.js action类型工具(alias:ActionTypes)
helper.js 常用工具方法(alias:Helper)
baseless/*.less 常用less方法(alias:BaseLess)
theme/ 主题目录
*.less
----------------------------------------------------
config/ 配置目录
*_config.js
----------------------------------------------------

mock:存放mock数据的目录

mock/
*.js
*.json

public:开发环境临时目录

public/
index.html

npm package:npm包

node_modules                npm包目录
node_shrinkwrap npm离线包目录

other files:根目录下其他文件

.gitignore                  git忽略配置
.gitlab-ci.yml gitlab-ci配置
npm-shrinkwrap.json npm包版本锁定配置
package.json npm包配置
webpack.config.js webpack配置入口
README.md 说明文档

UI篇

如何引入ant

// .babelrc or babel-loader option
{
"plugins": [
["import", { libraryName: "antd", style: "css" }] // `style: true` 会加载 less 文件
]
}

然后只需从 antd 引入模块即可,无需单独引入样式。等同于下面手动引入的方式。

// babel-plugin-import 会帮助你加载 JS 和 CSS
import { DatePicker } from 'antd';

如何定制antd

antd通过less变量提供了较灵活的主题定制功能。(参考:修改 Ant Design 的样式变量

需要修改babel-loader配置:

{
"plugins": [
["import", {
libraryName: "antd",
style: true // 这里需要修改为`style: true`以实现主题配置
}]
]
}

这里我们做了简单的封装:

1)在src/theme目录建一个主题文件,如:red.less(参考:antd默认主题文件

@primary-color: #f00;

2)在build/config.js文件配置主题文件的路径

{
...
theme: path.resolve(src, 'theme/red.less')
}

后续我们还将推出主题配置监听多主题切换等功能,敬请期待~

动手写一个antd组件

任意在项目中可复用的组件,都可以通过antd组件组合成一个通用(业务)组件。
注意,这里的组件是Component(见前面的定义)类型。

(演示)

工具篇

devServer什么鬼

  • 一个node express应用
  • 提供静态资源服务
  • 支持文件监听
  • 支持热加载
  • 支持路由代理
  • 支持接口转发

调用方式非常简单,在命令行执行:

webpack-dev-server --env development --port 3000 --hot --inline --progress --open

webpack中的相关配置:

//开发服务器配置
devServer: {
//告诉服务器从哪里提供内容。只有在你想要提供静态文件时才需要。
//devServer.publicPath 将用于确定应该从哪里提供 bundle,并且此选项优先。
contentBase: [
conf.contentBase,
path.join(conf.src, 'static'),
path.join(conf.src, 'vendor')
],
//信息显示配置
stats: "normal",
//是否显示全屏遮罩
overlay: true,
//watch配置
watchOptions: {
ignored: /node_modules/,
aggregateTimeout: 300,
poll: 100
},
//联调模式下,使用数据代理
proxy: isDebug ? conf.proxy : {},
//开启浏览器历史
historyApiFallback: true,
//扩展devServer
setup(app) {
//非联调模式下,使用mock数据
!isDebug && makeMock(app, conf.mock)
}
}

不容忽视的HtmlWebpackPlugin

  • 自动引用webpack生成的资源,支持过滤
  • 默认支持ejs模板,可以注入变量,语法亲和
  • 社区生态好,一众扩展插件可以满足各种需求(参考

dllPlugin教程没有教你

教程很多,随便找一篇:怎样令webpack的构建加快十倍、DllPlugin的用法

不实用!!!

get√到打开方式后,我们来针对实际情况做个总结:

  • 区分开发和生产环境
 开发环境生产环境
警告信息
内容压缩
文件hash
SourceMap 可选
  • 怎么在页面引用

使用add-asset-html-webpack-plugin插件

{
plugins:[
...
// 在入口页面中引入静态资源
new AddAssetHtmlPlugin({
//通过dllManifest读取dll文件名
filepath: path.resolve(conf.dllPath[NODE_ENV], `${dllManifest.name}.js`)
})
]
}
  • 加一个库就必须手写一下entry不需要!!!
 entry: {
//读取package.json中的依赖
vendor: Object.keys(conf.pkg.dependencies)
}
  • 完整的webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
const conf = require('./config');

module.exports = function( /*通过命令行参数--env传入*/ NODE_ENV) {
//是否生产环境
const isProd = NODE_ENV === 'production';
//文件名(不带后缀)
const name = `[name]${isProd?"_[chunkhash:8]":""}`;
//输出文件路径
const filePath = conf.dllPath[NODE_ENV];
//输出manifest路径
const manifest = conf.dllManifest[NODE_ENV];
//sourcemap配置
const devtool = isProd ? '' : 'source-map';
//插件
let plugins = [
new webpack.DllPlugin({
//解析包路径的上下文,这个要跟接下来配置的 webpack.config.js 一致。
context: __dirname,
//manifest.json文件的输出路径,这个文件会用于后续的业务代码打包
path: manifest,
//dll暴露的对象名,要跟output.library保持一致
name: name
})
];
//生产环境使用压缩版
if (isProd) {
plugins = plugins.concat([
//变量定义
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(NODE_ENV)
}),
// js压缩配置
new webpack.optimize.UglifyJsPlugin(conf.UglifyJsOptions)
])
}
return {
entry: {
//读取package.json中的依赖
vendor: Object.keys(conf.pkg.dependencies)
},
output: {
path: filePath,
filename: name + '.js',
//需要与filename保持一致,用于页面引用
library: name
},
devtool,
plugins
}
};

扩展阅读:webpack 构建性能优化策略小结
思考:相比其他方案,dllPlugin的优势在哪里?

SourceMap全方案

  • loader中启用SourceMap
{
loader: 'xxx-loader',
options: {
...
sourceMap: true
}
}
  • devtools中配置SourceMap类型

开发环境:唯快不破,最大化满足调试需求

{   
...
devtool: 'cheap-module-eval-source-map'
}

生产环境:需要考虑安全性和性能

{    
...
devtool: 'source-map'
}

扩展阅读:Webpack devtool source map

拆分配置文件

webpack.config.common.js       公共配置
webpack.config.dev.js 开发环境配置
webpack.config.prod.js 生产环境配置

拆分原则:

  • moduleresolve在开发和生产环境中的配置差异性相对较小,非常适合抽取到公共配置中
  • entryoutputplugins 相对来说开发和生产环境有不同的配置,因此放到devprod各自配置中
  • devtooldevServer 等仅出现在开发环境的配置直接放入dev配置中

协同篇

承载页

纯静态 vs 服务端渲染

  • 纯静态:服务端仅提供数据接口,彻底不需要后端维护,缺点是无法做资源回溯和切换
  • 服务端渲染:服务端提供数据接口,并将部分数据或状态渲染到页面,缺点是耦合了前后端部署逻辑

对服务端渲染的改进:

  • 将后端模板指向前端部署目录的html文件,如: index.html
  • 使用固定的后端模板,将数据或状态以JSON对象的方式输出
  • 使用HtmlWebpackPlugin,将后端模板注入到自动生成的页面中

build/config中的配置:

//html模板配置
template: {
all:{
title:'Matrix管理平台'
},
development:{
serverOutput:'<script src="/server/output"></script>'
},
production:{
serverOutput:'<script>window.REDUX_STATE = {!! $jsData !!};</script>'
}
}

webpack中的配置:

{
plugins:[
...
// 根据模板创建入口页面
new HtmlWebpackPlugin(Object.assign({
template: path.resolve(conf.src, 'index.ejs'),
filename: path.resolve(conf.contentBase, 'index.html')
}, /*全环境模板配置*/conf.template.all, /*当前环境模板配置*/conf.template[NODE_ENV]))
]
}

index.ejs模板中引用

<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<title><%= htmlWebpackPlugin.options.title %></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
</head>

<body>
<div id="root" style="height: 100%"></div>
<%= htmlWebpackPlugin.options.serverOutput %>
</body>

</html>

资源灰度 & 回溯

使用 assets-webpack-plugin插件,在webpack中生成资源映射json文件

{
plugins:[
...
//生成资源映射表
new AssetsPlugin({
path: conf.manifest,
filename: conf.manifestFileName(),
processOutput: function (assets) {
//注入dll依赖信息
assets.vendor = {
'js': conf.publicPath + dllManifest.name + '.js'
};
return JSON.stringify(assets);
}
})
]
}

两种方案:

  • 同一个html模板,仅切换资源。需提供资源映射表
  • 不同html模板,切换模板。需要提供模板映射表

数据接口

文档

——接口定义,告诉我们有哪些接口,接口支持哪些http方法,每个接口字段的含义是什么等

(演示)

mock

——接口没有开发完成,前端根据接口文档模拟的数据

(演示)

proxy

——接口已经开发完成,使用代理的方式实现本地接口联调

webpack中的相关配置:

devServer:{
...
proxy: {
"/api": "http://localhost:3000"
}
}

权限控制

页面权限

前端:

  • react-router中定义所有页面的路由
  • 页面初始化时,请求后端接口获取菜单权限
  • 仅显示具备权限的菜单

后端:

  • 提供获取菜单权限的接口
  • 将接受到的路由请求作过滤,具备权限的则转向前端页面
  • 将404/500等错误路由转到前端页面

操作权限

前端:

  • 请求后端接口获取操作权限
  • 根据操作权限控制操作按钮是否显示

后端:

  • 提供获取操作权限的接口
posted @ 2017-07-06 14:08  青草圆  阅读(1323)  评论(0编辑  收藏  举报