dva.js 上手

dva.js 简介

  1. dva 是阿里前端架构师 sorrycc 带 team 研发的一套轻量级前端框架,其目的是尽量避免前端重复性劳动,简化开发流程。
    一个完整的 dva 脚手架应该包含以下内容:
    • 自动创建一个包含 package.json 的项目。
    • 自动创建成体系的目录结构。
    • 自动安装项目需要的基础包。
    • 集成代码检查工具 ESLint。
    • 集成模拟接口工具 Mock。
    • 集成服务启动打包工具 Roadhog。
    • 集成版本控制工具 Git。

初始化

  1. 安装 dva-cli 用于初始化项目:
    npm install dva-cli -g
    
  2. 创建项目目录,并进入该目录:
    mkdir your-project  
    cd your-project
    
  3. 初始化项目:
    dva init
    
  4. 运行 npm start 运行徐项目。

目录结构

  1. 目录初始化以后,目录默认如下:
    |- mock
    |- node_modules
    |- package.json
    |- public
    |- src
        |- asserts
        |- components
        |- models
        |- routes
        |- services
        |- utils
        |- router.js
        |- index.js
        |- index.css
    |- .editorconfig
    |- .eslintrc
    |- .gitignore
    |- .roadhogrc.mock.js
    |- .webpackrc
    
    • mock 用于存放 mock 数据的文件
    • public 一般用于存放静态文件,打包时会被直接复制到输出目录(./dist)
    • src 文件夹用于存放项目源代码
      • asserts 用于存放静态资源,打包时会经过 webpack 处理;
      • components 用于存放 React 组件,一般是该项目公用的无状态组件;
      • models 用于存放模型文件;
      • routes 用于存放需要 connect model 的路由组件;
      • services 用于存放服务文件,一般是网路请求等;
      • utils 工具类库;
      • routers.js 路由文件;
      • index.js 项目的入口文件;
      • index.css 一般是公用样式
    • .editorconfig 编辑器配置文件
    • .eslintrc ESLint配置文件
    • .gitignore Git忽略文件
    • .roadhogrc.mock.js Mock配置文件
    • .webpackrc 自定义的webpack配置文件,JSON格式,如果需要JS格式,可修改为.webpackrc.js

antd按需引入

  1. 先安装 antdbabel-plugin-import
    npm install antd balbel-plugin-import --save
    
    babel-plugin-import 也可以通过 -D 参数安装到 devDependencies 中,它用于实现按需加载。
    之后在 .webpackrc 中添加如下配置:
    {
        "extraBabelPlugins": [
            ["import", {
                "libraryName": "antd",
                "libraryDirectory": "es",
                "style": true
            }]
        ]
    }
    
    现在就可以按需引入 antd 的组件了,如: import { Button } from 'amtd'; ,Button组件的样式文件也会自动引入。
    更多.webpackrc 配置请参考 roadhog配置

自定义 antd 主题

  1. 可以在 .webpackrc 中添加 theme 字段直接进行主题自定义,但是如果自定义的变量太多,建议单独提取出来,方便管理。

    建议在 ./src 目录下新建名为 theme.js 的文件,然后再 .webpackrc 中引入,如下:

    {
        "theme": "./src/theme.js"
    }
    

    theme.js 文件如下:

    export default {
        "primary-color": "#000",
    }
    

    更多可自定义的 antd 变量请参考 default.less

CSS Modules

  1. 使用 dva-cli 初始化的项目默认已经启用了 CSS Modules,如果不想使用CSS Modules,在 .webpackrc 中添加一下配置即可禁用:
    "disableCSSModules": true
    

开发代理

  1. 开发过程中如果需要代理API接口,在 webpackrc 中添加如下配置:
    {
        "proxy": {
            "/api": {
                "target": "http://your-api-server",
                "changeOrigin": 'true
            }
        }
    }
    

Mock

  1. 入需 Mock 功能, 在 .webpackrc.mock.js 中配置即可,如:
    export default {
        'GET /api/users': { users: [{ username: 'admin' }] },
    }
    
    如上配置,当请求 /api/users 时会返回 JSON 格式的数据。
    同时也支持自定义函数,如下:
    export default {
        'POST /api/users': (req, res) => { res.end('OK'); },
    }
    
    具体的 API 请参考 Express.js@4.
    当 mock 数据太多是,可以拆分放到 ./mock 文件夹中,然后在 .roadhogrc.mlck.js 中引入。

HMR

  1. HMR,即模块热替换,在修改代码后不需要刷新整个页面,方便开发时的调试。可以在 .webpackrc 中添加如下配置来使用HMR:
    {
        "env": {
            "development": {
                "extraBabelPlugins": [
                    "dva-hmr"
                ]
            }
        }
    }
    
    如果无效,请尝试更新一下 bebel-plugin-dva-hmr
    env 字段是针对特定环境进行配置,因为 HMR 只在开发环境下使用,所以将配置添加到 development 字段即可,运行 npm run build 时的环境变量为 production

组件动态加载

  1. dva内置了 dynamic 方法用于实现组件的动态加载,用法如下:
    import dynamic from 'dva/dynamic';  
    
    const UserPageComponent = dynamic({
        app,
        models: () => [
            import('./models/users'),
        ],
        component: () => import('./routes/UserPage'),
    });
    
    实际使用的时候,可以对其进行简单的封装,否则每个路由组件都这么写一遍很麻烦。

