- 1. 请说说react中Portal是什么?
- 2. 类组件和函数组件有什么区别?
- 3. useEffect 与 useLayoutEffect 有什么区别
- 4. 虚拟 DOM 一定要快么?
- 5. React- JSX 到 Fiber 的关系
- 6. React Hooks 在使用上有哪些限制?
- 7. react 在重新渲染(render)时,都做了什么?
- 8. 了解 React 中的 ErrorBoundary 吗,它有哪些使用场景?
- 9. React 中怎么实现状态自动保存(KeepAlive)?
- 10. React Fiber 是如何实现更新过程可控?
- 11. 简述fiber工作原理
- 12. React 的副作用的flags标识与位操作
- 13. 说说 react 的事件机制
- 14.react 创建组件的方式有哪些?
1. 请说说react中Portal是什么?
https://www.yuque.com/linhao-00fte/da7gav/lx71sk
2. 类组件和函数组件有什么区别?
相同点:
组件是 React 可复用的最小代码片段,它们会返回要在页面中渲染的 React 元素。也正因为组件是 React 的最小编码单位,所以无论是函数组件还是类组件,在使用方式和最终呈现效果上都是完全一致的。我们甚至可以将一个类组件改写成函数组件,或者把函数组件改写成一个类组件(虽然并不推荐这种重构行为)。从使用者的角度而言,很难从使用体验上区分两者,而且在现代浏览器中,闭包和类的性能只在极端场景下才会有明显的差别。所以,基本可认为两者作为组件是完全一致的。
不同点:
它们在开发时的心智模型上却存在巨大的差异。类组件是基于面向对象编程的,它主打的是继承、生命周期等核心概念;而函数组件内核是函数式编程,主打的是 immutable、没有副作用、引用透明等特点。之前,在使用场景上,如果存在需要使用生命周期的组件,那么主推类组件;设计模式上,如果需要使用继承,那么主推类组件。但现在由于 React Hooks 的推出,生命周期概念的淡出,函数组件可以完全取代类组件。其次继承并不是组件最佳的设计模式,官方更推崇组合优于继承的设计概念,所以类组件在这方面的优势也在淡出。性能优化上,类组件主要依靠 shouldComponentUpdate 阻断渲染来提升性能,而函数组件依靠 React.memo 缓存渲染结果来提升性能。从上手程度而言,类组件更容易上手,从未来趋势上看,由于 React Hooks 的推出,函数组件成了社区未来主推的方案。类组件在未来时间切片与并发模式中,由于生命周期带来的复杂度,并不易于优化。而函数组件本身轻量简单,且在 Hooks 的基础上提供了比原先更细粒度的逻辑组织与复用,更能适应 React 的未来发展。
3. useEffect 与 useLayoutEffect 有什么区别
共同点:
运用效果:useEffect 与 useLayoutEffect 两者都是用于处理副作用,这些副作用包括改变 DOM、设置订阅、操作定时器等。在函数组件内部操作副作用是不被允许的,所以需要使用这两个函数去处理
使用方式:useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl 方法,在使用上也没什么差别,基本可以直接替换。
- 不同点:
使用场景:useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理,所以需要避免在 useLayoutEffect 做计算量较大的耗时任务从而造成阻塞
使用效果:useEffect 是按照顺序执行代码的,改变屏幕像素之后执行(先渲染,后改变 DOM),当改变屏幕内容时可能会产生闪烁;useLayoutEffect 总是比 useEffect 先执行
在未来的趋势上,两个 API 是会长期共存的,暂时没有删减合并的计划,需要开发者根据场景去自行选择。React 团队的建议非常实用,如果实在分不清楚,先用 useEffect,一般问题不大;如果页面有异常,在直接替换为 useLayoutEffect 即可。
4. 虚拟 DOM 一定要快么?
React 中涉及到虚拟 DOM 的代码主要分为以下三部分,其中核心是第二步的 DOMDIFF 算法
- 把 render 中的 JSX(或者 createElement 这个 API)转化成虚拟 DOM
- 状态或属性改变后重新计算虚拟 DOM 并生成一个补丁对象(DOMDIFF)
- 通过这个补丁对象更新视图中的 DOM 节点
虚拟 DOM 不一定更快。
DOM 操作是性能杀手,因为操作 DOM 会引起页面的回流或者重绘。相比起来,通过多一些预先计算来减少 DOM 的操作要划算的多。
但是使用虚拟 DOM 会更快这句话并不一定适用于所有场景。例如一个页面就有一个按钮,点击一下,数字加一,那肯定是直接操作 DOM 更快。使用虚拟 DOM 无非白白增加了计算量和代码量。即使是复杂情况,浏览器也会对我们的 DOMc 操作进行优化,大部分浏览器会根据我们操作的时间和次数进行批量处理,所以直接操作 DOM 也未必很慢。
那么为什么现在的框架都使用虚拟 DOM 呢?因为使用虚拟 DOM 可以提高代码的性能下限,并极大的优化大量操作 DOM 可以提高代码的性能下限,并极大的优化大量操作 DOM 时产生的性能损耗。同时这些框架也保证了,即使在少数虚拟 DOM 不太给力的场景下,性能也在我们接受的范围内。而且,我们之所以喜欢 react、vue 等使用了虚拟 DOM 框架不光是因为他们块,还有很多其他更重要的原因。例如 React 对函数式编程的友好,vue 优秀的开发体验等。
5. React- JSX 到 Fiber 的关系
Jsx 到 Fiber
JSX 一种描述当前组件内容的数据结构,理解为骨架
Fiber 一种异步可中断架构,Fiber并不是计算机术语中的新名词,他的中文翻译叫做纤程,与进程(Process)、线程(Thread)、协程(Coroutine)同为程序执行过程。在很多文章中将纤程理解为协程的一种实现。在JS中,协程的实现便是Generator。所以,我们可以将纤程(Fiber)、协程(Generator)理解为代数效应思想在JS中的体现。
React Fiber可以理解为:React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态。
- 作为静态的数据结构来说,每个Fiber节点对应一个React element,保存了该组件的类型(函数组件/类组件/原生组件…)、对应的DOM节点等信息。
- 作为动态的工作单元来说,每个Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新…)
Fiber数据结构包含如下信息:
大致分为4类:1.静态数据结构属性2.用于连接其他Fiber节点形成Fiber树属性3.动态工作单元属性(更新相关)4.调度优先级属性
数据结构如下:function FiberNode(tag: WorkTag,pendingProps: mixed,key: null | string,mode: TypeOfMode,) {// 作为静态数据结构的属性this.tag = tag; // Fiber对应组件类型 function、class、hostthis.key = key; // key属性this.elementType = null; // 大部分情况同type,某些情况不同,被React.memo包裹等this.type = null; // 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagNamethis.stateNode = null; //Fiber对应的真实DOM节点// 用于连接其他Fiber节点形成Fiber树this.return = null;this.child = null;this.sibling = null;this.index = 0;this.ref = null;// 作为动态的工作单元的属性this.pendingProps = pendingProps;this.memoizedProps = null;this.updateQueue = null;this.memoizedState = null;this.dependencies = null;this.mode = mode;// 副作用this.effectTag = NoEffect;this.nextEffect = null;this.firstEffect = null;this.lastEffect = null;// 调度优先级相关this.lanes = NoLanes;this.childLanes = NoLanes;// 指向该fiber在另一次更新时对应的fiberthis.alternate = null;}
Jsx与Fiber关系
- 概述:jsx 映射出 静态结构Fiber(保存了该组件的类型(函数组件/类组件/原生组件…)、对应的DOM节点等信息); 动态结构Fiber动态的工作单元来说,保存了本次更新中该组件改变的状态、优先级、要执行的工作(需要被删除/被插入页面中/被更新…)等; 静态 + 动态组成完整的Fiber结构
- 过程如下:
- jsx 通过 @babel/plugin-transform-react-jsx 将其转换成 createElement(),函数名由babel配置决定
- createElement()函数内部调用ReactElement(),返回React Element(该对象包含了jsx编写的属性值、事件、className等信息)
- mount时,Reconciler(协调器)根据JSX描述的信息 生成对应的 Fiber节点(包含优先级信息、状态、被打上的标记)
- update时,Reconciler将根据更新后对应JSX生成对应的workInProgress Fiber 与 之前保存的 current Fiber对比patch,打上标记交给renderer,然后将workInProgress Fiber 变成current Fiber。workInProgress fiber的创建可以复用current Fiber树对应的节点数据,使用双缓存即在内存中进行
6. React Hooks 在使用上有哪些限制?
React Hooks 的限制主要有两条:
- 不要在循环、条件或嵌套函数种调用 Hook
- 在 React 的函数组件中调用 Hook
那么问什么会有这样的限制呢?就要从 Hooks 的设计说起
Hooks 的设计初衷是为了改进 React 组件的开发模式。在旧有的开发模式下遇到了三个问题
组件之间难以复用状态逻辑。过去常见的解决方案是高阶组件、render props 以及庄涛管理框架
复杂的组件变得难以理解。生命周期函数与业务逻辑耦合太深,导致关联部分难以拆分
人和机器都很容易混淆类。常见的有 this 的问题,但在 React 团队中还有难以优化的问题,他们希望在编译优化层面做出一些改进
这三个问题在一定程度上阻碍了 React 的后续发展,所以为了解决这三个问题,Hooks 基于函数组件开始设计。然而第三个问题决定了 Hooks 只支持函数组件。
那为什么不要在循环、条件或嵌套函数中调用 Hook 呢?因为 Hook 的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件或嵌套函数很有可能导致数组取值错位,执行错误的 Hook。当然,实质上 React 的源码不是数组而是链表。
7. react 在重新渲染(render)时,都做了什么?
会对新旧 VNode 进行对比,也就是我们所说的Diff算法。
对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到该节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面
遍历差异对象,根据差异的类型,根据对应对规则更新VNode
React 的处理 render 的基本思维模式是:每次一有变动就会去重新渲染整个应用。在 Virtual DOM 没有出现之前,最简单的方法就是直接调用 innerHTML。Virtual DOM厉害的地方并不是说它比直接操作 DOM 快,而是说不管数据怎么变,都会尽量以最小的代价去更新 DOM。React 将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。当 DOM 树很大时,遍历两棵树进行各种比对还是相当耗性能的,特别是在顶层 setState 一个微小的修改,默认会去遍历整棵树。尽管 React 使用高度优化的 Diff 算法,但是这个过程仍然会损耗性能
8. 了解 React 中的 ErrorBoundary 吗,它有哪些使用场景?
在推出之前报错会直接白屏,总是需要前端人员进行手动 try catch,react16 新增了两个生命周期 componentdidcatch 和 staticgetDeriveStateFromError 从框架级别让我们更方便捕捉异常并显示备用 UI。其实就是在整个 workloop 外面包一层 try catch,报错的时候遍历父组件找到这两个生命周期并把堆栈信息塞给生命周期进行判断。
ErrorBoundary可以在任务子组件中捕获js错误,只需在根组件中定义一次,即可捕获所有子组件的错误。
除了下述四种错误:
1.事件处理函数中使用try catch
2. 异步函数(setTimeout)
3. 服务端渲染
4. 当前ErrorBoundary抛出的错误
9. React 中怎么实现状态自动保存(KeepAlive)?
- 状态保存
假设有下述场景:移动端中,用户访问一个列表页,上拉浏览列表页的过程中,随着滚动高度逐渐增加,数据也将采用触底分页加载的形式逐步增加,列表页浏览到某个位置,用户看到了很感兴趣的项目,点击查看其详情,进入详情页,从详情页退回列表页时,需要停留在离开时列表页的浏览位置上。类似的场景还有已填写但未提交的表单,管理系统中可切换和可关闭的功能标签等,这类场景随着用户交互逐渐变化或增长,这里理解为状态,但在交互过程中,因为某些原因需要临时离开交互场景,则需要对状态进行保存
在 React 中,我们通常会使用路由去管理不同的页面,而在切换页面时,路由将会卸载掉未匹配的页面组件。所以上述列表页例子中,当用户从详情页退回列表页时,会回到列表页顶部,因为列表页组件被路由卸载后重建了,状态被丢失
- 如何实现 React 中的状态保存
在 Vue 中,我们可以非常便捷地通过标签实现状态的保存,该标签会缓存不活动的组件实例,而不是销毁它们而在 React 中并没有这个功能,曾经有人在官方提过相关 issue,但官方以为这个功能容易造成内存泄漏,表示暂时不考虑支持,所以我们需要自己想办法解决
- 常见的解决方式
手动保存状态,是比较常见的解决方式,可以配合 React 组件的 componentWillUnmount 生命周期通过 redux 之类的状态管理层对数据进行保存,通过 componentDidMount 周期进行数据恢复
在需要保存的状态比较少时,这种方式可以比较快地实现我们所需功能,但在数据量大或者情况多变时,手动保存状态就会变成一件麻烦事了。为了不需要每次都关心如何对数据进行保存恢复,我们需要研究如何自动保存状态,通过路由实现自动状态保存(通常使用 react-router)。既然 React 中的状态丢失是由于路由切换时卸载了组件引起的,那可以尝试从路由机制上去入手,改变路由对组件的渲染行为
- 有以下的方式去实现这个功能:
重写组件,可以参考 react-live-route。重写可以实现我们想要的功能,但成本也比较高,需要注意对原始功能的保存,以及多个 react-router 版本的兼容
重写路由库,可参考 react-keeper。重写路由库成本是一般开发者无法承受的,且完全替换掉路由方案是一个风险比较大的事情,需要较为慎重地考虑
基于组件现有行为做扩展,可参考 react-router-cache-route。在阅读源码后发现如果使用 componet 或者 render 属性,都无法避免路由在不匹配时被卸载掉的命运。但将 children 属性当作方法来使用,我们就有手动控制渲染行为的可能
上面几种方案,主要通过路由入手实现自动状态保存的可能,但终究不是真实的、纯粹的 KeepAlive 功能
10. React Fiber 是如何实现更新过程可控?
更新过程的可控主要体现在下面几个方面:
任务拆分
任务挂起、恢复、终止
任务具备优先级
任务拆分
在 react Fiber 机制中,它采用化整为零 的思想,将调和阶段(Reconiler)递归遍历 VDOM 这个大任务分成若干个小任务,每个任务只负责一个节点的处理
任务挂起、恢复、终止
workInProgress tree 表示当前正在执行更新的 Fiber 树。在 render 或者 setState 后,会构建一颗 fiber 树,也就是 workInProgress tree,这棵树在构建每一个节点的时候会收集当前节点的副作用,整棵树构建完成后,会形成一条完整的副作用链
currentFiber tree 表示上次渲染构建的 Fiber 树。在每一次更新完成后 workInProgress 会赋值给 currentFiber。在新一轮更新时 WorkInProgress tree 在重新构建,新 WorkInProgress 的节点通过 alternate 属性和 currentFiber 的节点建立联系。 在新的 WorkInProgress tree 的创建过程中,会同 currentFiber 的对应节点进行 diff 比较,收集副作用。同时也会复用和 currentFiber 对应的节点对象,减少新创建对象带来的开销。也就是说无论是创建还是更新、挂起、恢复以及终止操作都是发生在 WorkInProgress tree 创建过程中的。WorkInProgress tree 构建过程其实就是循环的执行任务和创建下一个任务
挂起
当第一个小任务完成后,先判断这一阵是否还有空闲时间,没有就挂起下一个任务的执行,记住当前挂起的节点,让出控制权给浏览器执行更高优先级的任务
恢复
在浏览器渲染完一帧后,判断当前帧是否有剩余时间,如果有就恢复之前挂起的任务。如果没有任务需要处理,代表调和阶段完成,可以开始进入渲染阶段。
终止
其实并不是每次更新都会走到提交阶段。当在调和过程中触发了新的更新,在执行下一个任务的时候,判断是否有优先级更高的执行任务,如果有就终止原来将要执行的任务,开始新的 workInProgressFiber 树构建过程,开始新的更新流程。这样可以避免重复更新操作。这也是在 React 16 以后生命周期函数 componentWillMount 有可能会执行多次的原因
任务具备优先级
React Fiber 除了通过挂起、恢复和终止来控制更新外,还给每个任务分配了优先级。具体点就是在创建或者更新 FiberNode 的时候,通过算法给每个任务分配一个到期时间(expirationTime)。在每个任务执行的时候除了判断剩余时间,如果当前处理节点已经过期,那么无论现在是否有空闲时间都必须执行该任务,过期时间的大小还代表着任务的优先级。
11. 简述fiber工作原理
12. React 的副作用的flags标识与位操作
位操作
按位非(~)
按位非运算符(~),反转操作数的位。
const a = 5; // 00000000000000000000000000000101const b = -3; // 11111111111111111111111111111101console.log(~a); // 11111111111111111111111111111010,即-6console.log(~b); // 00000000000000000000000000000010, 即2
按位非运算时,任何数字 x 的运算结果都是 -(x + 1)。例如,〜-5 运算结果为 4。
按位与(&)
按位与运算符 (&) 在两个操作数对应的二进位都为 1 时,该位的结果值才为 1,否则为 0。
按位或(|)
按位或运算符 (|) 在两个操作数对应的二进位只要有一个为 1 时,该位的结果值为 1,否则为 0。
按位异或(^)
有且仅有一个为 1 时,结果才为 1,否则为 0:
const a = 5; // 00000000000000000000000000000101const b = 3; // 00000000000000000000000000000011console.log(a ^ b); // 00000000000000000000000000000110,即6
React 为什么采用二进制表示副作用
原因可以归类为以下两点:
- 位运算快速
- 可以方便的给一个 fiber 节点添加多个副作用,同时内存开销小。
当然,代价是代码可读性差,谨慎在业务代码中使用此类操作
我们先来看下使用其他方式表示副作用会有什么问题。假设我们使用 2 表示插入,在 render 阶段,如果这个 fiber 节点是新的,我们就给这个 fiber 节点添加一个副作用:**fiber.flags = 2**。然后在 commit 阶段使用 **fiber.flags === 2** 判断节点是否需要插入。
这会带来一个问题,React 中一个 fiber 节点会有多个副作用,比如,既可以是插入,又可以是更新(类组件实现了 componentDidMount 方法,就是更新的副作用),如果使用十进制,我们可以很容易想到这样实现:
fiber.flags = [];fiber.flags.push(2); // 插入fiber.flags.push(4); // 更新,此时 fiber.flags有两个副作用:[2, 4]
在 commit 阶段就可以这样判断:
if (fiber.flags.includes(2)) {// 执行插入的逻辑}if (fiber.flags.includes(4)) {// 执行更新的逻辑}
这样做理论上是可以的,但是数组操作比较麻烦,还会冗余,比如,如果多次**fiber.flags.push(2)**就会有多个重复的 2。同时如果需要先删除插入的副作用,并添加一个更新的副作用,操作起来较繁琐
因此 React 采用了二进制标记这些副作用。不仅占用内存小,运算迅速,同时还能表示多个副作用
如果一个 fiber 节点,既要插入又要更新,可以这样标记:
fiber.flags |= Placement | Update; // Placement 0b000000000000000010 Update 0b000000000000000100
如果需要删除一个插入的副作用,并且添加一个更新的副作用,那么可以这样标记:
fiber.flags = (fiber.flags & ~Placement) | Update;
可以说是相当的方便了
Fiber flags
**PerformedWork** 是专门提供给 React Dev Tools 读取的。fiber 节点的副作用从 2 开始。0 表示没有副作用。
对于原生的 HTML 标签,如果需要修改属性,文本等,就视为有副作用。对于类组件,如果类实例实现了 componentDidMount、componentDidUpdate 等生命周期方法,则视为有副作用。对于函数组件,如果实现了 useEffect、useLayoutEffect 等 hook,则视为有副作用。以上这些都是副作用的例子。
React 在 render 阶段给有副作用的节点添加标志,并在 commit 阶段根据 fiber flags 执行对应的副作用操作,比如调用生命周期方法,或者操作真实的 DOM 节点。
React 支持的所有 flags
// 下面两个运用于 React Dev Tools,不能更改他们的值const NoFlags = 0b000000000000000000;const PerformedWork = 0b000000000000000001;// 下面的 flags 用于标记副作用const Placement = 0b000000000000000010; // 2 移动,插入const Update = 0b000000000000000100; // 4const PlacementAndUpdate = 0b000000000000000110; // 6const Deletion = 0b000000000000001000; // 8const ContentReset = 0b000000000000010000; // 16const Callback = 0b000000000000100000; // 32 类组件的 update.callbackconst DidCapture = 0b000000000001000000; // 64const Ref = 0b000000000010000000; // 128const Snapshot = 0b000000000100000000; // 256const Passive = 0b000000001000000000; // 512const Hydrating = 0b000000010000000000; // 1024const HydratingAndUpdate = 0b000000010000000100; // 1028 Hydrating | Update// 这是所有的生命周期方法(lifecycle methods)以及回调(callbacks)相关的副作用标志,其中 callbacks 指的是 update 的回调,比如调用this.setState(arg, callback)的第二个参数const LifecycleEffectMask = 0b000000001110100100; // 932 Passive | Update | Callback | Ref | Snapshot// 所有 host effects 的集合const HostEffectMask = 0b000000011111111111; // 2047// 下面这些并不是真正的副作用标志const Incomplete = 0b000000100000000000; // 2048const ShouldCapture = 0b000001000000000000; // 4096const ForceUpdateForLegacySuspense = 0b000100000000000000; // 16384
flags 位操作
这里简单列举一下 fiber flags 中一些位操作的含义。
// 1.移除所有的生命周期相关的 flagsfiber.flags &= ~LifecycleEffectMask;// 2.只保留 host effect 相关的副作用,移除其他的副作用位fiber.flags &= HostEffectMask;// 3.只保留 "插入" 副作用fiber.flags &= Placement;// 4.添加一个 “更新” 副作用,注意和第3点保留 “插入” 副作用的区别fiber.flags |= Update;// 5.移除 "插入" 副作用,添加 "更新" 副作用fiber.flags = (fiber.flags & ~Placement) | Update;
为什么可以用二进制来加减操作去可以不混淆,其实妙就妙在这个二进制数的设计上,每个副作用对应的二进制数只有 一位是1,其余都是0.
13. 说说 react 的事件机制
14.react 创建组件的方式有哪些?
组件是什么
组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式
在 React 中,一个类、一个函数都可以视为一个组件
组件所在的优势:
降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换日历、时间、范围等组件做具体的实现
调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
组件如何构建
在 React 目前来讲,组件的创建主要分成了三种方式:
- 函数式创建
- 通过 React.createClass 方法创建
- 继承 React.Component 创建
函数式创建
在 React Hooks 出来之间,函数式组件可以视为无状态组件,只负责根据传入的 props 来展示试图,不涉及对 state 状态的操作
大多数组件可以写为无状态组件,通过简单组合构建其他组件 在 React 中,通过函数简单创建组件的示例如下:
function HelloCom(props /* context */) {return <div>hello{props.name}</div>;}
通过 React.createClass 方法创建
React.createClass 是 react 刚开始推荐的创建组件的方式,目前这种创建方式已经不怎么用了。像上述通过函数式组件的方式,最终会通过 babel 转化成 React.createClass 这种形式,转化如下:
function HelloCom(props /* context */) {return React.createElement("div", null, "Hello", props.name);}
但因为编写方式过于冗杂,目前基本不使用
继承 React.Component 创建
同样在 React Hooks 出来之前,有状态的组件只能通过继承 React.Component 这种形式进行创建
有状态的组件也就是组件内部存在维护的数据,在类创建的方式中通过 this.state 进行访问
当调用 this.setState 修改组件的状态时,组件会再次调用 render()方法进行重新渲染
通过继承 React.Component 创建一个时钟示例如下:
class Timer extends React.Component {constructor(props) {super(props);this.state = { seconds: 0 };}tick() {this.setState((state) => ({seconds: state.seconds + 1,}));}componentDidMount() {this.interval = setInterval(() => this.tick(), 1000);}componentWillUnmount() {clearInterval(this.interval);}render() {return <div>Seconds:{this.state.seconds}</div>;}}
区别
由于 React.createClass 创建的方式过于冗杂,并不建议使用。而像函数式创建和类组件创建的区别主要在于需要创建的组件是否需要为有状态的组件:
对于一些无状态的组件创建,建议使用函数式创建的方式
由于react hooks的出现,函数式组件创建的组件通过hooks方法也能使之成为有状态组件,在加上目前推崇函数式编程,所以这里建议都使用函数式的方式来创建组件
在考虑组件的选择原则上,能用无状态组件则用无状态组件
