dva的進一步學(xué)習(xí)創(chuàng)建UCRD項目(簡書版)


title: dva的進一步學(xué)習(xí)創(chuàng)建UCRD項目(簡書版)
date: 2018-05-31 01:07:50
tags: dva


目錄

準(zhǔn)備工作

劃分結(jié)構(gòu)

設(shè)計model

在了解了項目基本的結(jié)構(gòu)劃分以后尘喝,我們將要開始設(shè)計 model瘩缆,在設(shè)計 model 之前狠怨,我們來回顧一下我們需要做的項目是什么樣的:

pic1

Model的抽象

從設(shè)計稿中我們可以看出糊啡,這部分功能基本是圍繞 以用戶數(shù)據(jù)為基礎(chǔ) 的操作泣懊,其中包含:

  1. 用戶信息的展示(查詢)
  2. 用戶信息的操作(增加嚷辅,刪除,修改

我們在項目的model 下新增一個users.js

我們可以按照兩個緯度的東西來看:1. 按照數(shù)據(jù)緯度 2. 按照業(yè)務(wù)緯度

數(shù)據(jù)緯度

按照數(shù)據(jù)維度的 model 設(shè)計原則就是抽離數(shù)據(jù)本身以及相關(guān)操作的方法脖隶,比如在本例的 users :

/**
 * model users.js的設(shè)計扁耐、
 * 按照數(shù)據(jù)維度的 model 設(shè)計原則就是抽離數(shù)據(jù)本身以及相關(guān)操作的方法,
 */
 export default {
     namespace:'users',
     state:{
         list:[],
         total:null,
     },
     effects:{
        *query(){},
        *create(){},
        // 因為delete是關(guān)鍵字
        *'delete'(){},
        *update(){},
     },
     reducers: {
        querySuccess(){},
        createSuccess(){},
        deleteSuccess(){},
        updateSuccess(){},
    }
 }

如果你寫過后臺代碼产阱,你會發(fā)現(xiàn)這跟我們常常寫的后臺接口是很類似的婉称,只關(guān)心數(shù)據(jù)本身,至于在使用 users model 的組件中所遇到的狀態(tài)管理等信息跟 model 無關(guān),而是作為組件自身的state維護王暗。

這種設(shè)計方式使得 model 很純粹悔据,在設(shè)計通用數(shù)據(jù)信息 model 的時候比較適用,比如當(dāng)前用戶登陸信息等數(shù)據(jù) model俗壹。但是在數(shù)據(jù)跟業(yè)務(wù)狀態(tài)交互比較緊密科汗,數(shù)據(jù)不是那么獨立的時候會有些不那么方便,因為在數(shù)據(jù)跟業(yè)務(wù)狀態(tài)緊密相連的場景下绷雏,將狀態(tài)放到 model 里面維護會使得我們的代碼更加清晰可控头滔,而這種方式就是下面將要介紹的業(yè)務(wù)維度方式的設(shè)計。

業(yè)務(wù)緯度

按照業(yè)務(wù)維度的 model 設(shè)計涎显,則是將數(shù)據(jù)以及使用強關(guān)聯(lián)數(shù)據(jù)的組件中的狀態(tài)統(tǒng)一抽象成 model 的方法坤检,(更多會考慮交互上的方法,比如loading 的展示或者消失期吓,modal的展示或者消失早歇,在這個例子中,我們的組件會使用ant-design中的設(shè)計)在本例中讨勤,users model設(shè)計如下:

/**
 * model users.js的設(shè)計箭跳、
 * 按照數(shù)據(jù)維度的 model 設(shè)計原則就是抽離數(shù)據(jù)本身以及相關(guān)操作的方法,
 */

 export default {
     namespace:'users',
     state:{
         list:[],
         total:null,
         loading: false, // 控制加載狀態(tài)
         current: null, // 當(dāng)前分頁信息
         currentItem: {}, // 當(dāng)前操作的用戶對象
         modalVisible: false, // 彈出窗的顯示狀態(tài)
         modalType: 'create', // 彈出窗的類型(添加用戶潭千,編輯用戶)
     },
     effects:{
        *query(){},
        *create(){},
        // 因為delete是關(guān)鍵字
        *'delete'(){},
        *update(){},
     },
     reducers: {
        //業(yè)務(wù)緯度的設(shè)計(更多會考慮交互上的問題)
        showLoading(){}, // 控制加載狀態(tài)的 reducer
        showModal(){}, // 控制 Modal 顯示狀態(tài)的 reducer
        hideModal(){},
        //數(shù)據(jù)緯度的設(shè)計
        querySuccess(){},
        createSuccess(){},
        deleteSuccess(){},
        updateSuccess(){},
    }
 }

組件設(shè)計方法

在初步確定了 model 的設(shè)計方法以后谱姓,讓我們來看看如何設(shè)計 dva 中的 React 組件。

React 應(yīng)用是由一個個獨立的 Component 組成的脊岳,我們在拆分 Component 的過程中要盡量讓每個 Component 專注做自己的事逝段。(這也是react的核心思想,組件化的思想割捅,在設(shè)計和使用的時候,我們拆分組件帚桩,讓組件做自己的事情亿驾。)

一般來說,我們的組件有兩種設(shè)計:

  • Container Component
  • Presentational Component

Container Component一般指的是具有監(jiān)聽數(shù)據(jù)行為的組件账嚎,一般來說它們的職責(zé)是綁定相關(guān)聯(lián)的 model 數(shù)據(jù)莫瞬。以數(shù)據(jù)容器的角色包含其它子組件。

通常的寫法為:

import React, { Component, PropTypes } from 'react';

// dva 的 connect 方法可以將組件和數(shù)據(jù)關(guān)聯(lián)在一起
import { connect } from 'dva';

// 組件本身
const MyComponent = (props)=>{};
MyComponent.propTypes = {};

// 監(jiān)聽屬性郭蕉,建立組件和數(shù)據(jù)的映射關(guān)系
function mapStateToProps(state) {
  return {...state.data};
}

// 關(guān)聯(lián) model
export default connect(mapStateToProps)(MyComponent);

Presentational Component 的名稱已經(jīng)說明了它的職責(zé)疼邀,展示形組件,一般也稱作:Dumb Component召锈,它不會關(guān)聯(lián)訂閱 model 上的數(shù)據(jù)旁振,而所需數(shù)據(jù)的傳遞則是通過 props 傳遞到組件內(nèi)部。

通常寫法為:

import React, { Component, PropTypes } from 'react';

// 組件本身
// 所需要的數(shù)據(jù)通過 Container Component 通過 props 傳遞下來
const MyComponent = (props)=>{}
MyComponent.propTypes = {};

// 并不會監(jiān)聽數(shù)據(jù)
export default MyComponent;
那么拆分組件有什么好處呢?
  • 讓項目的數(shù)據(jù)處理更加集中拐袜;
  • 讓組件高內(nèi)聚低耦合吉嚣,更加聚焦;

試想如果每個組件都去訂閱數(shù)據(jù) model蹬铺,那么一方面組件本身跟 model 耦合太多尝哆,另一方面代碼過于零散,到處都在操作數(shù)據(jù)甜攀,會帶來后期維護的煩惱秋泄。

除了寫法上訂閱數(shù)據(jù)的區(qū)別以外,在設(shè)計思路上兩個組件也有很大不同规阀。 Presentational Component是獨立的純粹的印衔,這方面很好的例子,大家可以參考 ant.design UI組件的React實現(xiàn) 姥敛,每個組件跟業(yè)務(wù)數(shù)據(jù)并沒有耦合關(guān)系奸焙,只是完成自己獨立的任務(wù),需要的數(shù)據(jù)通過 props 傳遞進來彤敛,需要操作的行為通過接口暴露出去与帆。 而 Container Component 更像是狀態(tài)管理器,它表現(xiàn)為一個容器墨榄,訂閱子組件需要的數(shù)據(jù)玄糟,組織子組件的交互邏輯和展示。

組件設(shè)計實踐

首先啊袄秩,咱們需要先創(chuàng)建路由阵翎,設(shè)置 Users Router Container 的訪問路徑,并且在 /routes/ 下創(chuàng)建我們的組件文件 Users.jsx之剧。

// .src/router.js
import React, { PropTypes } from 'react';
import { Router, Route } from 'dva/router';
import Users from './routes/Users';

export default function({ history }) {
  return (
    <Router history={history}>
      <Route path="/users" component={Users} />
    </Router>
  );
};

之后創(chuàng)建路由容器組件users.jsx

// .src/routes/Users.jsx
import React, { PropTypes } from 'react';

function Users() {
  return (
    <div>User Router Component</div>
  );
}

Users.propTypes = {
};

export default Users;

Users Container Component 的設(shè)計

我們采用自頂向下的設(shè)計方法郭卫,修改 ./src/routes/Users.jsx

// ./src/routes/Users.jsx
import React, { Component, PropTypes } from 'react';

// Users 的 Presentational Component
// 暫時都沒實現(xiàn)
import UserList from '../components/Users/UserList';
import UserSearch from '../components/Users/UserSearch';
import UserModal from '../components/Users/UserModal';

// 引入對應(yīng)的樣式
// 可以暫時新建一個空的
import styles from './Users.less';

function Users() {

  const userSearchProps = {};
  const userListProps = {};
  const userModalProps = {};

  return (
    <div className={styles.normal}>
      {/* 用戶篩選搜索框 */}
      <UserSearch {...userSearchProps} />
      {/* 用戶信息展示列表 */}
      <UserList {...userListProps} />
      {/* 添加用戶 & 修改用戶彈出的浮層 */}
      <UserModal {...userModalProps} />
    </div>
  );
}

export default Users;

創(chuàng)建UserList,UserSearch,UserModal這三個組件

// ./src/components/Users/UserSearch.jsx
import React, { PropTypes } from 'react';
export default ()=><div>user search</div>;
// ./src/components/Users/UserList.jsx
import React, { PropTypes } from 'react';
export default ()=><div>user list</div>;
// ./src/components/Users/UserModal.jsx
import React, { PropTypes } from 'react';
export default ()=><div>user modal</div>;

這樣如果你到現(xiàn)在都是成功的話,那么訪問本地的地址背稼,如:http://localhost:8000/#/users 應(yīng)該是展示

user search
user list
user modal

這樣的數(shù)據(jù)的贰军。

值得注意的地方,通常定義我們的組件一般有三種方式:

// 1. 傳統(tǒng)寫法
const App = React.createClass({});

// 2. es6 的寫法
class App extends React.Component({});

// 3. stateless 的寫法(我們推薦的寫法)
const App = (props) => ({});

其中第1種是我們不推薦的寫法蟹肘,第2種是在你的組件涉及 react 的生命周期方法的時候采用這種寫法词疼,而第3種則是我們一般推薦的寫法。詳細(xì)內(nèi)容可以參看Stateless Functions帘腹。

UserList 組件

暫時放下<UserSearch />和<UserModal />贰盗,先來看看<UserList />的實現(xiàn),這是一個用戶的展示列表阳欲,我們期望只需要把數(shù)據(jù)傳入進去舵盈,修改 ./src/components/Users/UserList.jsx:

這里我們采用antd UI組件

import { Table, message, Popconfirm } from 'antd';

這里我們不難發(fā)現(xiàn)陋率。我們使用Table組件,因此需要傳入一些參數(shù)书释。這里我們使用了stateless的寫法

// ./src/components/Users/UserList.jsx
import React, { Component, PropTypes } from 'react';

// 采用antd的UI組件
import { Table, message, Popconfirm } from 'antd';

// 采用 stateless 的寫法
// export default ()=><div>user list</div>;


const UserList = ({
    total,
    current,
    loading,
    dataSource,
}) => {
    const columns = [{
        title: '姓名',
        dataIndex: 'name',
        key: 'name',
        render: (text) => <a href="#">{text}</a>,
      }, {
        title: '年齡',
        dataIndex: 'age',
        key: 'age',
      }, {
        title: '住址',
        dataIndex: 'address',
        key: 'address',
      }, {
        title: '操作',
        key: 'operation',
        render: (text, record) => (
          <p>
            <a onClick={()=>{}}>編輯</a>
            &nbsp;
            <Popconfirm title="確定要刪除嗎翘贮?" onConfirm={()=>{}}>
              <a>刪除</a>
            </Popconfirm>
          </p>
        ),
      }];


      //定義分頁對象

      const pagination = {
        total,
        current,
        pageSize: 10,
        onChange: ()=>{},
      };

      return (
        <div>
          <Table
            columns={columns}
            dataSource={dataSource}
            loading={loading}
            rowKey={record => record.id}
            pagination={pagination}
          />
        </div>
      );



}
export default UserList;

需要注意的是,由于我們采用了 antd爆惧,所以我們需要在我們的代碼中添加樣式狸页,可以在 ./src/index.js 中添加一行:

import 'antd/dist/antd.css'

接下來。由于我們傳遞了 columns 扯再,dataSource 芍耘,loading ,rowKey熄阻,pagination等props斋竞,因此,我們需要制造一些假數(shù)據(jù)傳入秃殉,使得頁面更真實坝初。

在routes/Users.jsx中,新增假數(shù)據(jù)

  const userListProps = {
    //默認(rèn)先傳一些假數(shù)據(jù)
    total: 3,
    current: 1,
    loading: false,
    dataSource: [
      {
        name: '張三',
        age: 23,
        address: '成都',
      },
      {
        name: '李四',
        age: 24,
        address: '杭州',
      },
      {
        name: '王五',
        age: 25,
        address: '上海',
      },
    ],

  };

最終展現(xiàn)的效果圖如下所示:

效果圖

組件設(shè)計小結(jié)

雖然我們上面實現(xiàn)的代碼很簡單钾军,但是已經(jīng)包含了組件設(shè)計的主要思路鳄袍,可以看到 UserList 組件是一個很純粹的 Presentation Component,所需要的數(shù)據(jù)以及狀態(tài)是通過 Users Router Component 傳遞的吏恭,我們現(xiàn)在還是用的靜態(tài)數(shù)據(jù)拗小,接下來我們來看看如何在 model 創(chuàng)建 reducer 來將我們的數(shù)據(jù)抽象出來。

添加Reducer

如果對redux有所了解的話樱哼,那么就能很好的理解 Reducer哀九,在這里我做個不恰當(dāng)?shù)谋确剑帽壤钤讫堌?fù)責(zé)帶兵打仗搅幅,那么李云龍在整個react中其實是負(fù)責(zé)展示這一塊阅束,我們知道軍隊還有后勤這一塊,那么趙政委就是負(fù)責(zé)后勤這一塊盏筐,告訴李云龍围俘,這里不需要你負(fù)責(zé),你只要展示琢融,他們?nèi)绻故荆绻倏匚襾碡?fù)責(zé)簿寂。其實趙政委就是redux 漾抬。而Reducer 是主要控制狀態(tài)改變 也就是我們所說的 state,而發(fā)出改變的命令是action來操控的常遂。這也就有了dispatch的概念纳令,dispatch其實就是命令。當(dāng)然之后react-redux優(yōu)化了這一塊,使得我們在改變數(shù)據(jù)源的時候更加的方便平绩。

