将表单元素状态传递给兄弟/父元素的正确方法是什么?

IT技术 reactjs
2021-04-05 02:58:34
  • 假设我有一个 React 类 P,它呈现两个子类,C1 和 C2。
  • C1 包含一个输入字段。我将此输入字段称为 Foo。
  • 我的目标是让 C2 对 Foo 的变化做出react。

我想出了两个解决方案,但都感觉不太对。

第一个解决方案:

  1. 分配 P 一个状态,state.input
  2. onChange在 P 中创建一个函数,它接收一个事件并设置state.input
  3. 将此onChange作为 a传递给 C1 props,并让 C1 绑定this.props.onChangeonChangeFoo 的 。

这有效。每当 Foo 的值发生变化时,就会触发setStateP 中的 a,因此 P 将有输入传递给 C2。

但出于同样的原因,它感觉不太正确:我正在从子元素设置父元素的状态。这似乎违背了 React 的设计原则:单向数据流。
这是我应该怎么做,还是有更自然的 React 解决方案?

第二种解决方案:

只需将 Foo 放入 P 中即可。

但这是我构建应用程序时应该遵循的设计原则——将所有表单元素放在render最高级别的类中吗?

就像在我的例子中一样,如果我有大量的 C1 渲染,我真的不想仅仅因为 C1 有一个表单元素就把整个renderC1 放到renderP 中。

我该怎么做?

6个回答

所以,如果我理解正确的话,您的第一个解决方案是建议您在根组件中保持状态?我不能代表 React 的创建者,但总的来说,我发现这是一个合适的解决方案。

维护状态是创建 React 的原因之一(至少我认为)。如果您曾经实现过自己的状态模式客户端来处理具有许多相互依赖的移动部分的动态 UI,那么您会喜欢 React,因为它减轻了很多这种状态管理的痛苦。

通过在层次结构中进一步保持状态,并通过事件更新它,您的数据流仍然几乎是单向的,您只是响应 Root 组件中的事件,您并没有真正通过两种方式绑定获取数据,您正在告诉 Root 组件“嘿,这里发生了一些事情,请检查值”,或者您正在向上传递子组件中某些数据的状态以更新状态。您更改了 C1 中的状态,并且希望 C2 知道它,因此,通过更新 Root 组件中的状态并重新渲染,C2 的 props 现在处于同步状态,因为状态已在 Root 组件中更新并传递.

class Example extends React.Component {
  constructor (props) {
    super(props)
    this.state = { data: 'test' }
  }
  render () {
    return (
      <div>
        <C1 onUpdate={this.onUpdate.bind(this)}/>
        <C2 data={this.state.data}/>
      </div>
    )
  }
  onUpdate (data) { this.setState({ data }) }
}

class C1 extends React.Component {
    render () {
      return (
        <div>
          <input type='text' ref='myInput'/>
          <input type='button' onClick={this.update.bind(this)} value='Update C2'/>
        </div>
      )
    }
    update () {
      this.props.onUpdate(this.refs.myInput.getDOMNode().value)
    }
})

class C2 extends React.Component {
    render () {
      return <div>{this.props.data}</div>
    }
})

