javascript - react : Should HTML forms be Controlled or Uncontrolled components?

标签 javascript reactjs user-interface components reactive-programming

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

React 文档提到:

Identify every component that renders something based on that state. Find a common owner component (a single component above all the components that need the state in the hierarchy). Either the common owner or another component higher up in the hierarchy should own the state. If you can’t find a component where it makes sense to own the state, create a new component simply for holding the state and add it somewhere in the hierarchy above the common owner component.

我有一个简单的评论用户界面。

  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() 事件处理程序,提交帖子按钮上还有一个click 事件处理程序。我可以选择执行以下两项之一:

  1. comment-post.jsx 中,当 onChange 被触发时,将 commentBody 发送到 CommentSection.jsx 这里的问题是我会尽快发送 commentBody作为用户类型,然后在触发时稍后发送名称等等
  2. comment-post.jsx 中,当 onChange 被触发时保存状态中的值,然后在状态中命名名称字段,当用户单击提交按钮时发送到 CommentSection.jsx 好处是首先在状态中设置字段,当用户点击帖子时,它们会被发送给父级(应该是这样吧?)

Should the input fields of the comment i.e. commentBody and Name be saved in the comment-post.jsx (form component) or the parent?

现在我正在执行的状态 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>
    );
  }
}

最佳答案

I think the issue is onChange and onClick are two different handlers, the question is which one should pass the values to parent component ideally?

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

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

I am having a dilemma as to where Where My Form State Should Live.

这取决于...在您的情况下,您只有一个表单和两个表单元素,其中没有一个是可重复使用的。所以这些不受控制的 Forms 很适合你。但是,如果您想要一个可重用的表单组件,或者如果您想要一个包含 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>;

关于javascript - react : Should HTML forms be Controlled or Uncontrolled components?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54779394/

相关文章:

javascript - 如何在 Node.js 中显示仅限管理员的链接?

c++ - 如何在 C++ 中向 MFC 项目添加另一个 gui

javascript - ajax 中的空响应主体(或 206 部分内容)

javascript - Vue.js 2.x 选择内部组件未获取值

javascript - 在 Google Chrome 中检查 jsfiddle.net 上的 javascript

reactjs - 如何执行端到端登录过程并测试更多页面

java - 如何部署应用程序[寻求建议]

javascript - Firefox 无法访问同一域上的 iframe 打印

javascript - 为什么没有显示详细 View ?

jquery - 如何使用 jQuery UI 幻灯片效果制作更流畅的动画