Skip to content

React类组件和函数组件的本质区别 #12

Open
@jappp

Description

@jappp

什么是类组件

在 React 中,类组件就是基于ES6语法,通过继承 React.component 得到的组件,下面就是一个典型的类组件

class Demo extends React.Component {
  // 初始化类组件的 state
  state = {
    text: ""
  };
  // 编写生命周期方法 didMount
  componentDidMount() {
    // 省略业务逻辑
  }
  // 编写自定义的实例方法
  changeText = (newText) => {
    // 更新 state
    this.setState({
      text: newText
    });
  };
  // 编写生命周期方法 render
  render() {
    return (
      <div className="demoClass">
        <p>{this.state.text}</p>
        <button onClick={this.changeText}>点我修改</button>
      </div>
    );
  }
}

什么是函数组件

函数组件也称无状态组件,顾名思义就是以函数形态存在的 React 组件。如下就是典型的函数组件

function DemoFunction(props) {
  const { text } = props
  return (
    <div className="demoFunction">
      <p>{`function 组件所接收到的来自外界的文本内容是:[${text}]`}</p>
    </div>
  );
}

那么很多人会问到在 React 中类组件和函数组件的差异,就表象来说可以说很多条

  • 类组件有生命周期,函数组件没有
  • 类组件需要继承 Class,函数组件不需要
  • 类组件可以获取实例化的 this,并且基于 this 做各种操作,函数组件不行
  • 类组件内部可以定义并维护 state, 函数组件都称为无状态了,那肯定不行。

看上去类组件的功能包含了方方面面,大而全,大而全一定好吗?肯定不尽其然,大家也能知道类组件的内部逻辑容易和组件黏在一起,难以拆分和复用;大而全代表学习成本高,容易写出垃圾代码。

函数组件相比较类组件,优点是更轻量与灵活,便于逻辑的拆分复用。

那么难道 React 内部就是基于此大力推广 Hooks 和函数组件吗?肯定有这些方面的因素,但最重要的一点,我引用一下 React 团队核心成员和 Redux 作者 Dan 的一篇文章,函数式组件与类组件有何不同?

一句话概括:函数式组件捕获了渲染时所使用的值,这是两类组件最大的不同。

怎么理解这句话呢?
我们都知道,React 框架有一个经典的公式是 UI = f(data),React框架做的本质工作就是吃入数据,吐出UI,把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这也就是说React的数据应该紧紧的和渲染绑定在一起,但是问题的关键就在于类组件是做不到这一点的。

我们采用 Dan 文章中的例子

class ProfilePage extends React.Component {
  showMessage = () => {
    alert('Followed ' + this.props.user);
  };
  handleClick = () => {
    setTimeout(this.showMessage, 3000);
  };
  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

这个组件返回的是一个按钮,点击之后延迟三秒,页面弹出 ‘Followed XXX‘的文案。
看上去好像是没有什么问题,但是在sandBox例子中,如果你在dan用户下点击 follow按钮,并且在三秒内把用户切换到 Sophie, 最终弹出的提示框会变成 ‘Followed Sophie’,这明显很不合理。
fix

这个现象有点奇怪,user 是通过 props 下发的,props不可改变,那么造成数据改变的原因就一定是 this 指向改变了。
真正的原因也确实如此,虽然props不可改变,但是this是可变的,this.props 的每次调用都会去获取最新的 this 值,这也是React保证数据实时性的重要手段。

那么就很清晰了,当showMessage最终执行时,此时的 this 绑定的是 Sophie 对应的上下文,所以输出为 ‘Followed Sophie’;

如果我们把上面的类组件改造成函数组件

function ProfilePage(props) {
  const showMessage = () => {
    alert('Followed ' + props.user);
  };
  const handleClick = () => {
    setTimeout(showMessage, 3000);
  };
  return (
    <button onClick={handleClick}>Follow</button>
  );
}

最终的输出值明显为 ‘Followed Dan’,props 会在函数执行的瞬间就被捕获,而 props 本身又是不可变值,所以我们可以确保从当前开始读取到的 props 都是最初捕获到的。当父组件传入新的 props 尝试重新渲染函数时,本质是基于新的 props 入参重新调用了一次函数,并不会影响上一次调用。这就是 Dan 所说的函数式组件捕获了渲染所使用的值,并且我们还能进一步意识到:函数组件真正将数据和渲染紧紧的绑定到一起了

这里有个小Tips,很多人认为在函数组件中延迟输出的 state 是调用时的 state,而不是最新的 state 是一个Bug,恰恰相反,这是一个函数式组件的特性,是真正践行了React设计理念的正确方式。
Hooks也给出了获取最新的props和state的方法,就是 useRef,详细用法我不再赘叙,大家有兴趣可以自己去查阅。

那么显而易见的是,函数组件更符合 React 团队的设计理念,并且代码易于拆分和复用,用脚投票都知道 React 团队为什么要推出 Hooks 来扩展函数组件的功能,并且倡导大家使用函数组件了。

参考文章:
Dan Abramov 函数式组件与类组件有何不同

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions