应对:HTML表单应该是受控制还是不受控制的组件?

时间:2019-02-20 05:28:58

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

我对表格状态应该居住的位置感到困惑。

回复文档提及:

  

识别根据该状态呈现内容的每个组件。   找到一个共同的所有者组成部分(所有   需要层次结构中的状态的组件)。共同点   所有者或层次结构中更高级别的其他组件应拥有   州。如果找不到合适的组件   状态,创建一个仅用于保存状态的新组件并添加它   

我有一个简单的注释UI。

  1. 用户在文本区域中输入评论。
  2. 用户在输入字段中输入名称。
  3. 用户点击帖子,评论显示在下方。

组件高度如下:

 - 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中,input和textarea字段具有onChange()事件处理程序,并且submit post按钮上还具有click事件处理程序。我可以选择执行以下两项之一:

  1. comment-post.jsx中,当触发onChange时,将commentBody发送到CommentSection.jsx ,这里的问题是,我将在用户键入内容后立即发送commentBody,然后在触发时发送名称,然后等等
  2. comment-post.jsx中,当触发onChange时,将状态保存到状态,然后在状态中命名名称字段,当用户单击提交按钮发送到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>
    );
  }
}

1 个答案:

答案 0 :(得分:0)

  

我认为问题是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>;