ReactDOM.renderComponent(<Example/>, document.body)
上面的代码有 2 个错误 - 这两个错误都涉及未在正确的上下文中绑定“this”,我已经在上面进行了更正,也适用于需要 codepen 演示的任何人:codepen.io/anon/pen/wJrRpZ?editors=0011
2021-05-24 02:58:34
@captray 但是,如果C2有一个getInitialStatefor 数据并且在render它使用的内部this.state.data呢?
2021-06-01 02:58:34
@DmitryPolushkin 如果我正确理解您的问题,您希望将数据从根组件传递到 C2 作为道具。在 C2 中,该数据将被设置为初始状态(即 getInitialState: function() { return { someData: this.props.dataFromParentThatWillChange } } 并且您需要实现 componentWillReceiveProps 并使用新道具调用 this.setState 以更新C2 中的状态。自从我最初回答以来,我一直在使用 Flux,并且强烈建议您也看看它。它使您的组件更干净,并将改变您对状态的看法。
2021-06-07 02:58:34
@DmitryPolushkin 我想把这个贴出来跟进。facebook.github.io/react/tips /... 只要你知道你在做什么并且你知道数据会改变就可以了,但在许多情况下你可能会移动一些东西。同样重要的是要注意,您不必将其构建为层次结构。您可以将 C1 和 C2 挂载在 DOM 的不同位置,它们都可以监听某些数据的更改事件。我看到很多人在不需要分层组件时都在推动它们。
2021-06-07 02:58:34
没问题。写完这篇文章后,我实际上回去重新阅读了一些文档,这似乎符合他们的想法和最佳实践。React 有真正优秀的文档,每次我最终想知道应该去哪里时,他们通常会在文档的某个地方覆盖它。在这里查看状态部分,facebook.github.io/react/docs/...
2021-06-10 02:58:34

现在已经使用 React 构建了一个应用程序,我想就半年前提出的这个问题分享一些想法。

我推荐你阅读

第一篇文章对理解你应该如何构建你的 React 应用程序非常有帮助。

Flux 回答了为什么要以这种方式构建 React 应用程序的问题(而不是如何构建它)。React 仅占系统的 50%,而使用 Flux,您可以看到整个画面并了解它们如何构成一个连贯的系统。

回到问题。

至于我的第一个解决方案,让处理程序反向运行是完全可以的,因为数据仍然是单向的。

但是,根据您的情况,让处理程序触发 P 中的 setState 是对还是错。

如果应用程序是一个简单的 Markdown 转换器,C1 是原始输入,C2 是 HTML 输出,那么让 C1 触发 P 中的 setState 是可以的,但有些人可能会争辩说这不是推荐的方法。

但是,如果应用程序是待办事项列表,C1 是用于创建新待办事项的输入,C2 是 HTML 中的待办事项列表,您可能希望处理程序比 P 高两级 - 到dispatcher,这让store更新data store,然后将数据发送到 P 并填充视图。请参阅 Flux 文章。这是一个例子:Flux - TodoMVC

一般来说,我更喜欢 todo list 示例中描述的方式。应用中的状态越少越好。

我不确定你是什么意思。它们很好地处理了两种不同的事情,并且是关注点的适当分离。
2021-05-22 02:58:34
我经常在 React 和 Flux 的演讲中讨论这个问题。我想强调的一点是你上面描述的,那就是视图状态和应用程序状态的分离。在某些情况下,事物可以从只是视图状态转变为应用程序状态,尤其是在您保存 UI 状态(如预设值)的情况下。我觉得你一年后带着自己的想法回来真是太棒了。+1
2021-05-28 02:58:34
@wayofthefuture 在没有看到您的代码的情况下,我可以说我看到了很多意大利面 React,就像好的 ole spaghetti jQuery 一样。我能提供的最好建议是尝试遵循 SRP。使您的组件尽可能简单;如果可以的话,愚蠢的渲染组件。我还推动抽象,如 <DataProvider/> 组件。它很好地完成了一件事。提供数据。这通常成为“根”组件,并通过利用子项(和克隆)作为道具(定义的合同)来传递数据。最终,尝试首先考虑服务器。有了更好的数据结构,它会让你的 JS 更简洁。
2021-06-07 02:58:34
@captray 那么我们可以说 redux 在所有情况下都比 react 强大吗?尽管他们的学习曲线......(来自Java)
2021-06-11 02:58:34
我们有一个使用 redux 的项目,几个月后,似乎所有事情都使用了操作。这就像意大利面条式代码和无法理解的大混合,完全是灾难。状态管理可能很好,但可能会被严重误解。假设 flux/redux 应该只用于需要全局访问的状态部分是否安全?
2021-06-17 02:58:34

五年后,随着 React Hooks 的推出,现在有了更优雅的使用 useContext hook 的方法。

您在全局范围内定义上下文,在父组件中导出变量、对象和函数,然后将应用程序中的子组件包装在提供的上下文中,并在子组件中导入您需要的任何内容。下面是一个概念证明。

import React, { useState, useContext } from "react";
import ReactDOM from "react-dom";
import styles from "./styles.css";

// Create context container in a global scope so it can be visible by every component
const ContextContainer = React.createContext(null);

const initialAppState = {
  selected: "Nothing"
};

function App() {
  // The app has a state variable and update handler
  const [appState, updateAppState] = useState(initialAppState);

  return (
    <div>
      <h1>Passing state between components</h1>

      {/* 
          This is a context provider. We wrap in it any children that might want to access
          App's variables.
          In 'value' you can pass as many objects, functions as you want. 
           We wanna share appState and its handler with child components,           
       */}
      <ContextContainer.Provider value={{ appState, updateAppState }}>
        {/* Here we load some child components */}
        <Book title="GoT" price="10" />
        <DebugNotice />
      </ContextContainer.Provider>
    </div>
  );
}

// Child component Book
function Book(props) {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // In this child we need the appState and the update handler
  const { appState, updateAppState } = useContext(ContextContainer);

  function handleCommentChange(e) {
    //Here on button click we call updateAppState as we would normally do in the App
    // It adds/updates comment property with input value to the appState
    updateAppState({ ...appState, comment: e.target.value });
  }

  return (
    <div className="book">
      <h2>{props.title}</h2>
      <p>${props.price}</p>
      <input
        type="text"
        //Controlled Component. Value is reverse vound the value of the variable in state
        value={appState.comment}
        onChange={handleCommentChange}
      />
      <br />
      <button
        type="button"
        // Here on button click we call updateAppState as we would normally do in the app
        onClick={() => updateAppState({ ...appState, selected: props.title })}
      >
        Select This Book
      </button>
    </div>
  );
}

// Just another child component
function DebugNotice() {
  // Inside the child component you can import whatever the context provider allows.
  // Earlier we passed value={{ appState, updateAppState }}
  // but in this child we only need the appState to display its value
  const { appState } = useContext(ContextContainer);

  /* Here we pretty print the current state of the appState  */
  return (
    <div className="state">
      <h2>appState</h2>
      <pre>{JSON.stringify(appState, null, 2)}</pre>
    </div>
  );
}

const rootElement = document.body;
ReactDOM.render(<App />, rootElement);

您可以在代码沙盒编辑器中运行此示例。

编辑带上下文的传递状态

我们如何const ContextContainer = React.createContext(null);与子组件共享,以便我可以创建单独的文件?
2021-06-11 02:58:34

第一个解决方案,将状态保持在父组件中,是正确的但是,对于更复杂的问题,您应该考虑一些状态管理库redux是与 react 一起使用的最流行的一个。

同意。当大多数人用“纯 React”写东西时,我的回复被回信了。在磁通爆裂之前。
2021-06-07 02:58:34

我很惊讶在我写的那一刻,没有一个简单的惯用 React 解决方案的答案。所以这是一个(将大小和复杂性与其他人进行比较):

class P extends React.Component {
    state = { foo : "" };

    render(){
        const { foo } = this.state;

        return (
            <div>
                <C1 value={ foo } onChange={ x => this.setState({ foo : x })} />
                <C2 value={ foo } />
            </div>
        )
    }
}

const C1 = ({ value, onChange }) => (
    <input type="text"
           value={ value }
           onChange={ e => onChange( e.target.value ) } />
);

const C2 = ({ value }) => (
    <div>Reacting on value change: { value }</div>
);

我正在从子元素设置父元素的状态。这似乎违背了 React 的设计原则:单向数据流。

任何受控input(在 React 中使用表单的惯用方式)都会在其onChange回调中更新父状态,并且仍然不会泄露任何内容。

例如,仔细查看 C1 组件。您是否看到C1内置input组件和内置组件处理状态变化的方式有什么显着差异你不应该,因为没有。提升状态并传递 value/onChange 对是原始 React 的惯用方式。正如一些答案所暗示的那样,不使用 refs。

显然,您必须从 中提取状态成员this.state,渲染中缺少返回,并且必须将多个组件包装在 div 或其他东西中。不知道当我写原始答案时我怎么错过了。应该是编辑错误。
2021-05-25 02:58:34
那不是关于反应的版本。组件的代码有误。解决了,现在试试。
2021-06-07 02:58:34
你用的是什么版本的react?我遇到了实验类属性问题并且 foo 未定义。
2021-06-11 02:58:34
我喜欢这个解决方案。如果有人想修改它,这里有一个沙箱供您使用。
2021-06-20 02:58:34