1.11 setState到底是异步还是同步
有时表现出异步,有时表现出同步
setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
setState 的异步并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback)中的 callback 拿到更新后的结果。
setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
1.12 redux异步中间件之间的优劣?
redux-thunk优点:
体积小: redux-thunk的实现方式很简单,只有不到20行代码
使用简单: redux-thunk没有引入像redux-saga或者redux-observable额外的范式,上手简单
redux-thunk缺陷:
样板代码过多: 与redux本身一样,通常一个请求需要大量的代码,而且很多都是重复性质的
耦合严重: 异步操作与redux的action偶合在一起,不方便管理
功能孱弱: 有一些实际开发中常用的功能需要自己进行封装
redux-saga优点:
异步解耦: 异步操作被被转移到单独 saga.js 中,不再是掺杂在 action.js 或 component.js 中
action摆脱thunk function: dispatch 的参数依然是一个纯粹的 action (FSA),而不是充满 “黑魔法” thunk function
异常处理: 受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过try/catch 语法直接捕获处理
功能强大: redux-saga提供了大量的Saga 辅助函数和Effect 创建器供开发者使用,开发者无须封装或者简单封装即可使用
灵活: redux-saga可以将多个Saga可以串行/并行组合起来,形成一个非常实用的异步flow
易测试,提供了各种case的测试方案,包括mock task,分支覆盖等等
redux-saga缺陷:
额外的学习成本: redux-saga不仅在使用难以理解的 generator function,而且有数十个API,学习成本远超redux-thunk,最重要的是你的额外学习成本是只服务于这个库的,与redux-observable不同,redux-observable虽然也有额外学习成本但是背后是rxjs和一整套思想
体积庞大: 体积略大,代码近2000行,min版25KB左右
功能过剩: 实际上并发控制等功能很难用到,但是我们依然需要引入这些代码
ts支持不友好: yield无法返回TS类型
redux-observable优点:
功能最强: 由于背靠rxjs这个强大的响应式编程的库,借助rxjs的操作符,你可以几乎做任何你能想到的异步处理
背靠rxjs: 由于有rxjs的加持,如果你已经学习了rxjs,redux-observable的学习成本并不高,而且随着
rxjs的升级redux-observable也会变得更强大
redux-observable缺陷:
学习成本奇高: 如果你不会rxjs,则需要额外学习两个复杂的库
社区一般: redux-observable的下载量只有redux-saga的1/5,社区也不够活跃,在复杂异步流中间件
这个层面redux-saga仍处于领导地位
1.13 state 和 props 区别是啥?
props和state是普通的 JS 对象。虽然它们都包含影响渲染输出的信息,但是它们在组件方面的功能是不同的。即
- state 是组件自己管理数据,控制自己的状态,可变
- props 是外部传入的数据参数,不可变;
- 没有state的叫做无状态组件,有state的叫做有状态组件;
- 多用 props,少用 state,也就是多写无状态组件。
1.14 当调用setState时,React render 是如何工作的?
虚拟 DOM 渲染:当render方法被调用时,它返回一个新的组件的虚拟 DOM 结构。当调用setState()时,render会被再次调用,因为默认情况下shouldComponentUpdate总是返回true,所以默认情况下React 是没有优化的。
原生 DOM 渲染:React 只会在虚拟DOM中修改真实DOM节点,而且修改的次数非常少——这是很棒的React特性,它优化了真实DOM的变化,使React变得更快。
1.15 hooks
Hooks简介
React的组件创建方式,一种是类组件,一种是纯函数组件
- 纯函数组件没有状态
- 纯函数组件没有生命周期
- 纯函数组件没有this
使用Hooks的优点:
- 告别难以理解的Class( this 和 生命周期 的痛点)
- 解决业务逻辑难以拆分的问题
- 使状态逻辑复用变得简单可行
- 函数组件从设计思想上来看更加契合React的理念
Hooks并非万能:
- Hooks暂时还不能完全的为函数组件补齐类组件地能力(如生命周期的getSnapshotBeforeUpdate、componentDidCatch方法暂时还未实现)
- 将类组件的复杂变成函数组件的轻量,可能使用者并不能很好地消化这种复杂
- Hooks在使用层面有着严格地规则约束
Hook函数(9种)
- useState():状态钩子
- useContext():共享状态钩子
- useEffect():副作用钩子
- useReducer():Action钩子
- userRefef():Ref Hook可以在函数组件中存储、查找组件内的标签或任意其它数据
- useMemo(): 主要用来解决使用React hooks产生的无用渲染的性能问题
- useCallback(): 主要是为了性能的优化
- useLayoutEffect() :和useEffect相同,都是用来执行副作用,但是它会在所有的DOM变更之后同步调用effect。useLayoutEffect和useEffect最大的区别就是一个是同步,一个是异步。
- useImperativeHandle(): 可以在使用 ref 时自定义暴露给父组件的实例值。
自定义Hooks
自定义 Hooks:是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook
自定义Hooks:可以封装状态,能够更好的实现状态共享
打包工具
1.1 前端为什么要进行打包和构建
- 体积更小(Tree-Shaking、压缩、合并),加载更快
- 编译高级语言和语法(TS,ES6+,模块化,scss)
- 兼容性和错误检查(Polyfill、postcss、eslint)
- 统一、高效的开发环境
- 统一的构建流程和产出标准
- 集成公司构建规范(提测、上线等)
1.2 如何提高webpack的构建速度
- 优化babel-loader 开启缓存
- 使用module中的Noparse,不去解析属性值代表的库的依赖(需要在webpack.config.js的module节点添加noParse配置,使用|分割)
- 可以使用webpack内置插件lgnorePlugin插件(作用:忽略第三方包指定目录,让这些指定目录不要被打包进去)
- 使用happyPack多进程打包(需要下载)
- 使用parallelUgligyPlugin多进程压缩js(默认使用uglifyJs来压缩代码,单进程)
1.3 代码分割的本质是什么?
- 代码分割的本质就是在源代码直接上线和达成唯一脚本main.bundle.js这两种极端方案之间的一种更适合实际场景的中间状态。
- 源码直接上线:虽然过程可控,但是http请求多,性能开销大。
- 打包成唯一脚本:服务器压力小,但是页面空白期长,用户体验不好。
1.4webpack的基本功能有哪些?
名称 | 内容 |
---|---|
代码转换 | typescript编译成JavaScript、scss编辑成css |
文件优化 | 压缩JavaScript、css、html、压缩合并图片 |
代码分割 | 提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载 |
模块合并 | 采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件 |
自动刷新 | 监听本地源代码的变化,自动构建,刷新浏览器 |
代码校验 | 在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过 |
自动发布 | 更新完代码后,自动构建出线上发布代码并传输给发布系统 |
1.5 文件指纹是什么?
文件指纹是打包之后的文件后缀名。
chunkhash:和webpack打包的chunk有关,不同的entry会生出不同的chunkhash。
js后缀名:filename:'\[name]\[chunkhash:8].js',
contenthash:根据文件内容来定义hash,文件内容不变,则其不变。
css后缀名:filename:'\[name]\[contenthash:8].css',
hash:和整个项目构建有关,只要项目文件有修改,整个构建的hash值就会修改。
img后缀名:name:'\[name]\[hash:8].\[ext]'
1.6 为什么说vite比webpack更快?
- webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。
- vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。
- vite在启动的时候不需要打包,意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。
- 当浏览器请求某个模块时,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂、模块越多,vite的优势越明显。
- 在HMR方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。
- 当需要打包到生产环境时,vite使用传统的rollup进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的是ES Module,因此在代码中不可以使用CommonJS
1.7 vite工作原理
vite是一种现代化的前端开发工具,其工作原理主要分为以下几个步骤
- 基于ESM构建:Vite作为一款基于ESM的前端构建工具,通过ES模块提供的动态导入功能来实现快速的开发和构建。
- 零配置开发:Vite允许开发者在不需要任何配置的情况下启动一个服务器进行开发,通过对文件的即时编译和缓存,来提高开发效率。
- 基于浏览器原生的ESM加载:Vite将所有文件视为ES模块,并且在开发时会直接从源代码加载模块,而不是打包后的文件,从而可以避免打包的过程带来的性能损失。
- 按需编译和缓存:Vite会按需编译和缓存依赖项,只有当需要更新时才会进行重新编译,缓存让开发者可以忽略无关的代码变化。
- 插件化架构:Vite的插件化架构可以方便地扩展其功能,例如使用插件来处理CSS、处理图片、压缩源代码等等。
1.8 vite核心原理
- Vite其核心原理是利用浏览器现在已经支持ES6的import,碰见import就会发送一个HTTP请求去加载文件。
- Vite整个过程中没有对文件进行打包编译,做到了真正的按需加载,所以其运行速度比原始的webpack开发编译速度快出许多!
特点:
- 快速的冷启动:基于Esbuild的依赖进行预编译优化 (Esbuild 打包速度太快了,比类似的工具快10~100倍 )
- 增加缓存策略:源码模块使用协商缓存,依赖模块使用强缓;因此一旦被缓存它们将不需要再次请求
- HMR(热更新):当修改代码时,HMR 能够在不刷新页面的情况下,把页面中发生变化的模块,替换成新的模块,同时不影响其他模块的正常运作
- 基于 Rollup 打包:生产环境下由于esbuild对css和代码分割并使用Rollup进行打包
- 高效的热更新:基于ESM实现,同时利用HTTP头来加速整个页面的重新加载
1.9 Vite 冷启动为什么快
vite 运行 Dev 命令后只做了两件事情
- 启动本地服务器并注册了一些中间件
- 使用 ESbuild 预构建模块
1.10 vite生产环境缺点
Vite 在是直接把转化后的 es module 的JavaScript,扔给浏览器,让浏览器根据依赖关系,自己去加载依赖
-
那有人就会说了,那放到 生产环境 时,是不是可以不打包,直接在开个 Vite 服务就行,反正浏览器会自己去根据依赖关系去自己加载依赖。答案是不行的,为啥呢:
1、你代码是放在服务器的,过多的浏览器加载依赖肯定会引起更多的网络请求
2、为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割、CSS处理,这些优化操作,目前 esbuild 还不怎么完善
1.11 vite和webpack优缺点对比
- 更快的启动时间和更新速度
- 更好的开发体验:自动打开浏览器、自动刷新页面 配置简单。
- 不需要过多的配置就可以搭建基本的开发环境 更少的依赖。
- 借助原生的ES模块
- 避免了过多的额外依赖
缺点:
- vite的构建技术主要用于中小型项目,对于大型项目的支持不如webpack
- vite主要是针对vue3的单页面应用,对于多页面应用、ssr应用、自定义流程应用不如webpack
- 开发环境首屏加载慢,懒加载慢
- vite由于基于原生ES模块,不支持commonJs;webpack关注兼容性,vite关注浏览器端的开发体验,vite的生态还不如webpack