給UserModal添加Reducers

回到我們之前的 /models/users.js圈匆,我們在之前已經(jīng)定義好了它的 state,接下來我們看看如何根據(jù)新的數(shù)據(jù)來修改本身的 state捏雌,這就是 reducers 要做的事情跃赚。

export default {
  namespace: 'users',

  state: {
    list: [],
    total: null,
    loading: false, // 控制加載狀態(tài)
    current: null, // 當(dāng)前分頁信息
    currentItem: {}, // 當(dāng)前操作的用戶對象
    modalVisible: false, // 彈出窗的顯示狀態(tài)
    modalType: 'create', // 彈出窗的類型(添加用戶,編輯用戶)
  },
  effects: {
    *query(){},
    *create(){},
    *'delete'(){},
    *update(){},
  },
  reducers: {
    showLoading(){}, // 控制加載狀態(tài)的 reducer
    showModal(){}, // 控制 Modal 顯示狀態(tài)的 reducer
    hideModal(){},
    // 使用靜態(tài)數(shù)據(jù)返回
    querySuccess(state){
      const mock = {
        total: 3,
        current: 1,
        loading: false,
        list: [
          {
            id: 1,
            name: '張三',
            age: 23,
            address: '成都',
          },
          {
            id: 2,
            name: '李四',
            age: 24,
            address: '杭州',
          },
          {
            id: 3,
            name: '王五',
            age: 25,
            address: '上海',
          },
        ],

      };
      return {...state, ...mock, loading: false};
    },
    createSuccess(){},
    deleteSuccess(){},
    updateSuccess(){},
  }
}

