欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Dva双向数据流的理解

程序员文章站 2024-03-24 14:34:28
...

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

Dva双向数据流的理解&

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

Dva双向数据流的理解

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

Dva双向数据流的理解

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

Dva双向数据流的理解

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

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

Dva双向数据流的理解

(关于Route Components的理解&)

关于Route Components的理解&

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

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

Dva双向数据流的理解

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

Dva双向数据流的理解

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

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

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

Dva双向数据流的理解

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

Dva双向数据流的理解

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

Dva双向数据流的理解

Dva双向数据流的理解

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

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

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

Dva双向数据流的理解

(组件设计方法&)

组件设计方法&

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

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

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

Dva双向数据流的理解

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

Dva双向数据流的理解

Dva双向数据流的理解

案例分析

Dva双向数据流的理解

当前这个案例组件的拆分

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

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

Dva双向数据流的理解

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

Dva双向数据流的理解

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

Dva双向数据流的理解

Dva双向数据流的理解

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

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

Dva双向数据流的理解

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

Dva双向数据流的理解

完整Demo展示

git clone aaa@qq.com:dvajs/dva.git

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

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

项目结构

Dva双向数据流的理解

入口文件

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);