React
React 与 Vue 的区别 与 优劣
- 模板语法不同:Vue 使用基于 HTML 的模板语法,而 React 使用 JSX 语法,它允许开发者在 JavaScript 中编写类似于 HTML 的代码。
- 状态管理方式不同:Vue 提供了 Vuex 状态管理库,而 React 则提供了多个状态管理库,如 Redux、MobX 等。React 的状态管理更加灵活,可以根据项目的需要选择不同的状态管理库。
- 组件间通信方式不同:Vue 使用
props
和事件
来进行父子组件之间的通信,而 React 则使用props
和回调函数
进行组件之间的通信。 - 数据绑定方式不同:
Vue 使用双向数据绑定
,即数据的变化将自动更新视图,而React 则使用单向数据流
,即数据只能从父组件向子组件传递
。 - 渲染方式不同:
Vue 使用模板渲染
,在模板中定义组件和数据绑定,即将模板转化为渲染函数,再进行渲染。而 React 则使用 JavaScript 渲染,将组件和数据绑定封装成一个 JavaScript 函数,通过调用该函数来渲染组件。 - 更新策略不同:Vue 的虚拟 DOM 是基于 Snabbdom 实现的,Vue 的虚拟 DOM 在进行更新时,会将整个组件的虚拟 DOM 树进行比较,从而找出需要更新的部分,并将这些部分进行重新渲染。而 React Fiber 则采用了增量更新的策略,它可以在每次更新时,根据优先级和时间片划分等因素,决定哪些任务需要先执行,哪些任务可以暂缓执行,从而提高了性能。
- 对异步更新的支持不同:Vue 的虚拟 DOM 支持异步更新,但是它并没有像 React Fiber 那样针对异步更新进行优化。而 React Fiber 则专门针对异步更新进行了优化,通过时间片划分、任务优先级等机制,可以将更新任务分成多个小块,从而避免了长时间的阻塞,提高了用户体验。
React 性能优化手段
- shouldComponentUpdate
- memo
- getDerviedStateFromProps
- 使用 Fragment
- v-for 使用正确的 key
- 拆分尽可能小的可复用组件,ErrorBoundary
- 使用 React.lazy 和 React.Suspense 延迟加载不需要立马使用的组件
React Fiber 简介
- React Fiber 节点:是 React 中用于实现
增量更新和异步渲染的一种数据结构
,它包含了组件的状态、属性、子节点等信息,同时还包含了与渲染有关的信息,如渲染优先级、更新状态等。在 React Fiber 中,每个组件都对应一个 Fiber 节点。 - Fiber 树:所有的 Fiber 节点按照组件树的结构,形成了一个 Fiber 树。它是一个树状结构,每个节点都有指向其子节点、兄弟节点和父节点的指针。
- 工作单元:React Fiber 中的工作单元是一个包含
任务的单元
,它可以是一个组件的更新、一个新的组件的挂载、一个组件的卸载等。每个工作单元都有一个对应的 Fiber 节点。 - 优先级:React Fiber 中的优先级用于决定哪些任务需要先执行,哪些任务可以暂缓执行。优先级分为以下几个等级:同步、批量更新、动画、事件等。
- 时间切片:React Fiber 使用时间切片来划分任务的执行时间,每个时间片都有固定的时间长度,当一个任务执行的时间超过了时间片的长度时,就会被中断,并将剩余的任务留到下一个时间片中执行。(为了解决 CPU 的瓶颈与 IO 的瓶颈。实现上,则需要将同步的更新变为可中断的异步更新。
- State Hook 节点:State Hook 用于在
函数组件中添加局部状态
。它返回一个状态变量和一个更新函数,通过调用更新函数,可以更新状态变量。每个 State Hook 节点都有一个唯一的 ID,它用于标识状态变量和更新函数。 - Effect Hook 节点:Effect Hook 用于在
函数组件中添加副作用
。它可以在组件挂载
、更新
和卸载
时执行一些副作用操作,如访问 DOM、发送网络请求等。每个 Effect Hook 节点都有一个依赖数组,它用于指定副作用的触发条件。
在 React Fiber 中,每个组件都对应一个 Fiber 节点,这个节点中包含了组件的状态和属性等信息。当组件使用 Effect Hook 添加副作用时,会创建一个 Effect Hook 节点,这个节点中包含了副作用的回调函数和依赖数组等信息。当组件更新时,React Fiber 会遍历 Fiber 树,找到与更新相关的 Fiber 节点
和 Effect Hook 节点
,并执行相应的更新操作和副作用操作。
什么是 Fiber 架构,解决了什么问题
在 React16 以前,React 更新是通过树的深度优先遍历完成的,遍历是不能中断的,当树的层级深就会产生栈的层级过深,页面渲染速度变慢的问题,为了解决这个问题引入了 fiber,React fiber 就是虚拟 DOM,它是一个链表结构,返回了 return、children、siblings,分别代表父 fiber,子 fiber 和兄弟 fiber,随时可中断
diff 算法中的概念
为了防止概念混淆,这里再强调下
INFO
一个 DOM 节点
在某一时刻最多会有 4 个节点和他相关。
- current Fiber。如果该
DOM 节点
已在页面中,current Fiber 代表该DOM 节点
对应的 Fiber 节点。 - workInProgress Fiber。如果该
DOM 节点
将在本次更新中渲染到页面中,workInProgress Fiber 代表该DOM 节点
对应的 Fiber 节点。 DOM 节点
本身。- JSX 对象。即 ClassComponent 的 render 方法的返回结果,或 FunctionComponent 的调用结果。JSX 对象中包含描述
DOM 节点
的信息。
Diff 算法的本质是对比 1 和 4,生成 2。
React HOOk 闭包陷阱
React Hook 闭包陷阱是指在使用 React Hook 时,由于闭包的特性,可能会导致某些 Hook 的状态无法得到正确地更新,进而导致应用程序出现问题。即:就是 useEffect 等 hook 里用到了某个 state,但是没有加到 deps 数组里,这样导致 state 变了却没有执行新传入的函数,依然引用的之前的 state。所以要正确设置 deps 数组
hook 的实现原理
- hooks 的原理:hooks 就是在 fiber 节点上存放了 memorizedState 链表,每个 hook 都从对应的链表元素上存取自己的值,每个 hook 是存取各自的那个 memorizedState 来完成自己的逻辑
- hook 链表有创建和更新两个阶段,也就是 mount 和 update,第一次走 mount 创建链表,后面都走 update。
- update 时会取出新传入的 deps 和之前存在 memorizedState 的 deps 做对比,如果没有变,就直接用之前传入的那个函数,否则才会用新的函数。
常见的闭包陷阱与解决方案
- 在 useEffect 中使用
闭包变量
时,可能会导致 useEffect 钩子无法获取到最新的状态值,进而导致应用程序出现问题。所有一般是使用 useEffect 的依赖项
来触发更新 或 将状态值作为useEffect 的参数
传递进去 - 在 useState 中
使用函数更新状态
时,由于闭包的特性,可能会导致某些状态值无法得到正确地更新。为了避免这个问题,可以使用useState 的回调函数
来更新状态,或者使用useReducer
来代替 useState。 - 在 useMemo 和 useCallback 中使用
闭包变量
时,可能会导致缓存的值无法得到正确地更新
,进而导致应用程序出现问题。为了避免这个问题,可以将需要使用的状态值作为依赖项
传递进去,或者使用useRef
来代替 useState。
试验阶段的 React API 的例子
- Suspense
<SuspenseList revealOrder="forwards">
<Suspense fallback={'Loading...'}>
<ProfilePicture id={1} />
</Suspense>
<Suspense fallback={'Loading...'}>
<ProfilePicture id={2} />
</Suspense>
<Suspense fallback={'Loading...'}>
<ProfilePicture id={3} />
</Suspense>
...
</SuspenseList>
- useDeferredValue
- useTransition
const [isPending, startTransition] = useTransition()
React 大概得运行流程:
- 初始化:React 通过 ReactDOM.render() 方法将组件渲染到指定的 DOM 节点上。在这个过程中,React 会先创建虚拟 DOM,然后将虚拟 DOM 转换为真实的 DOM 并插入到指定节点上。
- 渲染阶段:React 会遍历组件树,根据组件的属性和状态生成对应的虚拟 DOM。在这个过程中,如果组件的属性或状态发生了变化,React 会重新生成虚拟 DOM。
- 协调阶段:在渲染阶段结束后,React 会进入协调阶段。在这个阶段,React 会比较前后两次生成的虚拟 DOM,并计算出最小的更新量。然后,React 会将这些更新应用到真实的 DOM 上,以更新页面内容。
- 生命周期:React 组件具有生命周期,包括挂载、更新和卸载三个阶段。在不同的生命周期阶段,React 会调用不同的生命周期方法,以便开发者在这些方法中执行一些自定义的操作,如初始化状态、发送网络请求、清理资源等。
- 事件处理:React 采用了合成事件的方式处理用户事件。在事件处理过程中,React 会将事件封装成 SyntheticEvent 对象,并提供一些方法和属性,以便开发者在事件处理函数中获取相关信息。
ReactDOM.render-->ReactDOMBlockingRoot-->updateContainer(更新容器,react-reconciler 包中,它串联了 react-dom 与 react-reconciler)-->scheduleUpdateOnFiber
创建 ReactDOMRoot 实例:ReactDOM.createRoot-->(原型上有 render,unmount)-->createRootImpl(创建 fiberRoot 对象)-->createFiberRoot(创建 React 应用首个 Fiber 对象,fiber.tag = HostRoot,其中 fiber.mode 属性,会与 3 种 RootTag(ConcurrentRoot,BlockingRoot,LegacyRoot) 关联起来)-->
创建 ReactDOMBlockingRoot 实例:ReactDOM.createBlockingRoot-->(原型上有 render,unmount)-->createRootImpl(创建 fiberRoot 对象)
注意:fiber 树中所有节点的 mode 都会和 HostRootFiber.mode 一致 (新建的 fiber 节点,其 mode 来源于父节点),所以 HostRootFiber.mode 非常重要,它决定了以后整个 fiber 树构建过程