63

我一直在试图找出管理我的反应表单的最佳方法。我尝试使用 onChange 触发操作并使用我的表单数据更新我的 redux 存储。我也尝试过创建本地状态,当我的表单被提交时,我触发并操作并更新 redux 存储。

我应该如何管理我的受控输入状态?

4

5 回答 5

49

我喜欢 Redux 合著者之一的回答: https ://github.com/reactjs/redux/issues/1287

将 React 用于与全局应用无关且不会以复杂方式发生变化的短暂状态。例如,某些 UI 元素中的切换、表单输入状态。将 Redux 用于全局重要或以复杂方式发生变异的状态。例如,缓存的用户或草稿。

有时你会想要从 Redux 状态转移到 React 状态(当在 Redux 中存储一些东西变得笨拙时)或者反过来(当更多组件需要访问一些曾经是本地的状态时)。

经验法则是:做任何不那么尴尬的事情。

也就是说,如果您确定您的表单不会影响全局状态或在组件卸载后需要保留,那么请保持在反应状态。

于 2016-11-18T12:31:48.020 回答
35
  1. 您可以使用组件自己的状态。然后获取该状态并将其作为操作的参数。这几乎就是React Docs中描述的“React 方式” 。

  2. 您还可以查看Redux Form。它基本上完成了您所描述的工作,并将表单输入与 Redux State 链接起来。

第一种方式基本上意味着您正在手动完成所有操作 - 最大控制和最大样板。第二种方式意味着您让高阶组件为您完成所有工作。然后是介于两者之间的一切。我见过多个包可以简化表单管理的特定方面:

  1. React Forms - 它提供了一堆帮助组件,使表单呈现和验证更加简单。

  2. React JSON 模式- 允许从 JSON 模式构建 HTML 表单。

  3. Formsy React - 正如描述所说:“这个 React JS 的扩展旨在成为灵活性和可重用性之间的“最佳点”。

更新:似乎这些天 Redux Form 被替换为:

  1. 反应最终形式

该领域另一个值得一试的重要竞争者是:

  1. 福米克

在我的上一个项目中尝试了 React Hook Form - 非常简单,占用空间小,并且可以正常工作:

  1. 反应钩子形式
于 2016-01-22T18:10:13.327 回答
8

TL;博士

使用适合您的应用程序的任何内容都可以(来源:Redux docs


确定应将哪种数据放入 Redux 的一些常见经验法则:

  • 应用程序的其他部分是否关心这些数据?
  • 您是否需要能够基于这些原始数据创建进一步的派生数据?
  • 是否使用相同的数据来驱动多个组件?
  • 能够将此状态恢复到给定的时间点(即时间旅行调试)对您有价值吗?
  • 您是否要缓存数据(即,如果它已经存在,则使用状态中的内容而不是重新请求它)?

这些问题可以轻松帮助您确定更适合您的应用的方法。以下是我在应用程序中使用的观点和方法(用于表单):

当地状态

  • 当我的表单与 UI 的其他组件无关时很有用。只需从input(s)捕获数据并提交。我大部分时间都将它用于简单的表格。
  • 在时间旅行中调试表单的输入流时,我看不到太多用例(除非其他一些 UI 组件依赖于此)。

还原状态

  • 当表单必须更新我的应用程序中的一些其他 UI 组件时很有用(很像双向绑定)。
  • 当我的表单input导致其他一些组件render取决于用户输入的内容时,我会使用它。
于 2017-05-28T07:22:28.077 回答
5

就个人而言,我强烈建议将所有内容保持在 Redux 状态并远离本地组件状态。这本质上是因为如果您开始将 ui 视为状态的函数,则可以进行完整的无浏览器测试,并且可以利用保留完整状态历史记录的参考(例如,输入中的内容,打开的对话框等) ,当出现错误时 - 不是他们从一开始的状态)供用户用于调试目的。来自clojure领域的相关推文

编辑补充:这是我们和我们的姊妹公司在生产应用程序以及我们如何处理 redux/state/ui 方面的发展方向

于 2016-01-25T04:37:10.307 回答
0

使用帮助程序库更快,并且避免了我们所有的样板。它们可能会被优化,功能丰富......等等。因为它们使所有不同的方面变得轻而易举。测试它们并让你的武器库知道什么是有用的并且更好地满足不同的需求,这就是要做的事情。

但是如果你已经自己实现了一切。走有节制的路。出于某种原因,您需要 redux。在我的一个项目中。我需要维护表单状态。因此,如果我转到另一个页面并返回,它将保持相同的状态。你只需要 redux,如果它是将更改传达给多个组件的一种手段。或者,如果它是存储状态的一种手段,则需要恢复。

