强制输入的 onChange 事件冒泡到父表单,其值存储在状态中

Force input's onChange event bubble to parent form with the value stored in the state

编辑

抱歉展示了错误的用例。 Form 中的所有输入都通过 this.props.children 传递,并且它们可以位于组件树的任何深处,因此将 handleChange 直接传递给输入的方法将不起作用完全没有。


这是重现问题的代码片段。

class CustomSelect extends React.Component {
  items = [
    { id: 1, text: "Kappa 1" },
    { id: 2, text: "Kappa 2" },
    { id: 3, text: "Kappa 3" }
  ]
  
  state = {
    selected: null,
  }
  
  handleSelect = (item) => {
    this.setState({ selected: item })
  }
  
  render() {
    var { selected } = this.state
    return (
      <div className="custom-select">
        <input
          name={this.props.name}
          required
          style={{ display: "none" }} // or type="hidden", whatever
          value={selected
            ? selected.id
            : ""
          }
          onChange={() => {}}
        />
        <div>Selected: {selected ? selected.text : "nothing"}</div>
        {this.items.map(item => {
          return (
            <button 
              key={item.id}
              type="button" 
              onClick={() => this.handleSelect(item)}
            >
              {item.text}
            </button>
          )
        })}
      </div>
    )
  }
}

class Form extends React.Component {
  handleChange = (event) => {
    console.log("Form onChange")
  }
  
  render() {
    return (
      <form onChange={this.handleChange}>
        {this.props.children}
      </form>
    )
  }
}

ReactDOM.render(
  <Form>
    <label>This input will trigger form's onChange event</label>
    <input />
    <CustomSelect name="kappa" />
  </Form>,
  document.getElementById("__root")
 )
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


<div id="__root"></div>

如您所见,当您在默认输入(受控或不受控,无论什么)中键入内容时,表单会捕获冒泡 onChange 事件。但是,当您以编程方式设置输入值时(在本例中为 state),不会触发 onChange 事件,因此我无法在表单的 onChange.[= 中捕获此更改20=]

有什么办法可以解决这个问题吗?我试图在 setState({ selected: input }) 之后立即 input.dispatchEvent(new Event("change", { bubbles: true })) 并在它的回调中,但没有结果。

我真的认为做你想做的最好的事情是首先确保控制每个单独的输入。保持这些值的状态,只处理表单中的 onSubmit 事件。 React 甚至在这里推荐了这种方法 https://reactjs.org/docs/uncontrolled-components.html

In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.

您可以在此处阅读有关控制的信息https://reactjs.org/docs/forms.html#controlled-components

如果你想看看我是如何用控件制作的,这看起来像那样https://codesandbox.io/s/2w9qnk8lxp你可以看到如果你点击输入表单提交事件并保持状态值。

class CustomSelect extends React.Component {
  items = [
    { id: 1, text: "Kappa 1" },
    { id: 2, text: "Kappa 2" },
    { id: 3, text: "Kappa 3" }
  ];

  render() {
    return (
      <div className="custom-select">
        <div>
          Selected: {this.props.selected ? this.props.selected.text : "nothing"}
        </div>
        {this.items.map(item => {
          return (
            <button
              key={item.id}
              type="button"
              onClick={() => this.props.onChange(item)}
            >
              {item.text}
            </button>
          );
        })}
      </div>
    );
  }
}

class Form extends React.Component {
  state = {
    firstInput: "",
    selected: null
  };

  handleSubmit = event => {
    event.preventDefault();
    console.log("Form submit", this.state);
  };

  handleInputChange = name => event => {
    this.setState({ [name]: event.target.value });
  };

  handleSelectedChanged = selected => {
    this.setState({ selected });
  };

  render() {
    console.log(this.state);
    return (
      <form onSubmit={this.handleSubmit}>
        <label>This input will trigger form's onChange event</label>
        <input
          value={this.state.firstInput}
          onChange={this.handleInputChange("firstInput")}
        />
        <CustomSelect
          name="kappa"
          selected={this.state.selected}
          onChange={this.handleSelectedChanged}
        />
      </form>
    );
  }
}

但是如果你真的想要你的方式,你应该将 handleChange 函数作为回调传递给 children 并在你点击一个元素时使用这个道具作为一个函数。这里的示例 https://codesandbox.io/s/0o8545mn1p.

