关于React的执行时机&小优化

我觉得:

代码代码,无非就是翻译。把想做的事用编程语言体现出来,第一步、第二步、第三步...

要想高效的解决问题,执行时机是特别需要关注的点。什么阶段做什么事情,什么阶段能做什么事情,我觉得是很重要的。

问题出来了,进行分析,熟读API执行时机、组件生命周期,快速发现问题、高效解决问题。

前辈说:别为了完成任务而完成任务,要想路越走越宽,一定要理解需求,为什么要这么设计,理解需求背景,把自己也当成设计师,适当时刻,提出见解。

正题

React18,函数组件执行时机

函数式组件的执行顺序是从上到下执行的,像普通的Javascript函数一样,当React需要渲染一个函数式组件时,它会调用这个函数,并传递props作为参数。组件函数内部的代码随后会按照书写的顺序执行。

在函数体内部,你可以定义状态钩子(如useState)、副作用钩子(如ueEffect)等,然后返回JSX,返回的JSX描述了组件的UI结构。以下是函数式组件的简单示例和执行顺序的说明:

js 复制代码
function MyComponent(props) {

 // 1、执行useState,定义state变量
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    // 2、这个函数会在组件被渲染到屏幕之后执行
  })
  
  // 3、执行任何其他逻辑代码
  const increment = () => {
    setCount(count + 1);
  }
  
  // 4、返回JSX,即函数式组件的渲染输出
  return (
    
  )
}

执行顺序是:

  1. useState 和其他Hooks被调用,初始化状态或其他Hooks相关的操作
  2. 执行任何自定义的逻辑代码,如事件处理函数的定义等
  3. 返回JSX,这一步是实际的"渲染"步骤,但它只是返回了一个需要渲染的元素的描述,并不是真正的渲染行为

总结: 组件函数返回的JSX会被React转换成虚拟DOM( Virtual DOM) , React库随后会根据这个虚拟DOM来更新实际的DOM。如果使用了副作用钩子useEffect,那么它的内容会在渲染发生后(即浏览器绘制屏幕之后)执行。重要的是要理解,render在React的上下文中指的是整个组件函数的执行过程,而不仅仅是返回JSX的那部分。因此,当我们说"函数式组件被渲染时",实际上是指整个组件函数从上到下的执行。

"组件函数返回的JSX会被React转换成虚拟DOM (Mirtual DOM),React库随后会根据这个虚拟DOM来更新实际的DOM。如果使用了副作用钩子useEffect,那么它的内容会在渲染发生后 (即浏览器绘制屏幕之后)执行"这句话怎么理解?

这句话描述了React在渲染函数式组件时的几个关键步骤和它们的执行时机。我们可以将其分解成以下几个部分来更清晰地理解:

  1. 组件函数返回的JSX会被React转换成虚拟DOM (Virtual DOM): 当React执行函数式组件时,组件返回的JSX被转换成React元素,它们是轻量级的对象表示,这个过程是在内存中进行的,称为虚拟DOM。虚拟DOM是实际DOM的抽象表示,允许React在进行任何实际的DOM操作之前预先计算出可能的更新。
  2. React库随后会根据这个虚拟DOM来更新实际的DOM: 一旦组件的虚拟DOM被创建(或更新),React接下来会进行DOM差异比较(diffing algorithm),找出与上一次渲染相比发生变化的部分。然后,React只会更新那些实际改变了的DOM元素,而不是整个DOM树。这个过程被称为协调(Reconciliation)
  3. 如果使用了副作用钩子useEffect,那么它的内容会在渲染发生后执行: useEffect是一个副作用钩子,允许你在函数式组件中执行副作用操作,例如数据获取、订阅或手动修改DOM。useEffect中的代码不会在组件的主渲染流程中同步执行,而是在组件输出已经被提交到DOM之后再延迟执行。这意味着,它们通常是在所有 DOM变更完成之后运行的,因此不会阻塞浏览器的绘制过程。

根据useEffect的依赖数组,它可以在组件每次渲染后运行,或者只在依赖项变化时运行。此外,useEffect可以返回一个清理函数,在组件卸载前或依赖项发生变化导致副作用重新运行前调用,用于执行必要的清理工作。

总结这三个部分,你可以这样理解:

  1. 首先,返回的JSX被转换成虚拟DOM;
  2. 然后,React根据虚拟DOM的差异更新实际DOM;
  3. 最后,DOM更新后,useEffect中定义的副作用操作会被执行,这个时刻通常是在所有的DOM更新都已经被浏览器处理完毕之后。这个过程确保了DOM操作的效率,并且允许副作用在DOM稳定之后运行,避免了可能的渲染问题和不一致状态。

React.memo

用过该Hook的人都知道,它是用来缓存组件的,当props没有发生改变时,不会进行不必要的渲染,导致多余的性能浪费。

