说说React render方法的原理?在什么时候会被触发?
render
函数里面可以编写JSX
,转化成createElement
这种形式,用于生成虚拟DOM
,最终转化成真实DOM
在 React
中,类组件只要执行了 setState
方法,就一定会触发 render
函数执行,函数组件使用useState
更改状态不一定导致重新render
组件的 props
改变了,不一定触发 render
函数的执行,但是如果 props
的值来自于父组件或者祖先组件的 state
在这种情况下,父组件或者祖先组件的 state
发生了改变,就会导致子组件的重新渲染
所以,一旦执行了setState
就会执行render
方法,useState
会判断当前值有无发生改变确定是否执行render
方法,一旦父组件发生渲染,子组件也会渲染
说说React事件和原生事件的执行顺序
为什么要有合成事件
- 在传统的事件里,不同的浏览器需要兼容不同的写法,在合成事件中
React
提供统一的事件对象,抹平了浏览器的兼容性差异 -
React
通过顶层监听的形式,通过事件委托的方式来统一管理所有的事件,可以在事件上区分事件优先级,优化用户体验
React
在合成事件上对于16
版本和17
版本的合成事件有很大不同,我们也会简单聊聊区别。
总结
-
16
版本先执行原生事件,当冒泡到document
时,统一执行合成事件, -
17
版本在原生事件执行前先执行合成事件捕获阶段,原生事件执行完毕执行冒泡阶段的合成事件,通过根节点来管理所有的事件
原生的阻止事件流会阻断合成事件的执行,合成事件阻止后也会影响到后续的原生执行
说说对受控组件和非受控组件的理解,以及应用场景?
-
受控组件:
- 受控组件是指组件的状态由 React 组件的 state 控制,并通过 props 传递给子组件,子组件通过回调函数来更新父组件的状态。
- 在受控组件中,数据流是单向的,父组件通过 props 将状态传递给子组件,子组件通过回调函数将事件传递回父组件。
- 应用场景:适用于需要实时控制和监控组件状态变化的情况,例如表单输入框、复选框等需要即时响应用户输入的组件。
-
非受控组件:
- 非受控组件是指组件的状态不受 React state 控制,而是由 DOM 元素本身维护状态,React 只负责渲染。
- 在非受控组件中,组件的状态由 DOM 元素自身管理,React 只负责初始化组件和处理生命周期方法。
- 应用场景:适用于简单的交互组件,不需要频繁地进行状态管理和更新的情况,可以减少一些状态管理的代码量,例如一些仅需初始化时设置数值的组件。
说说你对immutable的理解?如何应用在react项目中?
Immutable 是指数据不可变,一旦创建之后就不能被修改。在 JavaScript 中,原始类型(如字符串、数字)是不可变的,而对象和数组是可变的。
-
性能优化:使用 Immutable 数据结构可以帮助 React 进行浅比较,提高组件更新时的性能。通过比较引用而不是深层次比较对象内容,可以减少不必要的重新渲染。
-
简化状态管理:不可变数据结构可以减少状态管理的复杂性。在 Redux 或者其他状态管理工具中,配合 Immutable 数据结构可以更容易地追踪状态的变化,并且可以避免直接修改状态而导致的副作用。
-
防止意外修改:不可变数据结构可以减少由于在多处修改同一数据而引发的 bug。在 React 组件中,通过使用 Immutable 数据结构,可以更容易地追踪数据的变化,从而减少意外的状态修改。
在 React 项目中,可以通过以下方式应用 Immutable 数据结构:
- 使用 Immutable.js:Immutable.js 是一个专门为 JavaScript 提供不可变数据结构的库,可以使用其中的 Map、List 等数据结构来管理 React 组件的状态。
- 结合 Redux 或者 Context API:在使用 Redux 或者 React 的 Context API 进行状态管理时,可以结合 Immutable 数据结构来管理状态,以提高状态管理的效率和可维护性。
说说React Jsx转换成真实DOM过程?
-
JSX 编写阶段:
- 在编写 React 组件时,可以使用 JSX 语法来描述组件的结构和内容。
-
JSX 转换阶段:
- 在构建过程中,使用 Babel 或其他工具将 JSX 转换成普通的 JavaScript 代码。
- JSX 中的标签会被转换成对应的 React.createElement 函数调用。
- JSX 中的属性会被转换成一个对象,作为 createElement 函数的第二个参数传递。
- JSX 中的子元素会被转换成 createElement 函数的额外参数,作为子元素传递。
-
React.createElement 函数调用:
- 在转换后的代码中,每个 JSX 标签都会被转换成对应的 React.createElement 函数调用。
- React.createElement 接收三个参数:标签名(字符串或函数)、属性对象和子元素。
- React.createElement 函数会返回一个描述该元素的对象,称为 React 元素(React Element)。
-
虚拟 DOM 构建阶段:
- 在执行 React.createElement 函数时,会创建一个描述组件结构的虚拟 DOM 对象(Virtual DOM)。
-
虚拟 DOM 渲染阶段:
- 当组件准备好进行渲染时,React 会将虚拟 DOM 进行比对,并计算出需要进行更新的部分。
-
真实 DOM 更新阶段:
- 在虚拟 DOM 渲染阶段结束后,React 会将需要更新的部分转换为真实 DOM 操作。
说说你在React项目是如何捕获错误的?
在 React 项目中,采用错误边界、全局错误处理、组件生命周期方法、try...catch 等方式结合起来,可以有效地捕获和处理错误,提高应用的稳定性和用户体验。同时,及时记录和监控错误信息也是保障项目质量的重要手段。
-
错误边界(Error Boundaries):
- 使用 React 的错误边界是一种捕获组件树中 JavaScript 错误的方式。通过定义错误边界组件,在组件树中将错误限制在边界内,避免整个应用崩溃。
- 在类组件中,可以通过 componentDidCatch 钩子函数捕获组件生命周期中的错误。
- 在函数式组件中,可以使用 ErrorBoundary 组件包裹需要捕获错误的部分。
说说React服务端渲染怎么做?原理是什么?
-
服务器端渲染阶段:
- 当请求到达服务器时,服务器会根据请求的 URL 路径找到对应的 React 组件,并调用相应的生命周期方法或函数组件。
- 服务器使用 ReactDOMServer 提供的 renderToString 或 renderToNodeStream 方法,将组件渲染成一个字符串或流。
-
HTML 响应生成阶段:
- 服务器将渲染后的字符串或流包装在一个 HTML 模板中,并返回给客户端。
- 这个 HTML 模板通常包括渲染后的组件内容、样式和其他静态资源的链接。
-
客户端激活阶段:
- 客户端接收到 HTML 响应后,会解析 HTML 文档,并重新创建 React 组件树。
- 在客户端重新创建组件时,React 会尽量复用服务器端生成的组件实例,以节省计算和网络开销。
- 客户端会在组件挂载后,继续处理后续的交互和更新。
React Fiber 是如何实现更新过程可控?
-
可中断性:React Fiber 将整个组件更新过程分解为多个可中断的小任务单元,称为 fiber。这些 fiber 可以根据优先级和时间片来进行调度,使得 React 能够在更新过程中灵活地中断、暂停和恢复任务,从而提高用户界面的响应性。
-
优先级调度:每个 fiber 都有自己的优先级,React Fiber 根据优先级动态调整任务的执行顺序,确保高优先级任务能够优先执行,从而实现对更新过程的控制。
-
协调策略:在更新过程中会构建新的虚拟 DOM 树并与旧的虚拟 DOM 树进行比较,确定需要更新的部分,最终只对需要更新的部分进行实际 DOM 操作。
-
增量更新:React Fiber 使用增量更新的方式,将更新过程分解为多个阶段,通过差异对比和增量更新的方式,只更新发生变化的部分,避免全量重新渲染,提高了性能和效率。
setState 是同步,还是异步的?
setState
方法既可以是同步的,也可以是异步的,具体取决于使用 setState
的地方和上下文。
-
同步更新:当在生命周期方法(如
componentDidMount
、componentDidUpdate
)和原生事件处理函数中调用setState
时,通常会同步更新状态。这是因为 React 可以立即执行状态更新并重新渲染组件。 -
批量异步更新:在大多数情况下,
setState
是异步的,即在事件处理函数、异步回调或setTimeout
等代码块中调用setState
时,React 会将多个setState
调用合并成一个批量更新操作,以提高性能。这意味着连续的setState
调用不会立即触发组件的重新渲染,而是在当前代码块执行完毕后,React 根据一定策略进行更新。
简述下 React 的事件代理机制?
React 使用了一种称为事件代理)的机制来处理组件中的事件。事件代理是一种将事件处理函数绑定到父元素而不是每个子元素的技术,它利用事件冒泡机制将事件从子元素传递到父元素进行处理。
-
事件绑定:在 React 组件中,可以通过在 JSX 中添加事件监听器来绑定事件处理函数
-
事件冒泡:当用户在子元素上触发事件时,该事件会按照从子元素到父元素的顺序依次向上冒泡。也就是说,事件会首先在触发事件的子元素上被触发,然后沿着组件层级向上冒泡到更高层的父元素。
-
事件委派:React 在组件树的根节点上绑定事件监听器,这个根节点可以是最外层的 DOM 元素,也可以是某个组件的容器元素。当事件冒泡至根节点时,React 会根据事件类型找到对应的事件处理函数,并执行相应的处理逻辑。这种方式避免了为每个子元素都绑定事件监听器,提高了性能和内存利用率。
简述下 React 的生命周期?每个生命周期都做了什么?
-
挂载阶段:
-
constructor()
:组件的构造函数,在组件被创建时调用,用于初始化状态和绑定事件处理函数。 -
static getDerivedStateFromProps(props, state)
:在组件接收到新的 props 或者在组件初始化时调用,返回一个对象来更新状态,用于替代旧的componentWillReceiveProps
。 -
render()
:根据组件的状态和属性渲染组件的内容。 -
componentDidMount()
:组件挂载后调用,可以进行 DOM 操作或数据请求等副作用。
-
-
更新阶段:
-
static getDerivedStateFromProps(props, state)
:在组件接收到新的 props 时调用,返回一个对象来更新状态。 -
shouldComponentUpdate(nextProps, nextState)
:在组件接收到新的 props 或 state 时调用,用于控制组件是否重新渲染。 -
render()
:根据新的状态和属性重新渲染组件。 -
getSnapshotBeforeUpdate(prevProps, prevState)
:在最近一次渲染输出(提交到 DOM 上)之前调用,可以在此保存当前 DOM 的一些信息。 -
componentDidUpdate(prevProps, prevState, snapshot)
:组件更新完成后调用,可以进行 DOM 操作或处理更新前的信息。
-
-
卸载阶段:
-
componentWillUnmount()
:组件卸载前调用,用于清理定时器、取消订阅等操作。
-
为什么不能在循环、条件或嵌套函数中调用 Hooks?
react用链表来严格保证hooks的顺序。Hooks 依赖于 React 内部的调用顺序来确定每个 Hook 的对应关系,如果在循环、条件语句或嵌套函数中调用 Hooks,可能会导致 Hooks 调用顺序发生变化,从而破坏 React 内部的依赖关系。这种情况下,React 可能无法正确地管理状态,造成组件的不稳定行为。
说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?
常见的 React Hook 闭包陷阱包括:
- 在 useEffect 或 useCallback 中引用了过时的 state 或 props。
- 在自定义 Hook 中使用了闭包来保存状态或引用,可能导致多个实例之间状态共享。
- 在循环中使用 Hook,可能导致闭包中的变量无法正确捕获循环中的值。
解决 React Hook 的闭包陷阩可以采取以下方案:
- 使用 useEffect 的依赖项数组来确保正确捕获最新的 state 和 props 值,避免闭包中引用过时的值。
- 避免在自定义 Hook 中使用闭包来保存状态或引用,可以使用 useRef 来保存可变的引用,或者通过 useReducer 来管理状态。
- 在循环中使用 Hook 时,可以通过将循环变量作为 useEffect 的依赖项或传递给回调函数的方式来正确捕获循环中的值。
React18新特性
-
Concurrent Mode(并发模式):React 18 引入了 Concurrent Mode,这是一个可选的特性,可以帮助优化应用程序的性能和用户体验。通过 Concurrent Mode,React 可以更好地处理大型应用程序中的渲染优先级,并在不阻塞用户界面的情况下提高性能。
-
新的渲染器 Renderer:React 18 引入了新的渲染器,允许开发人员更好地控制渲染过程,并进行更细粒度的优化。
-
Automatic Batching(自动批处理):React 18 改进了更新批处理机制,使得在某些情况下不再需要手动进行批处理操作,从而提高了性能。
-
新的树形结构 Reconciler:React 18 中引入了新的树形结构 Reconciler,可以更好地处理组件树的更新和渲染,提高了整体的性能和效率。
-
新的事件系统:React 18 带来了全新的事件系统,使得事件处理更加灵活和高效。开发人员可以更容易地管理和优化事件处理逻辑。
useRef / ref / forwardsRef 的区别是什么?
-
ref
是 React 提供的一个属性,用于在组件中访问子组件或 DOM 元素。每次渲染都会被重新创建。 -
useRef
是一个 Hook 函数,用于在函数组件中创建可变的 ref 对象。会因为重新渲染而改变。 -
forwardRef
是一个高阶组件,用于在函数组件中向子组件传递 ref。
React 和 Vue 在技术层面有哪些区别?
-
语法和模板:
- React 使用 JSX(JavaScript XML)作为组件的声明语法,将 HTML 结构和 JavaScript 代码混合在一起。
- Vue 使用单文件组件(SFC)的方式,将模板、脚本和样式放在同一个文件中,使得代码更加模块化和清晰。
-
数据绑定:
- React 使用单向数据流,父组件通过 props 将数据传递给子组件,子组件需要通过回调函数来改变父组件的数据。
- Vue 使用双向数据绑定,通过
v-model
指令可以实现父子组件之间的数据双向绑定,简化了数据传递和状态管理。
-
状态管理:
- React 可以使用 Context API 或者第三方库(如 Redux、MobX)来进行状态管理,提供了更灵活的状态管理方式。
- Vue 提供了 Vuex 状态管理库,用于集中式存储管理应用的所有组件的状态。
-
组件通信:
- React 中组件之间的通信可以通过 props、Context API、回调函数等方式实现。
- Vue 中组件之间的通信可以通过 props、自定义事件、Vuex 等方式实现。
-
生命周期:
- React 组件的生命周期包括挂载、更新和卸载阶段,提供了一系列生命周期方法供开发者进行操作。
- Vue 组件也有生命周期钩子函数,但与 React 不同的是,Vue 的生命周期钩子函数更细粒度,例如
beforeCreate
、created
、beforeMount
、mounted
等。
-
虚拟 DOM:
- React 使用虚拟 DOM 来提高性能,通过比对虚拟 DOM 的差异来最小化页面重新渲染的开销。
- Vue 同样使用虚拟 DOM,但采用了模板编译的方式,将模板编译成渲染函数,提高了性能。
taro 的实现原理是怎么样的?
Taro 利用 Babel、React、Webpack 等技术,通过封装原生 API 和提供不同的 Polyfill 实现了多端适配,同时也支持复杂的样式表达和自动化导入组件等特性。Taro 的实现原理主要是通过源码转换、组件映射、API 封装和样式处理等方式,实现了多端统一开发的目标,
单页应用如何提高加载速度?
- 使用代码分割:将代码拆分成小块并按需加载(懒加载),以避免不必要的网络请求和减少加载时间。
- 缓存资源:利用浏览器缓存来存储重复使用的文件,例如 CSS 和 JS 文件、图片等。
- 预加载关键资源:在首次渲染之前,先提前加载关键资源,例如首页所需的 JS、CSS 或数据,以保证关键内容的快速呈现。
- 使用合适的图片格式:选择合适的图片格式(例如 JPEG、PNG、WebP 等),并根据需要进行压缩以减少文件大小。对于一些小图标,可以使用
iconfont
等字体文件来代替。 - 启用 Gzip 压缩:使用服务器端的 Gzip 压缩算法对文件进行压缩,以减少传输时间和带宽消耗。
- 使用 CDN:使用内容分发网络(CDN)来缓存和传递文件,以提高文件的下载速度和可靠性。
- 优化 API 请求:尽可能地减少 API 调用的数量,并使用缓存和延迟加载等技术来优化 API 请求的效率。
- 使用服务器端渲染:使用服务器端渲染(SSR)来生成 HTML,以减少客户端渲染所需的时间和资源。但需要注意,SSR 也可能增加了服务器的负担并使网站更复杂。
react-router 里的 <Link> 标签和 <a> 标签有什么区别?
-
性能优化:
-
<Link>
组件是 React-Router 提供的组件,它在用户点击时会使用 JavaScript 动态地改变 URL 而不会重新加载整个页面,从而实现单页应用的导航,避免了页面的完全刷新,提升了性能和用户体验。 -
<a>
标签是 HTML 中的超链接标签,在用户点击时会导致整个页面重新加载或跳转到新的页面,这种方式会导致页面的重复加载和渲染,影响性能。
-
-
路由管理:
-
<Link>
组件与 React-Router 的路由管理机制结合,可以在单页应用中实现路由的切换、动态参数传递等功能,同时保持页面状态的稳定。 -
<a>
标签通常用于传统的多页应用或简单的静态页面,点击后会直接加载新的页面或跳转到指定链接。
-
-
样式处理:
- 使用
<Link>
组件可以方便地进行样式控制,通过设置 activeClassName 等属性可以实现当前链接高亮显示等效果。 - 使用
<a>
标签需要自行处理样式,无法方便地根据路由状态来进行样式控制。
- 使用
说说你对React Router的理解?常用的Router组件有哪些?
react-router
等前端路由的原理大致相同,可以实现无刷新的条件下切换显示不同的页面
路由的本质就是页面的URL
发生改变时,页面的显示结果可以根据URL
的变化而变化,但是页面不会刷新
主要是提供了一些组件:
- BrowserRouter、HashRouter
- Route
- Link、NavLink
- switch
- redirect
说说React Router有几种模式,以及实现原理?
-
Hash 模式:
- 在 Hash 模式下,URL 中会包含一个
#
符号,例如http://www.example.com/#/about
。 - 实现原理是基于浏览器对 URL 中哈希部分的变化不会引起页面刷新的特性。React Router 监听 URL 中哈希部分的变化,并根据哈希值匹配相应的路由进行页面更新。
- 这种模式兼容性好,可以在不支持 HTML5 History API 的浏览器上正常工作,但 URL 中包含
#
符号可能会被认为不够美观。
- 在 Hash 模式下,URL 中会包含一个
-
History 模式:
- 在 History 模式下,URL 不包含
#
符号,例如http://www.example.com/about
。 - 实现原理是利用 HTML5 History API 中的
pushState
和replaceState
方法,通过改变 URL 而不引起页面刷新来实现页面路由的切换。 - 这种模式生成的 URL 更加美观,但需要确保服务器端能够正确处理这些 URL,以避免在用户直接访问这些 URL 时出现 404 错误。
- 在 History 模式下,URL 不包含
使用 redux 有哪些原则?
- 单一数据源
- 状态是只读的
- 纯函数的 reducer
- 不要在 reducer 中执行异步操作
- 合理使用中间件
- 按照功能划分 reducer
- 避免过度使用 Redux
Redux 和 Vuex 有什么区别,它们有什么共同思想吗?
们的共同思想包括:
-
集中化的状态管理:Redux 和 Vuex 都提倡将应用程序的状态集中管理,以便于统一管理和跟踪状态的变化。
-
单向数据流:两者都采用了单向数据流的思想,即数据的流动是单向的,便于状态的追踪和调试。
-
纯函数:Redux 和 Vuex 都鼓励使用纯函数来处理状态的变化,使得状态的变化更加可预测和可控。
-
开发者工具:两者都提供了开发者工具,便于开发者监控状态变化、调试和时间旅行等功能。
它们的区别主要在于以下几点:
-
框架依赖:Redux 是一个独立的状态管理库,可以与任何框架结合使用,而 Vuex 是专门为 Vue.js 设计的状态管理库。
-
概念和API:Redux 使用了 action、reducer、store 等概念和 API,而 Vuex 使用了 mutation、action、state、getter 等不同的概念和 API。
-
语法差异:Redux 的语法比较简洁,但在某些情况下需要编写较多的模板代码;而 Vuex 在 Vue.js 中能够更好地利用框架的特性,提供了更加简洁的语法。
-
生态和社区:Redux 有着庞大的生态和社区支持,而 Vuex 则更加贴近 Vue.js 生态,能够更好地与 Vue.js 整合。
React 中怎么实现状态自动保存(KeepAlive)?
-
使用 Context API 或 Redux:
- 可以使用 React 的 Context API 或 Redux 来全局管理组件的状态。将需要缓存的组件状态存储在全局状态管理中,这样即使组件被卸载再重新挂载时,状态仍然可以被保留。
-
使用组件缓存技术:
- 可以自定义一个高阶组件(HOC)或 Hook,用于缓存组件的状态。当组件被卸载时,将状态保存到缓存中;当组件重新挂载时,从缓存中读取之前保存的状态。
-
使用 React Router 中的路由状态:
- 如果是基于 React Router 实现页面导航的应用,可以利用 React Router 提供的路由状态来实现状态自动保存。当路由切换时,可以在路由状态中存储和恢复组件状态。
-
利用 LocalStorage 或 SessionStorage:
- 可以使用浏览器提供的 LocalStorage 或 SessionStorage 来保存组件状态。在组件的生命周期方法中或者 useEffect 钩子中将状态保存到 LocalStorage 或 SessionStorage 中,在组件重新挂载时再从中读取状态。
-
利用第三方库:
- 也可以考虑使用像
react-keep-alive
这样的第三方库来实现状态自动保存的功能。这些库一般提供了简单易用的方式来实现组件状态的缓存和自动保存。
- 也可以考虑使用像
useEffect 与 useLayoutEffect 有什么区别?
-
useEffect:
-
useEffect
是 React 提供的标准 Hook,它会在浏览器渲染完成后异步执行副作用操作。这意味着它不会阻塞浏览器渲染,并且会在浏览器布局和绘制更新之后执行。 - 通常用于处理数据获取、订阅或手动操作 DOM 等副作用操作,它不会阻塞浏览器的渲染过程。
-
-
useLayoutEffect:
-
useLayoutEffect
也是一个 React 提供的 Hook,与useEffect
类似,但它会在所有 DOM 变更之后同步执行副作用操作,但在浏览器布局和绘制之前执行。 - 由于
useLayoutEffect
是在 DOM 更新之后、页面重新布局之前同步执行的,因此如果在其中执行大量计算或操作,可能会导致性能问题,甚至造成页面卡顿。
-
因此,主要区别在于执行时机和对页面布局的影响。一般情况下,推荐优先使用 useEffect
来处理副作用操作,只有在确实需要在布局更新之前立即执行代码时才考虑使用 useLayoutEffect
。
react中懒加载的实现原理是什么?
-
动态导入:
- 懒加载的关键在于使用动态导入来按需加载组件或其他资源。通过使用
import()
函数,可以在需要时动态加载模块,而不是在应用初始化时一次性加载所有模块。 - 例如,可以将组件的引入语句改为
const LazyComponent = React.lazy(() => import('./LazyComponent'))
,这样在需要时才会加载 LazyComponent 组件。
- 懒加载的关键在于使用动态导入来按需加载组件或其他资源。通过使用
-
React.lazy 和 Suspense:
- React 提供了
React.lazy
和Suspense
这两个懒加载相关的 API。React.lazy
函数接受一个函数,该函数应该返回一个动态 import 的 Promise,以实现组件的懒加载。 - 使用
Suspense
组件可以在等待懒加载组件加载完成时显示 loading 界面,以提升用户体验。在最外层包裹Suspense
组件,并设置fallback
属性为加载中时显示的组件,即可实现懒加载时的 loading 效果。
- React 提供了
-
代码分割:
- 除了懒加载组件外,还可以利用代码分割(Code Splitting)来拆分应用代码,减少初始加载时的体积。通过将应用代码分割成多个块,并在需要时动态加载这些块,可以进一步优化页面加载性能。
React有哪些性能优化的方法?
React 渲染性能优化的三个方向,其实也适用于其他软件开发领域,这三个方向分别是:
- 减少计算的量。 -> 对应到 React 中就是减少渲染的节点 或者 降低组件渲染的复杂度
- 利用缓存。-> 对应到 React 中就是如何避免重新渲染,利用函数式编程的 memo 方式来避免组件重新渲染
- 精确重新计算的范围。 对应到 React 中就是绑定组件和状态关系, 精确判断更新的'时机'和'范围'. 只重新渲染'脏'的组件,或者说降低渲染范围
简单介绍下React中的 diff 算法
在 React 中,虚拟 DOM 的 diff 算法是用来比较前后两次 Virtual DOM 树的差异,以确定最小的更新量,从而高效地更新实际 DOM。React 采用了一种基于 Fiber 架构的调度算法,其中的 diff 算法是其核心之一。
React 中的 diff 算法主要包括以下步骤:
-
树的遍历:
- React 使用深度优先搜索算法(DFS)来遍历前后两棵 Virtual DOM 树,对节点进行比较。
-
节点比较:
- 对比同一层级的节点,首先会比较节点类型是否相同,然后再比较节点的属性。
- 如果节点类型不同,则直接替换整个节点及其子节点;如果节点类型相同,则比较节点属性,更新需要更新的属性。
-
列表节点的处理:
- 在处理列表时,React 会使用“key”来唯一标识列表中的每一项,以便更准确地进行节点的复用和更新。
-
差异的收集:
- 在比较过程中,React 会记录下需要进行更新、插入、移动或删除的操作,形成一份变更列表。
-
批量更新:
- 最后,React 会根据变更列表中的操作,批量更新实际 DOM,以最小化页面重绘和重新排版的开销。
react 的虚拟dom是怎么实现的?
通过 JavaScript 对象来模拟真实 DOM 的层次结构和状态,从而实现了对 DOM 的抽象和操作。虚拟 DOM 的实现可以简单概括为以下几个步骤:
-
创建虚拟 DOM 对象:
- 当使用 React 创建组件时,每个组件都会对应一个虚拟 DOM 对象。这个对象描述了组件在特定时间点的状态和结构。
-
渲染虚拟 DOM:
- 当组件状态发生变化或初始化时,React 会根据组件的 render 方法返回的 JSX 创建新的虚拟 DOM 对象。
-
对比更新:
- 当新的虚拟 DOM 对象产生后,React 会使用 diff 算法比较新旧虚拟 DOM 对象之间的差异,并找出最小的更新量。
-
应用更新:
- 经过对比后,React 会将变更部分应用到实际的 DOM 结构上,从而更新页面显示。
通过虚拟 DOM,React 实现了一种高效的页面更新机制。在组件状态发生变化时,React 首先更新虚拟 DOM,然后通过 diff 算法找出实际需要更新的部分,最终只更新实际 DOM 中发生变化的部分,避免了不必要的 DOM 操作,提高了页面渲染的效率。
为什么不能直接使用 this.state 改变数据?
setState通过一个队列机制来实现 state 更新。当执行 setState 的时候,会将需要更新的 state 合并后放入状态队列,而不会立刻更新 this.state。队列机制可以高效的批量更新 state,如果不通过 setState 而直接修改 this.state,那么该 state 将不会被放入状态队列中,当下次调用 setState 并对状态队列进行合并时,将会忽略之前被直接修改的 state,而造成无法预知的错误。