FanFanJUN/DailyLog

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

Opened this issue · 0 comments

什么是类组件

在 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 函数式组件与类组件有何不同