需要注意的是,它还有第二个参数,它是个函数,可以自定义比较方法,由编写者来决定深浅比较,是否将该组件缓存下不渲染!

官方点说: React.memo是一个高阶组件,用于对函数组件进行性能优化,通过记忆组件渲染结果,避免在相同props的情况下重新渲染组件。默认情况下,React.memo只会对组件的props进行浅比较,如果props没有变化,那么就不会重新渲染组件。

然而,有些场景下你可能需要更细致的控制来决定是否需要重新渲染组件。为了实现这一点,React.memo允许你传递第二个参数:一个自定义比较函数。这个函数接收两个参数,分别是组件的前一个props和新的props。

自定义比较函数:

js 复制代码
function areEqual(prevProps, nextProps) {
  // 返回true则不更新组件(即props相等,不重新渲染)
  // 返回false则更新组件(即props不相等,需要重新渲染)
}

这个函数应该返回一个布尔值,指示前后两次的props是否相等。如果返回true,则React.memo将跳过渲染组件;如果为false,则组件将重新渲染。

在自定义比较函数中,你可以实现深比较、比较特定属性,或者根据复杂逻辑来决定是否需要更新。这给了我们更多的灵活性来优化组件渲染性能。

示例:

以下是一个使用React.memo和自定义比较函数的简单示例:

js 复制代码
const MyComponent = React.memo(function MyComponent(props){
  // 你的组件逻辑
}, areEqual);

function areEqual(prevProps, nextProps) {
  // 如果对比逻辑确定props没有变化,则不需要再渲染组件
  if (prevProps.value === nextProps.value) {
    return true; // 不重新渲染
  }
    return false; // 重新渲染
}

在这个例子中,如果value属性没有变化,则组件不会重新渲染,否则它将更新。

注意事项

在使用React.memo和自定义比较函数时,要注意几个要点:

性能权衡:虽然自定义比较可以避免不必要的渲染,但比较函数本身也会消耗一定的资源。在复杂的比较逻辑中,可能会影响性能,因此需要谨慎使用。

只有函数组件:React.memo仅适用于函数组件。如果你需要优化类组件的渲染性能,可以考虑使用PureComponent或shouldComponentUpdate生命周期方法。

记忆的副作用:如果你依赖组件渲染中的副作用(如果在渲染函数中发起网络请求),使用React.memo可能会导致这些副作用不被触发。这是因为如果组件渲染被跳过,那么这些副作用也不会执行。

总而言之,React.memo和自定义比较函数是一个强大的组合,能够帮助你在确保性能的同时,精确控制组件何时更新,在实际应用中,你应该基于组件的具体性能需求和行为来决定是否使用它们。

useEffect和useLayoutEffect的区别?

执行时机是我想要强调的点,看看?

useEffect的执行时机:

useEffect 用于在组件渲染到屏幕之后执行副作用操作。这里的"之后"指的是所有的DOM变更已经完成,组件的输出已经被渲染到屏幕上。在这个时候,React会异步执行useEffect中的副作用函数,这意味着:

useEffect中的代码不会在组件的渲染过程中执行,它不会阻塞DOM的更新。

React将在当前的渲染任务完成后,在下一个事件循环(event loop)中运行useEffect的副作用函数。

因为useEffect是异步执行的,所以你的副作用代码不应该包括含任何同步变更DOM的操作,这些操作可能需要立即反应在屏幕上。

useLayoutEffect的执行时机:

useLayoutEffect 的用法与useEffect几乎相同,但它的执行时机不同。useLayoutEffect中的副作用函数是在所有DOM变更之后,但在浏览器进行绘制之前执行的。这意味着:

useLayoutEffect中的代码会在DOM更新完成后立即同步执行,它会阻塞浏览器的绘制,直到你的副作用代码执行完毕。

因为useLayoutEffect是在浏览器绘制前执行,所以它适用于需要同步读取或更新DOM的情况,例如,测量DOM元素的大小或位置,或者执行需要立即反应在屏幕上的DOM更新。

useLayoutEffect的同步特性可以确保用户看到的屏幕永远是经过你副作用代码更新后的状态,防止出现视觉上的不一致或闪烁。

总的来说

useLayoutEffect和useEffect的主要区别在于它们的执行时机和对浏览器绘制的影响。useEffect是为大多数副作用设计的,它不会阻塞用户界面的更新。而useLayoutEffect用于那些必须同步执行的副作用,以保证操作完成后才绘制页面,从而避免界面出现错误或不一致的状态。在实践中,大多数情况下应该优先使用useEffect,只有在特殊情况下,当你需要进行DOM测量或同步更新时,才使用useLayoutEffect。

'useEffect用于在组件渲染到屏幕之后执行副作用操作',有一个问题:组件都已经渲染到屏幕上了,如果我们在useEffect中调用了接口,更新了数据,那么组件是不是又再次渲染、绘制了一遍呢?

