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

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

React 文档提及:

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

我有一个简单的评论 UI。

  1. 用户在文本区域输入评论。
  2. 用户在输入字段中输入姓名。
  3. 用户点击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() 事件处理程序,并且在提交帖子按钮上还有一个单击事件处理程序。我可以选择以下两种方法之一:

  1. comment-post.jsx onChange 被触发时,将commentBody 发送到CommentSection.jsx 这里的问题是我会在用户输入后立即发送commentBody,然后在触发时发送名称等等强>
  2. 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"
          alt=""
          className="img-circle"
        />
      </a>
    );
  }
}

【问题讨论】:

    标签: javascript reactjs user-interface components reactive-programming


    【解决方案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>;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-30
      • 1970-01-01
      • 2021-03-21
      • 2017-07-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多