深度学习React-原理篇

453 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

什么是React?

React是一个网页UI框架

JQuery诞生于2005年,但限于浏览器兼容

Angular太复杂,双向绑定是最大的特色 1665815677662.png

React为什么要用JSX?

一句话解释JSX:JSX是一个js的语法扩展

核心概念:可以不用jsx,原理都是用React.CreateElement,最终都会被babel转化

React.CreateElement,但是jsx天生具有可读性,对于开发者而言,jsx是React.CreateElement的语法糖

对比其他形式:

模板(类似Angular,引入概念多)

模板字符串(结构复杂)

JSON(语法提示差)

如何避免生命周期中的坑?

挂载 -> 更新-> 卸载 才是生命周期,其他的只是生命周期的函数

挂载阶段

constructor

过去通常用于初始化数据

不推荐做初始化以外的操作

不属于生命周期,只是class的初始化函数

constructor移除之后,代码也更加简洁

getDerivedStateFromProps

作用是使组件在props变化时更新state

触发时机:当props被传入时,当state变化时,forceUpdate被调用时

UNSAFE-componentWillMount

用于加载前做某些操作,目前被弃用因为在React异步渲染机制下,改方法可能被多次调用

render

render是一个纯函数,返回jsx结构,用于描述具体内容,不能在这里调用setState,否则会进入死循环,因为state的改变会重新调用render函数

componentDidMount

主要用于组件加载完的操作,一般用于网络请求,或者绑定事件

更新阶段

getDerivedStateFromProps

跟挂载阶段的作用一样

shouldComponentUpDate

返回true和false,性能优化的关键场所,避免不必要的渲染,做比较值有没有发生变化去判断要不要更新

getSnapshotBeforeUpdate

该方法是根据新的渲染机制出现的,在DOM发生更新前调用,返回值作为componentDidMount的第三个参数

卸载阶段

componentWillUNmount

主要用于执行清理工作

比较常见的问题就是忘记清理定时器,一定要取消绑定,清理定时器

会发生重新渲染的情况

  1. 函数组件

    任何情况都会重新渲染,没有生命周期,官方提供React.memo优化手段

  2. React.Component

    1. 当state发生变化时
    2. 当父级组件传入的Props传入时
  3. React.PureComponent 默认实现了shouldComponentUpdate函数 仅在props和state进行浅比较后,确认有变化再改变

渲染时的报错,只能通过componentDidCatch捕获

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

相同点

最终呈现效果是一样的

不同点

类组件是面向对象编程,有继承,有属性,有内部状态管理

函数组件是函数式编程,结构化编程,有输入输出

函数组件更简单,纯粹,好测试。

其次就是this的指向。

业务逻辑散落在生命周期中,未来趋势也是函数式组件

类组件通过shouldComponentUpDate去阻断渲染

函数组件靠React.memo来优化

setState是同步还是异步?

合成事件:加入一个ul下面有10000个li,那么原生的添加事件就要10000个onClick事件,这就很不友好,所以react有一个事件委托机制,让他在ul上面写监听事件,然后判断是哪个li触发的事件

通常情况下setState看起来像是异步的

是否觉得react的SetState执行像一个队列

React根据队列逐一执行,合并state数据完成后执行回调,根据结果更新虚拟DOM触发渲染,根据队列逐一执行,合并状态数据完成后执行回调,根据结果更新虚拟DOM触发渲染

通过异步的操作方式,累计更新后批量处理,减少渲染次数,提升性能。

有一个控制点,isBatchingUpdates,在React的声明周期事件和合成事件中,可以拿到isBatchingUpdates的控制权,把isBatchingUpdates的值变为true,将状态放进队列之中,控制执行节奏,但是在原生事件中,如setTimeout,addEventListen,setIinterval中,不能拿到isBatchingUpdates的控制点,不走合并操作,所以还是为false,就执行同步更新

一般情况为异步的目的就是优化,减少渲染,但是react官方团队补充了两点,第一点是为了保持内部的统一性,因为props不是同步更新,第二点是启用并发更新完成异步渲染。

更新队列是核心关键点。

虚拟dom的工作原理是什么?

为什么要用虚拟dom?

起源是facebook简化前端开发,以及避免xss攻击。

react.createElement返回的结果应该是一个Object,有一个child

react有两个函数,一个是diff,一个是渲染函数,这也是为什么要引入两个库,react引入的是diff函数,react-dom引入的是渲染函数。react主要工作是组件实现、更新调度,reactDOM提供了网页渲染的基础。

