Dva双向数据流的理解

(Dva双向数据流的理解&)

Dva双向数据流的理解&

数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State

首先通过路由监听来调用dispatch来触发reducers或effect中对应的方法,最终还是要在reducer中通过return {...state,...loding.payload} ,通过return返回来改变state,state改变以后会自动触发UI中Dom的操作。

调用effect中的query方法中的put获取后台数据

拿到数据以后,调用put方法调用reducers中对应的方法。

通过return来改变state,从而更新dom。

(关于Route Components的理解&)

关于Route Components的理解&

在 dva 中我们通常将其约束为 Route Components,因为在 dva 中我们通常以页面维度来设计 Container Components。

所以在 dva 中,通常需要 connect Model的组件都是 Route Components,组织在/routes/目录下,而/components/目录下则是纯组件(Presentational Components)。

Model中的namespace是全局唯一的key,一个key对应一个state数据仓库。

Route Components中,通过this.props来获取connect的model中的state,一般是通过namespace获取model中对应的state数据。

要想要Route ComponentsModel进行绑定,要通过connect的方式进行连接。

{users}就是state,通过全局唯一的namespace来获取Model中的state的一种方式。

有两种方式绑定components与Model,一种是全局加载的方式,第一次加载的时候直接引入所有的Model与components。

第二种方式是RouterConfig懒加载处理,当用到components时才回去加载所对应的Model

当绑定好Router Components与model以后,就可以通过this.props获取对应model中的state了。

这边使用的是函数式编程,所以直接在User的参数中获取this.props的属性就可以啦。

This.props中默认有location和dispatch两个属性,以及最重要的从Model中继承而来的state了,这里是{user}

(组件设计方法&)

组件设计方法&

组件设计的原则就是尽量让每个component专注于做自己的事情。

我们开发的都是单页应用,对于单页应用而言,通过路由Route来进行对应Component的跳转。

Router Component一般作为数据容器存在的,它会包含其他的子组件。

另一种组件即为展示组件,它不会订阅Model的数据,所需要的数据也是通过props传递到组件内部的。

案例分析

当前这个案例组件的拆分

首先是路由组件,要绑定对应namespace获取state,上面已经展示过了如何绑定components与Model的。

路由组件下包含了三个子组件,通过react的prop,把路由组件绑定的state传递到子组件中。

users 的namespace中析构出model中state的数据,分模块传递进对应的子组件中。

每个子组件需要什么state的数据,就给他们封装起来一并传过去。

在组件中,通过this.props获取父组件封装并传递过来的属性。

在react中方法做为一种属性,和属性数据同样的方式传递到子组件中进行调用。

在标签中就可以调用父组件传递过来的方法执行响应的事件了。

完整Demo展示

git clone git@github.com:dvajs/dva.git

git clone git@github.com:dvajs/dva-example-user-dashboard.git

git clone git@github.com:devisions/dvajs-user-dashboard.git

项目结构

入口文件

import dva from 'dva';
import { browserHistory } from 'dva/router';
import createLoading from 'dva-loading';
// 1. Initialize
const app = dva({
 history:browserHistory
});

// 2. Plugins
app.use(createLoading());

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

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

// 5. Start
app.start('#root');

全局路由入口文件 router.js

import React from 'react';
import PropTypes from 'prop-types';
import { Router } from 'dva/router';

const registerModel = (app, model) => {
  if (!(app._models.filter(m => m.namespace === model.namespace).length === 1)) {
    app.model(model);
  }
};

const RouterConfig = ({ history, app }) => {
  /**
   * 添加路由,path是请求路基,name是名称说明
   * */
  const routes = [
    {
      path: '/',
      name: 'welcome',
      getComponent(nextState, cb) {
        require.ensure([], (require) => {
          cb(null, require('./routes/indexPage/IndexPage'));
        });
      },
    },
    {
      path: '/users',
      name: 'users',
      getComponent(nextState, cb) {
        require.ensure([], (require) => {
          registerModel(app, require('./models/users'));
          cb(null, require('./routes/users/Users'));
        });
      },
    },
  ];
  return <Router history={history} routes={routes} />;
};

RouterConfig.propTypes = {
  history: PropTypes.object,
  app: PropTypes.object,
};

