面试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
改变stateReact.Hooks : useStatethis.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

组件通信

  1. props
  2. 实例方法
  • 在父组件中可以用 refs 引用子组件,之后就可以调用子组件的实例方法了。这是另一种从父组件访问子组件的方式。
  1. 回调函数
  2. 事件冒泡
  3. 父组件
  4. 观察者模式
  5. 全局变量
  6. Context
  7. redux

react性能优化

  1. Code Splitting
  2. shouldComponentUpdate避免重复渲染
  3. 使用不可突变数据结构
  4. 组件尽可能的进行拆分、解耦
  5. 列表类组件优化
  6. bind函数优化
  7. 不要滥用props
  8. ReactDOMServer进行服务端渲染组件
贡献者: mankueng