React中的有状态与无状态功能组件

React是一个流行JavaScript前端库,用于构建交互式用户界面。 React的学习曲线相对较浅,这就是最近它得到所有关注的原因之一。

尽管有很多重要的概念需要讨论,但不可否认的是组件是React的灵魂。 对组件有充分的了解将使您作为React开发人员的生活变得轻松。

先决条件

本教程适用于已开始学习React并且需要更好地了解组件的初学者。 我们将从组件基础开始,然后进入更具挑战性的概念,例如组件模式以及何时使用这些模式。 涵盖了不同的组件分类,例如类与功能性组件,有状态与无状态组件以及容器与表示性组件。

在开始之前,我想向您介绍我们将在本教程中使用的代码段。 这是使用React构建的一个简单计数器。 在整个教程中,我将参考本示例的某些部分。

这是该示例的GitHub存储库。

因此,让我们开始吧。

什么是组件?

组件是自我维持的,独立的微实体,它们描述了UI的一部分。 可以将应用程序的UI拆分为较小的组件,其中每个组件都有自己的代码,结构和API。

例如,当您查看Facebook的Web应用程序时,Facebook拥有成千上万的功能接口。 这是一个有趣的事实:Facebook包含30,000个组件,并且数量还在不断增加。 组件体系结构使您可以独立思考每个组件。 每个组件都可以更新其范围内的所有内容,而不必担心它如何影响其他组件。

如果我们以Facebook的UI为例,搜索栏将是该组件的理想选择。 Facebook的Newsfeed将构成另一个组件(或一个包含许多子组件的组件)。 与搜索栏有关的所有方法和AJAX调用都将在该组件内。

组件也是可重用的。 如果您需要在多个地方使用相同的组件,那很容易。 借助JSX语法,您可以在希望它们出现的任何地方声明组件,仅此而已。

<div>
    Current count: {this.state.count}
    <hr />
    {/* Component reusability in action. */ }
    <Button sign = "+" count={this.state.count} 
        updateCount = {this.handleCount.bind(this) }/>
    <Button sign = "-" count={this.state.count}  
        updateCount = {this.handleCount.bind(this) }/>
</div>

道具与状态

组件需要使用数据。 可以使用两种不同的方式来组合组件和数据:作为propsstate 。 道具和状态决定了组件渲染的内容以及行为方式。 让我们从道具开始。

道具

如果组件是普通JavaScript函数,则props将是函数输入。 依此类推,组件接受输入(我们称为道具),对其进行处理,然后呈现一些JSX代码。

带道具的有状态组件与无状态组件

尽管props中的数据可以被组件访问,但是React的哲学是props应该是不可变的并且自上而下。 这意味着父组件可以将其想要的任何数据作为道具传递给子组件,但是子组件不能修改其道具。 因此,如果您尝试像下面那样编辑道具,则会收到“无法分配给只读” TypeError。

const Button = (props) => {
    // props are read only
    props.count =21;
.
.
}

另一方面,状态是声明它的组件所拥有的对象。 它的范围仅限于当前组件。 组件可以初始化其状态,并在必要时进行更新。 父组件的状态通常最终是子组件的道具。 当状态超出当前范围时,我们将其称为道具。

有状态的有状态与无状态的组件

现在我们已经了解了组件的基础知识,下面让我们看一下组件的基本分类。

类组件与功能组件

React组件可以有两种类型:类组件或功能组件。 从它们的名称可以明显看出两者之间的区别。

功能组件

功能组件只是JavaScript函数。 它们接受一个可选输入,正如我之前提到的,这就是我们所说的道具。

有状态与无状态组件功能组件

一些开发人员更喜欢使用新的ES6箭头功能来定义组件。 箭头函数更紧凑,并提供用于编写​​函数表达式的简洁语法。 通过使用箭头功能,我们可以跳过两个关键字的使用: functionreturn ,以及一对大括号。 使用新语法,您可以像这样在一行中定义一个组件。

const Hello = ({ name }) => (<div>Hello, {name}!</div>);

类组件

班级组件提供更多功能,更多功能带来更多行李。 选择类组件而不是功能组件的主要原因是它们可以具有state

state = {count:1}语法是公共类字段功能的一部分。 在下面的更多内容。