export default RouterConfig;

Model数据仓库

import { create, remove, update, query } from '../services/users';
import { parse } from 'qs';

export default {
    namespace: 'users',
  
    state: {
      list: [],
      total: null, 
      field: '', // 搜索框字段
      keyword: '', // 搜索框输入的关键字
      loading: false, // 控制加载状态
      current: 1, // 当前分页信息
      currentItem: {}, // 当前操作的用户对象
      modalVisible: false, // 弹出窗的显示状态
      modalType: 'create', // 弹出窗的类型(添加用户,编辑用户)
    },
      effects: {
        *query({ payload }, { call, put }) {
            console.log(payload);
            yield put({ type: 'showLoading' });
            yield put({ type: 'updateQueryKey', payload });
            const { data } = yield call(query, parse(payload));
            if (data) {
              yield put({
                type: 'querySuccess',
                payload: {
                  list: data.list,
                  total: data.total,
                  current: data.current,
                },
              });
            }
          },
          *create({ payload }, { call, put }) {
            console.log(payload);
            yield put({ type: 'hideModal' });
            yield put({ type: 'showLoading' });
            const { data } = yield call(create, payload);
            console.log(data);
            if (data) {
              yield put({
                type: 'createSuccess',
                payload: {
                  list: data.list,
                  total: data.total,
                  current: data.current,
                  field: '',
                  keyword: '',
                },
              });
            }
          },
          *'delete'({ payload }, { call, put }) {
            yield put({ type: 'showLoading' });
            const { data } = yield call(remove, { id: payload });
            if (data) {
              yield put({
                type: 'deleteSuccess',
                payload,
              });
            }
          },
          *update({ payload }, { select, call, put }) {
            yield put({ type: 'hideModal' });
            yield put({ type: 'showLoading' });
            const id = yield select(({ users }) => users.currentItem.id);
            const newUser = { ...payload, id };
            const { data } = yield call(update, newUser);
            if (data ) {
              yield put({
                type: 'updateSuccess',
                payload: newUser,
              });
            }
          },
      },
      subscriptions: {
        setup({ dispatch, history }) {
          history.listen(location => {
            if (location.pathname === '/users') {
              console.log(location);
              dispatch({
                type: 'query',
                payload: location.query
              });
            }
          });
        },
      },
      reducers: {
          showLoading(state) {// 控制加载状态的 reducer
            return { ...state, loading: true };
          }, 
          showModal(state, action) {// 控制 Modal 显示状态的 reducer
            return { ...state, ...action.payload, modalVisible: true };
          },
          hideModal(state) {
            return { ...state, modalVisible: false };
          },
          querySuccess(state, action){
              return {...state, ...action.payload, loading: false};              
          },
          createSuccess(state, action) {
            return { ...state, ...action.payload, loading: false };
          },
          deleteSuccess(state, action) {
            const id = action.payload;
            const newList = state.list.filter(user => user.id !== id);
            return { ...state, list: newList, loading: false };
          },
          updateSuccess(state, action) {
            const updateUser = action.payload;
            const newList = state.list.map(user => {
              if (user.id === updateUser.id) {
                return { ...user, ...updateUser };
              }
              return user;
            });
            return { ...state, list: newList, loading: false,currentItem: {} };
          },
          updateQueryKey(state, action) {
            return { ...state, ...action.payload };
          },
      }
  }

路由组件

import UserList from '../../components/users/UserList';
import UserSearch from '../../components/users/UserSearch';
import UserModal from '../../components/users/UserModal';
import PropTypes from 'prop-types'
import styles from './Users.less';
import { connect } from 'dva';
import React, { useEffect } from 'react'

