React的组件模式

554 阅读6分钟
原文链接: blog.csdn.net

受到我正为Meetup准备的技术分享启发,我打算花些时间分享下我所了解的React组件模式。组件是React的核心,因此了解如何利用它们对于构建优秀的设计结构至关重要。


什么是组件


根据React官网的介绍,“组件机制允许你将UI切分成独立、可复用的部分,并针对每个部分单独考虑一些事情”


当你第一次运行npm install react时,你得到了一个东西:组件以及它的API。就像JavaScript中的函数一样,组件接收被叫做Props的输入并返回React元素,它描述(声明)了用户界面(UI)应该长什么样。这就是为什么React被称为具有声明式API的原因,你只需要告诉它你想让UI长什么样,剩下的事情React会帮你处理。


可以将声明式想象成你坐出租车去某个地方,你只需告诉司机你想去哪儿,然后他/她会开车带你去那儿。而命令式编程恰好相反,你要亲自开车才能到达目的地。


组件的API


所以,当你安装React时得到的API到底是什么呢?它们总共有5个,分别是




  • render

  • state

  • props

  • context

  • lifecycle events


虽然每个组件都有完整使用上述API的能力,但你自然会发现,一些组件倾向于只使用其中的一些API,而另一些组件则只会使用另一些API。使用API种类的不同将组件们划分为两类,有状态组件和无状态组件。有状态组件通常使用一些状态API:如render, state和lifecycle events,而无状态组件则使用render, props和context。




从这里开始,我们就要介绍组件模式了。组件模式是使用React时的最佳实践,最初被引入时是为了解决将数据(逻辑)层和UI(展示)层分离的问题。通过划分组件职责,你能够创造出复用性更强,内聚性更高的组件,这些组件可以被用来组成复杂的UI。这对于构建可扩展应用是极其重要的。


组件模式


常见的组件模式有:

  • 容器组件

  • 展示组件

  • 高阶组件

  • 渲染回调


容器组件


“容器组件就是取数据,然后渲染子组件而已” —— Jason Bonta


蓝色代表容器组件,灰色代表展示组件


容器组件是应用的数据(逻辑)层,它一般使用状态API。使用生命周期函数,你可以连接到诸如Redux,Flux等状态管理容器,并将数据和回调函数作为props向下传递给子组件。容器组件的render方法是你将展示型子组件组合成UI的地方。为了能够访问到所有状态API,容器组件必须使用class的方式声明,而不是使用函数式方法声明。


在下面这个例子中,我们使用class的方式声明了一个组件,叫做Greeting,它具有状态,声明周期函数componentDidMount和render方法。

  1. class Greeting extends React.Component {
  2. constructor() {
  3. super();
  4. this.state = {
  5. name: "",
  6. };
  7. }
  8. componentDidMount() {
  9. // AJAX
  10. this.setState(() => {
  11. return {
  12. name: "William",
  13. };
  14. });
  15. }
  16. render() {
  17. return (
  18. <div>
  19. <h1>Hello! {this.state.name}</h1>
  20. </div>
  21. );
  22. }
  23. }

此时,该组件是有状态组件。为了让Greeting成为一个容器组件,我们可以将UI分离到一个展示组件中,我将在下面进行说明。

展示组件

展示组件使用props, render和context(无状态API),并且可以被写成语法纯粹的函数式无状态组件。

  1. const GreetingCard = (props) => {
  2. return (
  3. <div>
  4. <h1>{props.name}</h1>
  5. </div>
  6. )
  7. }

展示组件仅能从props中获取数据和回调函数,这些props由容器组件(父组件)提供。

蓝色是展示组件,灰色是容器组件

容器组件和展示组件一起将逻辑和表现封装在各自的组件中。

  1. const GreetingCard = (props) => {
  2. return (
  3. <div>
  4. <h1>{props.name}</h1>
  5. </div>
  6. )
  7. }
  8. class Greeting extends React.Component {
  9. constructor() {
  10. super();
  11. this.state = {
  12. name: "",
  13. };
  14. }
  15. componentDidMount() {
  16. // AJAX
  17. this.setState(() => {
  18. return {
  19. name: "William",
  20. };
  21. });
  22. }
  23. render() {
  24. return (
  25. <div>
  26. <GreetingCard name={this.state.name} />
  27. </div>
  28. );
  29. }
  30. }

如你所见,我已经将Greeting组件中展示相关的部分移动到了它自己的函数式展示组件中。当然,这是非常简单的应用,对于复杂应用,这种手法是相当基础的。


高阶组件

高阶组件是一种函数,它接受一个组件作为参数,然后返回一个新的组件。

这对于为多个组件获取数据和复用组件逻辑是一种强有力的模式。想想react-router-v4和redux。用了react-router-v4后,你可以使用withRouter()来继承以props形式传递给组件的各种方法。同样,用了redux,你就可以使用connect({})()来访问以props形式传递给组件的各种action。

高阶组件有虚线表示,它是一种返回组件的函数


以下面的代码为例

  1. import {withRouter} from 'react-router-dom';
  2. class App extends React.Component {
  3. constructor() {
  4. super();
  5. this.state = {path: ''}
  6. }
  7. componentDidMount() {
  8. let pathName = this.props.location.pathname;
  9. this.setState(() => {
  10. return {
  11. path: pathName,
  12. }
  13. })
  14. }
  15. render() {
  16. return (
  17. <div>
  18. <h1>Hi! I'm being rendered at: {this.state.path}</h1>
  19. </div>
  20. )
  21. }
  22. }
  23. export default withRouter(App);

导出组件时,我将组件用react-router-v4的withRouter()方法包裹。在上方的componentDidMount()生命周期函数中,我用this.props.location.pathname中提供的值更新state。组件被withRouter()包裹之后能够通过props访问react-router-v4提供的方法,因此就有了this.props.location.pathname。这只是众多例子中的一个。

渲染回调

和高阶组件类似,渲染回调或渲染props被用于共享或复用组件组件逻辑。虽然很多开发者更倾向于使用高阶组件来复用逻辑,但使用渲染回调依然有很多不错的理由和优势——Michael Jackson的《别再另写一个高阶组件》就是最好的解释。简而言之,渲染回调为我们难能可贵地减少了命名空间冲突,并更好的说明了逻辑来源。

虚线表示渲染回调

  1. class Counter extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.state = {
  5. count: 0,
  6. };
  7. }
  8. increment = () => {
  9. this.setState(prevState => {
  10. return {
  11. count: prevState.count + 1,
  12. };
  13. });
  14. };
  15. render() {
  16. return (
  17. <div onClick={this.increment}>{this.props.children(this.state)}</div>
  18. );
  19. }
  20. }
  21. class App extends React.Component {
  22. render() {
  23. return (
  24. <Counter>
  25. {state => (
  26. <div>
  27. <h1>The count is: {state.count}</h1>
  28. </div>
  29. )}
  30. </Counter>
  31. );
  32. }
  33. }

在上面的Counter这个类中,我将this.props.children嵌套在render方法中,它接收this.state作为参数。在下面的App类中,我能够将其他组件包裹在Counter组件内,因此也就有机会接触Counter组件内的逻辑。渲染回调的手法在第28行,在那里我写了{state => ()},bam!我自动获得了Counter组件逻辑的操作权。

原文地址:medium.com/teamsubchan…