Ant Design Pro 从零到一(学习 Model)

本贴最后更新于 1275 天前,其中的信息可能已经事过景迁

废话

学习了 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 目录下:

image.png

mock 目录下

image.png

接下来上代码(建议自己敲)

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 从零到一(Mock 使用)

下一篇:👀

Ant Design Pro 从零到一 (总结)

Ant Design Pro 系列:👀

Ant Design Pro 从零到一教程

React 从零到一 Demo 演练(上)

React 从零到一 Demo 演练(下)

Ant Design Pro 从零到一(认识 AntD)

Ant Design Pro 从零到一(页面创建)

Ant Design Pro 从零到一(Mock 使用)

Ant Design Pro 从零到一(学习 Model)

Ant Design Pro 从零到一 (总结)

  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...