function Users({ location, dispatch, getUsers,users }) {
  console.log(users);
  const {
    loading, list, total, current,field, keyword,
    currentItem, modalVisible, modalType
    } = users;

  const userSearchProps = {
    field,
    keyword,
    onAdd() {
      dispatch({
        type: 'users/showModal',
        payload: {
          modalType: 'create',
          currentItem : {}
        },
      });
    },
    onSearch(fieldsValue) {
      dispatch({
        type: 'users/query',
        payload: fieldsValue,
      });
    },
  };

  const userListProps={
		dataSource: list,
		total,
		loading,
    current,
    onPageChange(page) {
      dispatch(routerRedux.push({
        pathname: '/users',
        query: { field, keyword, page },
      }));
    },
    onDeleteItem(id) {
      dispatch({
        type: 'users/delete',
        payload: id,
      });
    },
    onEditItem(item) {
      console.log(item);
      dispatch({
        type: 'users/showModal',
        payload: {
          modalType: 'update',
          currentItem: item,
        },
      });
    },
  };
  
  const userModalProps = {
    item: modalType === 'create' ? {} : currentItem,
    type: modalType,
    visible: modalVisible,
    onOk(data) {
      dispatch({
        type: `users/${modalType}`,
        payload: data,
      });
    },
    onCancel() {
      dispatch({
        type: 'users/hideModal',
      });
    },
  };

  //useEffect(() => { getUsers },[])
  return (
    <div className={styles.normal}>
      {/* 用户筛选搜索框 */}
      <UserSearch {...userSearchProps} />
      {/* 用户信息展示列表 */}
      <UserList {...userListProps} />
      {/* 添加用户 & 修改用户弹出的浮层 */}
      <UserModal {...userModalProps} />
    </div>
  );
}

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

const mapStateToProps = ({users})=>{
  return {users};
}

//const mapDispatchToProps = (dispatch)=>{
//  return {
//    getUsers : ()=>{
//      dispatch({
//      type : "users/querySuccess",
//      payload : {}
//      });
//      return {
//        type : "users/querySuccess",
//        payload : {}
//      }
//    }
//  }
//}

export default connect(mapStateToProps)(Users);

Services数据接口

import request from '../utils/request';
import qs from 'qs';
import {url} from '../utils/commonConstant';

export async function query(params) {
  console.log(params);
  if(JSON.stringify(params) == "{}"){
    return request(`${url}/users/query`);
  }
  return request(`${url}/users/query?${qs.stringify(params)}`);
}

export async function create(params) {
  console.log(params);
  return request(`${url}/users/create`, {
    method: 'post',
    headers: {
      'Content-Type': 'application/json; charset=utf-8'
    },
    body: JSON.stringify(params),
  });
}

export async function remove(params) {
  console.log(params);
  return request(`${url}/users/delete/${params.id}`, {
    method: 'delete',
    body: qs.stringify(params),
  });
}

export async function update(params) {
  return request(`${url}/users/update`, {
    method: 'put',
    headers: {
      'Content-Type': 'application/json; charset=utf-8'
    },
    body: JSON.stringify(params),
  });
}

纯组件实现页面展示功能

import React, { PropTypes } from 'react';
import { Form, Input, Button, Select } from 'antd';
import styles from './UserSearch.less';

const UserSearch = ({
  field, keyword,
  onSearch,
  onAdd,
  form: {
    getFieldDecorator,
    validateFields,
    getFieldsValue,
    },
  }) => {
  function handleSubmit(e) {
    e.preventDefault();
    validateFields((errors) => {
      if (!!errors) {
        return;
      }
      onSearch(getFieldsValue());
    });
  }

  return (
    <div className={styles.normal}>
      <div className={styles.search}>
        <Form inline onSubmit={handleSubmit}>
          <Form.Item>
            {getFieldDecorator('field', {
              initialValue: field || 'name',
            })(
              <Select>
                <Select.Option value="name">姓名 &nbsp; </Select.Option>
                <Select.Option value="address">住址 &nbsp; </Select.Option>
              </Select>
            )}
          </Form.Item>
          <Form.Item
            hasFeedback
          >
            {getFieldDecorator('keyword', {
              initialValue: keyword || '',
            })(
              <Input type="text" />
            )}
          </Form.Item>
          <Button style={{ marginRight: '10px' }} type="primary" htmlType="submit">搜索</Button>
        </Form>
      </div>
      <div className={styles.create}>
        <Button type="ghost" onClick={onAdd}>Add</Button>
      </div>
    </div>
  );
};

UserSearch.propTypes = {
  form: PropTypes.object.isRequired,
  onSearch: PropTypes.func,
  onAdd: PropTypes.func,
  field: PropTypes.string,
  keyword: PropTypes.string,
};

