美文网首页
Dva最不佳实践

Dva最不佳实践

作者: mah93 | 来源:发表于2019-10-09 16:27 被阅读0次

    前言

    由于之前写了几个dva的项目,近期没怎么用有些遗忘了,写个小结记录一下。

    dva是基于react、react-router、redux封装的一个轻框架。详细的介绍在 dva官网,这里仅仅摘录部分。项目托管在GitHub上,点击这里

    特性

    • 易学易用:仅有 6 个 api,对 redux 用户尤其友好
    • elm 概念:通过 reducers, effectssubscriptions 组织 model
    • 支持 mobile 和 react-native:跨平台 (react-native 例子)
    • 支持 HMR:目前基于 babel-plugin-dva-hmr 支持 components、routes 和 models 的 HMR
    • 动态加载 Model 和路由:按需加载加快访问速度 (例子)
    • 插件机制:比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
    • 完善的语法分析库 dva-astdva-cli 基于此实现了智能创建 model, router 等
    • 支持 TypeScript:通过 d.ts (例子)

    准备工作

    • 确保 node 版本是 6.5 +
    • cnpmyarn 能节约你安装依赖的时间

    Step1. 安装 dva-cli 并创建应用

    先安装 dva-cli,并确保版本是 0.7.x。

    $ npm i dva-cli@0.7 -g
    $ dva -v
    0.7.0
    

    然后创建应用:

    $ dva new dva-learning
    

    创建成功后进入该文件夹:

    $ cd dva-learning 
    `项目的目录结构`
    dva_learning
    |-----mock                                  # => 前端模拟数据
    |-----node_modules                          # => 项目依赖采用npm管理,所有包均在此目录。
    |-----public                                # => 存放index.html
    |-----src                                   
           |------assets                        # => 项目静态资源文件夹(图片等)
           |------components                    # => 无状态组件文件夹
           |------models                        # => 状态model文件夹
           |------routes                        # => 路由配置文件夹,页面存放在该文件夹下
           |------services                      # => 服务层文件夹
           |------utils                         # => 工具函数文件夹
           |------index.css                     # => 全局通用样式
           |------index.js                      # => 单页引用入口js,dva项目初始化
           |------router.js                     # => 全局路由状态管理文件
    |-----package.json                          # => npm包管理文件
    |-----.eslintrc                             # => 代码规范配置文件
    |-----.roadhogrc                            # => 打包配置文件
    |-----.roadhogrc.mock.js                    # => 模拟数据配置文件
    |-----README.md
    `项目的目录结构`
    `运行项目`
    $ npm start
    

    如果运行成功的话,浏览器会自动弹出并访问8000端口,看到如下画面:

    run_success@2x.png

    Step2. 配置 antdbabel-plugin-import

    antd是由蚂蚁金服开发的一套UI组件,具有学习成本低、上手速度快、实现效果好的特点。十分适合初学者并且与dva无缝接入。如需了解更多请查看 ANT DESIGN

    babel-plugin-import 用于按需引入 antd 的 JavaScript 和 CSS,这样打包出来的文件不至于太大。

    $ npm i antd --save
    $ npm i babel-plugin-import --save-dev
    

    修改 .roadhogrc,在 "extraBabelPlugins" 里加上:

    ["import", { "libraryName": "antd", "style": "css" }]
    

    Step3. 添加新页面

    我们的目标是写一个登录的界面,成功之后显示dva默认的首页。所以在src/routes文件夹下,新建Login.js和Login.css文件。js文件用来写组件布局,css文件用来写样式,默认为js文件和css文件一一对应。

    在Login.js中:

    `Login.js`
    import React from 'react';
    import { connect } from 'dva';
    import { Input, Icon, Button } from 'antd';
    import styles from './Login.css';
    
    function Login() {
      return (
        <div className={styles.inputDiv}>
          <div>
            <Input
              placeholder="用户名"
              prefix={<Icon type="user" />}
              size="large"
              className={styles.inputUser}
            />
          </div>
          <div>
            <Input
              placeholder="密码"
              prefix={<Icon type="lock" />}
              size="large"
              className={styles.inputPass}
            />
          </div>
          <Button type="primary" className={styles.button}>
              登录
          </Button>
        </div>
      );
    }
    
    Login.propTypes = {
    };
    
    export default connect()(Login);
    
    

    为这个页面添加样式:

    `Login.css`
    .inputDiv {
      position: absolute;
      top: 50%;
      left: 50%;
      width: 200px;
      height: 220px;
      margin: -110px 0 0 -100px;
    }
    .inputUser {
      width: 200px;
    }
    .inputPass {
      width: 200px;
      margin-top: 20px;
    }
    .button {
      width: 200px;
      margin-top: 20px;
    }
    
    

    然后修改router.js页面,将新写的登录页面,放到默认显示页面

    `router.js`
    import React from 'react';
    import { Router, Route } from 'dva/router';
    import IndexPage from './routes/IndexPage';
    import Login from './routes/Login';
    
    function RouterConfig({ history }) {
      return (
        <Router history={history}>
          <Route path="/" component={Login} />
          <Route path="/home" component={IndexPage} />
        </Router>
      );
    }
    
    export default RouterConfig;
    

    然后运行,会看到你完成的页面

    dva登录@2x.png

    Step4. 添加事件

    之前完成的页面还没有添加点击事件,接下来添加几行代码,让它可以编辑,可以输入和点击

    页面中使用的Input, Icon, Button均是Antd中基本的组件,在 ANT DESIGN有对它们详细的介绍。

    为Button添加一个单击事件

    `点击button时触发`
    const submit = () => {
      alert('here')
    };
    `在Button中添加这个方法`
    <Button type="primary" className={styles.button} onClick={submit}>
    登录
    </Button>
    

    再次运行项目,点击登录按钮会显示

    dva单击事件@2x.png

    Step.5 处理逻辑

    如果需要这个登录界面更加真实的话,需要处理一些登录中的逻辑,比如点击登录按钮的时候,判断输入框中是否输入了数据等。

    dva中有专门的文件夹存放这些处理页面内逻辑的代码。查看src/models/example.js文件,这是一个标准的模版,每个处理逻辑的文件都包含下面几部分

    export default {
    
      namespace: 'example',                                 # => 唯一标识,应用中唯一
    
      state: {},                                            # => 需要存储的值,每次修改会刷新界面
    
      subscriptions: {                                      # => 订阅
        setup({ dispatch, history }) {  
        },
      },
    
      effects: {                                            # => 副作用,一般用来发起请求
        *fetch({ payload }, { call, put }) {  
          yield put({ type: 'save' });
        },
      },
    
      reducers: {                                           # => 只有在这里才能修改state的值
        save(state, action) {
          return { ...state, ...action.payload };
        },
      },
    
    };
    

    更多关于dva的api,请查看dva APIs

    在src/models中新建login.js文件

    `login.js`
    export default {
    
      namespace: 'login',
    
      state: {
        loading: false,
      },
    
      subscriptions: {
      },
    
      effects: {
      },
    
      reducers: {
        save(state, action) {
          return { ...state, ...action.payload };
        }
      },
    };
    

    然后在Login.js中调用save方法

    `Login.js`
    const userName = (e) => {
      dispatch({
        type: 'login/save',
        payload: {
          user: e.target.value,
        },
      });
    };
    

    这个方法的意思是:当输入用户名的时候,调用save方法,并将输入的值,保存在state.user中。

    Step.6 发送请求

    输入完用户名以及密码之后,单击登录按钮,将输入的值发送至后台校验,校验通过之后跳转到下一个页面。

    现在已经有了输入的用户名和密码,分别是login.user和login.password,现在需要将这两个数据发送到后台。由于现在并没有后台服务支持,dva支持mock数据,所以先在前台模拟一个后台服务。

    在项目根目录下新建.roadhogrc.mock.js并添加:

    `.roadhogrc.mock.js`
    const mock = {}
    require('fs').readdirSync(require('path').join(__dirname + '/mock')).forEach(function(file) {
      Object.assign(mock, require('./mock/' + file))
    })
    module.exports = mock
    

    之后在mock文件夹中新建login.js并添加:

    `login.js`
    const qs = require('qs');
    
    module.exports = {
      'POST /login' (req, res) {
        console.log(req.body);
        console.log('接受到请求');
        setTimeout(()=>res.json({code:'200',message:'从mock/example.js请求成功'}),2000)
      },
    };
    

    这个的意思是说,监听本地的8000端口,当访问http://localhost:8000/login的时候,会延迟2秒并返回数据。

    模拟的后台服务已经完成,现在要在button中添加点击事件,去请求这个接口。

    首先改造一下fetch请求,在utils/request.js中:

    export default function request(url, method, params) {
      if (method === 'POST') {
        const formData = new FormData();
    
        for (const key in params) {
          if (params[key] != null) {
            const value = params[key];
            formData.append(key, value);
          }
        }
        return fetch(url, {
          method: method,
          body: formData,
        }).then(checkStatus)
          .then(parseJSON)
          .then(data => ({ data }))
          .catch(err => ({ err }));
      }
      if (method === 'GET') {
        return fetch(url)
          .then(checkStatus)
          .then(parseJSON)
          .then(data => ({ data }))
          .catch(err => ({ err }));
      }
    }
    

    然后在services中新建login.js,这里可以理解为转发,从点击事件中传递到request里请求:

    import request from '../utils/request';
    
    export function login(params) {
      console.log('services处理');
      return request('/login', 'POST', params);
    }
    

    之后在models/login.js,发起这个请求:

      effects: {
        *fetch({ payload }, { call, put }) {  // eslint-disable-line
          if(!payload.userName || !payload.passWord) {
            message.error('请输入账号密码');
            return;
          }
          const { data } = yield call(service.login, payload);
        },
      },
    

    最后在界面的button的点击事件中,调用models里的事件:

      const submit = () => {
        dispatch({
          type: 'login/fetch',
          payload: {
            userName: login.user,
            passWord: login.password,
          },
        });
      };
    

    所有的都完成之后,重启项目,点击button之后可以在命令行中看到:

    dva的mock@2x.png

    undefined的原因是,roadhog的版本问题,获取不到从前台传递过来的参数。

    Step.7 完善细节

    整体的流程已经完成,现在要为它添加一些细节,让它看起来更加的真实

    添加一个加载等待的圈圈,在点击button的同时显示,后台反馈结果后消失并跳转到下一个界面。

    <Spin spinning={login.loading}>
    ...
    </Spin>
    

    用Spin标签将其他的标签包起来,当它显示的时候,会出现在被包裹的标签之上。

    通过控制login.loading来控制Spin的显示与消失。

      reducers: {
        ...
        loadingShow(state) {
          return { ...state, loading: true};
        },
        loadingHide(state) {
          return { ...state, loading: false};
        }
      },
    

    最后在请求的时候完成这个流程

      effects: {
        *fetch({ payload }, { call, put }) { 
          if(!payload.userName || !payload.passWord) {
            message.error('请输入账号密码');
            return;
          }
          yield put({ type: 'loadingShow' });
          const { data } = yield call(service.login, payload);
          if(data.code === '200') {
            yield put({ type: 'loadingHide' });
            browserHistory.push('/home');
          }else {
            yield put({ type: 'loadingHide' });
            message.error('登录失败');
          }
        },
      },
    

    如果需要在显示登录页面之前执行某些操作,可以在subscriptions中订阅:

      subscriptions: {
        setup({ dispatch, history }) {
          history.listen((location) => {
            if (location.pathname === '/') {
              message.info('进入了登录页');
            }
          });
        },
      },
    

    总结

    这个登录页面,展示了从页面到请求的整个过程,虽然看起来有点绕,涉及了很多的页面。但�是当文件多了,就会体现出dva这样分层的好处:各个文件夹各司其职,功能单一。

    最后实现的效果:

    dva-learning.gif

    相关文章

      网友评论

          本文标题:Dva最不佳实践

          本文链接:https://www.haomeiwen.com/subject/lxegpctx.html