面试react相关
React 会创建一个虚拟 DOM(virtual DOM)。当一个组件中的状态改变时,React 首先会通过 "diffing" 算法来标记虚拟 DOM 中的改变,第二步是调节(reconciliation),会用 diff 的结果来更新 DOM。
函数式编程
- 函数式编程是一种编程范式, 对过程进行抽象,将数据以输入输出流的方式封装进过程内部, 从而也降低系统的耦合度。
- 纯函数:即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用, 副作用包括但不限于:
- 打印/log
- 发送一个http请求
- DOM查询 可变数据
- 简单一句话, 即只要是与函数外部环境发生交互的都是副作用
vdom和diff
对于 vdom 和 diff,h 函数、vdom 数据结构、patch 函数。只比较同一层级,不跨级比较。tag 不相同,则直接删掉重建,不再深度比较。tag 和 key,两者都相同,则认为是相同节点,不再深度比较
jsx本质
- JSX的本质即React.createElement函数,React.createElement 即 h 函数,返回vnode
- React.createElement的第一个参数是渲染的标签,第二个参数为标签的属性,之后的为子元素
- 第一个参数,可能是组件,也可能是一个html tag(组件和tag区分是大小写,组件名首字母必须要大写)
- 函数返回值是一个vnode
- vnode在通过patch函数进行渲染
合成事件
所有的事件都挂在到document上
event不是原生的,是SyntheticEvent合成事件对象
和Vue事件不同,和DOM事件也不同
DOM事件会冒泡到document上,document会生成一个统一的React event,在合成事件层会派发事件(通过target可以获取到哪个元素触发的事件,找到这个元素所在组件这个)
为什么要合成事件机制?
- 更好的兼容性和跨平台
- 挂在到document,减少内存消耗,避免频繁解绑
- 方便事件的统一管理(如事物机制)
setState和batchUpdate
setState有时异步(普通使用),有时同步(setTimeout, 自己定义的DOM事件)
有时合并(对象形式), 有时不合并(函数形式)
调用了setState之后,newState会被存入pending队列
是否处于batch update机制中
- 处于batch update机制中, 保存组件与dirtyComponent(state被更新的component)中(走异步操作)
- 不处于batch update机制中,遍历所有的dirtyComponent,调用updateCompoennt,更新pending state or props(走同步操作)
如何判断是否处于batch update中,是需要根据isBatchingUpdates进行判断
因此:setState无所谓同步和异步,需要看是否命中batchUpdate机制,是否命中batchUpdate机制的判断就是isBatchingUpdate
那些能命中batchUpdate机制?
- 生命周期(和它调用的函数)
- React中注册的事件(和它调用的函数)
- React可以 “管理” 的入口
哪些不能够命中 batchUpdate 机制
- setTimeout、setInterval 等和它调用的函数
- 自定义的 DOM 事件和它调用的函数
- React 管不到的入口
transaction事务机制
对于 transition事务机制,开始处于 batchUpdate,isBatchUpdates = true,其它任何操作,结束,isBatchUpdates = false
组件渲染和更新过程
渲染过程
- 有了props和state
- render()生成vnode
- patch(elem, vnode)
更新过程
- setState()—> dirtyComponents(可能有子组件)
- 执行render函数,根据新的state和props生成newVnode
- patch(vnode, newVnode)
patch 更新的两个阶段
- reconciliation阶段——执行diff算法,纯JS计算
- commit阶段——将diff结果渲染DOM
可能出现的性能问题(如果不将patch更新分为两个阶段的话可能出现的心梗问题)
- JS 是单线程的,且和 DOM 渲染共用一个线程
- 当组件足够复杂的时候,组件更新时计算和渲染都压力大
- 同时再有 DOM 操作需求,如动画、鼠标拖拽等,将卡顿
fiber
- 将 reconciliation阶段进行任务拆分(但是 commit 无法拆分)
- DOM 需要渲染时暂停更新,空闲时修复
- 如何知道DOM需要渲染呢?是需要借助于window.requestIdleCallback
react优点
- 只需查看 render 函数就会很容易知道一个组件是如何被渲染的
- JSX 的引入,使得组件的代码更加可读,也更容易看懂组件的布局,或者组件之间是如何互相引用的
- 支持服务端渲染,这可以改进 SEO 和性能
- 易于测试
- React 只关注 View 层,所以可以和其它任何框架(如Backbone.js, Angular.js)一起使用
React函数组件和类组件的相同和区别
相同之处
- props不能改变
- 父组件props有变化时,子组件随之而改变
区别
区别 | 函数组件 | 类组件 |
---|---|---|
生命周期 | 无 | 有 |
this | 无 | 有 |
state | 无 | 有 |
改变state | React.Hooks : useState | this.setState() |
性能 | 高(不用实例化) | 低(需要实例化) |
react hooks
优点
- 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护。
- 组件树层级变浅。
- 不用再去考虑 this 的指向问题。
缺点
- 对一些钩子函数不支持。当下 v16.8 的版本中,还无法实现 getSnapshotBeforeUpdate 和 componentDidCatch 这两个在类组件中的生命周期函数。
规则
- 不在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。
- 不在普通的 JavaScript 函数中调用 Hook,在 React 的函数组件或者自定义 Hook 中调用 Hook。
Hook api
useState | 在函数组件中维护自己的状态 |
---|---|
useEffect | 在函数组件中实现生命周期钩子函数 |
useContext | 用来处理多层级传递数据的方式,减少组件嵌套 |
useReducer | 跟react-redux的使用方式一样,算是提供一个 mini 的 Redux 版本 |
useCallback | 获得一个记忆函数,避免在某些情况下重新渲染子组件,用来做性能优化 |
useMemo | 获得一个记忆组件,和useCallback非常类似,它适用于返回确定的值 |
useRef | 生成对 DOM 对象的引用,它是一个真正的引用,而不是把值拷过去 |
useImperativeHandle | 透传ref,用于让父组件获取子组件内的引用 |
useLayoutEffect | 同步执行副作用,在页面完全渲染完成后,操作DOM |
组件通信
- props
- 实例方法
- 在父组件中可以用 refs 引用子组件,之后就可以调用子组件的实例方法了。这是另一种从父组件访问子组件的方式。
- 回调函数
- 事件冒泡
- 父组件
- 观察者模式
- 全局变量
- Context
- redux
react性能优化
- Code Splitting
- shouldComponentUpdate避免重复渲染
- 使用不可突变数据结构
- 组件尽可能的进行拆分、解耦
- 列表类组件优化
- bind函数优化
- 不要滥用props
- ReactDOMServer进行服务端渲染组件