export default Form.create()(UserSearch);
// ./src/components/Users/UserList.jsx
import React, { Component, PropTypes } from 'react';
// 采用antd的UI组件
import { Table, message, Popconfirm, Pagination } from 'antd';

// 采用 stateless 的写法
const UserList = ({
    total,
    current,
    loading,
    dataSource,
    onPageChange,
    onDeleteItem,
    onEditItem,
}) => {
  const columns = [{
    title: '姓名',
    dataIndex: 'name',
    key: 'name',
    render: (text) => <a href="#">{text}</a>,
  }, {
    title: '年龄',
    dataIndex: 'age',
    key: 'age',
  }, {
    title: '性别',
    dataIndex: 'sex',
    key: 'sex',
  }, {
    title: '住址',
    dataIndex: 'address',
    key: 'address',
  }, {
    title: '操作',
    key: 'operation',
    render: (text, record) => (
      <p>
        <a onClick={()=>{onEditItem(record)}}>编辑</a>
        &nbsp;
        <Popconfirm title="确定要删除吗?" onConfirm={() => onDeleteItem(record.id)}>
          <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={false}
      />
      <Pagination
        className="ant-table-pagination"
        total={total}
        current={current}
        pageSize={10}
        onChange={onPageChange}
      />
    </div>
  );
}
export default UserList;
import React, { PropTypes } from 'react';
import { Form, Input, Modal } from 'antd';
const FormItem = Form.Item;

const formItemLayout = {
  labelCol: {
    span: 6,
  },
  wrapperCol: {
    span: 14,
  }
};

const UserModal = ({
  visible,
  item = item || {},
  onOk,
  onCancel,
  form: {
    getFieldDecorator,
    validateFields,
    getFieldsValue,
    },
  }) => {
  function handleOk() {
    validateFields((errors) => {
      if (errors) {
        return;
      }
      const data = { ...getFieldsValue(), id: item.id };
      onOk(data);
    });
  }

  function checkNumber(rule, value, callback) {
    if (!value) {
      callback(new Error('年龄必须填写!'));
    }
    if (!/^[\d]{1,2}$/.test(value)) {
      callback(new Error('请输入合法年龄!'));
    } else {
      callback();
    }
  }

  const modalOpts = {
    visible,
    onOk: handleOk,
    onCancel,
  };

  return (
    <Modal {...modalOpts} title={item.id ? '修改用户' : '添加用户'}>
      <Form horizontal>
        <FormItem
          label="姓名:"
          hasFeedback
          {...formItemLayout}
        >
          {getFieldDecorator('name', {
            initialValue: item.name,
            rules: [
              { required: true, message: '姓名必须填写!' },
            ],
          })(
            <Input type="text" />
          )}
        </FormItem>
        <FormItem
          label="年龄:"
          hasFeedback
          {...formItemLayout}
        >
          {getFieldDecorator('age', {
            initialValue: item.age,
            rules: [
              { validator: checkNumber },
            ],
          })(
            <Input type="text" />
          )}
        </FormItem>
        <FormItem
          label="性别:"
          hasFeedback
          {...formItemLayout}
        >
          {getFieldDecorator('sex', {
            initialValue: item.sex,
            rules: [
            { required: true, message: '性别必须填写!' },
            ],
          })(
            <Input type="text" />
          )}
        </FormItem>
        <FormItem
          label="住址:"
          hasFeedback
          {...formItemLayout}
        >
          {getFieldDecorator('address', {
            initialValue: item.address,
            rules: [
              { required: true, message: '住址必须填写!' },
            ],
          })(
            <Input type="address" />
          )}
        </FormItem>
      </Form>
    </Modal>
  );
};

UserModal.propTypes = {
  visible: PropTypes.any,
  form: PropTypes.object,
  item: PropTypes.object,
  onOk: PropTypes.func,
  onCancel: PropTypes.func,
};

export default Form.create({
  mapPropsToFields(props) {
    if (props.type==='create') {
      return {
        name: {},
        age: {},
        sex: {},
        address: {}
      }
    }
    return {
      name: {...props.item.name},
      age: {...props.item.age},
      sex: {...props.item.sex},
      address: {...props.item.address}
    }
  }
})(UserModal);


 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wespten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值