如果一块表走的不准,那它每一秒都是错的。
写在前面的话:博主之前一直使用的vue框架,最近入手公司项目,开始一点点接触React,我觉得学习任何东西的最好方法就是使用它、体验他。其实完成页面前我根本没有读过react的官方文档(是有些许枯燥)但我会仿照别人的代码一点点尝试,用过之后再去看官方使用文档会豁然开朗。这个方法值得推荐。因此,咱们今天来初试React开发,实现简单的个人中心-信息修改页面
1. 看页面需求
首先看一下页面需求。分析:进入页面调用接口初始化个人信息,修改后点击“保存”按钮,调接口保存数据后刷新页面。
2. 分析接口文档
页面初始化加载列表接口为detail,无需请求参数,携带用户token即可,返回数据包含mobile、email; 信息修改接口为updateUserInfo,包含两个请求参数,mobile、email必填
3. React目录文件夹说明
每一个模块定义一个文件夹,包含page.tsx(页面HTML样式)model.ts(编写接口)style.less(CSS样式)
4. 编写页面代码
在page.tsx页面中写
function Container({ dispatch, form, container: { editData } }: any) {
const { validateFields} = form
return (
<PageHeaderLayout title="列表" className={'commonList'}>
<Card
className={styles.table}
style={{ marginTop: 1, marginLeft: 1 }}
bordered={false}
bodyStyle={{ padding: '8px 32px 32px 32px' }}
>
<div style={{ textAlign: 'center', width: '700', marginLeft: '20' }}>
<Form
//labelAlign="left"
layout="horizontal"
className={styles.commonForm}
labelCol={{ span: 3 }}
wrapperCol={{ span: 12 }}
>
<Form.Item label="用户名">
{getFieldDecorator('name', {
initialValue: (editData && editData.name) || ''
})(<Input placeholder="请输入" disabled />)}
</Form.Item>
<Form.Item label="电话号码">
{getFieldDecorator('mobile', {
initialValue: (editData && editData.mobile) || '',
})(<Input placeholder="请输入" />)}
</Form.Item>
<Form.Item label="邮箱地址">
{getFieldDecorator('email', {
initialValue: (editData && editData.email) || '',
})(<Input placeholder="请输入" />)}
</Form.Item>
<Button className={styles.search} onClick={onSave}>
保存
</Button>
</Form>
</div>
</Card>
</PageHeaderLayout>
)
}
5. 页面初始化-数据回显
(1)在model.ts文件里写如下代码。发送请求manage/ucenter/detail,请求参数query其实为空,并将请求返回的数据data.data赋值给editData存储在setStore里面
init: async (action, { dispatch }) => {
const { query } = getLocation()
const data = await dispatch({
type: 'container/get',
params: ['manage/ucenter/detail', query]
})
dispatch({
type: 'container/setStore',
params: [{ editData: data.data }]
})
},
(2)在page页的Container中添加初始化调用的函数
const init = useCallback(() => {
dispatch('container/init')
}, [dispatch])
useEffect(() => {
init()
}, [dispatch, init])
(3)【数据回显】在page页面通过function Container({ dispatch, form, container: { editData } }: any) 将container中的editData拿到,因此可以放在表单里的initialValue。
<Form.Item label="用户名">
{getFieldDecorator('name', {
initialValue: (editData && editData.name) || ''
})(<Input placeholder="请输入" disabled />)}
</Form.Item>
至此就完成了页面初始化,可以看到数据显示了。
6. 保存功能的实现
(1)在model页面写update接口,接口为manage/ucenter/updateUserInfo,返回数据为a
update: async ({ params: [remoteValue] }, { dispatch }) => {
let a = await dispatch({
type: 'container/post',
params: ['manage/ucenter/updateUserInfo', remoteValue]
})
return a
}
(2)在保存按钮上添加点击事件onClick={onSave}并在page页面写onSave方法。其中fieldValue里面保存着form表单验证的数据对象,因此可以取到对应input的值。
const onSave = useCallback(() => {
const { validateFields } = form
validateFields((err: any, fieldValue: any) => {
if (err) return
let res = {
mobile: fieldValue.mobile,
email: fieldValue.email
}
dispatch({
type: 'container/update',
params: [res]
}).then((v: any) => {
console.log(v)
if (v.code === 200) {
message.success('保存成功')
init()
} else {
message.error(v.message)
}
})
})
}, [dispatch, form, init])
至此可以实现保存功能。
7. 表单验证
Ant Design 表单中getFieldDecorator、getFieldValue、setFieldValue用法Ant Design 表单中getFieldDecorator、getFieldValue、setFieldValue用法 - 健人雄 - 博客园
getFieldDecorator是一个方法,这个方法接收两个参数,第一个是表单的字段对象,第二个是验证规则。这个方法本身返回一个方法,需要将需要获取值的标签包裹进去。
(1)验证手机号
<Form.Item label="电话号码">
{getFieldDecorator('mobile', {
initialValue: (editData && editData.mobile) || '',
rules: [
{ required: true, message: '电话号码不能为空' },
{
pattern: /^((\+)?86|((\+)?86)?)0?1[3458]\d{9}$/,
message: '请输入正确的电话号码'
}
]
})(<Input placeholder="请输入" type="number" />)}
</Form.Item>
(2)验证邮箱
<Form.Item label="邮箱地址">
{getFieldDecorator('email', {
initialValue: (editData && editData.email) || '',
rules: [
{
pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/,
// pattern: /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/,
// pattern: /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.]){1,2}[A-Za-z\d]{2,5}$/g,
message: '邮箱格式不正确'
},
{
max: 50,
message: '邮箱不得超过50字符'
},
{ required: true, message: '邮箱地址不能为空' }
]
})(<Input placeholder="请输入" />)}
</Form.Item>
8. style样式
.commonForm{
margin-top: 20px;
:global{
.ant-input{
width:600px;
height:40px;
margin-left: 10px;
}
.ant-form.ant-form-inline{
display: flex !important
}
}
}
.search{
width: 120px;
height: 40px;
opacity: 1;
background: #2b7dff;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
text-align: left;
color: #ffffff;
line-height: 21px;
text-align: center;
display: block;
margin-left:300px
}
9. 页面效果
10. 页面源码
// model.ts
import commonModel from '../../../../models/commonModel'
import { modelExtend } from 'tyrael'
import { listMOM } from '../../../../common/dict'
import { getLocation } from '../../../../utils/tool'
const model: ModelExtend = modelExtend(commonModel, {
state: {
selectedRows: [],
listData: {},
productData: [],
editData: {},
detailData: {}
},
namespace: 'container',
effects: {
init: async (action, { dispatch }) => {
const { query } = getLocation()
const data = await dispatch({
type: 'container/get',
params: ['manage/ucenter/detail', query]
})
dispatch({
type: 'container/setStore',
params: [{ editData: data.data }]
})
},
update: async ({ params: [remoteValue] }, { dispatch }) => {
let a = await dispatch({
type: 'container/post',
params: ['manage/ucenter/updateUserInfo', remoteValue]
})
return a
}
},
reducers: {}
})
export default model
// page.tsx
import React, { useEffect, useCallback, useMemo, useState } from 'react'
import { connect } from 'react-redux'
import PageHeaderLayout from '../../../../components/PageHeaderLayout/index'
import { getLocation } from '../../../../utils/tool'
import styles from './style.less'
import { Form, Input, Button, Card, Select, message } from 'antd'
function Container({ dispatch, form, container: { editData } }: any) {
const { getFieldDecorator, validateFields, getFieldValue } = form
const { query } = getLocation()
const init = useCallback(() => {
dispatch('container/init')
}, [dispatch])
useEffect(() => {
init()
}, [dispatch, init])
const onSave = useCallback(() => {
const { validateFields } = form
validateFields((err: any, fieldValue: any) => {
if (err) return
let res = {
mobile: fieldValue.mobile,
email: fieldValue.email
}
dispatch({
type: 'container/update',
params: [res]
}).then((v: any) => {
console.log(v)
if (v.code === 200) {
message.success('保存成功')
init()
} else {
message.error(v.message)
}
})
})
}, [dispatch, form, init])
return (
<PageHeaderLayout title="列表" className={'commonList'}>
<Card
className={styles.table}
style={{ marginTop: 1, marginLeft: 1 }}
bordered={false}
bodyStyle={{ padding: '8px 32px 32px 32px' }}
>
<div style={{ textAlign: 'center', width: '700', marginLeft: '20' }}>
<Form
//labelAlign="left"
layout="horizontal"
className={styles.commonForm}
labelCol={{ span: 3 }}
wrapperCol={{ span: 12 }}
>
<Form.Item label="用户名">
{getFieldDecorator('name', {
initialValue: (editData && editData.name) || ''
})(<Input placeholder="请输入" disabled />)}
</Form.Item>
<Form.Item label="电话号码">
{getFieldDecorator('mobile', {
initialValue: (editData && editData.mobile) || '',
rules: [
{ required: true, message: '电话号码不能为空' },
{
pattern: /^((\+)?86|((\+)?86)?)0?1[3458]\d{9}$/,
message: '电话号码格式不正确'
}
]
})(<Input placeholder="请输入" type="number" />)}
</Form.Item>
<Form.Item label="邮箱地址">
{getFieldDecorator('email', {
initialValue: (editData && editData.email) || '',
rules: [
{
pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/,
// pattern: /^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/,
// pattern: /^[A-Za-z\d]+([-_.][A-Za-z\d]+)*@([A-Za-z\d]+[-.]){1,2}[A-Za-z\d]{2,5}$/g,
message: '邮箱格式不正确'
},
{
max: 50,
message: '邮箱不得超过50字符'
},
{ required: true, message: '邮箱地址不能为空' }
]
})(<Input placeholder="请输入" />)}
</Form.Item>
<Button className={styles.search} onClick={onSave}>
保存
</Button>
</Form>
</div>
</Card>
</PageHeaderLayout>
)
}
export default connect(({ container, loading }: any) => {
return {
container,
loading: loading.models.container
}
})(Form.create()(Container))