是的,如果在useEffect中执行了一个接口调用,并且接口返回的数据导致组件状态更新(例如,通过useState Hook的更新函数设置新的状态),那么组件会再次渲染。

这里是一个简化的流程:

  1. 组件初始化渲染,React将DOM渲染到屏幕上。
  2. useEffect被调用,因为它是在渲染后执行的,所以它不会阻塞屏幕的更新。
  3. 在useEffect内部,执行异步接口调用。
  4. 当异步接口调用完成并获得数据后,你可能通过更新状态来反应这个数据。
  5. 状态更新会触发组件的重新渲染。
  6. React再次调用组件函数,根据新的状态生成新的DOM,并进行必要的DOM更新。
  7. 如果useEffect指定了依赖(dependencies),并且依赖项在最新渲染中发生了变化,则useEffect会再次执行。

在这个过程中,你的组件可能会有两次渲染:第一次是初始渲染,第二次是因状态更新(由于接口返回数据)导致的重新渲染。React会尽量优化这个过程,只更新实际改变了的DOM元素。

'组件初始渲染,React将DOM渲染到屏幕上'这句话怎么理解?对于函数组件而言,初始渲染指的是函数至上而下执行到没有执行useEffect Hook?

针对函数组件的"初始渲染"实际上指的是React处理组件函数体时从开始到结束的过程。在这个过程中,React执行组件函数,计算出它返回的JSX应该如何被转换为DOM更新。这一过程包括以下步骤:

  1. 执行函数组件:当组件首次被渲染时,React会调用该函数组件,传入props作为参数。组件函数从上到下执行,因此任何在函数体中定义的Hooks(例如useState或useContext)都会在这个时候被初始化和调用。
  2. 返回JSX:组件函数返回JSX,这是组件的UI描述。React将这些JSX转换成React元素,这是对UI的轻量级描述。
  3. 构建虚拟DOM:React使用返回的React元素来构建虚拟DOM树。虚拟DOM是实际DOM的一个轻量级抽象。
  4. DOM比对:React将当前的虚拟DOM与上次渲染的虚拟DOM进行比对(如果是首次渲染,则与空虚拟DOM比对),计算出需要进行的最小化更新。
  5. 渲染到DOM:React将计算出的更新应用到真实DOM上,这通常涉及到修改、添加或删除DOM节点。
  6. 屏幕更新:一旦DOM更新完成,屏幕上就会展示出最新的界面给用户。此时用户可以看到第一次渲染的结果。

在这个初始渲染的过程中,useEffect Hook不会被执行。因为useEffect是设计为在DOM更新完成后执行的,确切地说,是在浏览器绘制了更新后的界面到屏幕上之后,在useEffect地副作用函数中,你可以执行与DOM无关的操作,例如数据获取、订阅、计时器设置等,并且可以根据需要设置状态,从而可能引发进一步的渲染。

因此,"组件初始渲染,React将DOM渲染到屏幕上"应该理解为:组件的函数体被执行,返回的JSX转换为虚拟DOM,React计算出必要的DOM更新,然后应用到真实的DOM上,最终界面更新显示在屏幕上。在这个过程完全完成之后,useEffect Hook才会执行它们的副作用函数。

useRef

useRef 是用来保存一个在组件的生命周期中持续存在的值,对这个值的修改不会引发组件的重新渲染。这个值可以是DOM引用,也可以是任何可变的数据。

useRef与useState最主要的区别

js 复制代码
  const currentRef = useRef(null);
  currentRef.current = 1; // 组件不会重新渲染
  
  const [value, setValue] = useState<string>('');
  setValue('1'); // 修改状态,组件重新渲染

有些时候,更改一个值,你不想让视图发生变化,可以借助useRef的力量。

持续记录,持续更新

待续...

相关推荐
hongkid2 分钟前
React Native 如何打包正式apk
javascript·react native·react.js
李少兄5 分钟前
简单讲讲 SVG:前端开发中的矢量图形
前端·svg
前端小万6 分钟前
告别 CJS 库加载兼容坑
前端·前端工程化
恋猫de小郭6 分钟前
Flutter 3.38.1 之后,因为某些框架低级错误导致提交 Store 被拒
android·前端·flutter
JarvanMo10 分钟前
Flutter 需要 Hooks 吗?
前端
光影少年20 分钟前
前端如何虚拟列表优化?
前端·react native·react.js
Moment22 分钟前
一杯茶时间带你基于 Yjs 和 reactflow 构建协同流程图编辑器 😍😍😍
前端·后端·面试
菩提祖师_36 分钟前
量子机器学习在时间序列预测中的应用
开发语言·javascript·爬虫·flutter
invicinble40 分钟前
对于前端数据的生命周期的认识
前端
PieroPc43 分钟前
用FastAPI 后端 和 HTML/CSS/JavaScript 前端写一个博客系统 例
前端·html·fastapi