1. 引言

为什么要了解 Function 写法的组件呢?因为它正在变得越来越重要。

那么 React 中 Function Component 与 Class Component 有何不同?

how-are-function-components-different-from-classes 这篇文章带来了一个独特的视角。

顺带一提,以后会用 Function Component 代替 Stateless Component 的说法,原因是:自从 Hooks 出现,函数式组件功能在不断丰富,函数式组件不再需要强调其无状态特性,因此叫 Function Component 更为恰当。

2. 概述

原文事先申明:并没有对 Function 与 Classes 进行优劣对比,而仅仅进行特性对比,所以不接受任何吐槽。

这两种写法没有好坏之分,性能差距也几乎可以忽略,而且 React 会长期支持这两种写法。

Capture props

对比下面两段代码。

Class Component:

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

Function Component:

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

(在线 Demo)

这两个组件都描述了同一个逻辑:点击按钮 3 秒后 alert 父级传入的用户名。

如下父级组件的调用方式:

<ProfilePageFunction user={this.state.user} />
<ProfilePageClass user={this.state.user} />
复制代码

那么当点击按钮后的 3 秒内,父级修改了 this.state.user,弹出的用户名是修改前的还是修改后的呢?

Class Component 展示的是修改后的值:

Function Component 展示的是修改前的值:

那么 React 文档中描述的 props 不是不可变(Immutable) 数据吗?为啥在运行时还会发生变化呢?

原因在于,虽然 props 不可变,是 this 在 Class Component 中是可变的,因此 this.props 的调用会导致每次都访问最新的 props

而 Function Component 不存在 this.props 的语法,因此 props 总是不可变的。

为了便于理解,笔者补充一些代码注解:

Function Component:

