假设我有一个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.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和上面解释的技术。
现在已经使用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 >= 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。
解释了将数据从父节点传递到子节点的概念,反之亦然。
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"));