反应:HTML 表单应该是受控组件还是不受控组件?

Posted

技术标签:

【中文标题】反应:HTML 表单应该是受控组件还是不受控组件?【英文标题】:REACT: Should HTML forms be Controlled or Uncontrolled components? 【发布时间】:2019-07-13 17:35:17 【问题描述】:

我对我的表单状态应该在哪里生活感到两难。

React 文档提及:

识别基于该状态呈现某些内容的每个组件。 找到一个共同的所有者组件(一个单一的组件高于所有 需要层次结构中状态的组件)。无论是普通 所有者或层次结构中更高的另一个组件应该拥有 状态。如果您找不到一个有意义的组件 state,创建一个简单的新组件来保存状态并添加它 在公共所有者组件上方的层次结构中的某个位置。

我有一个简单的评论 UI。

    用户在文本区域输入评论。 用户在输入字段中输入姓名。 用户点击post,下方显示评论。

组件等级如下:

 - CommentSection.jsx ---> main (should "Post" state (?))
   -comment-post.jsx  ---> User Comment Form
   - comment-table.jsx
     - comment.jsx 
       - comment-avatar.jsx 
       - comment-body.jsx 

问题:comment-post.jsx 中,输入和文本区域有 onChange() 事件处理程序,并且在提交帖子按钮上还有一个单击事件处理程序。我可以选择以下两种方法之一:

    comment-post.jsx onChange 被触发时,将commentBody 发送到CommentSection.jsx 这里的问题是我会在用户输入后立即发送commentBody,然后在触发时发送名称等等强> comment-post.jsx当onChange被触发时SAVE状态中的值,然后命名状态中的字段,当用户点击提交按钮时发送到CommentSection.jsx好处是字段首先设置在状态中,当用户点击他们发送给父母的帖子(应该是正确的?)

评论的输入字段,即commentBody和Name是否应该是 保存在comment-post.jsx(表单组件)还是父级?

现在我正在执行的状态 2. 在状态中保存表单字段,然后在提交时发送状态值。

我认为问题在于 onChange 和 onClick 是两个不同的处理程序,问题是理想情况下哪个应该将值传递给父组件?

    class CommentSection extends Component 
      state = 
        posts: []
      ;

      handleCommentPost = post => 
        const posts = this.state.posts;

        posts.push(post);
        this.setState( posts );
      ;

      render() 
        console.log("comment:", this.state.posts);

        return (
          <React.Fragment>
            <h1>comments</h1>
            <div className="row bootstrap snippets">
              <div className="col-md-6 col-md-offset-2 col-sm-12">
                <div className="comment-wrapper">
                  <Comment_Post onClick=this.handleCommentPost />
                  <Comment_Table comments=this.state.posts />
                </div>
              </div>
            </div>
          </React.Fragment>
        );
      
    

    class Comment_Table extends Component 
       render() 
        const posts = this.props.comments;
        let count = 0;

        return (
          <div className="panel panel-info">
            <hr />
            <ul className="media-list">
              posts.map(post => (
                <Comment key=count++ comment=post.commentBody />
              ))
            </ul>
          </div>
        );
      
    



class Comment extends Component 
  render() 
    return (
      <li className="media">
        <Comment_Avatar userAvatar=this.props.commentAvatar />
        <Comment_Body userComment=this.props.comment />
      </li>
    );
  


class Comment_Body extends Component 
  render() 
    const  userComment  = this.props;
    return (
      <div className="media-body">
        <span className="text-muted pull-right">
          <small className="text-muted">30 min ago</small>
        </span>
        <strong className="text-success">@MartinoMont</strong>
        <p>
          userComment
          
        </p>
      </div>
    );
  

class Comment_Post extends Component 
  state = 
    commentBody: null
  ;
  onCommentChange = e => 
    this.setState( commentBody: e.target.value );
  ;

  onCommentPost = e => 
    const commentBody = this.state.commentBody;
    if (commentBody !== null) 
      this.props.onClick( commentBody );
    
  ;
  onNameInput = e => ;

  onCommentPostError() 

  render() 
    return (
      <React.Fragment>
        <div className="panel-heading p-heading">Comment panel</div>
        <div className="panel-body">
          <textarea
            onChange=this.onCommentChange
            className="form-control"
            placeholder="write a comment..."
            rows="3"
          />
          <label htmlFor="fname" onChange=this.onNameInput>
            Name:" "
          </label>
          <input id="fname" placeholder="John" />
          <br />
          <button
            type="button"
            className="btn btn-info pull-right"
            onClick=this.onCommentPost
          >
            Post
          </button>
          <div className="clearfix" />
        </div>
      </React.Fragment>
    );
  


class Comment_Avatar extends Component 
  render() 
    return (
      <a href="#" className="pull-left">
        <img
          src="https://bootdey.com/img/Content/user_1.jpg"
          
          className="img-circle"
        />
      </a>
    );
  

【问题讨论】:

【参考方案1】:

我认为问题在于 onChange 和 onClick 是两个不同的处理程序,问题是理想情况下哪个应该将值传递给父组件?

我们在标准用户界面设计中使用了两种类型的表单。首先是输入中的任何更改都会保存更改。其次,在对表单元素进行更改后,您按下提交按钮,然后更改将被保存。

由于您已经实现了第二种类型,您的 onChange 应该处理控制您的 TextArea 状态,而您的 onClick 应该处理提交。所以你的代码很好。

我对我的表单状态应该住在哪里感到两难。

这取决于...在您的情况下,您只有一个表单和两个表单元素,其中没有一个是可重用的。所以这些不受控制的表格很适合你。但是,如果您想要一个可重用的表单组件,或者您想要一个包含 15 个字段的表单,您不会希望为每个表单编写单独的 onChange 处理程序。为此,您需要制作一个可以为您处理所有这些事情的受控表单组件。这是一个示例。

export default class Form extends React.Component 
  constructor(props) 
    super(props);

    this.state = 
      values: 
    ;
  

  @boundMethod
  handleSubmit(event) 
    event.preventDefault();

    this.props.submit(this.state.values);
  

  @boundMethod
  handleChange(event) 
    const  name, value  = event.target;
    const newValues = Object.assign(
       ...this.state.values ,
       [name]: value 
    );
    this.setState(
      values: newValues
    );
  

  public render() 
    const  values  = this.state;
    return (
      <form onSubmit=this.handleSubmit noValidate=true>
        <div>
          React.Children.map(
            this.props.children,
            child => (
                React.cloneElement(child, 
                  value: values[child.props.name],
                  onChange: this.handleChange
                )
            )
          )
          <div>
            <button type="submit">
              Submit
            </button>
          </div>
        </div>
      </form>
    );
  

那么你就可以像这样使用这个 Form 类了:

<Form
  submit=values => 
    /* work with values */
  
>
  <input type="hidden" name="name" />
  <input type="hidden" name="rating" />
</Form>;

【讨论】:

以上是关于反应:HTML 表单应该是受控组件还是不受控组件?的主要内容,如果未能解决你的问题,请参考以下文章

受控组件和不受控组件的区别

反应表单错误将类型文本的受控输入更改为不受控制

什么是 React 受控组件和非受控组件?

React中的受控组件和非受控组件

警告:组件正在将文本类型的受控输入更改为不受控制。 (反应.js)

React --- 收集表单数据(非受控组件和受控组件)