表单

(一) 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 手机号
email 邮箱
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;

(四) 其他:特殊的表单组件 file-input