028-86261949

当前位置:首页 > 技术交流 > Dva搭建项目

Dva搭建项目

2018/11/16 18:19 分类: 技术交流 浏览:120

DvaJS指南(推荐先去看一下)
 
dva 是一个基于 React 和 Redux 的轻量应用框架,概念来自 elm,支持 side effects、热替换、动态加载、react-native、SSR 等,已在生产环境广泛应用。
Github地址(里面有很多教程)
https://github.com/dvajs/dva/blob/master/README_zh-CN.md
AntD快速搭建教程:
一、安装 dva-cli
dva-cli 是 dva 的命令行工具,包含 init、new、generate 等功能,目前最重要的功能是可以快速生成项目以及你所需要的代码片段。
通过 npm 安装 dva-cli 并确保版本是 0.9.1 或以上。
$ npm install dva-cli -g
安装完成后,可以通过 dva -v 查看版本,以及 dva -h 查看帮助信息。
二、创建新应用
安装完 dva-cli 之后,可以通过 dva new projectName  创建新应用(projectName表示项目名称,根据需求自定义)。
$ dva new dva-quickstart
这会创建 dva-quickstart 目录,包含项目初始化目录和文件,并提供开发服务器、构建脚本、数据 mock 服务、代理服务器等功能。
然后我们 cd 进入 dva-quickstart 目录,并启动开发服务器:
$ cd dva-quickstart
$ npm start
启动成功后,在浏览器里打开 http://localhost:8000 ,你会看到 dva 的欢迎界面。
项目结构说明
①components:最基础的组件。这里面存放的只是最基本的UI组件,这些组件接收外部传过来的参数(数据),并将这些数据渲染的到界面。根据传入的参数的不同,界面渲染也不同。
②container(对应dva开发的项目目录routers):contatiner负责将数据的组件进行连接,相当于将compontent组件和store里面的数据进行包装,生成一个新的有数据的组件。然后,在router.js配置文件中引用container中的组件。
③routers:router目录其实和container目录基本一样,只是在利用dva开发的应用中叫router,在不是利用dva开发的应用中叫container而已,两者有一个即可。
④models:model是dva中的一个重要概念,也可以看作是前端中的数据层。State是整个应用的数据层。应用的state被存储在一个object tree中。应用的初始state在model中定义,也就是说,由model state组成全局state。dva将model以namespace作为唯一标识进行区分,然后将所有model的数据存储到redux中的store里面。在引用的时候,通过各个model的namespace进行引用。Model,是一个处理数据的地方,在model里面调用service层获取数据。
⑤services:services负责向后台请求数据,在services里调用后台提供的api获取数据。
⑥utils:工具类目录,比如常见的后台接口请求工具类。
⑦styles:存放css或less样式文件。
⑧constants.js:在里面定义一些通用的常量。
⑨router.js:配置整个应用的路由。
⑩index.js:整个应用的入口文件,dva和其它框架稍有不同。
三、使用 antd
通过 npm 安装 antd 和 babel-plugin-import 。babel-plugin-import 是用来按需加载 antd 的脚本和样式的,详见 repo
$ npm install antd babel-plugin-import --save
编辑 .webpackrc,使 babel-plugin-import 插件生效。
{
"extraBabelPlugins": [
["import", {"libraryName": "antd", "libraryDirectory": "es", "style": "css"}]
}

四、添加列表页面
在src/routes目录中新建demo文件夹,添加List.js页面。如下:
此处使用的是antd的Table组件
 
import React, { Component } from 'react';
import { Table, Divider, Tag } from 'antd';
 
const data = [{
  key: '1',
  name: 'John Brown',
  age: 32,
  address: 'New York No. 1 Lake Park',
  tags: ['nice', 'developer'],
}, {
  key: '2',
  name: 'Jim Green',
  age: 42,
  address: 'London No. 1 Lake Park',
  tags: ['loser'],
}, {
  key: '3',
  name: 'Joe Black',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
  tags: ['cool', 'teacher'],
}];

class DemoPage extends Component {
columns = [{
  title: 'Name',
  dataIndex: 'name',
  key: 'name',
  render: text => <a href="javascript:;">{text}</a>,
}, {
  title: 'Age',
  dataIndex: 'age',
  key: 'age',
}, {
  title: 'Address',
  dataIndex: 'address',
  key: 'address',
}, {
  title: 'Tags',
  key: 'tags',
  dataIndex: 'tags',
  render: tags => (
    <span>
      {tags.map(tag => <Tag color="blue" key={tag}>{tag}</Tag>)}
    </span>
  ),
}, {
  title: 'Action',
  key: 'action',
  render: (text, record) => (
    <span>
      <a>编辑</a>
      <Divider type="vertical" />
      <a>删除</a>
    </span>
  ),
}];

  render(){
    return (<div>
              <h1>列表页面</h1>
        <Button type={'primary'} >新增</Button>
              <Table columns={this.columns} dataSource={data} />
    </div>);
  }
}
export default DemoPage;

五、添加路由
添加路由后,可以通过相应的路径访问到添加到页面。
在src/router.js文件中添加
import Demo from './routes/demo/List';
 
<Route path="/demo" exact component={Demo} />
 
浏览器地址栏输入http://localhost:8000/#/demo , 可以看见添加的list页面  

六、定义model
1、在src/models目录中添加demo.js
  
import * as service from '../services/demo';

export default {

  namespace: 'demo',   // 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace为 key 合成
  state: { //该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
    data: [],
  },
  // 订阅事件

  subscriptions: {
    setup({ dispatch, history }) {  // eslint-disable-line
    },
  },
  // Action 处理器,处理异步动作
  effects: {
    *fetch({ payload }, { call, put }) {  // eslint-disable-line
      yield put({ type: 'save' });
    }
  },
  // Action 处理器,处理同步动作,用来算出最新的 State
  reducers: {
    save(state, action) {
      return { ...state, ...action.payload };
    },
  },

};
 
2、在src/index.js中加载这个model,添加如下代码
 
app.model(require('./models/demo').default);

七、使用connect绑定models
绑定后可以在this.props里面获取到对应的model的state数据
1、在List.js文件中引入 connect
import { connect } from 'dva';
2、修改最后一行  
export default DemoPage 为:
export default connect((state)=> {
  return { demo: state.demo };
})(DemoPage);
3、在models/demo.js文件的effects里添加方法:
*getUsers ({ payload }, { call, put }) {
    const res = yield call(service.get);
    console.log(res);
},
 
3、修改render里
const data = this.props.demo.data;
 
刷新页面可以看见页面数据已经变成models里面的数据。

八、获取数据
页面渲染完成后,发送请求去获取实时的数据
1、 在src/services添加demo.js,模拟后台数据处理
import request from '../utils/request';

const data = [{
  key: '1',
  name: '模拟请求获取的数据 张三',
  age: 32,
  address: 'New York No. 1 Lake Park',
  tags: ['nice', 'developer'],
}, {
  key: '2',
  name: '模拟请求获取的数据 李四',
  age: 42,
  address: 'London No. 1 Lake Park',
  tags: ['loser'],
}, {
  key: '3',
  name: '模拟请求获取的数据 王二',
  age: 32,
  address: 'Sidney No. 1 Lake Park',
  tags: ['cool', 'teacher'],
},
];


export function test() {
  return request('/users', {
    method: 'get',
  })
}

export function get() {
  saveUsers(data);  // 存储在本地,方便查看
  return data;  // 模拟后台返回的数据
}

export function add(values) {
  // 获取最新的人员信息
  const users = queryUsers();
  values.key = Math.random();
  // 模拟后台处理新增的人员信息,直接添加到人员列表里面
  users.push(values);
  // 保存处理后最新的人员信息
  saveUsers(users);  //  // 存储在本地,方便查看
  return users;  // 模拟后台返回的数据
}

export function deleteUser(values) {
  // 获取最新的人员信息
  const users = queryUsers();
  // 模拟后台处理删除的人员信息,使用filter过滤掉已经删除的人员
  const newUsers = users.filter(item => item.key !== values.key);
  // 保存处理后最新的人员信息
  saveUsers(newUsers);  // 存储在本地,方便查看
  return newUsers;  // 模拟后台返回的数据
}
export function editUser(values) {
  // 获取最新的人员信息
  const users = queryUsers();
  // 模拟后台处理新增的人员信息,直接添加到人员列表里面
  const newUsers = users.map((item) => {
    if (item.key === values.key) { // 相等,代表这个人员信息已被修改,需要用新的人员消息替换旧的人员信息;
      return values;
    } else { // 不相等,代表这个人员信息未被修改,直接返回该人员信息;
      return item;
    }
  });
  // 保存处理后最新的人员信息
  saveUsers(newUsers);  // 存储在本地,方便查看
  return newUsers;  // 模拟后台返回的数据
}

const saveUsers = (users) => {
  localStorage.setItem("users", JSON.stringify(users));
};

const queryUsers = () => {
  return JSON.parse(localStorage.getItem("users"));
};
2、在models/demo.js的effects里添加方法
*getUsers ({ payload }, { call, put }) {
  const data = yield call(service.get);
    yield put({ type: 'save', payload: { data } });
},
 
3、在List.js页面添加方法
componentDidMount = () => {
  this.props.dispatch({
    type: 'demo/getUsers',
  })
};
刷新页面可以看机页面数据已经更新;
九、添加新数据
List.js页面
1、在新增按钮上添加点击事件
<Button type={'primary'} onClick={this.showModal} >新增</Button>
2、引入modal模态框组件,用来添加新的人员,
替换后页面代码:
import React, { Component } from 'react';
import { Table, Divider, Tag, Button, Modal  } from 'antd';
import { connect } from 'dva';

class DemoPage extends Component {
  state = { visible: false };
  showModal = () => {
    this.setState({
      visible: true,
    });
  }

  handleOk = (e) => {
    console.log(e);
    this.setState({
      visible: false,
    });
  }

  handleCancel = (e) => {
    console.log(e);
    this.setState({
      visible: false,
    });
  }
  componentDidMount = () => {
    this.props.dispatch({
      type: 'demo/getUsers',
      payload: true,
    })
  };
columns = [
  {
    title: 'Name',
    dataIndex: 'name',
    key: 'name',
    render: text => <a>{text}</a>,
  }, {
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
  }, {
    title: 'Address',
    dataIndex: 'address',
    key: 'address',
  }, {
    title: 'Tags',
    key: 'tags',
    dataIndex: 'tags',
    render: tags => (
      <span>
      {tags.map(tag => <Tag color="blue" key={tag}>{tag}</Tag>)}
    </span>
    ),
  },
  {
    title: 'Action',
    key: 'action',
    render: (text, record) => (
      <span>
      <a>编辑</a>

      <Divider type="vertical" />
      <a onClick={this.handleDelete} >删除</a>
    </span>
    ),
  }
];

  render(){
    const data = this.props.demo.data;
    return (<div>
      <h1>列表页面</h1>
      <Button type={'primary'} onClick={this.showModal} >新增</Button>
      <Table columns={columns} dataSource={data} />
      <Modal
        title="Basic Modal"
        visible={this.state.visible}
        onOk={this.handleOk}
        onCancel={this.handleCancel}
      >
        <p>Some contents...</p>
        <p>Some contents...</p>
        <p>Some contents...</p>
      </Modal>
    </div>);
  }
}

DemoPage.propTypes = {
};

export default connect((state)=> {
  return { demo: state.demo };
})(DemoPage);
 
3、替换模态框里面的内容为表单,
注意:必须使用Form.create()调用组件,使用方式如下:
class DemoPage extends React.Component {}
DemoPage = Form.create({})(DemoPage);
 
替换后页面代码:
import React, { Component } from 'react';
import { Table, Divider, Tag, Button, Modal, Form, Input, Icon  } from 'antd';
import { connect } from 'dva';
const FormItem = Form.Item;
 
class DemoPage extends Component {
  state = { visible: false };
  showModal = () => {
    this.setState({
      visible: true,
    });
  }

  handleOk = (e) => {
    this.setState({
      visible: false,
    });
  }

  handleCancel = (e) => {
    console.log(e);
    this.setState({
      visible: false,
    });
  }
  componentDidMount = () => {
    this.props.dispatch({
      type: 'demo/getUsers',
      payload: true,
    })
  };
columns = [
  {
    title: 'Name',
    dataIndex: 'name',
    key: 'name',
    render: text => <a>{text}</a>,
  }, {
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
  }, {
    title: 'Address',
    dataIndex: 'address',
    key: 'address',
  }, {
    title: 'Tags',
    key: 'tags',
    dataIndex: 'tags',
    render: tags => (
      <span>
      {tags.map(tag => <Tag color="blue" key={tag}>{tag}</Tag>)}
    </span>
    ),
  },
  {
    title: 'Action',
    key: 'action',
    render: (text, record) => (
      <span>
      <a>编辑</a>

      <Divider type="vertical" />
      <a onClick={this.handleDelete} >删除</a>
    </span>
    ),
  }
];

  render(){
    const data = this.props.demo.data;
    const { getFieldDecorator } = this.props.form;
    return (<div>
      <h1>列表页面</h1>
      <Button type={'primary'} onClick={this.showModal} >新增</Button>
      <Table columns={columns} dataSource={data} />
      <Modal
        title="Basic Modal"
        visible={this.state.visible}
        onOk={this.handleOk}
        onCancel={this.handleCancel}
      >
        <Form className="login-form">
          <FormItem>
            {getFieldDecorator('name', {
              rules: [{ required: true, message: 'Please input your 名称!' }],
            })(
              <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="名称" />
            )}
          </FormItem>
          <FormItem>
            {getFieldDecorator('age', {
              rules: [{ required: true, message: 'Please input your 年龄!' }],
            })(
              <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="年龄" />
            )}
          </FormItem>
          <FormItem>
            {getFieldDecorator('address', {
              rules: [{ required: true, message: 'Please input your 地址!' }],
            })(
              <Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="地址" />
            )}
          </FormItem>
        <FormItem>
  {getFieldDecorator('tags', {
    rules: [{ required: true, message: 'Please input your 标签!' }],
  })(
    <Select
      mode="tags"
      style={{ width: '100%' }}
      placeholder="标签"
    />
  )}
</FormItem>

        </Form>
      </Modal>
    </div>);
  }
}

DemoPage.propTypes = {
};

DemoPage  = Form.create()(DemoPage);
export default connect((state)=> {
  return { demo: state.demo };
})(DemoPage);
 
4、修改提交方法,点击模态框的确定按钮时,提交新增人员信息
handleOk = (e) => {
  e.preventDefault();
  this.props.form.validateFields((err, values) => {
    if (!err) {
      console.log('Received values of form: ', values);
      this.props.dispatch({
        type: 'demo/addUser',
        payload: values,
      })
    }
  });
  this.setState({
    visible: false,
  });
}
5、在models/demo.js 的effects里面添加方法
*addUser ({ payload }, { call, put }) {
  const data = yield call(service.add, payload);
  yield put({ type: 'save', payload: { data } });
},

十、删除数据
1、删除按钮添加点击事件
<a onClick={() => this.handleDelete(record)} >删除</a>
2、新增删除方法
handleDelete =(record) => {
  this.props.dispatch({
    type: 'demo/deleteUser',
    payload: record,
  });
};
3、models/demo.js 的effects里添加方法
*deleteUser ({ payload }, { call, put }) {
  const data = yield call(service.deleteUser, payload);
  yield put({ type: 'save', payload: { data } });
},
十一、编辑数据
这里复用添加新数据的模态框,修改后代码:
import React, { Component } from 'react';
import { Table, Divider, Tag, Button, Modal, Form, Input, Icon, Select  } from 'antd';
import { connect } from 'dva';
const FormItem = Form.Item;

class DemoPage extends Component {
  state = { visible: false, selectedValue: {}};

  // 打开模态框,保存传入的selectedValue值,用于编辑时回显
  showModal = (selectedValue = {}) => {
    this.setState({
      visible: true,
      selectedValue,
    });
  };

  handleOk = (e) => {
    e.preventDefault();
    this.props.form.validateFields((err, values) => {
      if (!err) {
        console.log('Received values of form: ', values);
        let type = 'demo/addUser';
        const selectedValue = this.state.selectedValue;
        if(selectedValue.key) { // 编辑,处理数据,key作为人员的唯一标识符,编辑时不变
          type = 'demo/editUser';
          values.key = selectedValue.key;
        }
        this.props.dispatch({
          type,
          payload: values,
        })
        // 关闭模态框
        this.setState({
          visible: false,
        });
        // 重置表单的值,避免打开时出现上次编辑的数据
        this.props.form.resetFields();
      }
    });
  };

  handleCancel = (e) => {
    console.log(e);
    this.setState({
      visible: false,
    });
    // 重置表单的值,避免打开时出现上次编辑的数据
    this.props.form.resetFields();
  };
  // 页面加载完成后获取用户信息
  componentDidMount = () => {
    this.props.dispatch({
      type: 'demo/getUsers',
    })
  };
  // 处理删除
  handleDelete =(record) => {
    this.props.dispatch({
      type: 'demo/deleteUser',
      payload: record,
    });
  };
  // table的列信息
  columns = [
    {
      title: 'Name', // 该列的名字
      dataIndex: 'name', // 该列对应的属性
      key: 'name',
      render: text => <a>{text}</a>,
    }, {
      title: 'Age',
      dataIndex: 'age',
      key: 'age',
    }, {
      title: 'Address',
      dataIndex: 'address',
      key: 'address',
    }, {
      title: 'Tags',
      key: 'tags',
      dataIndex: 'tags',
      render: tags => (
        <span>
        {tags.map(tag => <Tag color="blue" key={tag}>{tag}</Tag>)}
      </span>
      ),
    },
    {
      title: 'Action',
      key: 'action',
      render: (text, record) => (
        <span>
        <a onClick={() => this.showModal(record)} >编辑</a>

        <Divider type="vertical" />
        <a onClick={() => this.handleDelete(record)} >删除</a>
      </span>
      ),
    }
  ];

  render(){
    const data = this.props.demo.data;
    // 给表单注册属性名时使用
    const { getFieldDecorator } = this.props.form;
    return (<div>
      <h1>列表页面</h1>
      <Button type={'primary'} onClick={this.showModal} >新增</Button>
      <Table columns={this.columns} dataSource={data} />
      <Modal
        title="Basic Modal"
        visible={this.state.visible}
        onOk={this.handleOk}
        onCancel={this.handleCancel}
      >
        <Form className="login-form">
          <FormItem>
            {/* 把name属性注册到表单里*/}
            {getFieldDecorator('name', {
              initialValue: this.state.selectedValue.name,  // 默认值
              rules: [{ required: true, message: 'Please input your 名称!' }],  // 验证规则
            })(
              <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="名称" />
            )}
          </FormItem>
          <FormItem>
            {getFieldDecorator('age', {
              initialValue: this.state.selectedValue.age,
              rules: [{ required: true, message: 'Please input your 年龄!' }],
            })(
              <Input prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="年龄" />
            )}
          </FormItem>
          <FormItem>
            {getFieldDecorator('address', {
              initialValue: this.state.selectedValue.address,
              rules: [{ required: true, message: 'Please input your 地址!' }],
            })(
              <Input prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />} placeholder="地址" />
            )}
          </FormItem>
          <FormItem>
            {getFieldDecorator('tags', {
              initialValue: this.state.selectedValue.tags || ['cool'],
              rules: [{ required: true, message: 'Please input your 标签!' }],
            })(
              <Select
                mode="tags"
                style={{ width: '100%' }}
                placeholder="标签"
              />
            )}
          </FormItem>
        </Form>
      </Modal>
    </div>);
  }
}

DemoPage.propTypes = {
};

DemoPage  = Form.create()(DemoPage);
export default connect((state)=> {
  return { demo: state.demo };
})(DemoPage);
 
如果你已经进行到这里,DVA入门这一步你已经成功迈出了。
 
 
   感谢源码时代教学讲师提供此文章!
   本文为原创文章,转载请注明出处!
#标签:Dva搭建,项目