废话
学习了 Mock、Service 和 page 的基本相关知识,勉强能够将项目跑起来了,突然你会发现,哦豁,model 为什么没有用到,这个是干嘛的呢?接下来我们就来学习一下。
建议
学习 Model 之前其实建议先去看看 DVA 的相关教程,有个理解,看起来不是那么费劲,或者你死记也可以。
这里推荐 DVAJS 进行学习入门课 | DvaJS https://dvajs.com/guide/introduce-class.html
为什么推荐先看看 DVA 呢?因为你看懂了那就很牛逼,看不懂了了解一下对下面也有帮助,因为 Model 就是用 Dva 写的。
model 倒是是个啥
model 其实就是对数据的处理过程又进行了一次封装。
一般来说在 AntD 中数据请求过程是这样的:
1.UI 组件交互操作;
2.调用 model 的 effect;
3.调用统一管理的 service 请求函数;
4.使用封装的 request.ts 发送请求;
5.获取服务端返回;
6.然后调用 reducer 改变 state;
7.更新 model。
通俗点来说,请求方式就是:
View->model->Service-> 后端(或 Mock)
当然你也可以先之前写的一样
View->Service-> 后端(Mock)
学习 Model
还是老样子,想看代码在学习:
import type {Effect} from "umi";
import {Reducer} from "umi";
import {getTableValue, selectTableValue} from "@/pages/table/service";
//设置类型
export type TableValue={
key?: String;
name?:String;
age?:Number;
address?: String;
time?:String;
}
export type allValue = {
tableValue?:TableValue;
}
export type TableModelType = {
namespace:'tableDemo';
state:allValue;
effects:{
getValue:Effect;
selectValue:Effect;
}
reducers:{
saveValue:Reducer<allValue>;
}
}
/**
* 上面函数的实现
*/
const TableModel:TableModelType={
//命名空间
namespace: "tableDemo",
//设置state
state: {
tableValue:{},
},
//提供给view调用的接口
effects: {
*getValue(_,{call,put}){
const res = yield call(getTableValue);
yield put({
type:'saveValue',
payload:res
})
},
*selectValue({payload},{call,put}){
console.log("model:"+payload)
const res = yield call(selectTableValue,payload);
console.log(res)
yield put({
type:'saveValue',
payload:res
})
}
},
//提供给effects通信put的接口
reducers: {
saveValue(date,action){
return{
...date,
tableValue: action.payload
};
}
}
}
export default TableModel;
开始解释:
1、export type:类似定义一下数据结构,数据类型(?:表示非必须字段)
2、
export type TableModelType = {
namespace:'tableDemo';
state:allValue;
effects:{
getValue:Effect;
selectValue:Effect;
}
reducers:{
saveValue:Reducer<allValue>;
}
}
学过 java 的可以把这个理解成你定义的接口,const TableModel:TableModelType 是对你定义接口的实现;
然后我们分别来说一下,这部分结构都有什么东西:
- namespace : model 的命名空间,Page 连接时候要用到
- state: 初始 state
- reducers: 同步的修改状态的操作,由 actions 触发(state,action) => state。说白了就是给 efffects 提供通信的接口,用来修改 state
- effects:异步的操作,并不直接修改 state,由 action 触发,也可以调用 actions。(action,{put,call,select})
- subscriptions:异步的只读操作,并不直接修改 state,可以调用 actions。
({ dispatch, history })
一个 Model 一般都有 namespace、state、reducers、effects
3、来看看对接口的实现
/**
* 上面函数的实现
*/
const TableModel:TableModelType={
//命名空间
namespace: "tableDemo",
//设置state
state: {
tableValue:{},
},
//提供给view调用的接口
effects: {
*getValue(_,{call,put}){
const res = yield call(getTableValue);
yield put({
type:'saveValue',
payload:res
})
},
*selectValue({payload},{call,put}){
console.log("model:"+payload)
const res = yield call(selectTableValue,payload);
console.log(res)
yield put({
type:'saveValue',
payload:res
})
}
},
//提供给effects通信put的接口
reducers: {
saveValue(date,action){
return{
...date,
tableValue: action.payload
};
}
}
}
其中 effects 内我们可以看到有一个带*号的方法,这个玩意是提供给 Page 调用的,其中 put、call、select 是提供通信的,例如 yield put()是对 reducers 中的方法调用。yield call 是去访问 service 中的方法。
来个 Demo
配置路由:
{
name: '表格通信练习',
icon: 'book',
path: '/table',
component: './table'
}
在 page 中添加 table 文件夹,创建 index.tsx index.less model.ts service.ts
在 mock 文件夹下建立 table.tx 如图:
page 目录下:
mock 目录下
接下来上代码(建议自己敲)
index.tsx
import {Button, Card, DatePicker, Form, Input, message, Modal, Popconfirm, Space, Table} from 'antd';
import React, {useState} from "react";
import {PageContainer} from "@ant-design/pro-layout";
import {connect} from "umi";
import Search from "antd/es/input/Search";
import styles from "./index.less"
import {TableValue} from "@/pages/table/model";
import moment from "moment";
import {addForm, deleteById} from "@/pages/table/service";
import emitter from './ev'
/**
* 组件APP为Index主组件调用
* 主要用作table组件渲染
* 包含 AddUser 静态模框渲染
*
* 父组件:Index
* 子组件:AddUser
*/
class App extends React.Component {
constructor(props) {
super(props);
}
//设置状态表示选中行
state = {
selectedRowKeys: [],
};
//将选中行存入state
onSelectChange = selectedRowKeys => {
console.log('selectedRowKeys changed: ', selectedRowKeys);
this.setState({selectedRowKeys});
};
//删除
confirm(e) {
console.log(e);
//调用Index的DeleteById函数与Service通信进行删除
this.props.DeleteById(e);
message.success('删除成功');
}
render() {
const columns = [
{
title: 'Id',
dataIndex: 'key',
}, {
title: '姓名',
dataIndex: 'name',
},
{
title: '年龄',
dataIndex: 'age',
},
{
title: '地址',
dataIndex: 'address',
}, {
title: '时间',
dataIndex: 'time',
}, {
title: '操作',
//函数式声明:text表示当前行内容
render: (text) => (<Space size="middle">
<Popconfirm //ant组件
title="您确定要删除吗?"
onConfirm={() => {
this.confirm(text.key); //调用当前confirm进行删除
}}
okText="确定"
cancelText="取消"
>
<Button danger type="primary">删除</Button>
</Popconfirm>
<AddUser valueTemp={"修改"} flag={true} value={text}/>
</Space>)
},
];
//定义选中行keys
const {selectedRowKeys} = this.state;
//选择
const rowSelection = {
selectedRowKeys,
onChange: this.onSelectChange,
selections: [
Table.SELECTION_ALL,
Table.SELECTION_INVERT,
Table.SELECTION_NONE,
{
key: 'odd',
text: 'Select Odd Row',
onSelect: (changeableRowKeys: any[]) => {
let newSelectedRowKeys = [];
newSelectedRowKeys = changeableRowKeys.filter((key, index) => {
if (index % 2 !== 0) {
return false;
}
return true;
});
this.setState({selectedRowKeys: newSelectedRowKeys});
},
},
{
key: 'even',
text: 'Select Even Row',
onSelect: (changeableRowKeys: any[]) => {
let newSelectedRowKeys = [];
newSelectedRowKeys = changeableRowKeys.filter((key, index) => {
if (index % 2 !== 0) {
return true;
}
return false;
});
this.setState({selectedRowKeys: newSelectedRowKeys});
},
},
],
};
//渲染:this.props.values表示获取Index中的values值
//将this.props.values(Index中的tableValue)值传给子组件Table
return <>
<Table rowSelection={rowSelection} columns={columns} dataSource={this.props.values}/>;
</>
}
};
/**
* 添加/修改界面的静态模框
* 父组件:APP、Index
* 子组件:UserForm
*/
const AddUser = (props) => {
//初始化组件状态,也就是静态模框状态,默认关闭
const [isModalVisible, setIsModalVisible] = useState(false);
const showModal = () => {
setIsModalVisible(true);
//展示时候打印是否是修改。后面要传值 UserForm => flag表示是否需要修改
if (props.flag) {
console.log("进入修改");
console.log(props.value);
}
};
const handleOk = () => {
setIsModalVisible(false);
};
const handleCancel = () => {
setIsModalVisible(false);
};
//Model=> visible:是否可见 onOK:点击确定回调 onCancel:点击取消回调 footer:底部是否可见
return (
<div>
<div className={styles.float}>
<Button onClick={showModal}>{props.valueTemp}</Button>
</div>
<Modal title="用户编辑" visible={isModalVisible} onOk={handleOk} onCancel={handleCancel} footer={null}>
<UserForm handleCancel = {props.handleCancel} userInfo={props.value} flag={props.flag}/>
</Modal>
</div>
);
};
/**
* 表单组件
* props:父界面 AddUser中的传入的props
* 父组件:AddUser
*/
const UserForm = (props) => {
const [form] = Form.useForm();
//定义user继承TableValue,值从父组件中获取
const user: TableValue = props.userInfo;
console.log("表单渲染:");
console.log(user);
//如果是修改,则进行输入框赋值
if (props.flag) {
form.setFieldsValue({
key: user.key,
name: user.name,
age: user.age,
address: user.address,
//moment函数 npm install moment 时间格式刷
time: moment(user.time + "")
})
}
//两个布局设置
const layout = {
labelCol: {span: 8},
wrapperCol: {span: 16},
};
const tailLayout = {
wrapperCol: {offset: 8, span: 16},
};
//点击确定后提交
const onFinish = async (values: any) => {
console.log(values);
console.log(JSON.stringify(values));
//异步调用Service
const result = await addForm(values);
console.log(result);
//采用不通组件之间通信方式
emitter.emit("callMe",result.data);
// fetch不支持mock模拟数据,需要更换为./mock/xx.json/形式
// fetch(`/api/addOrUpdate`,{
// method: 'POST',
// body:new FormData(values)
// }).then(res => {
// console.log(res);
// res.text();
// }).then(
// result => {
// }
// )
};
//重置表单
const onReset = () => {
form.resetFields();
};
return (
<Form {...layout} form={form} name="control-hooks" onFinish={onFinish} className="ant-col-8">
<Form.Item name="key" label="ID" rules={[{required: false}]}>
<Input/>
</Form.Item>
<Form.Item name="name" label="姓名" rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name="age" label="年龄" rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name="address" label="地址" rules={[{required: true}]}>
<Input/>
</Form.Item>
<Form.Item name="time" label="时间" rules={[{required: true}]}>
<DatePicker showTime format="YYYY-MM-DD HH:mm:ss"/>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
提交
</Button>
<Button htmlType="button" onClick={onReset} className={styles.buttons}>
重置
</Button>
</Form.Item>
</Form>
);
};
class Index extends React.Component {
private eventEmitter: any;
//设置构造函数
constructor(props) {
super(props);
this.state = {tableValue: this.props.tableValue};
//将函数进行绑定,必须绑定
this.DeleteById = this.DeleteById.bind(this);
}
//当props发生变化时执行,初始化render时不执行
componentWillReceiveProps(props) {
if (props.tableValue !== this.props.tableValue) {
this.setState({tableValue: props.tableValue});
}
}
//初始化render之后只执行一次
componentDidMount(){
// 声明一个自定义事件
// 在组件装载完成以后
this.eventEmitter = emitter.addListener("callMe",(msg)=>{
this.setState({tableValue:msg})
});
}
// 组件销毁前移除事件监听
componentWillUnmount(){
}
//行一次,在初始化render之前执行
componentWillMount() {
this.getValue();
}
getValue = () => {
const {dispatch} = this.props;
//调用Model
dispatch({
type: 'tableDemo/getValue',
payload: {},
});
};
//异步调用移除
async DeleteById(key) {
console.log("父组件方法 移除:");
console.log(key);
//调用Service
const result = await deleteById(key);
this.setState({tableValue: result.data})
}
render() {
const onSearch = value => {
console.log("进入:value:" + value);
const {dispatch} = this.props;
//调用Model
dispatch({
type: 'tableDemo/selectValue',
payload: value,
});
};
return <div>
<PageContainer>
<Card>
<div className={styles.top}>
<Search className={styles.search}
placeholder="请输入查询关键字"
allowClear
enterButton="查询"
size="middle"
onSearch={onSearch}
/>
<div className={styles.add}>
<AddUser valueTemp={"添加"} flag={false} value={null}/>
</div>
</div>
<App DeleteById={this.DeleteById} values={this.state.tableValue}/>
</Card>
</PageContainer>
</div>
//rander运行结束后移除 PS:这个写法不正规,但是能用
emitter.removeListener(this.eventEmitter);
}
}
//设置绑定的返回数据
const mapStateToProps = (state) => {
return {
tableValue: state.tableDemo.tableValue.data,
}
}
//连接
export default connect(mapStateToProps)(Index);
index.less
.top{
display: flex;
padding-bottom: 1rem;
.search{
width: 15rem;
}
.add{
margin-left: auto;
.exchange{
margin-right: 1rem;
}
}
}
.buttons{
margin-left: 1rem;
}
:global{
.ant-col-8 {
display: block;
/* flex: 0 0 33.33333333%; */
max-width: 76.33333333%;
}
}
model.ts
import type {Effect} from "umi";
import {Reducer} from "umi";
import {getTableValue, selectTableValue} from "@/pages/table/service";
//设置类型
export type TableValue={
key?: String;
name?:String;
age?:Number;
address?: String;
time?:String;
}
export type allValue = {
tableValue?:TableValue;
}
export type TableModelType = {
namespace:'tableDemo';
state:allValue;
effects:{
getValue:Effect;
selectValue:Effect;
}
reducers:{
saveValue:Reducer<allValue>;
}
}
/**
* 上面函数的实现
*/
const TableModel:TableModelType={
//命名空间
namespace: "tableDemo",
//设置state
state: {
tableValue:{},
},
//提供给view调用的接口
effects: {
*getValue(_,{call,put}){
const res = yield call(getTableValue);
yield put({
type:'saveValue',
payload:res
})
},
*selectValue({payload},{call,put}){
console.log("model:"+payload)
const res = yield call(selectTableValue,payload);
console.log(res)
yield put({
type:'saveValue',
payload:res
})
}
},
//提供给effects通信put的接口
reducers: {
saveValue(date,action){
return{
...date,
tableValue: action.payload
};
}
}
}
export default TableModel;
service.ts
import request from "@/utils/request";
//异步调用
export async function getTableValue(){
return request('/api/tableDemo');
}
export async function selectTableValue(temp: String){
console.log("service:"+temp);
return request(`/api/tableDemo?temp=${temp}`);
}
export async function deleteById(id: String){
console.log("service:"+id);
return request(`/api/deleteById`,{
method:'POST',
data:{'id':id},
});
}
export async function addForm(formData:any){
console.log("进入service:",formData);
return request('/api/addOrUpdate',{
method:'POST',
data:formData
});
}
ev.ts
import { EventEmitter } from "events";
export default new EventEmitter();
// npm install events --save
PS:ev.ts 是用于组件之间通信的,在使用之前请先执行
npm install events --save
相关链接
上一篇:👀
下一篇:👀
Ant Design Pro 系列:👀
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于