初探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);
相關文章
相關標籤/搜索