反应-将事件处理程序附加到子级

时间:2019-06-15 13:56:26

标签: javascript reactjs

我正在尝试创建一个React形式的表单组件,有点像Formik,但更简单。

我的思维方式涉及向所有孩子添加onChange处理程序。我正在使用children.map()进行此操作。可以,但是我得到一个关键警告

Warning: Each child in a list should have a unique "key" prop.

我知道没有办法抑制这种情况,所以也许有更好的方法来创建此Form组件? 另外,当<input>不是直系子女时,该如何处理?

编辑:我知道如何避免该问题,我主要是寻求解决此问题的最佳方法,包括嵌套输入的情况。

这是我要使用的方式:

<Form>
  <label htmlFor="owner">Owner</label>
  <input
    type="text"
    name="owner"
  <label htmlFor="description">Description</label>
  <input
    type="text"
    name="description"
  <input
    type="submit"
    value="Submit" />
</Form>

这是我的代码:

import React from 'react';
import PropTypes from 'prop-types';

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {}
    this.handleInputChange = this.handleInputChange.bind(this);
    // this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value =
      target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    console.log(`${name} : ${value}`)
    this.setState({
        [name]: value
    });
  }

  render() {
    return (
      <form>
        {this.props.children.map((child) => {
          if (child.type === "input") {
            return (
              <input
                onChange={this.handleInputChange}
                {...child.props} />
            )
          }
        })}
      </form>
    )
  }
}

export default Form;

2 个答案:

答案 0 :(得分:2)

如果使用渲染道具,则根本不会遇到 address <- list("1 Main Street", "2 Hope Street") lapply(address, py$print_address) 道具问题(这也是Formik的实现方式)。

您的组件很容易设置为将unique "key"传递给它的子代作为渲染道具,这也不需要您将handleChange作为直接子代。

input

使用方法如下:

class Form extends Component {
  ...
  handleInputChange() {...}

  render() {
    // Note that we call children as a function,
    // passing `handleChangeInput` as the argument.
    // If you want to pass other other things to the
    // children (handleSubmit, values from state), just
    // add them to the argument you're passing in.
    this.props.children({this.handleInputChange});
  }
}

编辑:您在评论中提到,您不想将处理程序显式传递给每个输入。实现此目标的另一种方法是使用React Context,在您的Form中提供一个Provider,并将每个<Form> // Notice that <Form> needs its immediate child to be // a function, which has your handler as the argument: {({handeInputChange}) => { return ( <form> <input type="text" name="owner" onChange={handleInputChange} /> <input type="checkbox" name="toggle" onChange={handleInputChange} /> <div> // inputs can be nested in other elements <input name=“inner” onChange={handleInputChange} /> <div> <form> ) }} </Form> 包装在使用者中:

input

事实上,Formik也具有此选项,可以使用const FormContext = React.createContext(); const FormInput = (props) => { const {handleInputChange} = useContext(FormContext); return <input handleInputChange={handleInputChange} {...props} /> } class Form extends Component { ... handleInputChange() {...} render() { // Pass anything you want into `value` (state, other handlers), // it will be accessible in the consumer <Provider value={{ handleInputChange: this.handleInputChange }}> <form> {this.props.children} </form> </Provider> } } // Usage: <Form> <FormInput type="text" name="owner" /> <FormInput type="submit" name="submit" /> <div> <FormInput type="checkbox" name="toggle" /> </div> </Form> 组件或Field函数。

答案 1 :(得分:1)

我认为这是您需要的,已经可以将子索引添加为 key 了,因为顺序不会改变,并且在这种情况下,reduce不会在数组中返回null,以防未输入child,map + filter也可以解决该问题:

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
    this.handleInputChange = this.handleInputChange.bind(this);
    // this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === "checkbox" ? target.checked : target.value;
    const name = target.name;
    console.log(`${name} : ${value}`);
    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        {this.props.children.reduce((childrenAcc, child, index) => {
          if (child.type === "input") {
            return [
              ...childrenAcc,
              <input
                key={index}
                onChange={this.handleInputChange}
                {...child.props}
              />
            ];
          }
          return childrenAcc;
        }, [])}
      </form>
    );
  }
}

function App() {
  return (
    <Form>
      <label htmlFor="owner">Owner</label>
      <input type="text" name="owner" />
      <label htmlFor="description">Description</label>
      <input type="text" name="description" />
      <input type="submit" value="Submit" />
    </Form>
  );
}

选中此sandbox