• 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等错误路由转到前端页面

    操作权限

    前端:

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

    后端:

    • 提供获取操作权限的接口
  • 相关阅读:
    pytorch中的detach和detach_
    matlab求导
    Introduction to the Tcl command language
    Tcl/Tk学习
    利用char, str2mat, strvcat创建多行字符串数组
    矩阵操作
    matlab创建三对角线矩阵
    TensorFlow v2.0的基本张量操作
    数据科学家应知道的关于数据科学项目的四个关键方面
    使用TensorFlow v2库实现线性回归
  • 原文地址:https://www.cnblogs.com/MonaSong/p/7126033.html
Copyright © 2020-2023  润新知