Dva is introduced

According to the official website of DVA, DVA is a data flow solution based on Redux and Redux-Saga. In order to simplify the development experience, DVA also has additional built-in React-Router and FETCH, so it can also be understood as a lightweight application framework.

features

  • Easy to learn and use, with only six apis, redux is especially user-friendly,After using with UMI, it is reduced to 0 API
  • Elm concept,The model was organized through Reducers, Effects and Subscriptions
  • Plug-in mechanisms such as DVA-loading can handle loading state automatically without having to write showLoading and hideLoading over and over again
  • Support HMR, implement COMPONENTS, routes and Models HMR based on babel-plugin-dva-hMR

Among them, Model isto integrate all reducer files related to Redux into a model file, distinguish models through namespace, store data through state and monitor history through subscriptions. Asynchronous operations were initiated using Effect and synchronous operations were performed using reducer.

Umi is introduced

Umi, which can be pronounced as Umi in Chinese, is an extensible enterprise front-end application framework. Umi is route-based and supports both configured and contracted routes to ensure complete functions of routes and extend functions. It is then paired with a well-lifecycle plug-in system that covers every lifecycle from source code to build artifacts, supporting various functional extensions and business requirements.

umiIt’s a hodgepodge of contracted routing and configuration routing. It’sIntegrate all React ecosystem things, antD, Dva, etc., and treat them as Umi plug-insWhen we need to use it, we can use it directly through configuration.

case

Use UMI and DVA as an example.

The directory structure

The results show

Content of the case

  1. src/pages/users/index.tsx

The main page, used to display the main content

import React, {useState, useRef, useEffect, FC} from 'react'
import { Table, Button, Pagination, message } from 'antd';
import ProTable, { ProColumns } from '@ant-design/pro-table';
import { connect, Loading, Dispatch, IUserState } from 'umi'
import UserModal from './components/UserModal';
import { editRecord, addRecord } from '.. /.. /services/users';
import {ISingleUser, IFormProps} from '.. /.. /models/data'

interface IUserPageProps {
    users: IUserState
    dispatch: Dispatch
    userListLoading: boolean
}

