{"action":"created","issue":{"url":"https://api.github.com/repos/ant-design/ant-design/issues/4901","repository_url":"https://api.github.com/repos/ant-design/ant-design","labels_url":"https://api.github.com/repos/ant-design/ant-design/issues/4901/labels{/name}","comments_url":"https://api.github.com/repos/ant-design/ant-design/issues/4901/comments","events_url":"https://api.github.com/repos/ant-design/ant-design/issues/4901/events","html_url":"https://github.com/ant-design/ant-design/issues/4901","id":208020218,"node_id":"MDU6SXNzdWUyMDgwMjAyMTg=","number":4901,"title":"表单相关的几个问题（表单拆分、展示->可编辑状态的切换、Form.Item中的受控组件)","user":{"login":"hansnow","id":4365899,"node_id":"MDQ6VXNlcjQzNjU4OTk=","avatar_url":"https://avatars2.githubusercontent.com/u/4365899?v=4","gravatar_id":"","url":"https://api.github.com/users/hansnow","html_url":"https://github.com/hansnow","followers_url":"https://api.github.com/users/hansnow/followers","following_url":"https://api.github.com/users/hansnow/following{/other_user}","gists_url":"https://api.github.com/users/hansnow/gists{/gist_id}","starred_url":"https://api.github.com/users/hansnow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hansnow/subscriptions","organizations_url":"https://api.github.com/users/hansnow/orgs","repos_url":"https://api.github.com/users/hansnow/repos","events_url":"https://api.github.com/users/hansnow/events{/privacy}","received_events_url":"https://api.github.com/users/hansnow/received_events","type":"User","site_admin":false},"labels":[{"id":222673469,"node_id":"MDU6TGFiZWwyMjI2NzM0Njk=","url":"https://api.github.com/repos/ant-design/ant-design/labels/Enhancement","name":"Enhancement","color":"eb6420","default":false}],"state":"closed","locked":false,"assignee":{"login":"benjycui","id":3580607,"node_id":"MDQ6VXNlcjM1ODA2MDc=","avatar_url":"https://avatars0.githubusercontent.com/u/3580607?v=4","gravatar_id":"","url":"https://api.github.com/users/benjycui","html_url":"https://github.com/benjycui","followers_url":"https://api.github.com/users/benjycui/followers","following_url":"https://api.github.com/users/benjycui/following{/other_user}","gists_url":"https://api.github.com/users/benjycui/gists{/gist_id}","starred_url":"https://api.github.com/users/benjycui/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benjycui/subscriptions","organizations_url":"https://api.github.com/users/benjycui/orgs","repos_url":"https://api.github.com/users/benjycui/repos","events_url":"https://api.github.com/users/benjycui/events{/privacy}","received_events_url":"https://api.github.com/users/benjycui/received_events","type":"User","site_admin":false},"assignees":[{"login":"benjycui","id":3580607,"node_id":"MDQ6VXNlcjM1ODA2MDc=","avatar_url":"https://avatars0.githubusercontent.com/u/3580607?v=4","gravatar_id":"","url":"https://api.github.com/users/benjycui","html_url":"https://github.com/benjycui","followers_url":"https://api.github.com/users/benjycui/followers","following_url":"https://api.github.com/users/benjycui/following{/other_user}","gists_url":"https://api.github.com/users/benjycui/gists{/gist_id}","starred_url":"https://api.github.com/users/benjycui/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/benjycui/subscriptions","organizations_url":"https://api.github.com/users/benjycui/orgs","repos_url":"https://api.github.com/users/benjycui/repos","events_url":"https://api.github.com/users/benjycui/events{/privacy}","received_events_url":"https://api.github.com/users/benjycui/received_events","type":"User","site_admin":false}],"milestone":null,"comments":2,"created_at":"2017-02-16T05:21:10Z","updated_at":"2018-07-13T08:45:55Z","closed_at":"2017-11-13T15:05:58Z","author_association":"CONTRIBUTOR","body":"主要想请教下面这些问题：\r\n1. Field太多导致的卡顿问题\r\n2. 拆分Form后所有子表单值的获取\r\n3. 表单展示以及由展示状态到可编辑状态的切换问题\r\n4. 表单组件中的自定义受控组件赋初始值的问题\r\n\r\n## 1. Field太多导致的卡顿问题\r\n\r\n当`<Form></Form>`中的表单组件过多时会出现输入卡顿的情况，复现方法是在下面的例子中的某个输入框里持续按住某个键，能感受到明显的卡顿\r\n``` jsx\r\nconst { Form, Card, Input, Row, Col } = antd\r\n\r\nclass MyForm extends React.Component {\r\n  render() {\r\n    const names = ['A', 'B', 'C', 'D', 'E', 'F']\r\n    const inputCount = 20\r\n    const formItemLayout = {\r\n      labelCol: { span: 4 },\r\n      wrapperCol: { span: 20 }\r\n    }\r\n    const subForms = names.map(name => {\r\n      const fields = Array.from(Array(inputCount).keys()).map(key => {\r\n        const {getFieldDecorator} = this.props.form\r\n        return (\r\n          <Row key={key}>\r\n            <Form.Item label={`${name}-${key}`} {...formItemLayout}>\r\n              {getFieldDecorator(`${name}-${key}`)(<Input/>)}\r\n            </Form.Item>\r\n          </Row>\r\n        )\r\n      })\r\n      return (\r\n        <Col span={4} key={name}>\r\n          <Card title={`Form${name}`}>\r\n            {fields}\r\n          </Card>\r\n        </Col>\r\n      )\r\n    })\r\n    return (\r\n      <Form>\r\n        <Row gutter={8}>\r\n          {subForms}\r\n        </Row>\r\n      </Form>\r\n    )\r\n  }\r\n}\r\n\r\nconst WrappedMyForm = Form.create()(MyForm)\r\nReactDOM.render(<WrappedMyForm />, document.getElementById('container'))\r\n```\r\n\r\ndemo1: http://codepen.io/anon/pen/XpGLem?editors=001\r\n\r\n当只有一列输入框时，输入明显不卡顿了\r\n\r\ndemo1.1: http://codepen.io/anon/pen/wgOLrY?editors=001\r\n\r\n参考 #1972  后尝试拆分表单，具体做法是将`<Form></Form>`包裹在每个`Card`外面，而且不再像刚才的例子中那样将6个`Card`共120个输入框放在一个Form中。但尝试之后卡顿现象依旧存在\r\n\r\ndemo2: http://codepen.io/anon/pen/JEzQpE?editors=001\r\n\r\n上面的拆分表单的方法没有奏效，于是我换了一种更加干净彻底的拆分写法\r\n\r\n``` jsx\r\nconst { Form, Card, Input, Row, Col } = antd\r\n\r\nconst inputCount = 20\r\nconst formItemLayout = {\r\n  labelCol: { span: 4 },\r\n  wrapperCol: { span: 20 }\r\n}\r\n\r\nconst createDemoForm = name => {\r\n  return class Adapter extends React.Component {\r\n    render() {\r\n      const fields = Array.from(Array(inputCount).keys()).map(key => {\r\n        const {getFieldDecorator} = this.props.form\r\n        return (\r\n          <Row key={key}>\r\n            <Form.Item label={`${name}-${key}`} {...formItemLayout}>\r\n              {getFieldDecorator(`${name}-${key}`)(<Input/>)}\r\n            </Form.Item>\r\n          </Row>\r\n        )\r\n      })\r\n      return (\r\n        <Card title={`Form${name}`}>\r\n          <Form>\r\n            {fields}\r\n          </Form>\r\n        </Card>\r\n      )\r\n    }\r\n  }\r\n}\r\n\r\nconst FormA = Form.create()(createDemoForm('A'))\r\nconst FormB = Form.create()(createDemoForm('B'))\r\nconst FormC = Form.create()(createDemoForm('C'))\r\nconst FormD = Form.create()(createDemoForm('D'))\r\nconst FormE = Form.create()(createDemoForm('E'))\r\nconst FormF = Form.create()(createDemoForm('F'))\r\n\r\nclass MyForm extends React.Component {\r\n  render() {\r\n    return (\r\n      <div>\r\n        <Row gutter={8}>\r\n          <Col span={4}><FormA /></Col>\r\n          <Col span={4}><FormB /></Col>\r\n          <Col span={4}><FormC /></Col>\r\n          <Col span={4}><FormD /></Col>\r\n          <Col span={4}><FormE /></Col>\r\n          <Col span={4}><FormF /></Col>\r\n        </Row>\r\n      </div>\r\n    )\r\n  }\r\n}\r\n\r\nReactDOM.render(<MyForm />, document.getElementById('container'))\r\n```\r\n\r\ndemo3: http://codepen.io/anon/pen/QdoXxR?editors=001\r\n\r\n这次起作用了，输入比demo1和demo2顺滑多了。\r\n\r\n我的问题是，**demo2中已经把Input包裹在不同的Form里了，为什么输入依然卡顿？demo2和demo3本质上的区别在哪儿？**\r\n\r\n## 2. 拆分Form后所有子表单值的获取\r\n\r\n表单拆分之后在获取FieldValue时需要单独获取每个Form的值然后再进行汇总，而`form.validateFields`是一个异步操作，所以我目前的做法是使用`ref`和`Promise`来获取所有子表单的值的获取和汇总\r\n\r\n``` jsx\r\nconst { Form, Card, Input, Button, Row, Col } = antd\r\n\r\nconst inputCount = 20\r\nconst formItemLayout = {\r\n  labelCol: { span: 4 },\r\n  wrapperCol: { span: 20 }\r\n}\r\n\r\nconst createDemoForm = name => {\r\n  return class Adapter extends React.Component {\r\n    render() {\r\n      const fields = Array.from(Array(inputCount).keys()).map(key => {\r\n        const {getFieldDecorator} = this.props.form\r\n        return (\r\n          <Row key={key}>\r\n            <Form.Item label={`${name}-${key}`} {...formItemLayout}>\r\n              {getFieldDecorator(`${name}-${key}`)(<Input/>)}\r\n            </Form.Item>\r\n          </Row>\r\n        )\r\n      })\r\n      return (\r\n        <Card title={`Form${name}`}>\r\n          <Form>\r\n            {fields}\r\n          </Form>\r\n        </Card>\r\n      )\r\n    }\r\n  }\r\n}\r\n\r\nconst FormA = Form.create()(createDemoForm('A'))\r\nconst FormB = Form.create()(createDemoForm('B'))\r\nconst FormC = Form.create()(createDemoForm('C'))\r\nconst FormD = Form.create()(createDemoForm('D'))\r\nconst FormE = Form.create()(createDemoForm('E'))\r\nconst FormF = Form.create()(createDemoForm('F'))\r\n\r\nconst flatten = arr => {\r\n  return arr.reduce((acc, value) => {\r\n    const newFields = Object.assign({}, acc.fields, value.fields)\r\n    const newErrs = Object.assign({}, acc.errs, value.errs)\r\n    return Object.assign(acc, {fields: newFields}, {errs: newErrs})\r\n  })\r\n}\r\n\r\nclass MyForm extends React.Component {\r\n  constructor() {\r\n    super()\r\n    this.state = {\r\n      fields: {}, // 存放所有fieldsValue\r\n      errs: {},   // 存放所有error\r\n    }\r\n  }\r\n  getAllFieldsValue() {\r\n    Promise.all([\r\n      ::this.getSubFormValue('formA'),\r\n      ::this.getSubFormValue('formB')\r\n    ]).then(result => {\r\n      console.log(flatten(result))\r\n    })\r\n  }\r\n  getSubFormValue(formName) {\r\n    return new Promise(resolve => {\r\n      this[formName].validateFields((errs, fields) => {\r\n        resolve({errs, fields})\r\n      })\r\n    })\r\n  }\r\n  render() {\r\n    return (\r\n      <div>\r\n        <Row>\r\n          <Button\r\n            type=\"primary\"\r\n            onClick={::this.getAllFieldsValue}\r\n          >getAllFieldsValue</Button>\r\n        </Row>\r\n        <Row gutter={8}>\r\n          <Col span={4}><FormA ref={formA => {this.formA = formA}}/></Col>\r\n          <Col span={4}><FormB ref={formB => {this.formB = formB}}/></Col>\r\n          <Col span={4}><FormC ref={formC => {this.formC = formC}}/></Col>\r\n          <Col span={4}><FormD ref={formD => {this.formD = formD}}/></Col>\r\n          <Col span={4}><FormE ref={formE => {this.formE = formE}}/></Col>\r\n          <Col span={4}><FormF ref={formF => {this.formF = formF}}/></Col>\r\n        </Row>\r\n      </div>\r\n    )\r\n  }\r\n}\r\n\r\nReactDOM.render(<MyForm />, document.getElementById('container'))\r\n```\r\n\r\ndemo4: http://codepen.io/anon/pen/pRYMjg?editors=001\r\n\r\n请问，**除了上面这种方式外，有没有其他更合适的方法来做这件事呢？**\r\n\r\n## 3. 表单展示以及由展示状态到可编辑状态的切换问题\r\n\r\n业务场景是，在一个复杂表单的展示页面，用户点击编辑按钮将表单变为可编辑状态，在用户点击保存之后提交新的表单结果。\r\n\r\n我目前的做法是在表单的组附件中维护一个`editable`来控制表单的显示状态。当表单由展示状态切换为可编辑状态时，使用`initialValue`来给每个表单组件赋值。\r\n\r\n只贴一部分主要代码吧。在每个子表单中：\r\n``` jsx\r\n<Form.Item label={`${name}-${key}`} {...formItemLayout}>\r\n  {\r\n    // 在每个子Form中通过editable来决定显示状态或是编辑状态\r\n    // 并通过initialValue赋初始值\r\n    this.props.editable ?\r\n    getFieldDecorator(`${name}-${key}`, {\r\n      initialValue: this.props.originData[`${name}-${key}`]\r\n    })(<Input/>)\r\n    :\r\n    <span>{this.props.originData[`${name}-${key}`] || '--'}</span>\r\n  }\r\n</Form.Item>\r\n```\r\n\r\n在父组件中把`editable`和`originData`传给子表单\r\n``` jsx\r\n<FormA\r\n  editable={this.state.editable}\r\n  originData={this.state.originData}\r\n  ref={formA => {this.formA = formA}}\r\n/>\r\n```\r\n\r\ndemo5: http://codepen.io/anon/pen/zNXOGJ?editors=001\r\n\r\n**上面这种做法虽然实现了需求，但是InitialValue的给组件赋值的时机似乎不是很稳定，关于InitialValue的问题我在第四个问题中再做讨论，我想问有没有更直接更稳定的做法来实现这种需求呢**\r\n\r\n## 4. 表单组件中的自定义受控组件赋初始值的问题\r\n\r\n在实际业务中，表单中的组件往往不会都是简单的文本输入框，很可能有一些复杂的自定义组件，以文档中的[自定义表单控件为例](https://ant.design/components/form-cn/#components-form-demo-customized-form-controls)\r\n\r\n以下是一个简单版的值-单位输入框，没有考虑值是否合法等情况\r\n\r\n``` jsx\r\nclass MyInput extends React.Component {\r\n  constructor() {\r\n    super()\r\n    this.state = {\r\n      value: '',\r\n      unit: 'RMB'\r\n    }\r\n  }\r\n  handleValueChange(e) {\r\n    const {value} = e.target\r\n    this.setState({value}, ()=>{this.triggerChange()})\r\n  }\r\n  handleUnitChange(unit) {\r\n    this.setState({unit}, ()=>{this.triggerChange()})\r\n  }\r\n  triggerChange() {\r\n    const {onChange} = this.props\r\n    if (onChange) {\r\n      onChange({...this.state})\r\n    }\r\n  }\r\n  componentWillReceiveProps(nextProps) {\r\n    if ('value' in nextProps && nextProps.value) {\r\n      this.setState({\r\n        value: nextProps.value.value,\r\n        unit: nextProps.value.unit\r\n      })\r\n    }\r\n  }\r\n  render() {\r\n    return (\r\n      <span>\r\n        <Input\r\n          value={this.state.value}\r\n          onChange={::this.handleValueChange}\r\n        />\r\n        <Select\r\n          value={this.state.unit}\r\n          onChange={::this.handleUnitChange}\r\n        >\r\n          <Select.Option key=\"RMB\">RMB</Select.Option>\r\n          <Select.Option key=\"HKD\">HKD</Select.Option>\r\n        </Select>\r\n      </span>\r\n    )\r\n  }\r\n}\r\n```\r\n\r\nFormItem中也要做相应更改\r\n\r\n``` jsx\r\n<Form.Item label={`${name}-${key}`} {...formItemLayout}>\r\n  {\r\n    this.props.editable ?\r\n    getFieldDecorator(`${name}-${key}`, {\r\n      initialValue: this.props.originData[`${name}-${key}`]\r\n    })(<MyInput />)\r\n    :\r\n    <span>{\r\n      this.props.originData[`${name}-${key}`] ? `${this.props.originData[`${name}-${key}`].value}(${this.props.originData[`${name}-${key}`].unit})` : '--'\r\n    }</span>\r\n  }\r\n</Form.Item>\r\n```\r\n\r\ndemo6: http://codepen.io/anon/pen/VPNwPb?editors=001\r\n\r\n在demo6中点击edit后A-0和A-1并没有正确的被赋值，**此时在FormA中的其他输入框如A-3内输入时，A-0和A-1的值被正确显示出来。**\r\n\r\n后来在Debug的过程中发现，FormItem给它的子组件赋初始值时，没有通过`componentWillReceiveProps`这个hook，而是直接执行了`render`方法，所以为了解决这个问题，我们应该在`render`方法中`fixControlledValue`，改造之后的`MyInput`代码如下\r\n\r\n``` jsx\r\nconst fixControlledValue = (rawValue, type) => {\r\n  if (rawValue) {\r\n    return rawValue[type]\r\n  } else {\r\n    return undefined\r\n  }\r\n}\r\nclass MyInput extends React.Component {\r\n  constructor() {\r\n    super()\r\n    this.state = {\r\n      value: '',\r\n      unit: 'RMB'\r\n    }\r\n  }\r\n  handleValueChange(e) {\r\n    const {value} = e.target\r\n    this.setState({value}, ()=>{this.triggerChange()})\r\n  }\r\n  handleUnitChange(unit) {\r\n    this.setState({unit}, ()=>{this.triggerChange()})\r\n  }\r\n  triggerChange() {\r\n    const {onChange} = this.props\r\n    if (onChange) {\r\n      onChange({\r\n        value: this.state.value,\r\n        unit: this.state.unit\r\n      })\r\n    }\r\n  }\r\n  render() {\r\n    const {value} = this.props\r\n    const fixedValue = fixControlledValue(value, 'value')\r\n    const fixedUnit = fixControlledValue(value, 'unit')\r\n    return (\r\n      <span>\r\n        <Input\r\n          value={fixedValue}\r\n          onChange={(e) => {this.handleValueChange(e)}}\r\n        />\r\n        <Select\r\n          value={fixedUnit}\r\n          onChange={(key) => {this.handleUnitChange(key)}}\r\n        >\r\n          <Select.Option key=\"RMB\">RMB</Select.Option>\r\n          <Select.Option key=\"HKD\">HKD</Select.Option>\r\n        </Select>\r\n      </span>\r\n    )\r\n  }\r\n}\r\n```\r\n\r\ndemo7: http://codepen.io/anon/pen/YNMzYr?editors=001\r\n\r\n结合3和4，我想问的是**这种表单由展示状态切换为编辑状态的需求有没有其他做法？如果我的这种写法没太大问题的话，受控组件这里我理解的正确吗？**\r\n\r\n## 最后\r\n\r\n以上就是我最近在做项目的过程中遇到的一些表单相关的问题，同时也附上了一些我自己的解决方法，但总感觉是workaround，不是best practice，希望各位能给我一些指导，谢谢！\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/ant-design/ant-design/issues/comments/404768952","html_url":"https://github.com/ant-design/ant-design/issues/4901#issuecomment-404768952","issue_url":"https://api.github.com/repos/ant-design/ant-design/issues/4901","id":404768952,"node_id":"MDEyOklzc3VlQ29tbWVudDQwNDc2ODk1Mg==","user":{"login":"cfk199244","id":18102224,"node_id":"MDQ6VXNlcjE4MTAyMjI0","avatar_url":"https://avatars3.githubusercontent.com/u/18102224?v=4","gravatar_id":"","url":"https://api.github.com/users/cfk199244","html_url":"https://github.com/cfk199244","followers_url":"https://api.github.com/users/cfk199244/followers","following_url":"https://api.github.com/users/cfk199244/following{/other_user}","gists_url":"https://api.github.com/users/cfk199244/gists{/gist_id}","starred_url":"https://api.github.com/users/cfk199244/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cfk199244/subscriptions","organizations_url":"https://api.github.com/users/cfk199244/orgs","repos_url":"https://api.github.com/users/cfk199244/repos","events_url":"https://api.github.com/users/cfk199244/events{/privacy}","received_events_url":"https://api.github.com/users/cfk199244/received_events","type":"User","site_admin":false},"created_at":"2018-07-13T08:45:55Z","updated_at":"2018-07-13T08:45:55Z","author_association":"NONE","body":"不好意思, 这么久了还挖你帖子. 我想问一下, 多个表单之间的状态你是怎么控制的. 比如我又FormA, FormB. 当然, 主界面还有Button Group.     1、我FormA里某个元素是否禁用取决于FormB里某个字段的值.  2、ButtonGroup是否禁用取决于FormB里某个字段的值."}}
{"actor":{"display_login":"cfk199244"}}