虚拟dom的优势,性能优越,避免xss,可跨平台(只要加上react-native提供native层元素就可以)

虚拟dom比真实dom慢,但是直接操作大量虚拟dom会页面性能下降,所以基于虚拟dom和diff处理可以降低dom操作的范围,提高性能。首次和微量的时候虚拟dom是比较慢的。虚拟dom内部确保字符转义,但是也不是百分比安全,因为有一个api跳过了转义。

也不是没有虚拟dom就不可以跨平台,nativeScript没有虚拟dom层,他是通过提供兼容原生api的js api实现跨平台,但是虚拟dom跨平台的成本更低。

缺点就是虚拟dom占用比较高。

image.png

react的虚拟算法有何不同?

image.png

更新之后生成一个patch,根据patch操作真实dom。

遍历算法 react采用的是深度优先遍历,因为广度优先遍历可能导致生命周期错乱。

优化策略 传统的diff算法时间复杂度是n3,react是n,采用的方法是分治,分而治之。

策略一:忽略跨层级比对,提高比对效率,同一层节点比较,如果不存在直接不比较了

策略二:如果组件的class一致,则默认相似结构,否则不相似

策略三:同一层级子节点,通过key来比较

react16新增的fiber

Fiber机制下节点与树分别采用FiberNode 与 FiberTree进行重构

FiberNode采用双链表结构,可以直接找到兄弟节点和子节点(这也是为什么可以随时更新和暂停),FiberTree是通过FiberNode构建的树

Fiber 机制下整个更新过程由current 与 worklnProgress两株树

双缓冲完成,worklnProgress更新完之后,直接改变current的指针,直接抛弃之前的树。

vue3前期也引入了事件切片,后来因为效果不明显删除了

image.png

如何解释react的渲染流程?

事务 不可再分 事务通过调度

stack reconciler

react15 渲染的整体策略是递归 并通过事务建立react与虚拟dom的联系并完成递归

fiber reconciler

fiber指一种最轻量化的线程,协调调度

协作式多任务模式,基于循环遍历计算diff

image.png

react的渲染异常会造成什么后果?

image.png

如何分析和调优性能瓶颈?

FCP

首次绘制内容的耗时

TTI

页面可以交互的时间(骨架屏) 懒加载和异步加载实现,让用户先用起来

核心内容:在react中同步加载

非核心内容:采取异步加载的方式延迟加载

内容中的图片:采用懒加载的方式避免占用网络资源

Page Load

页面完整加载时间

也可以通过异步加载的形式完成

异步加载主要由webpack打包common chunk与异步组件的方式完成

FPS 帧率

主要代表卡顿的情况

在react中引起卡顿的主要原因有长列表和重渲染

静态资源

api请求率

对于静态资源来说能用cdn就用cdn,能大幅提升静态资源的成功率
cdn(静态资源加速)

image.png

如何避免重复渲染?

TP99是指满足百分之99的请求需要花的最短时间

长列表 虚拟滚动解决

**重复渲染 **还没有比较完美的解决方案

image.png

React Hook的使用有什么限制?

为什么不能在if函数里面调用setState? 可能会造成数组的取值错位,所以循环,嵌套不能使用,当然实际上不是数组,是链表。

image.png

useEffect和useLayoutEffect有什么区别?

相同点

他们使用的是同一个签名,在源码中他们调用的是同一个函数。这两者都是用于处理副作用,这些副作用包括改变DOM,设置订阅,操作定时器等

不同点

在源码中,useEffect先调用了mountEffect,再调用mountEffectImpl useLayoutEffect先调用mountEffectImgl再调用mountEffect。

既然内部调用的都是同一个函数,为什么会有这样的区别呢?这就要从hooks原理去理解

hooks设计原理

所有的hooks都导入到dispatcher对象中,在调用hooks时,会调用dispatcher对应的hook函数,所有的hook是会按照顺序存入hook队列中,这样调用就知道当前的hook对应哪个fiber了。

image.png

image.png

标记为HookLayout的effect会在所有的DOM变更之后同步调用,所以可以使用它来读取DOM布局并同步触发重渲染,计算量较大的耗时任务必然会造成阻塞,所以就需根据实际情况酌情考虑。

react官方给出的答案是一般情况就用useEffect,如果出现了闪烁或者dom操作之后导致的页面渲染问题再使用useLayoutEffect。

image.png

react的常用工具库

分类:

由第三方的react-app-rewired 库对create-react-app提供拓展能力

image.png

image.png