const Index: FC<IUserPageProps> = ({users, dispatch, userListLoading}) = > {
    const columns:ProColumns<ISingleUser>[] = [
        {
          title: 'Id'.dataIndex: 'id'.key: 'id'.render: (text: any) = > <a>{text}</a>}, {title: 'Name'.dataIndex: 'name'.key: 'name'}, {title: 'create_time'.dataIndex: 'create_time'.valueType: 'dateTime'.key: 'create_time'}, {title: 'Action'.key: 'action'.valueType: 'option'.render: (text: any, record: ISingleUser) = > [
            <Button type="primary" size="small" onClick={()= > editModal(record)}>edit</Button>.<Button danger size="small">delete</Button>],},];// Control the display and hiding of the pop-up box
    const [isModalVisible, setIsModalVisible] = useState(false);
    // Record each row in the table
    const [record, setRecord] = useState<ISingleUser | undefined> (undefined);

    // Modify the information
    const editModal = (record: ISingleUser) = > {
        setIsModalVisible(true)
        setRecord(record)
    }
    

    // Add information
    const handleAdd = () = > {
        setRecord(undefined)
        setIsModalVisible(true)}// The cancel button in the pop-up box
    const handleClose = () = > {
        setIsModalVisible(false)}// Modify or add forms to validate by submitting data
    const onFinish = async (values: IFormProps) => {
        let id = 0;
        if (record) {
            id = record.id ? record.id : 0
        }
        let serverFun;
        if (id) {
            serverFun = editRecord
        } else {
            serverFun = addRecord
       }
       const result = await serverFun(values, id)
       if (result) {
           // Close the modification box
            setIsModalVisible(false)
            message.success(`${id === 0 ? 'add' : 'change'}Successful `)
            dispatch({
                type: 'users/getRemove'.payload: {
                    page: users.meta.page,
                    per_page: users.meta.per_page
                }
            })
       }else message.error(`${id === 0 ? 'add' : 'change'}Failure `)};// Retrieve the data when the page number changes
    const handlePageNum = (page: number, pageSize? :number) = > {
        console.log(page, pageSize);
        dispatch({
            type: 'users/getRemove'.payload: {
                page,
                per_page: pageSize ? pageSize : users.meta.per_page
            }
        })
    }
    // const handlePageSize = (current: number, size: number) => {
        // console.log(current, size)
        // dispatch({
        // type: 'users/getRemove',
        // payload: {
        // page: current,
        // per_page: size
        / /}
        // })
    // }

    // Refresh operation
    const reloadHandle = () = > {
        dispatch({
            type: 'users/getRemove'.payload: {
                page: users.meta.page,
                per_page: users.meta.per_page
            }
        })
    }
    return (
        <div className="list-table">{/* Table component */}<ProTable 
              columns={columns}
              dataSource={users.data}
              rowKey='id'
              loading={userListLoading}
              search={false}
              pagination={false}
              headerTitle="user list"
              toolBarRender={()= > [
                <Button type="primary" onClick={handleAdd}>add</Button>} options={{density: true, fullScreen: true, reload: () => {reloadHandle()}, setting: true}} /> {/* paging component */}<Pagination
                total={users.meta.total}
                pageSize={users.meta.per_page}
                showSizeChanger
                showQuickJumper
                showTotal={total= >OnChange ={handlePageNum} // onShowSizeChange={handlePageSize} current={users.meta. Page} /><UserModal isModalVisible={isModalVisible} handleClose={handleClose} record={record} onFinish={onFinish} />
        </div>)}const mapStateToProps = ({users, loading}: {users: IUserState, loading: Loading}) = > {
    // users = model with namespace users, user: {}
    User: {data: []} user: {data: []}
    console.log('users', users, loading);
    return {
        users,
        userListLoading: loading.models.users
    }
}
const mapDispatchToProps = (dispatch:Dispatch) = > {
    return {
        dispatch
    }
}
export default connect(mapStateToProps, mapDispatchToProps)(Index)
Copy the code
  1. src/pages/users/components/UserModal.tsx

Sub-component: a component used for the home page and a pop-up box for adding or modifying user information

import React, {useState, useEffect, FC} from 'react'
import { Modal, Form, Input, DatePicker, Switch } from 'antd';
import {ISingleUser, IFormProps} from '.. /.. /.. /models/data'
import moment from 'moment'


interface IUserModalProps {
    isModalVisible: boolean
    record: ISingleUser | undefined
    handleClose: () = > void
    onFinish: (values: IFormProps) = > void
}

const UserModal: FC<IUserModalProps>= (props) = > {
    console.log('props', props);
    const [form] = Form.useForm();
  
    const onFinishFailed = (errorInfo: any) = > {
        console.log('Failed:', errorInfo);
    };

    useEffect(() = > {
        if(props.record) { form.setFieldsValue({ ... props.record,create_time: moment(props.record.create_time),
                status: props.record.status === 1 ? true : false
            });
        }else {
            form.resetFields()
        }
        
        return () = >{}; }, [props.isModalVisible]);const handleOk = () = > {
        form.submit()
    }

    const layout = {
        labelCol: { span: 6 },
        wrapperCol: { span: 18}};return (
        <div>
            <Modal 
            title={props.record? 'Modify' +props.record?.id :'add'}visible={props.isModalVisible}
            onOk={handleOk}
            onCancel={props.handleClose}
            forceRender
            >
                <Form
                    {. layout}
                    name="basic"
                    onFinish={props.onFinish}
                    onFinishFailed={onFinishFailed}
                    form={form}
                    initialValues={{
                        status: true
                    }}
                    >
                    <Form.Item
                        label="Name"
                        name="name"
                        rules={[{ required: true.message: 'Please input your name!' }]}
                    >
                        <Input />
                    </Form.Item>

                    <Form.Item
                        label="Email"
                        name="email"
                        rules={[{ required: true.message: 'Please input your email!' }]}
                    >
                        <Input />
                    </Form.Item>
                    <Form.Item
                        label="Create Time"
                        name="create_time"
                    >
                        <DatePicker showTime />
                    </Form.Item>
                    <Form.Item
                        label="Status"
                        name="status"
                        valuePropName="checked"
                    >
                        <Switch />
                    </Form.Item>
                </Form>
            </Modal>
        </div>)}export default  UserModal
Copy the code
  1. src/models/UsersModel.ts

Model files organized through State, Reducers, effects and Subscriptions in DVA. For data sharing, asynchronous requests.

import { Effect, ImmerReducer, Reducer, Subscription } from 'umi';
import { getRemoveList, editRecord, addRecord } from '.. /services/users';
import { message } from 'antd'
import {ISingleUser} from './data'

export interface IUserState {
    data: ISingleUser[],
    meta: {
        total: number
        per_page: number
        page: number}}interface IUserModel {
    namespace: 'users'
    state: IUserState
    reducers: {
        getList: Reducer<IUserState>
    }
    effects: {
        getRemove: Effect
        edit: Effect
        add: Effect
    }
    subscriptions: {
        setup: Subscription
    }
}


const UserModel: IUserModel = {
    namespace: 'users'.state: {
        data: [].meta: {
            total: 0.per_page: 10.page: 1}},reducers: {
        getList(state, action) {
            return action.payload
        }
    },
    effects: {*getRemove({payload: {page, per_page}}, effects) {
            const data = yield effects.call(getRemoveList, { page, per_page})
            if (data) {
                yield effects.put({
                    type: 'getList'.payload: data
                })
            }
        },
        *edit({payload: {id, values}}, effects) {
            const data = yield effects.call(editRecord, id, values)
            console.log('Edited results', data)
            // Call getRemove to retrieve the data
            if (data) {
                message.success('Edit succeeded! ')
                const { page, per_page } = yield effects.select((state: any) = > state.users.meta )
                yield effects.put({
                    type: 'getRemove'.payload: {
                        page,
                        per_page
                    }
                })
            }else {
                message.error('Edit failed! ')}}, *add({payload: {values}}, effects) {
            console.log('payload', values)
              
            const data = yield effects.call(addRecord, values)
            // Call getRemove to retrieve the data
            if (data) {
                message.success('Added successfully! ')
                const { page, per_page } = yield effects.select((state: any) = > state.users.meta )
                yield effects.put({
                    type: 'getRemove'.payload: {
                        page,
                        per_page
                    }
                })
            }else {
                message.error('Add failed! ')}}},subscriptions: {
        setup({ dispatch, history }, done) {
            return history.listen((location, action) = > {
                if(location.pathname === '/users' || location.pathname === '/my') {
                    // Listen for route changes. When route is '/users', send action to get data and return to page.
                    dispatch({
                        type: 'getRemove'.payload: {
                            page: 1.per_page: 5}})}})}}};export default UserModel;
Copy the code
  1. src/services/users.ts

The request function defined in usersmodel. ts calls the request function in users.ts and receives the return result.

import { message } from 'antd'
import request, { extend } from 'umi-request';
import {IFormProps} from '.. /models/data'

const errorHandler = function(error: any) {
  if (error.response) {
    // console.log(error.response.status);
    // console.log(error.response.headers);
    // console.log(error.data);
    // console.log(error.request);
    if (error.response.status > 400) {
        message.error(error.data.message ? error.data.message : error.data)
    }
  } else {
    // The request was made but no response was received or error occurs when setting up the request.
    // console.log(error.message);
    message.error('network error')}throw error;
};

// 1. Unified processing
const extendRequest = extend({ errorHandler });

// Get the data request function
export const getRemoveList = async({page, per_page}:{page: number.per_page: number= > {})return extendRequest(`/api/users? page=${page}&per_page=${per_page}`, {
        method: 'get',
    })
    .then((response) = > {
        return response
    })
    .catch((error) = > {
        return false
    });
}

// Request function to modify data
export const editRecord = async(values: IFormProps, id: number) = > {return extendRequest('/api/users/'+id, {
        method: 'put'.data: values
    })
    .then((response) = > {
        return true
    })
    .catch((error) = > {
        return false
    });
}

// Add the data request function
export const addRecord = async(values: IFormProps, id? :number) = > {return extendRequest('/api/users', {
        method: 'post'.data: values
    })
    .then((response) = > {
        return true
    })
    .catch((error) = > {
        return false
    });
}
Copy the code
  1. src/models/data.d.ts

Interface is used to define the interface used between different files


export interface ISingleUser {
    id: number.name: string.email: string.create_time: string.update_time: string.status: number
}
export interface IFormProps {
    [name: string] :any
}
Copy the code
  1. .umirec.ts

Umi configuration file.

import { defineConfig } from 'umi';

export default defineConfig({
  nodeModulesTransform: {
    type: 'none',},// Configure the proxy
  proxy: {
    '/api': {
      'target': 'http://public-api-v1.aspirantzhang.com'.'changeOrigin': true.'pathRewrite': { '^/api' : ' '}},}});Copy the code

There is no routes item configured in the file. Umi uses the default route to access the server through localhost:8000/users.