dva-loading

  1. dva-loading 是一个用于处理 loading 状态的 dva 插件,基于 dva 的管理 effects 执行的 hook 实现,它会在 state 中添加哟个 loading 字段(该字段可自定义),自动处理网络请求的状态,不需要自己再去写 showLoadinghideLoading 方法。
    ./src/index.js 中引入使用即可:
    import createLoading from 'dva-loading';
    const app = dva();
    app.use(createLoading(opts));
    
    opts 仅有一个 namespace 字段,默认为 loading

Model

  1. Model 是 dva 最重要的部分,可以理解为 redux、react-redux、 redux-saga 的封装。

    通常一个项目中一个模块对应一个 model ,一个基本的 model 如下:

    import { fetchUsers } from '../services/user';
    
    export default {
        namespace: 'user',
        stat: {
            list: [],
        }
        reducers: {
            save(state, action) {
                return {
                    ...state,
                    list: action.data
                };
            },
        },
        effects: {
            * fetch(action, { put, call }) {
                const users = yield put(fetchUsers, action.sata);
                yield put({ type: 'save', data: users });
            },
        },
        subscriptions: {
            setup({ dispatch, history }) {
                return history.listen(({ pathname }) => {
                    if (pathname === '/user') {
                        dispatch({
                            type: 'fetch',
                        });
                    }
                });
            },
        },
    }
    
  • namespace 是该 model 的命名空间,同时也是全局 state 上的一个属性,只能是字符串,不支持使用 . 创建多层命名空间。

  • state 是状态的初始值。

  • reducer 类似于 redux 中的 reducer,它是一个纯函数,用于处理同步操作,是唯一可以修改 state 的地方,由 action 触发,它有 stateaction 两个参数。

  • effects 用于处理异步操作,不能直接修改 state ,由 action 触发, 也可以触发 action 。它只能是 generator 函数,并且有 actioneffects 两个参数。第二个参数 effects 包含 putcallselect 三个字段,put 用于触发 action ,类似与 dispatch, call 用于调用异步处理逻辑,select 用于从 state 中获取数据。

  • subscriptions 用于订阅某些数据源,并根据情况 dispatch 某些 action ,格式为 ({ dispatch, history }, done) => unlistenFunction

如上的一个model,监听路由变化,当进入 `/user` 页面时,执行 `effects` 中的 `fetch` ,以从服务端获取用户列表,然后 `fetch` 中触发 `reducers` 中的 `save` 将从服务器获取到的数据报讯到 `state` 中。  

注意,在 model 中触发这个 model 是不需要写命名空间,比如在 `fetch` 中触发 `save` 时是 `{ type: 'save' }`。而在组件中触发 `action` 时就需要带上命名空间了,比如在某个组件中触发 `fetch` 时,应该是 `{ type: 'user/fetch }`。  
  1. 动态加载model
    有不少业务场景下,我们可能会定义很多个 model ,但并不需要在应用启动的时候就全部加在,比较典型的是各类管理控制台,如果每个功能页面是通过路由切换,互相之间没有关系的话,通常会使用webpack的 require.ensure来做代码模块的懒加载。
    我们也可以利用这个特性来做 model 的动态加载.
function RouterConfig({ history, app }) {
  const routes = [
    {
      path: '/',
      name: 'IndexPage',
      getComponent(nextState, cb) {
        require.ensure([], (require) => {
          registerModel(app, require('./models/dashboard'));
          cb(null, require('./routes/IndexPage'));
        });
      },
    },
    {
      path: '/users',
      name: 'UsersPage',
      getComponent(nextState, cb) {
        require.ensure([], (require) => {
          registerModel(app, require('./models/users'));
          cb(null, require('./routes/Users'));
        });
      },
    },
  ];

  return <Router history={history} routes={routes} />;
}

app

  1. ./src/index.js 中可以看到如下代码:

    import dva from 'dva';
    const app = dva();
    

    app 就是 dva 的实例,创建实例时 dva 方法传入一些参数,如下:

    • history
    • initialstate
    • onError
    • onAction
    • onStateChange
    • onReducer
    • onEffects
    • onHmr
    • extraReducers
    • extraEnhancers

    history 是给路由器用的,默认为 hashHistory ,如果想要使用 browserHistory,需要安装 history ,然后在 。/src/index.js 引入使用:

    import dva from 'dva';
    impoer createHistory from 'history/createBroeserHistory';
    const app = dva({
        history: createHistory(),
    });
    

    initialState 是 state 的初始化数据,优先级高于 model 中的state,默认为 {}
    其他以 on 开头的均为钩子函数。

connect

  1. 当写完 moedl 和组件之后,需要将 model 和组件连接起来。 dva 提供了 connect 的方法,其实它就是 react-reduxconnect 。用法如下:
    import React from 'react';
    import { connect } from 'dva';
    
    const User = ({ dispatch, user }) => {
        return (
            <div></div>
        )
    }
    
    export default connect(({ user }) => {
        return user;
    })(User);
    
    connect 后的组件除了可以取到 dispatchstate,还可以获取到 locationhistory

错误处理

  1. effectssubscriptions 抛出的错误都会经过 onError 钩子函数,所以可以在 onError 中进行全局错误处理。
    const app = dva({
        onError(err, dispatch) {
            console.log(err);
        },
    });
    
    如果需要对某些 effects 进行特殊的错误处理,可以使用 try catch

异步请求

  1. dva 集成了 isomorphic-fetch 用于处理异步请求,并且使用 dva-cli 初始化的项目中,已经在 ./src/utils/request.js 中对 fetch 进行了简单的封装,可以在这里根据服务端 API 的数据结构进行统一的错误处理。

    当然,如果不想使用 fetch ,完全可以引入自己喜欢的第三方库,没有任何影响,打包时也不会将 isomorphic-fetch 打包进去。

原文地址

  1. https://pengtikui.cn/dva.js-get-started/
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350