zoukankan      html  css  js  c++  java
  • dva上手

    dva是什么? 为什么要使用?怎么用?

      why dva?

        redux 像这种数据流的控制可以让应用更可控,以及让逻辑更清晰。但是概念太多,并且reducer,saga,action都是分离文件,带来的问题就是:编辑成本高,需要再reducer,saga,action之间来回切换; 不便于组织业务模型,比如我们写了一个userList之后,要写一个productlist,需要复制很多文件;书写麻烦。

      dva基于redux和redux-saga的数据流方案,为了简化开发体验,dva额外内置了react-router和fetch,也可以理解为一个轻量级的应用框架。

      基于现有的应用架构(redux+react-router+redux-saga)轻量封装

      dva是framework,不是library,明确地告诉每个部件应该怎么写,对于团队而言,会更可控。

    特性:

      易学易用,仅有6个api,对redux用户尤其友好,配合umi使用,降低为0API

      elm概念,通过reducer,effects和subscriptions组织model

      插件机制,比如dva-loading,可以自动处理loading状态,不用一遍遍写showLoading和hideLoading

      支持HMR(模块热替换),基于babel-plugin-dva-hmr实现components、routes和models的HMR

    初始化

    安装dva-cli用户初始化项目

    npm install -g dva-cli
    # 或
    yarn global add dva-cli
    dva -v
    dva-cli version 0.9.1

    创建新应用

    安装完dva-cli之后,可以在命令行里访问到dva命令。现在,可以通过dva new 创建新应用

    dva new dva-quickstart
    

    这会创建 dva-quickstart 目录,包含项目初始化目录和文件,并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。

    然后运行 npm start 或 yarn start 即可运行项目。

    目录结构

    项目初始化以后,默认的目录结构如下:

    |- 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 工具类库
      • router.js 路由文件
      • index.js 项目的入口文件
      • index.css 一般是共用的样式
    • .editorconfig 编辑器配置文件
    • .eslintrc ESLint配置文件
    • .gitignore Git忽略文件
    • .roadhogrc.mock.js Mock配置文件
    • .webpackrc 自定义的webpack配置文件,JSON格式,如果需要 JS 格式,可修改为 .webpackrc.js

    项目中使用antd

    通过npm安装antd和babel-plugin-import。babel-plugin-import是用来按需加载antd的脚本和样式。(详情https://ant.design/docs/react/getting-started-cn#%E6%8C%89%E9%9C%80%E5%8A%A0%E8%BD%BD

    npm install antd babel-plugin-import --save
    

    编辑 .webpackrc,使 babel-plugin-import 插件生效。

    {
      "extraBabelPlugins": [
        ["import", {
          "libraryName": "antd",
          "libraryDirectory": "es",
          "style": true
        }]
      ]
    }
    

      

    现在就可以按需引入 antd 的组件了,如 import { Button } from 'antd',Button 组件的样式文件也会自动帮你引入。

    更多 .webpackrc 的配置请参考 roadhog 配置。(https://github.com/sorrycc/roadhog/blob/master/README_zh-cn.md#%E9%85%8D%E7%BD%AE

    开发代理

    如需开发过程中代理API接口,在.webpackrc 中添加如下配置即可:

    {
      "proxy": {
        "/api": {
          "target": "http://your-api-server",
          "changeOrigin": true
        }
      }
    }
    

    .webpackrc 

     {
      "extraBabelPlugins": [  //项目中按需加载antd
        ["import", {
          "libraryName": "antd",
          "libraryDirectory": "es",
          "style": true
        }]
      ],
      "proxy": { // 请求代理
        "/api": {
          "target": "http://your-api-server",
          "changeOrigin": true
        }
      },
      "env": { //热更新
        "development": {
          "extraBabelPlugins": [
            "dva-hmr"
          ]
        }
      }
    }
    

      

    Mock

    如需mock功能,在 .roadhogrc.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.mock.js中引入。

    HMR

    HTR,即模块热替换,在修改代码后不需要刷新整个页面,方便开发时的调试。可以在.webpackrc 中添加如下配置以使用 HMR:

    {
      "env": {
        "development": {
          "extraBabelPlugins": [
            "dva-hmr"
          ]
        }
      }
    }
    

     

    如果无效,请尝试更新一下 babel-plugin-dva-hmr

    env 字段是针对特定环境进行配置,因为 HMR 只在开发环境下使用,所以将配置添加到 development 字段即可,运行 npm run build 时的环境变量为 production

    定义路由

    我们要写个应用来显示产品列表。首先第一步是创建路由,路由可以想象成组件应用的不同页面。

    新建 route component routes/Products.js,内容如下:

    import React from 'react';
    
    const Products = (props) => (
      <h2>List of Products</h2>
    );
    
    export default Products;
    

      添加路由信息到路由表,编辑 router.js :

    + import Products from './routes/Products';
    ...
    + <Route path="/products" exact component={Products} />
    

      然后在浏览器里打开 http://localhost:8000/#/products ,你应该能看到前面定义的 <h2> 标签

    编写 UI Component

    随着应用的不断拓展,需要多个页面共用公共组件,直接将组件抽离成公共组件,以在项目中实现共用。

    编写一个ProductList

    新建 components/ProductList.js 文件:

    import React from 'react';
    import PropTypes from 'prop-types';
    import { Table, Popconfirm, Button } from 'antd';
    
    const ProductList = ({ onDelete, products }) => {
      const columns = [{
        title: 'Name',
        dataIndex: 'name',
      }, {
        title: 'Actions',
        render: (text, record) => {
          return (
            <Popconfirm title="Delete?" onConfirm={() => onDelete(record.id)}>
              <Button>Delete</Button>
            </Popconfirm>
          );
        },
      }];
      return (
        <Table
          dataSource={products}
          columns={columns}
        />
      );
    };
    
    ProductList.propTypes = {
      onDelete: PropTypes.func.isRequired,
      products: PropTypes.array.isRequired,
    };
    
    export default ProductList;
    

    定义Model

    完成UI后,处理数据和逻辑。

    Model是dva最重要的部分,可以理解为redux、react-redux、redux-saga的封装。通常项目中一个模块对应一个model,

    dva通过Model的概念把一个领域的模型管理起来,包含同步更新state的reducers,处理异步逻辑的effects,订阅数据源的subscriptions

    export default {
      namespace: 'products',
      state: [],
      reducers: {
        'delete'(state, { payload: id }) {
          return state.filter(item => item.id !== id);
        },
      },
    };
    

      

    import { fetchUsers } from '../services/user';
    
    export default {
      namespace: 'user',
      state: {
        list: [],
      },
      reducers: {
        save(state, action) {
          return {
            ...state,
            list: action.data,
          };
        },
      },
      effects: {
        *fetch(action, { put, call }) {
          const users = yield put(fetchUsers, action.data);
          yield put({ type: 'save', data: users });
        },
      },
      subscriptions: {
        setup({ dispatch, history }) {
          return history.listen(({ pathname }) => {
            if (pathname === '/user') {
              dispatch({ type: 'fetch' });
            }
          });
        },
      },
    }
    

      

    这个 model 里:

    • namespace 表示在全局 state 上的 key,是该model的命名空间,同时也是全局state上的一个属性,只能是字符串,不支持使用.创建多层命名空间。
    • state 是初始值,在这里是空数组
    • reducers 等同于 redux 里的 reducer,接收 action,同步更新 state,纯函数,用于处理同步操作,是唯一可以修改state的地方,由action触发,它有state和action两个参数
    • effects用于处理异步操作,不能直接修改state,由action触发,也可以触发action。只能是generator函数,并且有action和effects两个参数。第二个参数effects包含put、call和select三个字段,put用于触发action,call用于调用异步处理逻辑,select用于从state中获取数据。
    • subscriptions 用于订阅某些数据源,并根据情况 dispatch 某些 action,格式为 ({ dispatch, history }, done) => unlistenFunction

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

    注意,在model中触发这个model中的action时不需要命名空间,比如在fetch中触发save时是{ type: 'save' }。在组件中触发action时就需要带上命名空间了,比如在某个组件中触发fetch时,应该是{ type: 'user/fetch' }

    然后别忘记在 index.js 里载入他:

    app.model(require('./models/products').default);
    

    connect 起来

    这里,我们已经单独完成了model和component,那么他们如何串联起来呢?

    dva提供了connect方法。如果你熟悉redux,这个connect就是react-redux的connect。

    编辑 routes/Products.js,替换为以下内容:

    import React from 'react';
    import { connect } from 'dva';
    import ProductList from '../components/ProductList';
    
    const Products = ({ dispatch, products }) => {
      function handleDelete(id) {
        dispatch({
          type: 'products/delete',
          payload: id,
        });
      }
      return (
        <div>
          <h2>List of Products</h2>
          <ProductList onDelete={handleDelete} products={products} />
        </div>
      );
    };
    
    // export default Products;
    export default connect(({ products }) => ({
      products,
    }))(Products);

    connect 后的组件除了可以获取到 dispatch 和 state,还可以获取到 location 和 history

      最后,我们还需要一些初始数据让这个应用 run 起来。编辑 index.js

    const app = dva({
    	initialState: {
    		products: [
    			{ name: 'dva', id: 1 },
    			{ name: 'antd', id: 2 },
    		],	
    	},
    });
    

      

    异步请求

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

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


    参考链接

  • 相关阅读:
    flask的类视图
    flask的消息提示flash和abort
    css—left和margin-left的区别
    Git(2)—特性
    JS 基础
    css
    搭建vue环境
    Git(1)—基础
    C中 输出格式
    软件测试_对于堆栈的单元测试
  • 原文地址:https://www.cnblogs.com/jcxfighting/p/11195496.html
Copyright © 2011-2022 走看看