我們把之前 UserList 組件中模擬的靜態(tài)數(shù)據(jù)性湿,移動到了 reducers 中纬傲,通過調(diào)用 users/query/success 這個 reducer,我們就可以將 Users Model 的數(shù)據(jù)變成靜態(tài)數(shù)據(jù)肤频,那么我們?nèi)绾握{(diào)用這個 reducer叹括,能夠讓這個數(shù)據(jù)傳入 UserList 組件呢,接下來需要做的是:關(guān)聯(lián)Model宵荒。

關(guān)聯(lián)Model

// ./src/routes/Users.jsx
import React, { Component, PropTypes } from 'react';

// 引入 connect 工具函數(shù)
import { connect } from 'dva';

// Users 的 Presentational Component
// 暫時都沒實現(xiàn)
import UserList from '../components/Users/UserList';
import UserSearch from '../components/Users/UserSearch';
import UserModal from '../components/Users/UserModal';

// 引入對應(yīng)的樣式
// 可以暫時新建一個空的
import styles from './Users.less';

function Users({ location, dispatch, users }) {

  const {
    loading, list, total, current,
    currentItem, modalVisible, modalType
    } = users;

  const userSearchProps={};
  const userListProps={
        dataSource: list,
        total,
        loading,
        current,
    };
  const userModalProps={};

  return (
    <div className={styles.normal}>
      {/* 用戶篩選搜索框 */}
      <UserSearch {...userSearchProps} />
      {/* 用戶信息展示列表 */}
      <UserList {...userListProps} />
      {/* 添加用戶 & 修改用戶彈出的浮層 */}
      <UserModal {...userModalProps} />
    </div>
  );
}

Users.propTypes = {
  users: PropTypes.object,
};

// 指定訂閱數(shù)據(jù)汁雷,這里關(guān)聯(lián)了 users
function mapStateToProps({ users }) {
  return {users};
}

// 建立數(shù)據(jù)關(guān)聯(lián)關(guān)系
export default connect(mapStateToProps)(Users);

在之前的 組件設(shè)計 中講到了 Presentational Component 的設(shè)計概念,在訂閱了數(shù)據(jù)以后报咳,就可以通過 props 訪問到 model 的數(shù)據(jù)了侠讯,而 UserList 展示組件的數(shù)據(jù),也是 Container Component 通過 props 傳遞的過來的少孝。

組件和 model 建立了關(guān)聯(lián)關(guān)系以后继低,如何在組件中獲取 reduers 的數(shù)據(jù)呢,或者如何調(diào)用 reducers呢稍走,就是需要發(fā)起一個 action袁翁。

Action

actions 的概念跟 reducers 一樣,也是來自于 dva 封裝的 redux婿脸,表達(dá)的概念是發(fā)起一個修改數(shù)據(jù)的行為粱胜,主要的作用是傳遞信息:

dispatch({
    type: '', // action 的名稱,與 reducers(effects)對應(yīng)
    ... // 調(diào)用時傳遞的參數(shù)狐树,在 reducers(effects)可以獲取
});

需要注意的是:action的名稱(type)如果是在 model 以外調(diào)用需要添加 namespace焙压。

回到例子中,目前傳入 UserList 組件的只是默認(rèn)空數(shù)據(jù)抑钟,那么如何調(diào)用 reducers 獲取剛才定義的靜態(tài)數(shù)據(jù)呢涯曲?發(fā)起一個 actions:

dispatch({
    type: 'users/querySuccess', // 調(diào)用一個actions
    payload: {}, // 調(diào)用時傳遞的參數(shù)
});

知道了如何發(fā)起一個 action,那么剩下的就是發(fā)起的時機了在塔,通常我們建議在組件內(nèi)部的生命周期發(fā)起幻件,如:

componentDidMount() {
    this.props.dispatch({
        type: 'model/action',
    });
}

不過在本例中采用另一種發(fā)起 action 的場景,在本例中獲取用戶數(shù)據(jù)信息的時機就是訪問 /users/ 這個頁面蛔溃,所以我們可以監(jiān)聽路由信息绰沥,只要路徑是 /users/ 那么我們就會發(fā)起 action篱蝇,獲取用戶數(shù)據(jù):

// ./src/models/users.js
import { hashHistory } from 'dva/router';

export default {
  namespace: 'users',

  state: {
    list: [],
    total: null,
    loading: false, // 控制加載狀態(tài)
    current: null, // 當(dāng)前分頁信息
    currentItem: {}, // 當(dāng)前操作的用戶對象
    modalVisible: false, // 彈出窗的顯示狀態(tài)
    modalType: 'create', // 彈出窗的類型(添加用戶,編輯用戶)
  },

  // Quick Start 已經(jīng)介紹過 subscriptions 的概念徽曲,這里不在多說
  subscriptions: {
    setup({ dispatch, history }) {
      history.listen(location => {
        if (location.pathname === '/users') {
          dispatch({
            type: 'querySuccess',
            payload: {}
          });
        }
      });
    },
  },

  effects: {
    *query(){},
    *create(){},
    *'delete'(){},
    *update(){},
  },
  reducers: {
    showLoading(){}, // 控制加載狀態(tài)的 reducer
    showModal(){}, // 控制 Modal 顯示狀態(tài)的 reducer
    hideModal(){},
    // 使用靜態(tài)數(shù)據(jù)返回
    querySuccess(state){
      const mock = {
        total: 3,
        current: 1,
        loading: false,
        list: [
          {
            name: '張三',
            age: 23,
            address: '成都',
          },
          {
            name: '李四',
            age: 24,
            address: '杭州',
          },
          {
            name: '王五',
            age: 25,
            address: '上海',
          },
        ],

      };
      return {...state, ...mock, loading: false};
    },
    createSuccess(){},
    deleteSuccess(){},
    updateSuccess(){},
  }
}

以上代碼在瀏覽器訪問 /users 路徑的時候就會發(fā)起一個 action零截,數(shù)據(jù)準(zhǔn)備完畢,別忘了回到 index.js 中秃臣,添加我們的 models:

// ./src/index.js
import './index.html';
import './index.less';
import dva, { connect } from 'dva';

import 'antd/dist/antd.css';

// 1. Initialize
const app = dva();

// 2. Model
app.model(require('./models/users.js'));

// 3. Router
app.router(require('./router'));

// 4. Start
app.start(document.getElementById('root'));

這樣我們依舊可以看到如下的效果:

效果圖

小結(jié)

在這個例子中涧衙,我們在 合適的時機(進入 /users/ )發(fā)起(dispatch)了一個 action,修改了 model 的數(shù)據(jù)甜刻,并且通過 Container Components 關(guān)聯(lián)了 model绍撞,通過 props 傳遞到 Presentation Components,組件成功顯示得院。如果你想了解更多關(guān)于 reducers & actions 的信息傻铣,可以參看 redux。

Effects

在之前的教程中祥绞,我們已經(jīng)完成了靜態(tài)數(shù)據(jù)的操作非洲,但是在真實場景,數(shù)據(jù)都是從服務(wù)器來的蜕径,我們需要發(fā)起異步請求两踏,在請求回來以后設(shè)置數(shù)據(jù),更新 state兜喻,那么在 dva 中梦染,這一切是怎么操作的呢,首先我們先來簡單了解一下 Effects朴皆。

Effects 來源于 dva 封裝的底層庫 redux-sagas 的概念帕识,主要指的是處理 Side Effects ,指的是副作用(源于函數(shù)式編程)遂铡,在這里可以簡單理解成異步操作肮疗,所以我們是不是可以理解成 Reducers 處理同步,Effects 處理異步扒接?這么理解也沒有問題伪货,但是要認(rèn)清 Reducers 的本質(zhì)是修改 model 的 state,而 Effects 主要是 控制數(shù)據(jù)流程 钾怔,所以最終往往我們在 Effects 中會調(diào)用 Reducers碱呼。

給Users Model添加Effects

// ./src/models/users.js
import { hashHistory } from 'dva/router';
//import { create, remove, update, query } from '../services/users';

// 處理異步請求
import request from '../utils/request';
import qs from 'qs';
async function query(params) {
  return request(`/api/users?${qs.stringify(params)}`);
}

export default {
  namespace: 'users',

  state: {
    list: [],
    total: null,
    loading: false, // 控制加載狀態(tài)
    current: null, // 當(dāng)前分頁信息
    currentItem: {}, // 當(dāng)前操作的用戶對象
    modalVisible: false, // 彈出窗的顯示狀態(tài)
    modalType: 'create', // 彈出窗的類型(添加用戶,編輯用戶)
  },

  subscriptions: {
    setup({ dispatch, history }) {
      history.listen(location => {
        if (location.pathname === '/users') {
          dispatch({
            type: 'query',
            payload: {}
          });
        }
      });
    },
  },

  effects: {
    *query({ payload }, { select, call, put }) {
      yield put({ type: 'showLoading' });
      const { data } = yield call(query);
      if (data) {
        yield put({
          type: 'querySuccess',
          payload: {
            list: data.data,
            total: data.page.total,
            current: data.page.current
          }
        });
      }
    },
    *create(){},
    *'delete'(){},
    *update(){},
  },
  reducers: {
    showLoading(state, action){
      return { ...state, loading: true };
    }, // 控制加載狀態(tài)的 reducer
    showModal(){}, // 控制 Modal 顯示狀態(tài)的 reducer
    hideModal(){},
    // 使用服務(wù)器數(shù)據(jù)返回
    querySuccess(state, action){
      return {...state, ...action.payload, loading: false};
    },
    createSuccess(){},
    deleteSuccess(){},
    updateSuccess(){},
  }
}

首先我們需要增加 *query 第二個參數(shù) *query({ payload }, { select, call, put }) 宗侦,其中 call 和 put 是 dva 提供的方便操作 effects 的函數(shù)巍举,簡單理解 call 是調(diào)用執(zhí)行一個函數(shù)而 put 則是相當(dāng)于 dispatch 執(zhí)行一個 action,而 select 則可以用來訪問其它 model凝垛,更多可以參看 redux-saga-in-chinese懊悯。

而在 query 函數(shù)里面,可以看到我們處理異步的方式跟同步一樣梦皮,所以能夠很好的控制異步流程炭分,這也是我們使用 Effects 的原因,關(guān)于相關(guān)的更多內(nèi)容可以參看 Generator 函數(shù)的含義與用法剑肯。

這里我們把請求的處理直接寫在了代碼里面捧毛,接下來我們需要把它拆分到 /services/ 里面統(tǒng)一處理

定義Services

之前我們已經(jīng):

  1. 設(shè)計好了 model state -> 抽象數(shù)據(jù)
  2. 完善了組件 -> 完善展示
  3. 添加了 Reducers -> 數(shù)據(jù)同步處理
  4. 添加了 Effects -> 數(shù)據(jù)異步處理

接下來就是將請求相關(guān)(與后臺系統(tǒng)的交互)抽離出來,單獨放到 /services/ 中让网,進行統(tǒng)一維護管理呀忧,所以我們只需要將之前定義在 Effects 的以下代碼,移動到 /services/users.js 中即可:

import request from '../utils/request';

import qs from 'qs';

export async function query(params) {
    return request(`/api/users?${qs.stringify(params)}`);
}

然后在 users model 中引入:

import { query } from '../services/users';

之后無論是更新溃睹,刪除而账、添加等操作,跟用戶相關(guān)的都可以統(tǒng)一放置在 /services/users.js 中因篇。

沒錯泞辐,我們雖然有了接口,但是我們還沒有數(shù)據(jù)竞滓,在 dva 中咐吼,我們配套的工具能夠很方便的模擬數(shù)據(jù),這樣就可以脫離服務(wù)器復(fù)雜的環(huán)境進行模擬的本地調(diào)試開發(fā)商佑。下面一節(jié)就會一起來看下锯茄,如何 mock 數(shù)據(jù)。

mock數(shù)據(jù)

我們采用了dora-plugin-proxy工具來完成了我們的數(shù)據(jù) mock 功能茶没。

roadhog

如果不用dora-plugin-proxy 肌幽,我們可以采用roadhog。Roadhog 是一個包含 dev礁叔、build 和 test 的命令行工具牍颈,他基于 react-dev-utils,和 create-react-app 的體驗保持一致琅关。你可以想象他為可配置版的 create-react-app煮岁。

Features

  • ?? 開箱即用的 react 應(yīng)用開發(fā)工具,內(nèi)置 css-modules涣易、babel画机、postcss、HMR 等
  • ?? create-react-app 的體驗
  • ?? JSON 格式的 webpack 配置
  • ?? mock
  • ?? 基于 jest 的 test新症,包括 UI 測試(基于 enzyme)

遺留問題

目前不知是不是proxy 的問題步氏,導(dǎo)致無法獲取到請求到數(shù)據(jù)。先上傳代碼徒爹。該問題之后有空來解決荚醒。

參考的文檔上芋类,給出的源碼已經(jīng)更改,我的github上的代碼是根據(jù)文檔來的界阁,但是無法獲取到數(shù)據(jù)侯繁。

github地址在這里。

問題報錯

app.model: namespace should be defined

遇到的問題查詢github的issue解決

參考文獻(xiàn)

redux-sagas中文文檔

參考文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泡躯,一起剝皮案震驚了整個濱河市贮竟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌较剃,老刑警劉巖咕别,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異写穴,居然都是意外死亡惰拱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門确垫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弓颈,“玉大人,你說我怎么就攤上這事删掀∠杓剑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵披泪,是天一觀的道長纤子。 經(jīng)常有香客問我,道長款票,這世上最難降的妖魔是什么控硼? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮艾少,結(jié)果婚禮上卡乾,老公的妹妹穿的比我還像新娘。我一直安慰自己缚够,他們只是感情好幔妨,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著谍椅,像睡著了一般误堡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雏吭,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天锁施,我揣著相機與錄音,去河邊找鬼。 笑死悉抵,一個胖子當(dāng)著我的面吹牛肩狂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播基跑,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼婚温,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了媳否?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤荆秦,失蹤者是張志新(化名)和其女友劉穎篱竭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體步绸,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡掺逼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瓤介。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吕喘。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖刑桑,靈堂內(nèi)的尸體忽然破棺而出氯质,到底是詐尸還是另有隱情,我是刑警寧澤祠斧,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布闻察,位于F島的核電站,受9級特大地震影響琢锋,放射性物質(zhì)發(fā)生泄漏辕漂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一吴超、第九天 我趴在偏房一處隱蔽的房頂上張望钉嘹。 院中可真熱鬧,春花似錦鲸阻、人聲如沸跋涣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仆潮。三九已至,卻和暖如春遣臼,著一層夾襖步出監(jiān)牢的瞬間性置,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工揍堰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鹏浅,地道東北人嗅义。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像隐砸,于是被迫代替她去往敵國和親之碗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容