React 函数组件和类组件的区别

函数组件和类组件有什么不同,在编码过程中应该如何选择呢?

一、什么是函数组件

定义一个组件最简单的方式就是使用 JavaScript 函数:

import React from 'react'
const Welcome = (props) => {
  return <h1>welcome, {props.name}</h1>
}
export default Welcome

这个函数接收一个 props 对象并返回一个 react 元素

二、什么是类组件

React 可以使用 ES6 class 语法去写一个组件:

import React from 'react'
class Welcome extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return <h1>welcome, {this.props.name}</h1>
  }
}

export default Welcome

这两个版本是等价的,它们具有相同的输出。那么我们应该去选择哪一种实现方式呢?

三、函数组件与类组件的区别

1、语法上

两者最明显的不同就是在语法上:
函数组件是一个纯函数,它接收一个 props 对象返回一个 react 元素;
类组件需要去继承 React.Component 并且创建 render 函数返回 react 元素,虽然实现的效果相同,但需要更多的代码。

2、状态管理

因为函数组件是一个纯函数,所以不能在组件中使用 setState(),这也是为什么把函数组件称作为无状态组件。
如果要在组件中使用 state,可以选择创建一个类组件或者将 state 提升到你的父组件中,然后通过 props 对象传递到子组件。

3、生命周期钩子

函数组件中不能使用生命周期钩子,原因和不能使用 state 一样,所有的生命周期钩子都来自于继承的 React.Component 中。
因此,如果要使用生命周期钩子,就需要使用类组件。

注意:在 react16.8 版本中添加了 hooks,使得我们可以在函数组件中使用 useState 钩子去管理 state,使用 useEffect 钩子去使用生命周期函数。
因此,2、3 两点就不是它们的区别点。
而从这个改版中我们也可以看出 React 团队更看重函数组件,而且曾提及到在 react 之后的版本将会对函数组件的性能方面进行提升。

4、调用方式

如果 SayHi 是一个函数,React 需要调用它:

// 你的代码 
function SayHi() { 
    return <p>Hello, React</p> 
} 
// React 内部 
const result = SayHi(props) // » <p>Hello, React</p>

如果 SayHi 是一个类,React 需要先用 new 操作符将其实例化,然后调用刚才生成实例的 render 方法:

// 你的代码 
class SayHi extends React.Component { 
    render() { 
        return <p>Hello, React</p> 
    } 
} 
// React 内部 
const instance = new SayHi(props) // » SayHi {} 
const result = instance.render() // » <p>Hello, React</p>

可想而知,函数组件重新渲染将重新调用组件方法返回新的 react 元素,类组件重新渲染将 new 一个新的组件实例,然后调用 render 类方法返回 react 元素,这也说明为什么类组件中 this 是可变的。

5、获取渲染时的值

这一点是他们最大差异,但又常常被人们忽略。

考虑以下组件:

function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  }

  const handleClick = () => {
    setTimeout(showMessage, 3000);
  }

  return (
    <button onClick={handleClick}>Follow</button>
  )
}

UserProfile 组件很简单,就一个 Follow 按钮,该按钮使用了 setTimeout 模拟网络请求。用户点击这个按钮之后会弹出一个警告框。如果 props.user'Dan',它将在三秒钟后显示 'Followed Dan'

我们如何将其编写为类?天真的翻译可能像这样:

class ProfilePage extends React.Component {
  showMessage() {
    alert('Followed ' + this.props.user);
  }

  handleClick() {
    setTimeout(this.showMessage.bind(this), 3000);
  }

  render() {
    return <button onClick={this.handleClick.bind(this)}>Follow</button>
  }
}

通常认为这两个代码段是等效的。人们经常在这些模式之间自由重构,而没有注意到它们的含义

但是,这两个代码段是完全不同的。

分别按下面的顺序来操作 Follow 按钮:

  1. 先点击 Follow 按钮
  2. 3s 之前更改下拉选择项的选项
  3. 阅读弹出的警告框内容

这就发现函数组件和类组件是有区别的:

函数组件:按上面所列的三个步骤操作时,当用户在 3s 前更改下拉选择框的选项时,h1 的用户名会立马改变,而 3s 后弹出的警告框中的用户名并不会改变
类组件:按上面所列的三个步骤操作时,当用户在 3s 前更改下拉选择框的选项时,h1 中的用户名会立马改变,而 3s 后弹出的警告框中的用户名也会改变


那么,为什么我们的类示例会这样表现呢?

让我们仔细看一下 showMessage 类中的方法:

showMessage() {
    alert('Followed ' + this.props.user);
  }

showMessage 方法中读取了 this.props.user(也是我们要输出的用户名称)。而 React 中的 props 是不可变的,但是 this 是可变的,而且是一直是可变的。这也是类组件中 this 的目的。React 自身会随着时间的推移对 this 进行修改,以便在 render 函数或生命周期中读取新的版本。

因此,如果组件在请求重新渲染时,this.props 将会改变。showMessage 方法会从新的 props 中读取 user。所看到的效果也正是因为这个原因。

React 中的组件,UI 在概念上可以理解是程序当前状态的函数,那么事件处理就是让 UI 的渲染结果一部分一部分可视化输出。我们的事件处理程序属于具有特定 propsstate 的特定渲染。但是,当回调超时的话,this.props 就会打破这种联系。示例中的 showMessage 方法在回调时没有绑定到任何特定的渲染,因此它会丢失真正的 props

那么有没有一种较好的方式可以使用正确的 props 来修复 rendershowMessage 回调之间的联系呢?
我们可以在事件发生的早期,将 this.props 传递给超时完成的处理程序来尝试着解决这个问题。这种解决方式属于闭包的范畴。

class ProfilePage extends React.Component {
  showMessage(user) {
    alert('Followed ' + user);
  }

  handleClick() {
    cosnt {user} = this.props
    setTimeout(this.showMessage.bind(this, user), 3000);
  }

  render() {
    return <button onClick={this.handleClick.bind(this)}>Follow</button>
  }
}

我们使用闭包机制将上一状态的值保存下来待 showMessage 方法调用。即使 this.props 发生变化,但并不改变 user

这种方法虽然解决我们前面所提到的问题,但是这种方法代码会随着 props 的个数增加,代码也会变得更加冗余也易于出错。
如果我们也需要访问 state。如果 showMessage 调用另一个方法,该方法会读取 this.props.somethingthis.state.something
我们又会碰到同样的问题。所以我们必须通过 this.props 作为 showMessage 的参数来修复它们之间存在的问题。

但这么做会破坏类提供的特性,也令人难于记住或执行。

另外,在 handleClick 中内联 alert 中的代码并不能解决更大的问题。
我们希望以一种允许代码分解成更多方法的方式来构造代码,同时还可以读取与其相关的 render 所对应的 propsstate

或许,我们可以在类的构造函数中绑定这些方法:

class ProfilePage extends React.Component {
  render() {
    // 获取 props
    cosnt props = this.props
    
    // 它们不是类方法
    const showMessage = () => {
        alert('Followed ' + props.user);
    }
    
    const handleClick = () => {
        setTimeout(showMessage, 3000)
    }
    
    return <button onClick={handleClick}>Follow</button>
  }
}

这样一来,函数组件和类组件所达到的效果都一样了。在类组件中可以捕获渲染时的 props。效果上看上去是一样了,但看起来怪怪的。如果在类组件中的 render 中定义函数而不是使用类方法,那么还有使用类的必要性?

posted @ 2021-05-15 22:23  Leophen  阅读(1083)  评论(0编辑  收藏  举报