class CustomSelect extends React.Component {
  items = [
    { id: 1, text: "Kappa 1" },
    { id: 2, text: "Kappa 2" },
    { id: 3, text: "Kappa 3" }
  ];

  state = {
    selected: null
  };

  handleSelect = item => {
    this.setState({ selected: item });

    this.props.onChange({ selected: item });
  };

  render() {
    var { selected } = this.state;
    return (
      <div className="custom-select">
        <input
          name={this.props.name}
          required
          style={{ display: "none" }} // or type="hidden", whatever
          value={selected ? selected.id : ""}
          onChange={() => {}}
        />
        <div>Selected: {selected ? selected.text : "nothing"}</div>
        {this.items.map(item => {
          return (
            <button
              key={item.id}
              type="button"
              onClick={() => this.handleSelect(item)}
            >
              {item.text}
            </button>
          );
        })}
      </div>
    );
  }
}

class Form extends React.Component {
  handleChange = event => {
    console.log("Form onChange");
  };

  render() {
    return (
      <form onChange={this.handleChange}>
        <label>This input will trigger form's onChange event</label>
        <input />
        <CustomSelect name="kappa" onChange={this.handleChange} />
      </form>
    );
  }
}

如果您从表单中传递函数,您可以手动触发它。您只需要创建 new Event() 来满足您对信息的需求。由于它是一个道具,如果父元素中发生任何方法更改,它将同步。

由于您使用道具在 form 中生成元素,因此您必须像这样映射它们。这是仅添加到自定义元素的事件。

class CustomSelect extends React.Component {
  propTypes: {
        onChange: React.PropTypes.func
    }
  items = [
    { id: 1, text: "Kappa 1" },
    { id: 2, text: "Kappa 2" },
    { id: 3, text: "Kappa 3" }
  ]
  
  state = {
    selected: null,
  }
  
  handleSelect = (item) => {
    this.setState({ selected: item });
    this.props.onChange.self(new Event('onchange'))
  };
  
  render() {
    var { selected } = this.state
    return (
      <div className="custom-select">
        <input
          name={this.props.name}
          required
          style={{ display: "none" }} // or type="hidden", whatever
          value={selected
            ? selected.id
            : ""
          }
          onChange={() => {}}
        />
        <div>Selected: {selected ? selected.text : "nothing"}</div>
        {this.items.map(item => {
          return (
            <button 
              key={item.id}
              type="button" 
              onClick={() => this.handleSelect(item)}
            >
              {item.text}
            </button>
          )
        })}
      </div>
    )
  }
}

class Form extends React.Component {
  handleChange = (event) => {
    console.log("Form onChange")
  }
  
  render() {
    let self = this.handleChange;
    let children = React.Children.map(this.props.children, (child, i) => {
          if(typeof child.type === "function"){
            return React.cloneElement(child, {
              onChange: {self}
            });
          }
          return child;
        });
    return (
      <form onChange={this.handleChange}>
        {children}
      </form>
    )
  }
}

ReactDOM.render(
  <Form>
    <label>This input will trigger form's onChange event</label>
    <input />
    <CustomSelect name="kappa" />
  </Form>,
  document.getElementById("__root")
 )
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>


<div id="__root"></div>

使用以下内容更新您的 CustomSelect 组件:

class CustomSelect extends React.Component {

    ...

    // you'll use this reference to access the html input.
    ref = React.createRef();

    handleSelect = item => {
        this.setState({ selected: item });

        // React overrides input value setter, but you can call the
        // function directly on the input as context
        const inputValSetter = Object.getOwnPropertyDescriptor(
            window.HTMLInputElement.prototype,
            "value"
        ).set;
        inputValSetter.call(this.ref.current, "dummy");

        // fire event
        const ev = new Event("input", { bubbles: true });
        this.ref.current.dispatchEvent(ev);
    };

    ...

    render() {
        ...

        return (
            <div className="custom-select">
                <input
                    // you'll use the reference in `handleSelect`
                    ref={this.ref}
                    name={this.props.name}
                    required
                    style={{ display: "none" }} // or type="hidden", whatever
                    value={selected ? selected.id : ""}
                    onChange={() => {}}
                />

                ...

            </div>
        );
    }

    ...
}

您的 Form 组件具有以下内容:

class Form extends React.Component {

    handleChange = event => {
        console.log("Form onChange");

        // remove synthetic event from pool
        event.persist();
    };

    ...
}