function ProfilePage(props) {setTimeout(() => {// 就算父组件 reRender,这里拿到的 props 也是初始的console.log(props);}, 3000);
}
复制代码

Class Component:

class ProfilePage extends React.Component {render() {setTimeout(() => {// 如果父组件 reRender,this.props 拿到的永远是最新的。// 并不是 props 变了,而是 this.props 指向了新的 props,旧的 props 找不到了console.log(this.props);}, 3000);}
}
复制代码

如果希望在 Class Component 捕获瞬时 Props,可以: const props = this.props;,但这样的代码很蹩脚,所以如果希望拿到稳定的 props,使用 Function Component 是更好的选择。

Hooks 也具有 capture value 特性

看下面的代码:

function MessageThread() {const [message, setMessage] = useState("");const showMessage = () => {alert("You said: " + message);};const handleSendClick = () => {setTimeout(showMessage, 3000);};const handleMessageChange = e => {setMessage(e.target.value);};return (<><input value={message} onChange={handleMessageChange} /><button onClick={handleSendClick}>Send</button></>);
}
复制代码

(在线 Demo)

在点击 Send 按钮后,再次修改输入框的值,3 秒后的输出依然是 点击前输入框的值。这说明 Hooks 同样具有 capture value 的特性。

利用 useRef 可以规避 capture value 特性:

function MessageThread() {const latestMessage = useRef("");const showMessage = () => {alert("You said: " + latestMessage.current);};const handleSendClick = () => {setTimeout(showMessage, 3000);};const handleMessageChange = e => {latestMessage.current = e.target.value;};
}
复制代码

只要将赋值与取值的对象变成 useRef,而不是 useState,就可以躲过 capture value 特性,在 3 秒后得到最新的值。

这说明了利用 Function Component + Hooks 可以实现 Class Component 做不到的 capture props、capture value,而且 React 官方也推荐 新的代码使用 Hooks 编写。

3. 精读

原文 how-are-function-components-different-from-classes 从一个侧面讲述了 Function Component 与 Class Component 的不同点,之所以将 Function Component 与 Class Component 相提并论,几乎都要归功于 Hooks API 的出现,有了 Hooks,Function Component 的能力才得以向 Class Component 看齐。

关于 React Hooks,之前的两篇精读分别有过介绍:

  • 精读《React Hooks》
  • 精读《怎么用 React Hooks 造轮子》

但是,虽然 Hook 已经发布了稳定版本,但周边生态跟进还需要时间(比如 useRouter)、最佳实践整理还需要时间,因此不建议重构老代码。

为了更好的使用 Function Component,建议时常与 Class Component 的功能做对比,方便理解和记忆。

下面整理一些常见的 Function Component 问题:

非常建议完整阅读 React Hooks FAQ。

怎么替代 shouldComponentUpdate

说实话,Function Component 替代 shouldComponentUpdate 的方案并没有 Class Component 优雅,代码是这样的:

const Button = React.memo(props => {// your component
});
复制代码

或者在父级就直接生成一个自带 memo 的子元素:

function Parent({ a, b }) {// Only re-rendered if `a` changes:const child1 = useMemo(() => <Child1 a={a} />, [a]);// Only re-rendered if `b` changes:const child2 = useMemo(() => <Child2 b={b} />, [b]);return (<>{child1}{child2}</>);
}
复制代码

相比之下,Class Component 的写法通常是:

class Button extends React.PureComponent {}
复制代码

这样就自带了 shallowEqualshouldComponentUpdate

怎么替代 componentDidUpdate

由于 useEffect 每次 Render 都会执行,因此需要模拟一个 useUpdate 函数:

const mounting = useRef(true);
useEffect(() => {if (mounting.current) {mounting.current = false;} else {fn();}
});
复制代码

更多可以查看 精读《怎么用 React Hooks 造轮子》

怎么替代 forceUpdate

React 官方文档提供了一种方案:

const [ignored, forceUpdate] = useReducer(x => x + 1, 0);function handleClick() {forceUpdate();
}
复制代码

每次执行 dispatch 时,只要 state 变化就会触发组件更新。当然 useState 也同样可以模拟:

const useUpdate = () => useState(0)[1];
复制代码

我们知道 useState 下标为 1 的项是用来更新数据的,而且就算数据没有变化,调用了也会刷新组件,所以我们可以把返回一个没有修改数值的 setValue,这样它的功能就仅剩下刷新组件了。

更多可以查看 精读《怎么用 React Hooks 造轮子》

state 拆分过多

useState 目前的一种实践,是将变量名打平,而非像 Class Component 一样写在一个 State 对象里:

class ClassComponent extends React.PureComponent {state = {left: 0,top: 0,width: 100,height: 100};
}// VSfunction FunctionComponent {const [left,setLeft] = useState(0)const [top,setTop] = useState(0)const [width,setWidth] = useState(100)const [height,setHeight] = useState(100)
}
复制代码

实际上在 Function Component 中也可以聚合管理 State:

function FunctionComponent() {const [state, setState] = useState({left: 0,top: 0,width: 100,height: 100});
}
复制代码

只是更新的时候,不再会自动 merge,而需要使用 ...state 语法:

setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
复制代码

可以看到,更少的黑魔法,更可预期的结果。

获取上一个 props

虽然不怎么常用,但是毕竟 Class Component 可以通过 componentWillReceiveProps 拿到 previousPropsnextProps,对于 Function Component,最好通过自定义 Hooks 方式拿到上一个状态:

function Counter() {const [count, setCount] = useState(0);const prevCount = usePrevious(count);return (<h1>Now: {count}, before: {prevCount}</h1>);
}function usePrevious(value) {const ref = useRef();useEffect(() => {ref.current = value;});return ref.current;
}
复制代码

通过 useEffect 在组件渲染完毕后再执行的特性,再利用 useRef 的可变特性,让 usePrevious 的返回值是 “上一次” Render 时的。

可见,合理运用 useEffect useRef,可以做许多事情,而且封装成 CustomHook 后使用起来仍然很方便。

未来 usePrevious 可能成为官方 Hooks 之一。

性能注意事项

useState 函数的参数虽然是初始值,但由于整个函数都是 Render,因此每次初始化都会被调用,如果初始值计算非常消耗时间,建议使用函数传入,这样只会执行一次:

function FunctionComponent(props) {const [rows, setRows] = useState(() => createRows(props.count));
}
复制代码

useRef 不支持这种特性,需要写一些冗余的函判定是否进行过初始化。

掌握了这些,Function Component 使用起来与 Class Component 就几乎没有差别了!

4. 总结

Function Component 功能已经可以与 Class Component 媲美了,但目前最佳实践比较零散,官方文档推荐的一些解决思路甚至不比社区第三方库的更好,可以预料到,Class Component 的功能会被五花八门的实现出来,那些没有被收纳进官方的 Hooks 乍看上去可能会眼花缭乱。

总之选择了 Function Component 就同时选择了函数式的好与坏。好处是功能强大,几乎可以模拟出任何想要的功能坏处是由于可以灵活组合,如果自定义 Hooks 命名和实现不够标准,函数与函数之间对接的沟通成本会更大。

讨论地址是:精读《Stateless VS Class 组件》 · Issue #137 · dt-fe/weekly

如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

special Sponsors

  • DevOps 全流程平台

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)

精读《Function VS Class 组件》相关推荐

  1. 【韩松】Deep Gradient Comression_一只神秘的大金毛_新浪博客

    <Deep Gradient Compression> 作者韩松,清华电子系本科,Stanford PhD,深鉴科技联合创始人.主要的研究方向是,神经网络模型压缩以及硬件架构加速. 论文链 ...

  2. 【韩松】Deep Gradient Comression

    <Deep Gradient Compression> 作者韩松,清华电子系本科,Stanford PhD,深鉴科技联合创始人.主要的研究方向是,神经网络模型压缩以及硬件架构加速. 论文链 ...

  3. [文献阅读] Sparsity in Deep Learning: Pruning and growth for efficient inference and training in NN

    文章目录 1. 前言 2. Overview of Sparsity in Deep Learning 2.1 Generalization 2.2 performance and model sto ...

  4. 【翻译】Batch Normalization: Accelerating Deep Network Trainingby Reducing Internal Covariate Shift

    Batch Normalization: Accelerating Deep Network Trainingby Reducing Internal Covariate Shift Sergey I ...

  5. 模型加速--CLIP-Q: Deep Network Compression Learning by In-Parallel Pruning-Quantization

    CLIP-Q: Deep Network Compression Learning by In-Parallel Pruning-Quantization CVPR2018 http://www.sf ...

  6. 论文笔记30 -- (视频压缩)【CVPR2021】FVC: A New Framework towards Deep Video Compression in Feature Space

    <FVC: A New Framework towards Deep Video Compression in Feature Space> CVPR 2021 的一篇Oral 提出了特征 ...

  7. 端到端图像压缩《Asymmetric Gained Deep Image Compression With Continuous Rate Adaptation》

    Asymmetric Gained Deep Image Compression With Continuous Rate Adaptation 一 简介 二 内容 2.1 目前方法的缺陷 2.2 整 ...

  8. 深度学习视频压缩1—DVC: An End-to-end Deep Video Compression Framework

    本文是第一篇端到端使用神经网络来进行视频压缩的论文, github地址:GitHub - GuoLusjtu/DVC: DVC: An End-to-end Deep Video Compressio ...

  9. 【论文阅读】Deep Compositional Captioning: Describing Novel Object Categories without Paired Training Data

    [论文阅读]Deep Compositional Captioning: Describing Novel Object Categories without Paired Training Data ...

  10. CVPR 2018 TRACA:《Context-aware Deep Feature Compression for High-speed Visual Tracking》论文笔记

    理解出错之处望不吝指正. 本文的模型叫做TRACA.模型中使用多个expert auto-encoder,在预训练阶段,每个expert auto-encoder针对一个特定类进行训练:在tracki ...

最新文章

  1. C#程序如何对接比特币钱包节点?
  2. java构造方法嵌套,laravel查询构建器中的嵌套查询
  3. 图解Hadoop hdfs写数据流程
  4. MongoDB启动报错
  5. 对可操作对象的占用状态、锁定状态、解锁状态的一些方案
  6. 数字图像处理 频率域锐化 MATLAB实验
  7. cad新手必练300图_零基础学习CAD软件难吗?超全CAD入门学习教程送给你
  8. H.265 SAO技术
  9. 【固件下载】iPhone 全系OS官方固件和自制固件下载和刷机升级方法(更新os4.02自制)
  10. 计算机硬盘 打开很慢,电脑读写运行速度慢的8种解决办法
  11. ajax的三种传参方式
  12. 普适计算机技术特征的事例,普适计算、物联网、云计算与未来社会信息化需求分析...
  13. 弹性盒子 -- flex
  14. 北大计算机博士有多难考,我是怎样考上北大博士的
  15. 小说视频图片站群采集工具程序源码
  16. JAVA建行银企直连密钥传输交换接口
  17. 用图机器学习探索 A 股个股相关性变化
  18. vivado:正确断开hardware target硬件管理连接
  19. 图的最短路径之迪杰斯特拉算法和弗洛伊德算法
  20. SAP库存表与MMBE关系

热门文章

  1. Ubuntu Server 14.04 下root无法ssh登陆
  2. RedHat linux inittab详解
  3. 使用request实现网站中的注册功能
  4. 机器学习项目实战----信用卡欺诈检测
  5. 基于Grafana的Repeate Panel快速定义面板
  6. Windows环境下在IDEA编辑器中spark开发安装步骤
  7. SpringBoot05 数据操作01 - JPA的基本使用、基本使用02
  8. css禁用鼠标点击事件
  9. iscroll 滚动刷新
  10. 企业网站推广方案详解