表单
(一) value与onChange
如果我们在表单元素中直接使用value绑定数据,我们将会发现表单无法更改,打开控制台,也会报出如下错误。
Warning: Failed form propType: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`. Check the render method of `xxx`.
这个时候有两种处理方法,第一种是将表单组件处理为不受控组件,加上defaultValue就可以支持更改了。
<input ref='name' type='text' defaultValue='shanghai' />
另一种稍微麻烦一些。我们组件处理成受控组件,使用state来控制input的value,通过onChange控制数据的刷新。
<input ref='name' type='text' value={this.state.province} onChange={this.handleProvince} />
getInitialState: function() {
return {
province: 'shanghai'
};
},
handleProvince: function(e) {
this.setState({
province: e.target.value
});
}
一般来说,我们会通过受控组件来控制表单组件。
(二)最原始的表单处理
实例
const React = require('react');
const MyForm = React.createClass({
render: function() {
return (
<div>
<h1>学生信息表</h1>
<label htmlFor='stuName'>请输入姓名</label>
<input type='text' name='stuName' value={this.state.stu.name} onChange={this.handleStuName} />
<br />
<label htmlFor='gender'>请选择性别</label>
<input
type='radio'
name='gender'
checked={this.state.stu.gender === 0}
value='0'
id='male'
onChange={this.handleStuGender}
/>
<label htmlFor='male'>男</label>
<input
type='radio'
name='gender'
checked={this.state.stu.gender === 1}
value='1'
id='female'
onChange={this.handleStuGender}
/>
<label htmlFor='female'>女</label>
<br />
喜欢的运动:
<input
id='basketball'
type='checkbox'
value='basketball'
checked={this.state.stu.hobby.indexOf('basketball') !== -1}
onChange={this.handleStuHobby}
/>
<label htmlFor='basketball'>篮球</label>
<input
id='football'
type='checkbox'
value='football'
checked={this.state.stu.hobby.indexOf('football') !== -1}
onChange={this.handleStuHobby}
/>
<label htmlFor='football'>足球</label>
<input
id='other'
type='checkbox'
value='other'
checked={this.state.stu.hobby.indexOf('other') !== -1}
onChange={this.handleStuHobby}
/>
<label htmlFor='other'>其他</label>
<br />
来源:
<select value={this.state.area} onChange={this.handleOriginChange}>
<option value='beijing'>北京地区</option>
<option value='shanghai'>上海地区</option>
<option value='guangzhou'>广州地区</option>
</select>
<br />
<label htmlFor='note'>备注</label>
<textarea value={this.state.note} onChange={this.handleNoteChange} />
</div>
);
},
getInitialState: function() {
return {
stu: {
name: '',
note: '',
gender: 0,
hobby: [],
area: ''
}
};
},
handleStuName: function(e) {
let updateStuInfo = { name: e.target.value };
let stuInfo = Object.assign({}, this.state.stu, updateStuInfo);
this.setState({
stu: stuInfo
});
},
handleNoteChange(e) {
let updateStuInfo = { note: e.target.value };
let stuInfo = Object.assign({}, this.state.stu, updateStuInfo);
this.setState({
stu: stuInfo
});
},
handleStuGender(e) {
let updateStuInfo = { gender: parseInt(e.target.value) };
let stuInfo = Object.assign({}, this.state.stu, updateStuInfo);
this.setState({
stu: stuInfo
});
},
handleStuHobby(e) {
const checked = e.target.checked;
const hobby = e.target.value;
let stuHobby = JSON.parse(JSON.stringify(this.state.stu.hobby));
if (checked && stuHobby.indexOf(hobby) === -1) {
stuHobby.push(hobby);
} else {
stuHobby = stuHobby.filter(function(item) {
return item !== hobby;
});
}
let updateStuInfo = { hobby: stuHobby };
let stuInfo = Object.assign({}, this.state.stu, updateStuInfo);
this.setState({
stu: stuInfo
});
},
handleOriginChange: function(e) {
let updateStuInfo = { origin: e.target.value };
let stuInfo = Object.assign({}, this.state.stu, updateStuInfo);
this.setState({
stu: stuInfo
});
}
});
module.exports = MyForm;
(二)更快的处理表单事件
在react中,表单的处理略微繁琐,因为表单的value与state绑定在一起,我们要更新value,只能先去更新state,相当于我们需要手动刷新value值,可以看到上例中存在很多刷新表单value的事件。不断重复的写相同功能的事件显得代码十分臃肿,那么有没有更好的办法处理事件呢?当然有,请看以下
const React = require('react');
const MyForm = React.createClass({
render: function() {
return (
<div>
<h1>学生信息表</h1>
<label htmlFor='stuName'>请输入姓名</label>
<input type='text' id='stuName' value={this.state.stu.name} onChange={this.handleForm.bind(this, 'name')} />
<br />
<label htmlFor='gender'>请选择性别</label>
<input
type='radio'
name='gender'
checked={this.state.stu.gender === '0'}
value='0'
id='male'
onChange={this.handleForm.bind(this, 'gender')}
/>
<label htmlFor='male'>男</label>
<input
type='radio'
name='gender'
checked={this.state.stu.gender === '1'}
value='1'
id='female'
onChange={this.handleForm.bind(this, 'gender')}
/>
<label htmlFor='female'>女</label>
<br />
喜欢的运动:
<input
id='basketball'
type='checkbox'
value='basketball'
checked={this.state.stu.hobby.indexOf('basketball') !== -1}
onChange={this.handleForm.bind(this, 'hobby')}
/>
<label htmlFor='basketball'>篮球</label>
<input
id='football'
type='checkbox'
value='football'
checked={this.state.stu.hobby.indexOf('football') !== -1}
onChange={this.handleForm.bind(this, 'hobby')}
/>
<label htmlFor='football'>足球</label>
<input
id='other'
type='checkbox'
value='other'
checked={this.state.stu.hobby.indexOf('other') !== -1}
onChange={this.handleForm.bind(this, 'hobby')}
/>
<label htmlFor='other'>其他</label>
<br />
来源:
<select value={this.state.area} onChange={this.handleForm.bind(this, 'area')}>
<option value='beijing'>北京地区</option>
<option value='shanghai'>上海地区</option>
<option value='guangzhou'>广州地区</option>
</select>
<br />
<label htmlFor='note'>备注</label>
<textarea value={this.state.note} onChange={this.handleForm.bind(this, 'note')} />
</div>
);
},
getInitialState: function() {
return {
stu: {
name: '',
note: '',
gender: '0',
hobby: [],
area: ''
}
};
},
handleForm: function(name, e) {
let stu = JSON.parse(JSON.stringify(this.state.stu));
let val = e.target.value;
if (name !== 'hobby') {
stu[name] = e.target.value;
} else {
const isChecked = e.target.checked;
let stuHobby = JSON.parse(JSON.stringify(this.state.stu.hobby));
if (isChecked && stuHobby.indexOf(val) === -1) {
stuHobby.push(val);
} else {
stuHobby = stuHobby.filter(function(item) {
return item !== val;
});
}
stu.hobby = stuHobby;
}
this.setState(
{
stu: stu
},
function() {
console.log(this.state);
}
);
}
});
module.exports = MyForm;
(三)设计表单组件
实际的开发中,表单常常是根据不同的对象分为不同的模块。这些模块往往是一个一个的组件,那么我们在开发表单组件的时候要注意什么呢?
- 考虑输入(数据源来自何处)
- 我们要接受用户哪些数据
- 数据清理:数据怎么传输才方便我们处理
- 考虑输出
- 如何将数据传输出去
- 兼用
- 表单一般呈现出两种状态:新增状态和修改状态,两种不同的状态如何由参数控制,又有何区别
- 表单校验
- 表单输入的时候是否校验、要校验哪些内容
- 注意以下常用校验
- 长度校验(注意边界)
- 类型校验
- 格式校验
以下是我们根据以上考虑设计的表单组件实例
假设我们目前要展现员工信息,有以下字段
字段名称 | 字段描述 | 备注 |
---|---|---|
staffNo |
员工编号 | 不可编辑 |
name | 姓名 | 2-10个字之间 |
gender | 性别 | 0 男 1女 |
birthday | 出生年月 | 提供年的选择范围为当前年40年-当前年 |
phone | 手机号 | |
邮箱 | ||
departmentId |
部门 | |
jobId |
职位 | |
isOnWork |
是否在职 | 0离职,1在职 |
const React = require('react');
const StaffForm = React.createClass({
render: function() {
const _this = this;
return (
<div>
<h1>员工信息表</h1>
<label htmlFor='staffNo'>员工编号</label>
<input type='text' value={this.state.staff.no} onChange={this.handleForm.bind(this, 'no')} />
<br />
<label htmlFor='name'>姓名</label>
<input type='text' id='name' value={this.state.staff.name} onChange={this.handleForm.bind(this, 'name')} />
<br />
<label htmlFor='gender'>性别</label>
<input
type='radio'
name='gender'
value='0'
checked={this.state.staff.gender === '0'}
id='male'
onChange={this.handleForm.bind(this, 'gender')}
/>
<label htmlFor='male'>男</label>
<input
type='radio'
name='gender'
value='1'
checked={this.state.staff.gender === '1'}
id='female'
onChange={this.handleForm.bind(this, 'gender')}
/>
<label htmlFor='female'>女</label>
<br />
出生年月:
<select value={this.state.staff.birthdayYear} onChange={this.handleForm.bind(this, 'birthdayYear')}>
{this.year.map(function(item) {
return (
<option value={item} key={item}>
{item}
</option>
);
})}
</select>
年
<select value={this.state.staff.birthdayMonth} onChange={this.handleForm.bind(this, 'birthdayMonth')}>
{this.month.map(function(item) {
return (
<option value={item} key={item}>
{item}
</option>
);
})}
</select>
月
<br />
<label htmlFor='name'>手机号</label>
<input type='text' id='name' value={this.state.staff.phone} onChange={this.handleForm.bind(this, 'phone')} />
<br />
<label htmlFor='name'>邮箱</label>
<input type='email' id='name' value={this.state.staff.email} onChange={this.handleForm.bind(this, 'email')} />
<br />
部门:
<select value={this.state.staff.departmentId} onChange={this.handleForm.bind(this, 'departmentId')}>
{this.department.map(function(item, index) {
return (
<option value={item.departmentId} key={item.departmentId}>
{item.name}
</option>
);
})}
</select>
<br />
职位:
<select value={this.state.staff.jobId} onChange={this.handleForm.bind(this, 'jobId')}>
{this.job.map(function(item, index) {
return (
<option value={item.jobId} key={item.jobId}>
{item.name}
</option>
);
})}
</select>
<br />
是否在职:
<select value={this.state.staff.isOnWork} onChange={this.handleForm.bind(this, 'isOnWork')}>
<option value='0'>离职</option>
<option value='1'>在职</option>
</select>
</div>
);
},
getInitialState: function() {
return {
staff: {
no: '',
name: '',
gender: '0',
birthdayYear: '',
birthdayMonth: '',
phone: '',
email: '',
departmentId: '',
jobId: '',
isOnWork: ''
}
};
},
componentWillMount: function() {
this.department = [];
this.job = [];
this.month = [];
this.year = [];
for (let i = 1; i < 13; i++) {
this.month.push(i);
}
const nowYear = new Date().getFullYear();
const MinYear = nowYear - 18;
for (let i = 0; i < 50; i++) {
this.year.push(MinYear - i);
}
},
componentDidMount: function() {
// action add 新增 alter修改,不要用update,update可能会与sql冲突
// 修改操作的时候往往要查询数据,我们这里设定为查询从内部发起
const action = 'alter';
// 查询部门
const departmentRes = this.getDepartmentRequest();
this.department = departmentRes;
this.job = departmentRes[0].job;
if (action === 'alter') {
const data = this.getStateInfoRequest();
const res = data.info;
const birthdayYear = res.birthday.substr(0, 4);
let birthdayMonth = res.birthday.substr(4, 2);
if (birthdayMonth[0] === '0') {
birthdayMonth = birthdayMonth[1];
}
const staff = {
no: res.no,
name: res.name,
gender: res.gender,
birthdayYear: birthdayYear,
birthdayMonth: birthdayMonth,
phone: res.phone,
email: res.email,
isOnWork: res.isOnWork,
departmentId: res.departmentId,
jobId: res.jobId
};
const staffDepartment = departmentRes.filter(function(item) {
return item.departmentId.toString() === res.departmentId;
});
this.job = staffDepartment[0].job;
this.setState({
staff: staff
});
}
},
handleForm: function(name, e) {
let staff = JSON.parse(JSON.stringify(this.state.staff));
staff[name] = e.target.value;
this.setState({
staff: staff
});
},
// 获取员工信息
getStateInfoRequest: function() {
return {
info: {
no: '1110',
name: '李枫',
gender: '0',
birthday: '198904',
phone: '12334562345',
email: '123212312@gmail.com',
departmentId: '1',
jobId: '2',
isOnWork: '1'
}
};
},
getDepartmentRequest: function() {
return [
{
departmentId: 0,
name: '企业发展事业群',
job: [
{
jobId: 0,
name: 'Android客户端高级工程师'
},
{
jobId: 1,
name: '后台开发高级工程师'
},
{
jobId: 2,
name: '高级产品经理'
},
{
jobId: 3,
name: '数据分析工程师'
}
]
},
{
departmentId: 1,
name: '互动娱乐事业群',
job: [
{
jobId: 0,
name: '版本管理'
},
{
jobId: 1,
name: '平台运营策划'
},
{
jobId: 2,
name: '手游运营推广'
},
{
jobId: 3,
name: '运营经理'
}
]
},
{
departmentId: 2,
name: '技术工程事业部',
job: [
{
jobId: 0,
name: '物联网平台架构师'
},
{
jobId: 1,
name: '边缘计算平台架构师'
},
{
jobId: 2,
name: 'FPGA研发工程师'
},
{
jobId: 3,
name: '异构计划专家工程师'
}
]
},
{
departmentId: 3,
name: '云与智慧产业事业部',
job: [
{
jobId: 0,
name: '交通流系统工程师'
},
{
jobId: 1,
name: '商务分析经理'
},
{
jobId: 2,
name: '语音识别算法研究员'
},
{
jobId: 3,
name: '商务拓展经理'
}
]
},
{
departmentId: 4,
name: '平台与内容事业部',
job: [
{
jobId: 0,
name: '信息流后台开发工程师'
},
{
jobId: 1,
name: '平台产品运营'
},
{
jobId: 2,
name: 'IOS开发工程师'
},
{
jobId: 3,
name: 'web前端工程师'
}
]
}
];
}
});
module.exports = StaffForm;