假设我有一个React类P,它呈现两个子类,C1和C2。 C1包含一个输入字段。我将把这个输入字段称为Foo。 我的目标是让C2对Foo中的更改做出反应。

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

第一个解决方案:

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

这个作品。每当Foo的值发生变化时,它就会触发P中的setState,这样P就有了要传递给C2的输入。

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

第二个解决方案:

把Foo放到P中。

但是,当我构造我的应用程序——把所有表单元素放在最高级别类的渲染中——时,这是我应该遵循的设计原则吗?

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

我该怎么做呢?


当前回答

使用React >= 16.3,你可以使用ref和forwardRef,从父节点获取子节点的DOM。不要再用旧的裁判方式了。 下面是使用你的案例的例子:

import React, { Component } from 'react';

export default class P extends React.Component {
   constructor (props) {
      super(props)
      this.state = {data: 'test' }
      this.onUpdate = this.onUpdate.bind(this)
      this.ref = React.createRef();
   }

   onUpdate(data) {
      this.setState({data : this.ref.current.value}) 
   }

   render () {
      return (
        <div>
           <C1 ref={this.ref} onUpdate={this.onUpdate}/>
           <C2 data={this.state.data}/>
        </div>
      )
   }
}

const C1 = React.forwardRef((props, ref) => (
    <div>
        <input type='text' ref={ref} onChange={props.onUpdate} />
    </div>
));

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

有关Refs和ForwardRef的详细信息,请参见Refs和ForwardRef。

其他回答

你应该学习Redux和ReactRedux库。它将在一个存储中构建您的状态和道具,您可以稍后在组件中访问它们。

正确的做法是把状态放在父组件中,避免ref之类的 一个问题是在输入字段时避免不断更新所有子字段 因此,每个子组件都应该是一个组件(而不是PureComponent),并实现shouldComponentUpdate(nextProps, nextState) 这样,当输入表单字段时,只有该字段更新

下面的代码使用来自ES的@bound注释。下一个babel-plugin-transform-decorators- BabelJS 6的遗产和class-properties(注释在成员函数上设置这个值,类似于bind):

/*
© 2017-present Harald Rudell <harald.rudell@gmail.com> (http://www.haraldrudell.com)
All rights reserved.
*/
import React, {Component} from 'react'
import {bound} from 'class-bind'

const m = 'Form'

export default class Parent extends Component {
  state = {one: 'One', two: 'Two'}

  @bound submit(e) {
    e.preventDefault()
    const values = {...this.state}
    console.log(`${m}.submit:`, values)
  }

  @bound fieldUpdate({name, value}) {
    this.setState({[name]: value})
  }

  render() {
    console.log(`${m}.render`)
    const {state, fieldUpdate, submit} = this
    const p = {fieldUpdate}
    return (
      <form onSubmit={submit}> {/* loop removed for clarity */}
        <Child name='one' value={state.one} {...p} />
        <Child name='two' value={state.two} {...p} />
        <input type="submit" />
      </form>
    )
  }
}

class Child extends Component {
  value = this.props.value

  @bound update(e) {
    const {value} = e.target
    const {name, fieldUpdate} = this.props
    fieldUpdate({name, value})
  }

  shouldComponentUpdate(nextProps) {
    const {value} = nextProps
    const doRender = value !== this.value
    if (doRender) this.value = value
    return doRender
  }

  render() {
    console.log(`Child${this.props.name}.render`)
    const {value} = this.props
    const p = {value}
    return <input {...p} onChange={this.update} />
  }
}

解释了将数据从父节点传递到子节点的概念,反之亦然。

import React, { Component } from "react"; import ReactDOM from "react-dom"; // taken refrence from https://gist.github.com/sebkouba/a5ac75153ef8d8827b98 //example to show how to send value between parent and child // props is the data which is passed to the child component from the parent component class Parent extends Component { constructor(props) { super(props); this.state = { fieldVal: "" }; } onUpdateParent = val => { this.setState({ fieldVal: val }); }; render() { return ( // To achieve the child-parent communication, we can send a function // as a Prop to the child component. This function should do whatever // it needs to in the component e.g change the state of some property. //we are passing the function onUpdateParent to the child <div> <h2>Parent</h2> Value in Parent Component State: {this.state.fieldVal} <br /> <Child onUpdate={this.onUpdateParent} /> <br /> <OtherChild passedVal={this.state.fieldVal} /> </div> ); } } class Child extends Component { constructor(props) { super(props); this.state = { fieldValChild: "" }; } updateValues = e => { console.log(e.target.value); this.props.onUpdate(e.target.value); // onUpdateParent would be passed here and would result // into onUpdateParent(e.target.value) as it will replace this.props.onUpdate //with itself. this.setState({ fieldValChild: e.target.value }); }; render() { return ( <div> <h4>Child</h4> <input type="text" placeholder="type here" onChange={this.updateValues} value={this.state.fieldVal} /> </div> ); } } class OtherChild extends Component { render() { return ( <div> <h4>OtherChild</h4> Value in OtherChild Props: {this.props.passedVal} <h5> the child can directly get the passed value from parent by this.props{" "} </h5> </div> ); } } ReactDOM.render(<Parent />, document.getElementById("root"));

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

我建议你读一读

React中的思考 通量

第一篇文章对于理解如何构建React应用非常有帮助。

Flux回答了为什么要这样构造React应用程序(而不是如何构造它)的问题。React只占整个系统的50%,而Flux可以让你看到整个系统,看到它们如何构成一个连贯的系统。

回到刚才的问题。

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

但是,让处理程序触发P中的setState是否正确取决于您的情况。

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

然而,如果应用程序是一个待办事项列表,C1是创建新待办事项的输入,C2是HTML中的待办事项列表,你可能想要处理程序比P高两级——到调度程序,它让存储更新数据存储,然后将数据发送给P并填充视图。请参阅Flux的文章。这里有一个例子:Flux - TodoMVC

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

那么,如果我理解正确的话,您的第一个解决方案是建议您将状态保存在根组件中?我不能代表React的创造者说话,但总的来说,我觉得这是一个合适的解决方案。

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

By keeping state further up in the hierarchy, and updating it through eventing, your data flow is still pretty much unidirectional, you're just responding to events in the Root component, you're not really getting the data there via two way binding, you're telling the Root component that "hey, something happened down here, check out the values" or you're passing the state of some data in the child component up in order to update the state. You changed the state in C1, and you want C2 to be aware of it, so, by updating the state in the Root component and re-rendering, C2's props are now in sync since the state was updated in the Root component and passed along.

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)