文章分类 其他 react.js 前端 vue.js javascript ecmascript 阅读数 : 178 阅读时长 : 9分钟
render
函数里面可以编写JSX
,转化成createElement
这种形式,用于生成虚拟DOM
,最终转化成真实DOM
在 React
中,类组件只要执行了 setState
方法,就一定会触发 render
函数执行,函数组件使用useState
更改状态不一定导致重新render
组件的 props
改变了,不一定触发 render
函数的执行,但是如果 props
的值来自于父组件或者祖先组件的 state
在这种情况下,父组件或者祖先组件的 state
发生了改变,就会导致子组件的重新渲染
所以,一旦执行了setState
就会执行render
方法,useState
会判断当前值有无发生改变确定是否执行render
方法,一旦父组件发生渲染,子组件也会渲染
React
提供统一的事件对象,抹平了浏览器的兼容性差异React
通过顶层监听的形式,通过事件委托的方式来统一管理所有的事件,可以在事件上区分事件优先级,优化用户体验React
在合成事件上对于16
版本和17
版本的合成事件有很大不同,我们也会简单聊聊区别。
16
版本先执行原生事件,当冒泡到document
时,统一执行合成事件,17
版本在原生事件执行前先执行合成事件捕获阶段,原生事件执行完毕执行冒泡阶段的合成事件,通过根节点来管理所有的事件原生的阻止事件流会阻断合成事件的执行,合成事件阻止后也会影响到后续的原生执行
Immutable 是指数据不可变,一旦创建之后就不能被修改。在 JavaScript 中,原始类型(如字符串、数字)是不可变的,而对象和数组是可变的。
性能优化:使用 Immutable 数据结构可以帮助 React 进行浅比较,提高组件更新时的性能。通过比较引用而不是深层次比较对象内容,可以减少不必要的重新渲染。
简化状态管理:不可变数据结构可以减少状态管理的复杂性。在 Redux 或者其他状态管理工具中,配合 Immutable 数据结构可以更容易地追踪状态的变化,并且可以避免直接修改状态而导致的副作用。
防止意外修改:不可变数据结构可以减少由于在多处修改同一数据而引发的 bug。在 React 组件中,通过使用 Immutable 数据结构,可以更容易地追踪数据的变化,从而减少意外的状态修改。
在 React 项目中,可以通过以下方式应用 Immutable 数据结构:
JSX 编写阶段:
JSX 转换阶段:
React.createElement 函数调用:
虚拟 DOM 构建阶段:
虚拟 DOM 渲染阶段:
真实 DOM 更新阶段:
在 React 项目中,采用错误边界、全局错误处理、组件生命周期方法、try...catch 等方式结合起来,可以有效地捕获和处理错误,提高应用的稳定性和用户体验。同时,及时记录和监控错误信息也是保障项目质量的重要手段。
错误边界(Error Boundaries):
服务器端渲染阶段:
HTML 响应生成阶段:
客户端激活阶段:
可中断性:React Fiber 将整个组件更新过程分解为多个可中断的小任务单元,称为 fiber。这些 fiber 可以根据优先级和时间片来进行调度,使得 React 能够在更新过程中灵活地中断、暂停和恢复任务,从而提高用户界面的响应性。
优先级调度:每个 fiber 都有自己的优先级,React Fiber 根据优先级动态调整任务的执行顺序,确保高优先级任务能够优先执行,从而实现对更新过程的控制。
协调策略:在更新过程中会构建新的虚拟 DOM 树并与旧的虚拟 DOM 树进行比较,确定需要更新的部分,最终只对需要更新的部分进行实际 DOM 操作。
增量更新:React Fiber 使用增量更新的方式,将更新过程分解为多个阶段,通过差异对比和增量更新的方式,只更新发生变化的部分,避免全量重新渲染,提高了性能和效率。
setState
方法既可以是同步的,也可以是异步的,具体取决于使用 setState
的地方和上下文。
同步更新:当在生命周期方法(如 componentDidMount
、componentDidUpdate
)和原生事件处理函数中调用 setState
时,通常会同步更新状态。这是因为 React 可以立即执行状态更新并重新渲染组件。
批量异步更新:在大多数情况下,setState
是异步的,即在事件处理函数、异步回调或 setTimeout
等代码块中调用 setState
时,React 会将多个 setState
调用合并成一个批量更新操作,以提高性能。这意味着连续的 setState
调用不会立即触发组件的重新渲染,而是在当前代码块执行完毕后,React 根据一定策略进行更新。
React 使用了一种称为事件代理)的机制来处理组件中的事件。事件代理是一种将事件处理函数绑定到父元素而不是每个子元素的技术,它利用事件冒泡机制将事件从子元素传递到父元素进行处理。
事件绑定:在 React 组件中,可以通过在 JSX 中添加事件监听器来绑定事件处理函数
事件冒泡:当用户在子元素上触发事件时,该事件会按照从子元素到父元素的顺序依次向上冒泡。也就是说,事件会首先在触发事件的子元素上被触发,然后沿着组件层级向上冒泡到更高层的父元素。
事件委派:React 在组件树的根节点上绑定事件监听器,这个根节点可以是最外层的 DOM 元素,也可以是某个组件的容器元素。当事件冒泡至根节点时,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()
:组件卸载前调用,用于清理定时器、取消订阅等操作。react用链表来严格保证hooks的顺序。Hooks 依赖于 React 内部的调用顺序来确定每个 Hook 的对应关系,如果在循环、条件语句或嵌套函数中调用 Hooks,可能会导致 Hooks 调用顺序发生变化,从而破坏 React 内部的依赖关系。这种情况下,React 可能无法正确地管理状态,造成组件的不稳定行为。
常见的 React Hook 闭包陷阱包括:
解决 React Hook 的闭包陷阩可以采取以下方案:
Concurrent Mode(并发模式):React 18 引入了 Concurrent Mode,这是一个可选的特性,可以帮助优化应用程序的性能和用户体验。通过 Concurrent Mode,React 可以更好地处理大型应用程序中的渲染优先级,并在不阻塞用户界面的情况下提高性能。
新的渲染器 Renderer:React 18 引入了新的渲染器,允许开发人员更好地控制渲染过程,并进行更细粒度的优化。
Automatic Batching(自动批处理):React 18 改进了更新批处理机制,使得在某些情况下不再需要手动进行批处理操作,从而提高了性能。
新的树形结构 Reconciler:React 18 中引入了新的树形结构 Reconciler,可以更好地处理组件树的更新和渲染,提高了整体的性能和效率。
新的事件系统:React 18 带来了全新的事件系统,使得事件处理更加灵活和高效。开发人员可以更容易地管理和优化事件处理逻辑。
ref
是 React 提供的一个属性,用于在组件中访问子组件或 DOM 元素。每次渲染都会被重新创建。useRef
是一个 Hook 函数,用于在函数组件中创建可变的 ref 对象。会因为重新渲染而改变。forwardRef
是一个高阶组件,用于在函数组件中向子组件传递 ref。语法和模板:
数据绑定:
v-model
指令可以实现父子组件之间的数据双向绑定,简化了数据传递和状态管理。状态管理:
组件通信:
生命周期:
beforeCreate
、created
、beforeMount
、mounted
等。虚拟 DOM:
Taro 利用 Babel、React、Webpack 等技术,通过封装原生 API 和提供不同的 Polyfill 实现了多端适配,同时也支持复杂的样式表达和自动化导入组件等特性。Taro 的实现原理主要是通过源码转换、组件映射、API 封装和样式处理等方式,实现了多端统一开发的目标,
iconfont
等字体文件来代替。性能优化:
<Link>
组件是 React-Router 提供的组件,它在用户点击时会使用 JavaScript 动态地改变 URL 而不会重新加载整个页面,从而实现单页应用的导航,避免了页面的完全刷新,提升了性能和用户体验。<a>
标签是 HTML 中的超链接标签,在用户点击时会导致整个页面重新加载或跳转到新的页面,这种方式会导致页面的重复加载和渲染,影响性能。路由管理:
<Link>
组件与 React-Router 的路由管理机制结合,可以在单页应用中实现路由的切换、动态参数传递等功能,同时保持页面状态的稳定。<a>
标签通常用于传统的多页应用或简单的静态页面,点击后会直接加载新的页面或跳转到指定链接。样式处理:
<Link>
组件可以方便地进行样式控制,通过设置 activeClassName 等属性可以实现当前链接高亮显示等效果。<a>
标签需要自行处理样式,无法方便地根据路由状态来进行样式控制。react-router
等前端路由的原理大致相同,可以实现无刷新的条件下切换显示不同的页面
路由的本质就是页面的URL
发生改变时,页面的显示结果可以根据URL
的变化而变化,但是页面不会刷新
主要是提供了一些组件:
Hash 模式:
#
符号,例如 http://www.example.com/#/about
。#
符号可能会被认为不够美观。History 模式:
#
符号,例如 http://www.example.com/about
。pushState
和 replaceState
方法,通过改变 URL 而不引起页面刷新来实现页面路由的切换。们的共同思想包括:
集中化的状态管理: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 整合。
使用 Context API 或 Redux:
使用组件缓存技术:
使用 React Router 中的路由状态:
利用 LocalStorage 或 SessionStorage:
利用第三方库:
react-keep-alive
这样的第三方库来实现状态自动保存的功能。这些库一般提供了简单易用的方式来实现组件状态的缓存和自动保存。useEffect:
useEffect
是 React 提供的标准 Hook,它会在浏览器渲染完成后异步执行副作用操作。这意味着它不会阻塞浏览器渲染,并且会在浏览器布局和绘制更新之后执行。useLayoutEffect:
useLayoutEffect
也是一个 React 提供的 Hook,与 useEffect
类似,但它会在所有 DOM 变更之后同步执行副作用操作,但在浏览器布局和绘制之前执行。useLayoutEffect
是在 DOM 更新之后、页面重新布局之前同步执行的,因此如果在其中执行大量计算或操作,可能会导致性能问题,甚至造成页面卡顿。因此,主要区别在于执行时机和对页面布局的影响。一般情况下,推荐优先使用 useEffect
来处理副作用操作,只有在确实需要在布局更新之前立即执行代码时才考虑使用 useLayoutEffect
。
动态导入:
import()
函数,可以在需要时动态加载模块,而不是在应用初始化时一次性加载所有模块。const LazyComponent = React.lazy(() => import('./LazyComponent'))
,这样在需要时才会加载 LazyComponent 组件。React.lazy 和 Suspense:
React.lazy
和 Suspense
这两个懒加载相关的 API。React.lazy
函数接受一个函数,该函数应该返回一个动态 import 的 Promise,以实现组件的懒加载。Suspense
组件可以在等待懒加载组件加载完成时显示 loading 界面,以提升用户体验。在最外层包裹 Suspense
组件,并设置 fallback
属性为加载中时显示的组件,即可实现懒加载时的 loading 效果。代码分割:
React 渲染性能优化的三个方向,其实也适用于其他软件开发领域,这三个方向分别是:
在 React 中,虚拟 DOM 的 diff 算法是用来比较前后两次 Virtual DOM 树的差异,以确定最小的更新量,从而高效地更新实际 DOM。React 采用了一种基于 Fiber 架构的调度算法,其中的 diff 算法是其核心之一。
React 中的 diff 算法主要包括以下步骤:
树的遍历:
节点比较:
列表节点的处理:
差异的收集:
批量更新:
通过 JavaScript 对象来模拟真实 DOM 的层次结构和状态,从而实现了对 DOM 的抽象和操作。虚拟 DOM 的实现可以简单概括为以下几个步骤:
创建虚拟 DOM 对象:
渲染虚拟 DOM:
对比更新:
应用更新:
通过虚拟 DOM,React 实现了一种高效的页面更新机制。在组件状态发生变化时,React 首先更新虚拟 DOM,然后通过 diff 算法找出实际需要更新的部分,最终只更新实际 DOM 中发生变化的部分,避免了不必要的 DOM 操作,提高了页面渲染的效率。
setState通过一个队列机制来实现 state 更新。当执行 setState 的时候,会将需要更新的 state 合并后放入状态队列,而不会立刻更新 this.state。队列机制可以高效的批量更新 state,如果不通过 setState 而直接修改 this.state,那么该 state 将不会被放入状态队列中,当下次调用 setState 并对状态队列进行合并时,将会忽略之前被直接修改的 state,而造成无法预知的错误。
更多【react.js-前端面试题《react》】相关视频教程:www.yxfzedu.com