思路
- 讲(概念)说(用途)理(思路)列(优缺点)
- 一句话解释 核心概念 方案对比
- 知道流程(只会用)理解理念核心思路(还可以)有实际应用场景(棒)
- 工程化即标准化,按照规范来做
1、React是什么? => 你对React的理解
- React是一个网页UI框架,通过组件化的方式解决视图开发复用的问题,本质是一个组件化框架。
核心思路
=>- 声明式 => 更直观,便于组合
- 组件化 => 视图拆分与模块复用
- 通用性 => 一次学习,随处编写(靠虚拟dom实现)
2、为什么使用声明式(JSX)
- jsx是javascript的语法扩展,结构类似XML,主要用于声明React元素
- 对比
- 模版 => 引入概念太多
- 模版字符串 => 结构描述复杂,代码提示差
- Json => 代码提示差
3、理解生命周期
componentWillMount()=> 已弃用(在react异步渲染中会造成多次渲染)componentWillUpdate()=> 已弃用 (在react异步渲染中会出现暂停更新渲染)componentWillReceiveProps()=> 已弃用(性能问题,被下面这个生命周期替代)- componentDidmount() 组件加载完做某些请求
- getDerivedStateFromProps() => props改变时更新state(父级组件渲染就会被调用)
- shouldConponentUpdate() 通过对比值来确定是否重新渲染,用于性能优化
- componentWillUnmount() 解除事件绑定,清理定时器等操作
- getDerivedStateFromError() 报错了处理state状态
- componentDidCatch() 捕获错误,上传服务端,不捕获报错了页面会白屏,渲染时的报错,只能componentDidCatch获取
异步请求放在componentDidmount中
4、组件渲染
函数组件没有生命周期
=> 任何情况下都会重新渲染, 可以通过memo优化(memo
:跳过渲染组件的操作,直接复用最后一次渲染的结果)class组件
=> 如不实现shouldConponentUpdate函数,有两种情况会重新渲染(state值改变时,父组件props传入时)PureComponent组件
=> 默认实现shouldConponentUpdate函数,仅在state与state浅比较后有变更才会渲染
5、类组件与函数组件区别
共同点
- 作为组件来说,二者在使用方式和最终呈现效果是一样的
不同点
编程方式
=> 类组件是基于面向对象编程,函数是函数式编程性能优化
=> 类组件依靠shouldConponentUpdate,函数依靠Memo使用场景
=> 现在是一样的,因为函数在hooks的加持下也有生命周期了- React主推函数式组件(why? a、this的模糊性 b、业务逻辑在生命周期中 c、组件代码缺乏标准的拆分模式)
6、如何设计React组件
有状态组件
=> 面向业务、功能丰富、复杂度高、复用性低- 容器组件 => 主要用于获取数据和组合组件、复用性低
- 高阶 => 复用组件逻辑的高级技术
高阶函数
的参数是函数,执行后返回一个函数高阶组件
的参数是组件,执行后返回一个组件函数高阶组件渲染劫持
通过控制render函数修改输出内容,常见形式是显示加载元素(super.render(),获取渲染劫持原本的渲染结果)高阶组件缺陷
丢失静态函数,refs属性不能透传
无状态组件
=> 通用性强,复用率高- 代理组件(如对antd的button再封装,便于以后切换其他ui组件)
- 样式组件(传入样式)
- 布局组件(固定的布局)
7、setState是同步还是异步?
为什么是异步的?
=> 性能优化,减少渲染次数,保持内部一致性,启用并发更新- react通过isBatchingUpdates(值为true/false)来控制是否是异步的
异步场景
=> react可以控制的,将多个操作放在一起更新同步场景
=> react无法控制的,如原生事件(setTimeOut,addEventListener,setInInterval等)
8、跨组件通信 流行的做法是组件所有的能力都能通过props来控制
父传子
=> props传递state子传父
=> 回调,实例 ref(不推荐)兄弟传值
=> 父组件中转七大姑八大姨传值
=> 1、使用Context Api,2、使用全局变量 3、状态管理mobx、redux
9、React状态管理
- Mobx => 通过监听数据属性的方式触发渲染,响应式
- Flux => 使用单向数据流来组合react组件的应用架构,项目结构简化了视图层的设计,明确了分工,数据与业务逻辑统一存放管理,使在大型架构项目中更容易管理,维护代码
- Redux 是javascript的状态管理容器,他提供的状态管理实现了撤销、重做、实时编辑、时间旅行,优点:结果可预测,代码结构严格易维护,模块分离清晰
- store 单一数据源,整个应用的state存在store中(UI状态)
- Reducer 描述Action如何改变state,需要编写Reducer(业务逻辑)
- Action 唯一可改变state的方法(事件)
- Action -> Reducer -> store -> View
10、virtual dom(虚拟dom) react会初始化一个虚拟dom树,在状态变更后,触发虚拟dom的更改,再以此修改真实dom
- 优点:性能好、防止xss攻击(窃取用户信息),可跨平台
- 缺点:内存占用高(存了真实和虚拟两个dom),无法进行极致优化
- react有两个函数
- diff函数 计算变更前后虚拟dom的差异
- 渲染函数 渲染dom树或差异点
11、diff算法
是什么?
=> diff算法是指生成更新补丁的方式干什么?
=> 主要应用于虚拟dom树变化,更新真实dom流程
=> 真实dom生成虚拟dom -> 对比新旧虚拟dom得到差异dom -> 更新真实dom -> 更新视图更新时机
=> 发生在setState、hooks调用等操作遍历算法
(同步的,不可以被打断) => 深度优先遍历算法 先从左子树纵向遍历,回溯再遍历右(复杂度为o(n^3))优化策略
=> 优化o(n^3)至 o(n)- 树对比 => 对同一层节点比较,如果发现节点不存在则删除节点及其子节点
- 组件对比 => 对组件类型进行比较,是就进行树比对,不是就放入补丁
- 元素对比 => 同一层级的子节点可以通过标记key的方式进行对比
优化代码
- 根据diff算法的设计原则,应尽量避免跨层级节点移动。
- 通过设置唯一 key 进行优化,尽量减少组件层级深度。因为过深的层级会加深遍历深度,带来性能问题。
- 设置shouldComponentUpdate或者React.pureComponet减少 diff 次数。
12、React 16 Fiber架构
为什么有fiber新架构
=> 原老架构Stack Reconcilers有主线程的超时占用问题, 因为Stack Reconcilers是一个同步的递归过程,意味着一旦更新开始,根本停不下来。会造成卡顿。- Reconcilers => 通过抽离公共函数与diff算法使声明式渲染、自定义组件、state、生命周期等实现跨平台工作。
Fiber架构核心
:(可中断,可恢复,优先级,异步)fiber更新任务都会被赋予一个优先级,新任务来临时会检查优先级,如果比现有更高,会中断现有,执行高优先级,之后再恢复原执行
13、React的渲染流程 渲染过程分为React16以前和16以后,
- 16以
前
采用的是stack,渲染流程包括挂载、更新、卸载,stack是一个同步递归的过程 - 16以
后
采用的是fiber,渲染过程包括render和commit,fiber是按照优先级策略,多任务模式,在动画,手势中性能提升明显
14、React渲染异常的后果
白屏
出现了javascript错误(为什么白屏:因为react内部状态被破坏,导致应用崩溃)如何处理
捕获错误- getDerivedStateFromError() 报错了处理state状态
- componentDidCatch()
- 跳转到兜底页面(报警)
- 预防(工程化,按标准来)
15、性能优化
lighthouse
收集性能数据(google分析网页性能工具)performance
google推出主要用于查询javascript执行栈的耗时时间,确认函数卡顿点优化
- loading
- 骨架屏
- CDN(利用最靠近用户的服务器获取静态资源)
- ssr(服务端渲染,就是在服务器端将对页面进行渲染生成html文件,将html页面传递给浏览器)
- 核心同步加载
- 非核心异步加载
- 图片使用懒加载
16、避免重复渲染
长列表
=> 使用虚拟滚动可以很好的优化重复渲染
- 使用purecomponent
- 使用memo
- 使用shouldcomponentupdate
- 合理的使用箭头函数,因为箭头函数会生存一个新函数,导致重新渲染
17、如何提升代码可维护性
- 可分析 => 能快速定位线上问题,工具可以使用ESLint,Sentry
- 可改变 => 代码易于迭代
- 稳定性 => 避免修改代码对线上的影响
- 易测试 => 易于发现代码的潜在问题
- 标准化 => 前端工程化,按标准写代码
18、hooks使用限制
- 不要在循环、条件或者嵌套函数中调用hooks
- hooks是基于数组实现的,在调用时按顺序加入数组中,如果使用循环条件等,容易导致数组取值错位
- 在react的函数组件中调用hooks
- 为什么限制
- 组件之间难以复用逻辑(只能使用高阶组件,状态管理)
- 生命周期与业务逻辑耦合
19、useEffect和useLayoutEffect区别
共同点
- 都是用于处理副作用(改变dom,设置订阅,操作定时器等)
不同点
- 大多数情况用useEffect(异步),如果你的页面因代码引起了闪烁使用useLayoutEffect,直接操作dom或引起dom样式更新也使用useLayoutEffect(在所有dom变更后最后同步渲染useLayoutEffect)
20、hooks设计模式
常见问题
-
获取上一轮props或state的不方便(使用useRef记录上一次)
-
以前是通过生命周期来思考逻辑,hooks是通过副作用
-
使用useMemo可以更精细化控制,因为memo并不能控制组件内部共享状态的变化
-
由于函数组件每次渲染都会重新执行,常量应该放在函数外部,避免每次创建,如果常量是函数,且需要使用组件内部的变量,那么要使用useCallback缓存函数
-
useeffect第二个参数是浅比较,尽量不要传引用类型,会判断为不相等
-
自定义hook (抽离公共逻辑)
export default function useCountDown (initCount = 10) {
const [count, setCount] = useState(initCount)
useEffect(() => {
let timeChange = setTimeout(() => {
setCount(count - 1)
}, 1000)
}, [count]
)
return {count}
}
//使用
const {count} = useCountDown()
21、React-Router
- hash和history
- 提供Router、MemoryRouter(app)容器
- 提供路由匹配功能Route、Redirect、Switch
- 提供react-router-dom的Link、NavLink、Deeplinking(app页面跳转)
如何防止用户在编写内容时误触返回? 使用react-router-dom提供的useHistory钩子来判断是返回还是前进history.action === 'PUSH'/'POP'
22、React用到的工具
- 创建项目 => create-react-app(简单不易扩展,可用react-app-rewired扩展)
- 路由 => React-Router
- 样式 => css、scss、less
- 基础组件 => Antd
- 功能组件 => monent、echarts、axios、react-quill(富文本)、react-dnd(实现拖拽)、video-react(视频播放)、react-pdf-viewer(预览pdf)、
- 状态管理 => mobx、redux
- 构建工具 => webpack、esbuild、vite
- 代码规范检查 => EsLint
- 发布 => 可以使用s3-plugin-webpack完成文件上传到cdn
23、启发式算法
React16
的expirationTimes模型
只能区分是否>=expirationTimes
决定节点是否更新。React17
的lanes模型
可以选定一个更新区间
,并且动态的向区间
中增减优先级
,可以处理更细粒度的更新。