创建类组件有两种方法。 传统方法是使用React.createClass() 。 ES6引入了语法糖,使您可以编写扩展React.Component类。 但是,这两种方法都可以做相同的事情。

类组件也可以无状态存在。 这是一个接受输入props并渲染JSX的类组件的示例。

class Hello extends React.Component {
    constructor(props) {
        super(props);
    }
    
    render() {
        return(
            <div>
                Hello {props}
            </div>
        )
    }
}

我们定义了一个接受props作为输入的构造方法。 在构造函数内部,我们调用super()传递从父类继承的任何东西。 以下是您可能错过的一些详细信息。

首先,在定义组件时,构造函数是可选的。 在上述情况下,组件没有状态,构造函数似乎没有做任何有用的事情。 无论是否定义了构造函数, render()使用的this.props都将起作用。 但是,这是官方文档中的内容

类组件应始终使用props调用基本构造函数。

作为最佳实践,我建议对所有类组件使用构造函数。

其次,如果使用构造函数,则需要调用super() 。 这不是可选的,否则会出现语法错误“ 构造函数中 缺少super()调用

最后一点是关于super()super(props) 。 如果要在构造函数中调用this.props则应使用super(props) 。 否则,仅使用super()就足够了。

有状态组件与无状态组件

这是对组件进行分类的另一种流行方法。 而且分类的标准很简单:具有状态的组件和没有状态的组件。

状态组件

有状态组件始终是类组件。 如前所述,有状态组件具有在构造函数中初始化的状态。

// Here is an excerpt from the counter example
constructor(props) {
  super(props);
  this.state = { count: 0 };
}

我们创建了一个状态对象,并将其初始化为0。建议使用另一种语法来简化此操作,即类字段 。 它还不是ECMAScript规范的一部分,但是,如果您使用的是Babel转译器,则此语法应该可以立即使用。

class App extends Component {
  
  /*
  // Not required anymore
  constructor() {
      super();
      this.state = {
        count: 1
      }
  }
  */
  
  state = { count: 1 };
  
  handleCount(value) {
      this.setState((prevState) => ({count: prevState.count+value}));
  }

  render() {
    // omitted for brevity
  }
  
}

您可以避免将构造函数与新语法一起使用。

现在,我们可以在包括render()的类方法中访问状态。 如果要在render()使用它们来显示当前计数的值,则需要将其放在大括号内,如下所示:

render() {
return (
    Current count: {this.state.count}
    )
}

这里的this关键字是指当前组件的实例。

初始化状态还不够-我们需要能够更新状态才能创建交互式应用程序。 如果您认为这行得通,不,那不会。

//Wrong way

handleCount(value) {
    this.state.count = this.state.count +value;
}

React组件配备了一个名为setState的方法来更新状态。 setState接受一个对象,该对象包含count的新状态。

// This works

handleCount(value) {
    this.setState({count: this.state.count+ value});
}

setState()接受一个对象作为输入,并且我们将count的先前值增加1,这可以按预期工作。 但是,有一个陷阱。 当有多个setState调用读取状态的先前值并将新值写入其中时,我们可能会遇到竞争条件。 这意味着最终结果将与预期值不匹配。

这是一个示例,应该使您清楚明白。 在上面的codeandbox代码段中尝试此操作。

// What is the expected output? Try it in the code sandbox.
handleCount(value) {
    this.setState({count: this.state.count+100});
    this.setState({count: this.state.count+value});
    this.setState({count: this.state.count-100});
}

我们希望setState将计数增加100,然后将其更新为1,然后删除之前添加的100。 如果setState以实际顺序执行状态转换,我们将获得预期的行为。 但是,setState是异步的,为了更好的UI体验和性能,可能会将多个setState调用批处理在一起。 因此,以上代码产生的行为与我们期望的行为不同。

因此,您可以传入具有签名的updater函数,而不是直接传递对象:

(prevState, props) => stateChange

prevState是对先前状态的引用,并保证是最新的。 props是指组件的props,在这里我们不需要props来更新状态,因此我们可以忽略它。 因此,我们可以使用它来更新状态并避免竞争状态。

// The right way

handleCount(value) {
    
  this.setState((prevState) => {
    count: prevState.count +1
  });
}

setState()该组件,您就可以使用有状态的组件。