如果状态需要是全局的,则需要 redux。否则你不需要它。就此而言,您可以 在此处查看这篇精彩的文章以进行深入了解。

您可能遇到的问题之一!使用受控输入时。是不是您可以在每次击键时发送更改。你的表格将开始冻结。它变成了一只蜗牛。

永远不要在每次更改时直接调度和使用 redux 通量。您可以做的是将输入状态存储在组件本地状态中。并使用setState(). 一旦状态改变,你就设置一个有延迟的定时器。它在每次击键时被取消。最后一次击键将在指定延迟后跟随调度动作。(一个好的延迟可能是 500 毫秒)。

(要知道setState,默认情况下会有效地处理多个连续的击键。否则我们会使用与上面提到的相同的技术(就像我们在 vanilla js 中所做的那样)[但在这里我们将依赖setState])

下面是一个例子:

onInputsChange(change, evt) {
    const { onInputsChange, roomId } = this.props;

    this.setState({
        ...this.state,
        inputs: {
            ...this.state.inputs,
            ...change
        }
    }, () => {
        // here how you implement the delay timer
        clearTimeout(this.onInputsChangeTimeoutHandler); // we clear at ever keystroke
              // this handler is declared in the constructor
        this.onInputsChangeTimeoutHandler = setTimeout(() => {
           // this will be executed only after the last keystroke (following the delay)
            if (typeof onInputsChange === "function")
                    onInputsChange(this.state.inputs, roomId, evt);
        }, 500);
    })
}

您可以使用反模式使用 props 初始化组件,如下所示:

constructor(props) {
    super(props);

    const {
        name,
        description
    } = this.props;

    this.state = {
        inputs: {
            name,
            description
        }
    }

在构造函数或componentDidMount钩子中,如下所示:

componentDidMount () {
    const {
        name, 
        description
    } = this.props;

    this.setState({
        ...this.state,
        inputs: {
            name,
            description
        }
    });
}

后者允许我们在每个组件安装时从存储中恢复状态。

此外,如果您需要从父组件更改表单,您可以向该父组件公开一个函数。通过设置绑定setInputs()的方法。在构造中,您执行道具(即 getter 方法)。(一个有用的情况是当您想在某些条件或状态下重置表单时)。getSetInputs()

constructor(props) {
    super(props);
    const {
         getSetInputs
    } = this.props;

   // .....
   if (typeof getSetInputs === 'function') getSetInputs(this.setInputs);
}

为了更好地理解我在上面所做的事情,这里我是如何更新输入的:

// inputs change handlers
onNameChange(evt) {
    const { value } = evt.target;

    this.onInputsChange(
        {
            name: value
        },
        evt
    );
}

onDescriptionChange(evt) {
    const { value } = evt.target;

    this.onInputsChange(
        {
            description: value
        },
        evt
    );
}

/**
 * change = {
 *      name: value
 * }
 */
onInputsChange(change, evt) {
    const { onInputsChange, roomId } = this.props;

    this.setState({
        ...this.state,
        inputs: {
            ...this.state.inputs,
            ...change
        }
    }, () => {
        clearTimeout(this.onInputsChangeTimeoutHandler);
        this.onInputsChangeTimeoutHandler = setTimeout(() => {
            if (typeof onInputsChange === "function")
                onInputsChange(change, roomId, evt);
        }, 500);
    })
}

这是我的表格:

 const {
        name='',
        description=''
 } = this.state.inputs;

// ....

<Form className="form">
    <Row form>
        <Col md={6}>
            <FormGroup>
                <Label>{t("Name")}</Label>
                <Input
                    type="text"
                    value={name}
                    disabled={state === "view"}
                    onChange={this.onNameChange}
                />
                {state !== "view" && (
                    <Fragment>
                        <FormFeedback
                            invalid={
                                errors.name
                                    ? "true"
                                    : "false"
                            }
                        >
                            {errors.name !== true
                                ? errors.name
                                : t(
                                        "You need to enter a no existing name"
                                    )}
                        </FormFeedback>
                        <FormText>
                            {t(
                                "Enter a unique name"
                            )}
                        </FormText>
                    </Fragment>
                )}
            </FormGroup>
        </Col>
        {/* <Col md={6}>
            <div className="image">Image go here (one day)</div>
        </Col> */}
    </Row>

    <FormGroup>
        <Label>{t("Description")}</Label>
        <Input
            type="textarea"
            value={description}
            disabled={state === "view"}
            onChange={this.onDescriptionChange}
        />
        {state !== "view" && (
            <FormFeedback
                invalid={
                    errors.description
                        ? "true"
                        : "false"
                }
            >
                {errors.description}
            </FormFeedback>
        )}
    </FormGroup>
</Form>
于 2019-03-06T14:04:32.763 回答