React的组件模式

受到我正为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方法。

class Greeting extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "",
    };
  }

  componentDidMount() {
    // AJAX
    this.setState(() => {
      return {
        name: "William",
      };
    });
  }

  render() {
    return (
      <div>
        <h1>Hello! {this.state.name}</h1>
      </div>
    );
  }
}

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

展示组件

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

const GreetingCard = (props) => {
  return (
    <div>
      <h1>{props.name}</h1>
    </div>
  )
}

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

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

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

const GreetingCard = (props) => {
  return (
    <div>
      <h1>{props.name}</h1>
    </div>
  )
}

class Greeting extends React.Component {
  constructor() {
    super();
    this.state = {
      name: "",
    };
  }

  componentDidMount() {
    // AJAX
    this.setState(() => {
      return {
        name: "William",
      };
    });
  }

  render() {
    return (
      <div>
       <GreetingCard name={this.state.name} />
      </div>
    );
  }
}

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


高阶组件

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

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

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


以下面的代码为例

import {withRouter} from 'react-router-dom';

class App extends React.Component {
  constructor() {
    super();
    this.state = {path: ''}
  }
  
  componentDidMount() {
    let pathName = this.props.location.pathname;
    this.setState(() => {
      return {
        path: pathName,
      }
    })
  }
  
  render() {
    return (
      <div>
        <h1>Hi! I'm being rendered at: {this.state.path}</h1>
      </div>
    )
  }
}

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的《别再另写一个高阶组件》就是最好的解释。简而言之,渲染回调为我们难能可贵地减少了命名空间冲突,并更好的说明了逻辑来源。

虚线表示渲染回调

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  increment = () => {
    this.setState(prevState => {
      return {
        count: prevState.count + 1,
      };
    });
  };

  render() {
    return (
      <div onClick={this.increment}>{this.props.children(this.state)}</div>
    );
  }
}

class App extends React.Component {
  render() {
    return (
      <Counter>
        {state => (
          <div>
            <h1>The count is: {state.count}</h1>
          </div>
        )}
      </Counter>
    );
  }
}

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

猜你喜欢

转载自blog.csdn.net/gstianfu/article/details/80984791
今日推荐