ReactJS:通过表单获取和更新数据的惯用方式是什么?

ReactJS: What is the idiomatic way to fetch and update data through a form?

我正在努力思考通过 HTML 表单更新数据的最佳方式。以 ReactJS 网页为例:

https://facebook.github.io/react/docs/tutorial.html

假设您想要实现用户编辑她过去发布的评论的功能(即不在当前会话中,因此必须从服务器获取)。编辑评论页面需要预先填写评论的现有作者姓名和文本。您将如何实现获取评论数据并预填写评论表单?以下是我脑子里的矛盾想法,我无法理清(让我们称新组件为CommentEdit):

老实说,我只是希望组件可以改变自己的道具。看起来它会使组件更可重用。

Honestly, I just wish components could change their own props. Seems like it would make components a lot more reusable.

并非如此,可以更改自己的 props 的组件并不比那些不能更改的组件更可重用,只是它们的底层实现会变得更加复杂。

当前的 react.js 数据流和事件冒泡使组件 "very" 具有确定性,因为在组件生命周期中的任何时候,您都知道组件将根据您的输入生成什么输出给它。这在调试和单元测试应用程序时也有很大帮助。

现在,让您进退两难的是,CommentEdit 只需要 content 属性,因为作者不会更改,并且永远是登录该网站的人。其他状态不需要,因为编辑的内容会在用户点击save按钮时冒泡,当comments列表存储在state的时候会被插入到列表中组件祖先之一将被更新。

如果您为每个评论正确设置 key 属性,那么 React 会很好地注意到编辑评论的 html 元素不需要重新创建,只有呈现评论的 html 元素的 innerHTML 需要更改。

如果您想要显示作者,您可以将其添加为道具,它不会 affect/improve 组件本身,除非您想使用此布局构建多个站点。

Flux 是您可以用来从应用程序中获取数据的最佳工具,您不必在组件内部使用状态,使用 props 来收取初始数据,然后使用 react.DOMNode 来获取来自您需要的表单的每个元素的数据 https://facebook.github.io/react/docs/top-level-api.html#react.finddomnode ,还在所有表单组件中使用 refs,最后您可以使用 flux 将数据发送到服务器并保存它。

我最终通过向我的每个组件添加 "wrapper" parent 来解决这个问题。包装器组件负责从服务器获取数据,并将其存储在其状态中。包装器会将获取的状态作为道具传递给它的 children。然后我将这个设置抽象为 mixin,创建一个包装器 mixin 和包装器 child mixin,这样我就可以 re-use 轻松地进行这个设置。

此模式非常适合您应用中的典型表单。包装器将获取数据到 pre-populate 表单,而 child 将表单发送到服务器并将负责 error-handling.

这是一个例子:

// This handles the form submission and error handling
var EditFormMixin = {
  getInitialState: function() {
    return {formState:FORM_STATE_EDITING, errors:{}}
  },
  handleSubmitBase: function(event, url, data, method, success, error) {
    event.preventDefault();
    this.setState({formState:FORM_STATE_SUBMITTING, errors:{}});
    $.ajax({
        url: url,
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        data: JSON.stringify(data),
        method: method,
        success: function(data) {
            this.setState({formState:FORM_STATE_SUBMITTED});
            if (success) {
                success();
            };
        }.bind(this),
        error: function(xhr, status, err) {
            this.setState({formState:FORM_STATE_EDITING});
            if (xhr.status == 400) {
                this.setState({errors:xhr.responseJSON});
            }
            if (error) {
                error();
            }
        }.bind(this)
    });
  }
};

// This handles fetching the initial data for the form
var EditFormWrapperMixin = {
  getInitialState: function() {
    return {data: {}, loadState:LOAD_STATE_UNLOADED, url:this.getUrl()};
  },
  fetch: function() {
    this.setState({loadState:LOAD_STATE_LOADING})
    $.ajax({
        url: this.state.url,
        dataType: "json",
        method: "GET",
        success: function(data) {
            this.setState({data:data, loadState:LOAD_STATE_LOADED});
        }.bind(this),
        error: function(xhr, status, err) {
        }.bind(this)
    });
  }
  componentDidMount: function() {
    this.fetch();
  },
  componentWillReceiveProps: function(nextProps) {
    var url = this.getUrl();
    this.setState({url:url}, function() {
        this.fetch();
    });
  }
};