业务场景对照
高频业务场景的 Vue 2 和 React + dva + TypeScript 完整代码对照
列表查询页
表格展示 + 分页 + 搜索过滤
<template>
<div>
<!-- 搜索区域 -->
<div class="search-bar">
<input
v-model="keyword"
placeholder="搜索..."
@input="handleSearch"
/>
</div>
<!-- 表格 -->
<el-table :data="list" :loading="loading">
<el-table-column prop="name" label="名称" />
<el-table-column prop="status" label="状态" />
<el-table-column label="操作">
<template #default="{ row }">
<el-button @click="handleEdit(row)">
编辑
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
:current-page="page"
:page-size="pageSize"
:total="total"
@current-change="handlePageChange"
/>
</div>
</template>
<script>
export default {
data() {
return {
keyword: '',
list: [],
loading: false,
page: 1,
pageSize: 10,
total: 0
}
},
mounted() {
this.fetchList()
},
methods: {
async fetchList() {
this.loading = true
const res = await api.getList({
keyword: this.keyword,
page: this.page,
pageSize: this.pageSize
})
this.list = res.data.list
this.total = res.data.total
this.loading = false
},
handleSearch() {
this.page = 1
this.fetchList()
},
handlePageChange(page) {
this.page = page
this.fetchList()
}
}
}
</script>
// models/list.ts
import { Effect, Reducer } from 'dva'
interface ListItem {
id: string
name: string
status: string
}
interface ListModelState {
list: ListItem[]
total: number
page: number
keyword: string
}
interface ListModelType {
namespace: 'list'
state: ListModelState
effects: {
fetchList: Effect
}
reducers: {
setList: Reducer<ListModelState>
setPage: Reducer<ListModelState>
setKeyword: Reducer<ListModelState>
}
}
const ListModel: ListModelType = {
namespace: 'list',
state: {
list: [],
total: 0,
page: 1,
keyword: ''
},
effects: {
*fetchList({ payload }, { call, put, select }) {
const { page, keyword } = yield select(state => state.list)
const res = yield call(api.getList, {
keyword: payload?.keyword ?? keyword,
page: payload?.page ?? page,
pageSize: 10
})
yield put({ type: 'setList', payload: res.data })
}
},
reducers: {
setList(state, { payload }) {
return { ...state, list: payload.list, total: payload.total }
},
setPage(state, { payload }) {
return { ...state, page: payload }
},
setKeyword(state, { payload }) {
return { ...state, keyword: payload, page: 1 }
}
}
}
export default ListModel
// ============================================
// pages/ListPage.tsx
// ============================================
import React, { useEffect } from 'react'
import { connect } from 'dva'
import { Table, Input, Pagination } from 'antd'
interface ListPageProps {
list: ListItem[]
total: number
page: number
keyword: string
loading: boolean
dispatch: Dispatch
}
const ListPage: React.FC<ListPageProps> = ({
list, total, page, keyword, loading, dispatch
}) => {
useEffect(() => {
dispatch({ type: 'list/fetchList' })
}, [page, keyword])
const handleSearch = (value: string) => {
dispatch({ type: 'list/setKeyword', payload: value })
}
const handlePageChange = (newPage: number) => {
dispatch({ type: 'list/setPage', payload: newPage })
}
const columns = [
{ title: '名称', dataIndex: 'name' },
{ title: '状态', dataIndex: 'status' },
{
title: '操作',
render: (_: any, record: ListItem) => (
<Button onClick={() => handleEdit(record)}>
编辑
</Button>
)
}
]
return (
<div>
<Input.Search
value={keyword}
onChange={(e) => handleSearch(e.target.value)}
placeholder="搜索..."
/>
<Table
dataSource={list}
columns={columns}
loading={loading}
rowKey="id"
pagination={false}
/>
<Pagination
current={page}
total={total}
pageSize={10}
onChange={handlePageChange}
/>
</div>
)
}
const mapStateToProps = (state: any) => ({
list: state.list.list,
total: state.list.total,
page: state.list.page,
keyword: state.list.keyword,
loading: state.loading.effects['list/fetchList']
})
export default connect(mapStateToProps)(ListPage)
表单提交
表单验证 + 异步提交 + 错误处理
<template>
<el-form
:model="form"
:rules="rules"
ref="formRef"
>
<el-form-item label="名称" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item>
<el-button
:loading="submitting"
@click="handleSubmit"
>
提交
</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
form: { name: '' },
submitting: false,
rules: {
name: [
{ required: true, message: '请输入名称' }
]
}
}
},
methods: {
async handleSubmit() {
try {
await this.$refs.formRef.validate()
this.submitting = true
await api.submit(this.form)
this.$message.success('提交成功')
} catch (error) {
this.$message.error('提交失败')
} finally {
this.submitting = false
}
}
}
}
</script>
import React, { useState } from 'react'
import { Form, Input, Button, message } from 'antd'
interface FormValues {
name: string
}
const SubmitPage: React.FC = () => {
const [form] = Form.useForm<FormValues>()
const [submitting, setSubmitting] = useState(false)
const handleSubmit = async (values: FormValues) => {
setSubmitting(true)
try {
await api.submit(values)
message.success('提交成功')
form.resetFields()
} catch (error) {
message.error('提交失败')
} finally {
setSubmitting(false)
}
}
return (
<Form
form={form}
onFinish={handleSubmit}
>
<Form.Item
name="name"
rules={[{ required: true, message: '请输入名称' }]}
label="名称"
>
<Input />
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
loading={submitting}
>
提交
</Button>
</Form.Item>
</Form>
)
}
export default SubmitPage
弹窗组件
父子组件通信、控制显示隐藏
<template>
<el-dialog
:visible="visible"
@close="$emit('close')"
>
<p>弹窗内容</p>
</el-dialog>
</template>
<script>
export default {
props: {
visible: { type: Boolean, required: true }
}
}
</script>
import React from 'react'
import { Modal } from 'antd'
interface MyModalProps {
visible: boolean
onClose: () => void
onConfirm: () => Promise<void>
}
const MyModal: React.FC<MyModalProps> = ({
visible,
onClose,
onConfirm
}) => {
return (
<Modal
visible={visible}
onCancel={onClose}
onOk={onConfirm}
>
<p>弹窗内容</p>
</Modal>
)
}
// 使用
const Parent: React.FC = () => {
const [visible, setVisible] = useState(false)
return (
<>
<Button onClick={() => setVisible(true)}>
打开弹窗
</Button>
<MyModal
visible={visible}
onClose={() => setVisible(false)}
onConfirm={async () => {
// 处理确认
setVisible(false)
}}
/>
</>
)
}
权限控制
路由守卫、按钮权限、数据权限
import { useModel } from '@@/plugin-model/useModel'
// 权限 Hook
export function usePermission() {
const { initialState } = useModel('@@initialState')
const { permissions = [] } = initialState || {}
const hasPermission = (code: string) => {
return permissions.includes(code)
}
return { hasPermission, permissions }
}
// 在组件中使用
const UserList: React.FC = () => {
const { hasPermission } = usePermission()
return (
<div>
{hasPermission('user:create') && (
<Button type="primary">
新建用户
</Button>
)}
</div>
)
}