每个React应用都是由相互作用的组件组成的。这些组件如何交流是UI架构的一个重要方面。随着应用程序越来越大、越来越复杂,组件的交互变得更加重要。
React提供了几种处理这种需求的方法,每一种都有其适当的用例。让我们从最简单的方法开始,即父-子交互。
父子间的道具
组件之间最简单的交流形式是通过属性--通常称为道具。道具是由父代传递给子代组件的参数,类似于函数的参数。
Props允许变量被传递到子组件中,当值被改变时,它们会在子组件中自动更新,如清单1。
清单1.道具(基于类)
function App(){
return <div>
<AppChild name="Matt" />
</div>
}
function AppChild(props){
return <span>
My name is {props.name}
</span>
}
ReactDOM.render(<App />, document.getElementById('app'));
清单1显示了在基于函数的组件树中是如何处理道具的。这个过程与类类似。基于函数的例子展示了函数式的更精简的语法。你可以在这里看到这段代码的操作。
使用函数道具的子代父代
清单1允许数值从父代传到子代。当子代需要更新父代的变化时,他们不能只是修改属性。子女不能更新props。
如果你试图直接修改一个子代的道具,你会在控制台看到以下类型的错误。
Uncaught TypeError: Cannot assign to read only property 'foo' of object '#<Object>'
相反,父代可以传入一个功能道具,而子代可以调用该功能。这种功能道具是一种面向事件的编程。你可以在清单2中看到这个动作。
清单2.功能性道具
function App(){
const [name, setName] = React.useState("Matt");
return <div>
<AppChild name={name} onChangeName={()=>{setName("John")}}/>
</div>
}
function AppChild(props){
return <span>
My name is {props.name}
<button onClick={props.onChangeName}>Change Name</button>
</span>
}
ReactDOM.render(<App />, document.getElementById('app'));
清单2介绍了useState ,用于管理状态。这是一个简单的机制,你可以在这里了解更多。功能性道具的本质是,当按钮被点击时,由App 组件传入的函数被执行。这样,就实现了孩子与父母的交流。 你可以在这里看到这个代码的实况。
一般来说,要记住的概念是这样的。道具向下流向孩子,事件向上流向父母。这是一个有价值的设计原则,有助于保持应用程序的组织性和可管理性。
向上传递信息给父类
经常发生的情况是,子组件需要将参数与它们的事件一起向上传递。这可以通过向功能道具回调添加参数来实现。这在清单3中得到了处理。
清单3.向功能道具传递参数
function App(){
const [name, setName] = React.useState("Matt"); //test
return <div>
<AppChild name={name} onChangeName={(newName)=>{setName(newName)}}/>
</div>
}
function AppChild(props){
return <span>
My name is {props.name}
<button onClick={()=>props.onChangeName("Bill")}>Change Name</button>
</span>
}
ReactDOM.render(<App />, document.getElementById('app'));
注意清单3中的一行onClick={()=>props.onChangeName("Bill")} 。这里我们使用箭头语法来创建一个匿名函数,包括我们想要的参数。这也是一个简单的问题,可以传递一个由组件本身修改的变量,其语法如下。onClick={(myVar)=>props.onChange(myVar)}.这段代码可以在这里看到。
顺便提一下,这里看到的作为事件处理程序的内联箭头函数有时会因为 性能 问题 而受到批评 ,尽管这可能是被夸大了。
函数道具和React Router
另一个重要的用例是在React Router中传递参数。清单4提供了一个如何实现这一目的的例子。
清单4.通过Router传递函数props
// In the route definition:
<Route path=’/foopath’ render={(props) => <Child {…props} />} />
// In the child component:
<Route appProps={{ onTitleChange }} />
实质上,清单4是允许通过覆盖路由的渲染来直接传递属性的。
兄弟姐妹间的交流
到目前为止,我们所看到的功能提供了处理兄弟姐妹交流的能力。这在React文档中被称为 "提升状态"。
这里的想法是,当处于组件树同一级别的子代必须共享状态时,该状态被推送到父代。然后,父代通过道具将状态分享给需要它的子代。子代们在父代处引发事件以更新状态,这将自动反映在共享属性上。
React Context API
React本身提供的另一个选择是Context API。Context API被设计用来管理简单的、全局感兴趣的值。也就是说,那些被整个应用中的许多组件使用的值。
文档中 给出的例子是一个主题设置。许多组件都会对这个设置感兴趣(为了反映正确的主题),如果用prop来传递,就会非常不方便。
Context API不是用来处理复杂的应用程序数据的。它实际上是专门为避免在深度嵌套的组件中进行复杂的道具处理而设计的。在清单5中可以看到一个简单的例子。
清单5.上下文API
// defining the context value
<ThemeContext.Provider value="dark">
// Consuming the context value later on
<Button theme={this.context} />;
Redux的集中状态
更复杂的应用可能需要更复杂的状态架构。在React中最常见的处理这种情况的库仍然是Redux。Redux不是一个简单的集中式存储。它是一个有意见和结构化的事件系统。
Redux的核心思想是,组件通过称为调度器的专门对象引发事件(在Redux中称为行动)。这些动作事件被还原器观察到,然后将动作应用到状态中。然后,视图中的组件会自动更新以反映状态。
从这个简短的描述中,你可以看到Redux在你的应用程序中引入了相当多的复杂性和正规性。在使用Redux时,应该仔细平衡结构和可理解性的好处。
其他集中式存储
存在其他管理集中式存储的方法,包括MobX和滚动你自己。 尽管这些解决方案可能提供了比Redux更多的优势,但它们必须与Redux的流行所提供的优势进行权衡,即熟悉性和了解它的开发人员的可用性。
React通过props和函数props提供了非常强大和简单的组件交互。这种方法在更大、更复杂的应用中可能会被打破。利用更复杂的选项,如Context API和Redux可以解决这些更复杂的需求。