初探dva——购物车实战

Dva简介

dva 是一个基于 redux 和 redux-saga的数据流方案,而且为了简化开发体验,dva 还额外内置了 react-router 和 fetch,因此也能够理解为dva是一个轻量级的应用框架。 -- 摘自官网

购物车实例

本练习使用dva+antd作了个购物车小练习,目的是为了更快上手dva,因此练习的复杂度不高,更可能是基础运用开发。 项目参考来源

演示网址
github代码javascript

基本配置

  • 安装dva脚手架
    npm install dva-cli -g
  • 建立新项目
    dva new shopping-cart-dva
    cd shopping-cart-dva
  • 在dva项目里使用antdcss

    • yarn add antd babel-plugin-import
    • 编辑.webpackrc,使babel-plugin-import插件生效前端

      {
          "extraBabelPlugins": [
              ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
          ]
      }
  • 编辑.webpackrc,配置proxy属性,代理请求java

    "proxy": {
        "/api": {
            "target": "http://localhost:3000", // 后端接口
            "changeOrigin": true,
            // "pathRewrite": { "^/api" : "" }
        }
    }

搭建后台

本次练习主要为了熟悉dva作状态管理,对后台要求不高,只是简单地返回商品数据
  • yarn add cors express
  • 新建server文件夹,用express搭建一个简单的后台node

    • 新建data/products.json,存放json格式的商品数据react

      {
        "products": [
          {
            "id": 12,
            "bigImage":"http://localhost:3000/images/12064273040195392_1.jpg",
            "smallImage":     "http://localhost:3000/images/12064273040195392_2.jpg",
            "sku": 12064273040195392,
            "title": "Cat Tee Black T-Shirt",
            "description": "4 MSL",
            "availableSizes": ["S", "XS"],
            "style": "Black with custom print",
            "price": 10.9,
            "installments": 9,
            "currencyId": "USD",
            "currencyFormat": "$",
            "isFreeShipping": true
          }
        ]
      }
    • 新建app.js,并编辑webpack

      const path = require('path');
      const express = require('express');
      const cors = require('cors');
      const app = express();
      
      app.use(cors());
      
      const port = 3000;
      
      app.get('/api/products', (req, res) => {
        res.sendFile(path.join(__dirname, 'data', 'products.json'));
      });
      
      app.listen(port, () => {
        console.log(`[products] API listening on port ${port}.`);
      });

前台开发

  • 本次练习使用concurrently并行命令git

    • yarn add concurrently
    • 编辑package.jsongithub

      "scripts": {
          "start": "roadhog server", 
          "server": "nodemon server/app",
          "build": "roadhog build",
          "lint": "eslint --ext .js src test",
          "precommit": "yarn run lint",
          "dev": "concurrently \"yarn run server\" \"yarn run start\""
      },
    • 到此,咱们能够直接使用yarn run dev命令同时启动先后端,在http://localhost:8000访问
  • 建立商品列表路由web

    • 新建src\routes\products\index.jsindex.css (dva默认支持CSS Module模块化, 若是须要使用connect()方法,须要从dva中引入)

      import React, {Component} from 'react';
      import { connect } from 'dva';  // 引入connect()
      // import { Badge, Icon } from 'antd';  在组件中使用antd
      import styles from './index.css';  // 按需引入css
      
      class Products extends Component {
        constructor(){
          super();
        }
      
        render(){
          return (
            <div className={styles.container}>
              soooo many products!!!
            </div>
          );
        }
      };
      
      // 这里的products是namespace为products的model层
      const mapStateToProps = ({ products })=>{
        return {
          counts: products.counts
        }
      };
      // 跟react-redux的connect使用方法类似
      export default connect(mapStateToProps)(Products);
    • 编辑src/router.js

      import React from 'react';
          import { Router, Route, Switch } from 'dva/router';
          import IndexPage from './routes/IndexPage';
          import Products from './routes/products';
          
          // 使用hash前端路由模式
          function RouterConfig({ history }) {
            return (
              <Router history={history}>
                <Switch>
                  <Route path="/" exact component={IndexPage} />
                  <Route path="/products" exact component={Products} />
                  </Switch>
              </Router>
            );
          }
          
          export default RouterConfig;
  • dva已经在src/utils/request.js中为咱们封装了fetch请求。因此咱们能够新建src/services/products.js来封装一个向后台请求商品数据的方法

    import request from '../utils/request';
    
    export function fetch() {
      return request('/api/products');
    }
  • dva 经过 model 的概念把一个领域的模型管理起来,包含同步更新 state 的 reducers,处理异步逻辑的 effects,订阅数据源的 subscriptions

    新建src/models/products.js

    import * as productsService from '../services/products';
        
    export default {
        namespace: 'products',
        state: {
            list: [],  // 商品列表
        },
        reducers: {
            save(state, { payload: { data: list } }) {
                return { ...state, list };
            }
        },
        effects: {
            *fetch({}, { call, put }) {
                const { data } = yield call(productsService.fetch);
                yield put({
                    type: 'save',
                    payload: { data: data.products },
                });
            },
        },
        subscriptions: {
            setup({ dispatch, history }) {
                return history.listen(({ pathname }) => {
                    if (pathname === '/products') {
                      dispatch({ type: 'fetch' });
                    }
                });
            },
        },
    };

    到此,model层和service层咱们已经简单搭建好,能够在组件中拿到商品数据

  • 拓展:model中的subscription订阅

    • model中的subscription至关于一个监听器,能够监听路由变化,鼠标,键盘变化,服务器链接变化,状态变化等,这样在其中就能够根据不一样的变化作出相应的处理,在这个subsription中的方法名是随意定的,每次变化都会一次去调用里面的全部方法,因此一边会加相应的判断
  • dva-loading插件
    在实际开发中,因为存在向后端请求商品数据,可能不能第一时间拿到商品数据,因此每每在数据加载的过程当中,咱们会给用户一个“正在加载中...”的提示。而dva-loading插件能够帮咱们简化这一过程。

    • yarn add dva-loading
    • 修改src/index.js

      import createLoading from 'dva-loading';
      app.use(createLoading());
    • 接着咱们就能够在组件中使用

      const ProductsList = ({ list, loading }) => {
          ...
          // loading的值即为true,false
          // 插件会自动设置数据里的 loading 状态为 true 或 false 
          // 咱们能够经过loading的值判断是否正处于加载数据的状态,从而给用户一些提示
      }
      
      export default connect(({ products, loading }) => ({
        list: products.list,
        // 注意:products为所须要的model层的namespace
        loading: loading.models.products
      }))(ProductsList);
相关文章
相关标签/搜索