假设我有一个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解决方案。所以这里是一个(比较大小和复杂性):
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的设计原则:单向数据流。
任何受控输入(React中使用表单的惯用方式)在onChange回调中更新父状态,仍然不会泄露任何信息。
例如,仔细看看C1组件。在C1和内置输入组件处理状态变化的方式上,您看到有什么显著的区别吗?你不应该,因为根本就没有。提升状态并传递value/onChange对是原始React的惯用方法。而不是像一些答案所暗示的那样使用裁判。
现在已经使用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.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解决方案。所以这里是一个(比较大小和复杂性):
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的设计原则:单向数据流。
任何受控输入(React中使用表单的惯用方式)在onChange回调中更新父状态,仍然不会泄露任何信息。
例如,仔细看看C1组件。在C1和内置输入组件处理状态变化的方式上,您看到有什么显著的区别吗?你不应该,因为根本就没有。提升状态并传递value/onChange对是原始React的惯用方法。而不是像一些答案所暗示的那样使用裁判。