6

编辑

抱歉显示错误的用例。内部的所有输入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.

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

4

3 回答 3

3

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

在大多数情况下,我们建议使用受控组件来实现表单。在受控组件中,表单数据由 React 组件处理。另一种方法是不受控制的组件,其中表单数据由 DOM 本身处理。

您可以在此处阅读有关受控的信息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函数作为回调传递给孩子,并在你点击一个元素时使用这个道具作为一个函数。此处示例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>
    );
  }
}
于 2019-04-15T12:15:59.633 回答
3

如果您从表单传递函数,您可以手动触发它。您只需要创建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>

于 2019-04-15T12:51:35.183 回答
2

使用以下内容更新您的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();
    };

    ...
}
于 2019-04-21T23:37:18.303 回答