假设我有一个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的渲染中。

我该怎么做呢?


当前回答

第一个解决方案(将状态保存在父组件中)是正确的。然而,对于更复杂的问题,你应该考虑一些状态管理库,redux是react最常用的一个。

其他回答

使用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是react最常用的一个。

最近的回答有一个例子,它使用React.useState

将状态保存在父组件中是推荐的方法。父组件需要访问它,因为它跨两个子组件管理它。不建议将其移动到全局状态,就像由Redux管理的状态一样,原因与在软件工程中全局变量通常不如局部变量的原因相同。

当状态在父组件中时,如果父组件在props中给了子组件值和onChange处理程序,子组件就可以改变它(有时它被称为值链接或状态链接模式)。下面是你如何用钩子做这件事:


function Parent() {
    var [state, setState] = React.useState('initial input value');
    return <>
        <Child1 value={state} onChange={(v) => setState(v)} />
        <Child2 value={state}>
    </>
}

function Child1(props) {
    return <input
        value={props.value}
        onChange={e => props.onChange(e.target.value)}
    />
}

function Child2(props) {
    return <p>Content of the state {props.value}</p>
}

整个父组件将在子组件的输入更改时重新呈现,如果父组件很小/重新呈现的速度很快,这可能不是问题。在一般情况下(例如大型表单),父组件的重新呈现性能仍然是一个问题。这在你的案例中解决了问题(见下文)。

状态链接模式和无父重渲染使用第三方库更容易实现,如Hookstate -增压React。useState涵盖各种用例,包括您的用例。(声明:我是该项目的作者之一)。

这是胡克州的情况。Child1会改变输入,Child2会对它做出反应。Parent将保留状态,但不会在状态改变时重新呈现,只有Child1和Child2会。

import { useStateLink } from '@hookstate/core';

function Parent() {
    var state = useStateLink('initial input value');
    return <>
        <Child1 state={state} />
        <Child2 state={state}>
    </>
}

function Child1(props) {
    // to avoid parent re-render use local state,
    // could use `props.state` instead of `state` below instead
    var state = useStateLink(props.state)
    return <input
        value={state.get()}
        onChange={e => state.set(e.target.value)}
    />
}

function Child2(props) {
    // to avoid parent re-render use local state,
    // could use `props.state` instead of `state` below instead
    var state = useStateLink(props.state)
    return <p>Content of the state {state.get()}</p>
}

附注:这里还有很多类似的例子,它们涵盖了更复杂的场景,包括深度嵌套数据、状态验证、使用setState钩子的全局状态等等。网上还有一个完整的示例应用程序,它使用了Hookstate和上面解释的技术。

正确的做法是把状态放在父组件中,避免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} />
  }
}

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