无状态组件

您可以使用函数或类来创建无状态组件。 但是,除非需要在组件中使用生命周期挂钩,否则应该使用无状态功能组件。 如果您决定在此处使用无状态功能组件,将会有很多好处。 它们易于编写,理解和测试,因此您可以完全避免使用this关键字。 但是,从React v16开始,使用无状态功能组件比使用类组件没有性能优势。

缺点是您不能拥有生命周期挂钩。 生命周期方法ShouldComponentUpdate()通常用于优化性能并手动控制要呈现的内容。 您还不能将其与功能组件一起使用。 引用也不受支持。

容器组件与演示组件

这是编写组件时非常有用的另一种模式。 这种方法的好处是行为逻辑与表示逻辑是分离的。

表现成分

呈现组件与视图或事物外观结合在一起。 这些组件从其容器对应对象接受道具并进行渲染。 与描述用户界面有关的所有内容都应放在此处。

表示组件是可重用的,应保持与行为层的分离。 一个表示性组件专门通过props接收数据和回调,并且当事件发生时(如按下一个按钮),它通过props对容器组件执行回调以调用事件处理方法。

除非需要状态,否则功能组件应该是编写表示性组件的首选。 如果表示组件需要状态,则它应与UI状态有关,而不是实际数据。 呈现组件不会与Redux存储交互或进行API调用。

容器组件

容器组件将处理行为部分。 容器组件告诉呈现组件应使用道具渲染什么。 它不应包含有限的DOM标记和样式。 如果您使用的是Redux,则容器组件包含将动作分派给商店的代码。 另外,在这里您应该放置API调用并将结果存储到组件的状态。

通常的结构是,在顶部有一个容器组件,该组件将数据向下传递给其作为道具的子表示组件。 这适用于较小的项目; 但是,当项目变大并且您有很多只接受道具并将其传递给子组件的中间组件时,这将变得很讨厌且难以维护。 发生这种情况时,最好创建叶子组件特有的容器组件,这将减轻中间组件的负担。

那么什么是PureComponent?

您将在React圈子中经常听到术语纯组件,然后是React.PureComponent 。 当您不熟悉React时,所有这些听起来都有些混乱。 如果在给定相同的道具和状态的情况下保证返回相同的结果,则该组件被称为纯组件。 功能组件是纯组件的一个很好的例子,因为在给定输入的情况下,您知道将要呈现的内容。

const HelloWorld = ({name}) => (
 <div>{`Hi ${name}`}</div>
);

只要类的props和状态是不可变的,它们也可以是纯组件。 如果您的组件具有一组“深”不可变的道具和状态,那么React API就有一个叫做PureComponent东西。 React.PureComponent类似于React.Component ,但它实现了ShouldComponentUpdate()有点不同方法。 ShouldComponentUpdate()某些内容之前,应调用ShouldComponentUpdate() 。 默认行为是它返回true,以便对状态或道具的任何更改都重新呈现组件。

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

但是,使用PureComponent可以对对象进行浅表比较。 浅比较意味着您比较对象的立即内容,而不是递归比较对象的所有键/值对。 因此,仅比较对象引用,并且如果对状态/属性进行了突变,则可能无法按预期工作。

React.PureComponent用于优化性能,没有理由不考虑使用它,除非遇到某种性能问题。

最后的想法

无状态功能组件更加优雅,通常是构建表示组件的不错选择。 因为它们只是函数,所以您将不会很难编写和理解它们,而且它们非常容易测试。

应该注意的是,无状态功能组件在优化和性能方面没有优势,因为它们没有ShouldComponentUpdate()挂钩。 这可能会在React的未来版本中发生变化,在该版本中可能会优化功能组件以提高性能。 但是,如果您对性能不苛刻,则应坚持使用视图/表示的功能组件以及容器的有状态类组件。

希望本教程为您提供了基于组件的体系结构和React中不同组件模式的高级概述。 您对此有何想法? 通过评论分享。

在过去的几年中,React越来越受欢迎。 实际上,我们在Envato Market中有许多项目可供购买,审查,实施等。 如果您正在寻找有关React的其他资源,请随时检查

翻译自: https://code.tutsplus.com/tutorials/stateful-vs-stateless-functional-components-in-react--cms-29541

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值