关于 React 性能优化和数栈产品中的实践

这篇具有很好参考价值的文章主要介绍了关于 React 性能优化和数栈产品中的实践。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。

本文作者:的卢

引入

在日常开发过程中,我们会使用很多性能优化的 API,比如像使用 memouseMemo优化组件或者值,再比如使用 shouldComponentUpdate减少组件更新频次,懒加载等等,都是一些比较好的性能优化方式,今天我将从组件设计、结构上来谈一下 React 性能优化以及数栈产品内的实践。

如何设计组件会有好的性能?

先看下面一张图:

关于 React 性能优化和数栈产品中的实践

这是一颗 React 组件树,App 下面有三个子组件,分别是 HeaderContentFooter,在 Content组件下面又分别有 FolderTreeWorkBenchSiderBar三个子组件,现在如果在 WorkBench 中触发一次更新,那么 React 会遍历哪些组件呢?Demo1

关于 React 性能优化和数栈产品中的实践

function FolderTree() {
  console.log('render FolderTree');
  return <p>folderTree</p>;
}

function SiderBar() {
  console.log('render siderBar');
  return <p>i'm SiderBar</p>;
}

export const WorkBenchGrandChild = () => {
  console.log('render WorkBenchGrandChild');
  return <p>i'm WorkBenchGrandChild</p>
};

export const WorkBenchChild = () => {
  console.log('render WorkBenchChild');
  return (
    <>
      <p>i'm WorkBenchChild</p>
      <WorkBenchGrandChild />
    </>
  );
};

function WorkBench() {
  const [num, setNum] = useState<number>(1);
  console.log('render WorkBench');
  return (
    <>
      <input
        value={num}
        onChange={(e) => {
          setNum(+e.target.value || 0);
        }}
      />
      <p>num is {num}</p>
      <WorkBenchChild />
    </>
  );
}


function Content() {
  console.log('render content');
  return (
    <>
      <FolderTree />
      <WorkBench />
      <SiderBar />
    </>
  );
};

function Footer() {
  console.log('render footer');
  return <p>i'm Footer</p>
};


function Header() {
  console.log('render header');
  return <p>i'm Header</p>;
}


// Demo1
function App() {
  // const [, setStr] = useState<string>();
  return (
    <>
      <Header />
      <Content />
      <Footer />
      {/* <input onChange={(e) => { setStr(e.target.value) }} /> */}
    </>
  );
};

关于 React 性能优化和数栈产品中的实践

根据上面断点和日志就可以得到下面的结论:

  1. 子孙组件每触发一次更新,React都会重新遍历整颗组件树

input 输入数字,引起 updateNum变更状态后,react-dombeginWorkcurrent由顶层组件依次遍历

  1. React更新时会过滤掉未变化的组件,达到减少更新的组件数的目的

在更新过程中,虽然 React重新遍历了组件树,但 没有打印没有变化的 HeaderFooterFolderTreeSiderBar组件内的日志

  1. 父组件状态变化,会引起子组件更新

WorkBenchChild属于 WorkBench的子组件,虽然 WorkBenchChild没有变化,但仍被重新渲染,打印了输入日志,如果更近一步去断点会发现 WorkBenchChildoldPropsnewProps是不相等的,会触发 updateFunctionComponent更新。

综上我们可以得出一个结论,就是 React自身会有一些性能优化的操作,会尽可能只更新变化的组件,比如 Demo1 中 WorkBenchWorkBenchChildWorkBenchGrandChild组件,而会绕开 不变的 HeaderFooter等组件,那么尽可能的让 React更新的粒度就是性能优化的方向,既然尽可能只更新变化的组件,那么如何定义组件是否变化?

如何定义组件是否变化?

React是以数据驱动视图的单向数据流,核心也就是数据,那么什么会影响数据,以及数据的承载方式,有以下几点:

  • props
  • state
  • context
  • 父组件不变!

父组件与当前组件其实没有关联性,放到这里是因为,上面的例子中 WorkBenchChild组件中没有 state、props、context,理论上来说就不变,实际上却重新 render 了,因为 其父组件 WorkBench有状态的变动,所以这里也提了一下,在不使用性能优化 API 的前提下,只要保证 props、state、context & 其父组件不变,那么组件就不变

还是回到刚刚的例子 Demo WorkBench

export const WorkBenchGrandChild = () => {
  console.log('render WorkBenchGrandChild');
  return <p>i'm WorkBenchGrandChild</p>
};

export const WorkBenchChild = () => {
  console.log('render WorkBenchChild');
  return (
    <>
      <p>i'm WorkBenchChild</p>
      <WorkBenchGrandChild />
    </>
  );
};

function WorkBench() {
  const [num, setNum] = useState<number>(1);
  console.log('render WorkBench');
  return (
    <>
      <input
        value={num}
        onChange={(e) => {
          setNum(+e.target.value || 0);
        }}
      />
      <p>num is {num}</p>
      <WorkBenchChild />
    </>
  );
}

export default WorkBench;

看一下这个 demoWorkBench组件有一个 num状态,还有一个 WorkBenchChild的子组件,没有状态,纯渲染组件,同时 WorkBenchChild组件也有一个 纯渲染组件 WorkBenchGrandChild子组件,当输入 input改变 num的值时,WorkBenchChild组件 和 WorkBenchGrandChild组件都重新渲染。我们来分析一下在 WorkBench 组件中,它的子组件 WorkBenchChild 自始至终其实都没有变化,有变化的其实是 WorkBench 中的 状态,但是就是因为 WorkBench 中的 状态发生了变化,导致了其子组件也一并更新,这就带来了一定的性能损耗,找到了问题,那么就需要解决问题。

如何优化?

使用性能优化 API

export const WorkBenchGrandChild = () => {
  console.log('render WorkBenchGrandChild');
  return <p>i'm WorkBenchGrandChild</p>
};

export const WorkBenchChild = React.memo(() => {
  console.log('render WorkBenchChild');
  return (
    <>
      <p>i'm WorkBenchChild</p>
      <WorkBenchGrandChild />
    </>
  );
});

// Demo WorkBench
function WorkBench() {
  const [num, setNum] = useState<number>(1);
  console.log('render WorkBench');
  return (
    <>
      <input
        value={num}
        onChange={(e) => {
          setNum(+e.target.value || 0);
        }}
      />
      <p>num is {num}</p>
      <WorkBenchChild />
    </>
  );
}

export default WorkBench;

关于 React 性能优化和数栈产品中的实践

关于 React 性能优化和数栈产品中的实践

我们可以使用 React.memo()包裹 WorkBenchChild组件,在其 diff的过程中 props改为浅对比的方式达到性能优化的目的,通过断点可以知道 通过 memo包裹的组件在 diffoldPropsnewProps仍然不等,进入了 updateSimpleMemoComponent中了,而 updateSimpleMemoComponent 中有个 shallowEqual浅比较方法是结果相等的,因此没有触发更新,而是复用了组件。

状态隔离(将状态隔离到子组件中)

function ExchangeComp() {
  const [num, setNum] = useState<number>(1);
  console.log('render ExchangeComp');
  return (
    <>
      <input
        value={num}
        onChange={(e) => {
          setNum(+e.target.value || 0);
        }}
      />
      <p>num is {num}</p>
    </>
  );
};

// Demo WorkBench
function WorkBench() {
  // const [num, setNum] = useState<number>(1);
  console.log('render WorkBench');
  return (
    <>
      <ExchangeComp />
      <WorkBenchChild />
    </>
  );
}

export default WorkBench;

关于 React 性能优化和数栈产品中的实践

关于 React 性能优化和数栈产品中的实践

上面 Demo1 的结论,父组件更新,会触发子组件更新,就因为 WorkBench状态改变,导致 WorkBenhChild也更新了,这个时候可以手动创造条件,让 WorkBenchChild的父组件也就是 WorkBench组件剥离状态,没有状态改变,这种情况下 WorkBenchChild 满足了 父组件不变的前提,且没有 statepropscontext,那么也能够达到性能优化的结果。

对比

  1. 结果一样,都是对 WorkBenchChild进行了优化,在 WorkBench组件更新时, WorkBenchChildWorkBenchGrandChild没有重新渲染
  2. 出发点不一样,用 memo 性能优化 API 是直接作用到子组件上面,而状态隔离是在父组件上面操作,而受益的是其子组件

结论

  1. 只要结构写的好,性能不会太差
  2. 父组件不变,子组件可能不变

性能优化方向

  1. 找到项目中性能损耗严重的组件(节点)

在业务项目中,找到卡顿、崩溃 的组件(节点)

  1. 在根组件(节点)上使用性能优化 API

在根组件上使用的目的就是避免其祖先组件如果没有做好组件设计会给根组件带来无效的重复渲染,因为上面提到的,父组件更新,子组件也会更新

  1. 在其他节点上使用 状态隔离的方式进行优化

优化祖先组件,避免给子组件造成无效的重复渲染

总结

我们从 组件结构 和 性能优化 API 上介绍了性能优化的两种不同的优化方式,在实际项目使用上,也并非使用某一种优化方式,而是多种优化方式结合着来以达到最好的性能

产品中的部分实践

  1. 将状态隔离到子组件内部,避免引起不必要的更新

    import React, { useCallback, useEffect, useState } from 'react';
    import { connect } from 'react-redux';
    import type { SelectProps } from 'antd';
    import { Select } from 'antd';
    
    import { fetchBranchApi } from '@/api/project/optionsConfig';
    
    const BranchSelect = (props: SelectProps) => {
    	const [list, setList] = useState<string[]>([]);
    	const [loading, setLoading] = useState<boolean>(false);
    	const { projectId, project, tenantId, ...otherProps } = props;
    	const init = useCallback(async () => {
    		try {
    			setLoading(true);
    			const { code, data } = await fetchBranchApi(params);
    			if (code !== 1) return;
    			setList(data);
    		} catch (err) {
    		} finally {
    			setLoading(false);
    		}
    	}, []);
    	useEffect(() => {
    		init();
    	}, [init]);
    
    	return (
    		<Select
    			showSearch
    			optionFilterProp="children"
    			filterOption={(input, { label }) => {
    				return ((label as string) ?? '')
    					?.toLowerCase?.()
    					.includes?.(input?.toLowerCase?.());
    			}}
    			options={list?.map((value) => ({ label: value, value }))}
    			loading={loading}
    			placeholder="请选择代码分支"
    			{...otherProps}
    			/>
    	);
    };
    
    export default React.memo(BranchSelect);
    

    比如在中后台系统中很多表单型组件 SelectTreeSelectCheckbox,其展示的数据需要通过接口获取,那么此时,如果将获取数据的操作放到父组件,那么每次请求数据不仅会导致需要数据的那个表单项组件更新,同时,其他的表单项也会更新,这就有一定的性能损耗,那么按照上面的例子这样将其状态封装到内部,避免请求数据影响其他组件更新,就可以达到性能优化的目的,一般建议在外层再加上 memo性能优化 API,避免因为外部组件影响内部组件更新。

  2. Canvas render & Svg render

    关于 React 性能优化和数栈产品中的实践

    // 画一个小十字
    export function createPlus(
    		point: { x: number; y: number },
    		{ radius, lineWidth, fill }: { radius: number; lineWidth: number; fill: string }
    ) {
    		// 竖 横
    		const colWidth = point.x - (1 / 2) * lineWidth;
    		const colHeight = point.y - (1 / 2) * lineWidth - radius;
    		const colTop = 2 * radius + lineWidth;
    		const colBottom = colHeight;
    		const rowWidth = point.x - (1 / 2) * lineWidth - radius;
    		const rowHeight = point.y - (1 / 2) * lineWidth;
    		const rowRight = 2 * radius + lineWidth;
    		const rowLeft = rowWidth;
    		return `
    				<path d="M${colWidth} ${colHeight}h${lineWidth}v${colTop}h-${lineWidth}V${colBottom}z" fill="${fill}"></path>
    				<path d="M${rowWidth} ${rowHeight}h${rowRight}v${lineWidth}H${rowLeft}v-${lineWidth}z" fill="${fill}"></path>
    		`;
    }
    
    
    renderPlusSvg = throttle(() => {
    	const plusBackground = document.getElementById(`plusBackground_${this.randomKey}`);
    	const { scrollTop, scrollLeft, clientHeight, clientWidth } = this._container || {};
    	const minWidth = scrollLeft;
    	const maxWidth = minWidth + clientWidth;
    	const minHeight = scrollTop;
    	const maxHeight = minHeight + clientHeight;
    	const stepping = 30;
    	const radius = 3;
    	const fillColor = '#EBECF0';
    	const lineWidth = 1;
    	let innerHtml = '';
    	try {
    		// 根据滚动情况拿到容器的四个坐标点, 只渲染当前滚动容器内的十字,实时渲染
    		for (let x = minWidth; x < maxWidth; x += stepping) {
    			for (let y = minHeight; y < maxHeight; y += stepping) {
    				// 画十字
    				innerHtml += createPlus({ x, y }, { radius, fill: fillColor, lineWidth });
    			}
    		}
    		plusBackground.innerHTML = innerHtml;
    	} catch (e) {}
    });
    

    问题源于在大数据情况下,由 canvas 渲染的 小十字背景渲染失败,经测试,业务数据在 200条左右 canvas 画布绘制宽度就已经达到了 70000px,需要渲染的小十字 数量级在 10w 左右,canvas 不适合绘制尺寸过大的场景(超过某个阀值就会出现渲染失败,具体阀值跟浏览器有关系),而 svg 不适合绘制数量过多的场景,目前的业务场景却是 画布尺寸大,绘制元素多,后面的解决方式就是 采用 svg 渲染,将 画布渲染出来,同时监听容器的滚动事件,同时只渲染滚动容器中可视区域内的背景,实时渲染,渲染数量在 100 左右,实测就无卡顿现象,问题解决

参考:

  1. React 性能优化的一切
  2. React 源码解析之 Fiber渲染
  3. 魔术师卡颂

最后

欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈UED团队持续为广大开发者分享技术成果,相继参与开源了欢迎star文章来源地址https://www.toymoban.com/news/detail-711034.html

  • 大数据分布式任务调度系统——Taier
  • 轻量级的 Web IDE UI 框架——Molecule
  • 针对大数据领域的 SQL Parser 项目——dt-sql-parser
  • 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
  • 一个速度更快、配置更灵活、使用更简单的模块打包器——ko

到了这里,关于关于 React 性能优化和数栈产品中的实践的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SHA-512在Go中的实战应用: 性能优化和安全最佳实践

    在当今数字化的世界中,数据安全已成为软件开发的核心议题之一。特别是在数据传输和存储过程中,保护数据不被未经授权的访问和篡改是至关重要的。为了达到这一目的,开发者们经常依赖于强大的哈希算法来加强数据的安全性。SHA-512,作为一种被广泛认可和使用的安全

    2024年02月20日
    浏览(35)
  • 理解React页面渲染原理,如何优化React性能?

    当使用React编写应用程序时,可以使用JSX语法来描述用户界面的结构。JSX是一种类似于HTML的语法,但实际上它是一种JavaScript的扩展,用于定义React元素。React元素描述了我们想要在界面上看到的内容和结构。 在运行React应用程序时,JSX会被转换成真实的DOM元素,这个过程主要

    2024年02月08日
    浏览(29)
  • React Native性能优化指南

    本文将介绍在React Native开发中常见的性能优化问题和解决方案,包括ScrollView内无法滑动、热更新导致的文件引用问题、高度获取、强制横屏UI适配、低版本RN适配iOS14、缓存清理、navigation参数取值等。通过代码案例演示和详细说明,帮助开发者更好地理解和解决React Native中的

    2024年01月23日
    浏览(36)
  • 5.React.memo 性能优化

    性能优化, React.memo 2. React.memo类似纯组件,可提高组件性能表现(类组件PureComponent)

    2024年02月11日
    浏览(26)
  • 【React】组件性能优化、高阶组件

    React更新机制 ◼ React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树。 ◼ React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI: ​  如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n³),其

    2024年01月23日
    浏览(31)
  • React组件设计之性能优化篇

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 本文作者:空山 由于笔者最近在开发中遇到了一个重复渲染导致子组件状态值丢失的问题,因此关于性能优化做了以下的分析,欢迎大

    2024年02月16日
    浏览(35)
  • React Hooks ——性能优化Hooks

    Hooks从语法上来说是一些函数。这些函数可以用于在函数组件中引入状态管理和生命周期方法。 简洁 从语法上来说,写的代码少了 上手非常简单 基于函数式编程理念,只需要掌握一些JavaScript基础知识 与生命周期相关的知识不用学,react Hooks使用全新的理念来管理组件的运作

    2024年02月06日
    浏览(33)
  • React性能优化之Memo、useMemo

    React 的渲染机制,组件内部的 state 或者 props 一旦发生修改,整个组件树都会被重新渲染一次,即时子组件的参数没有被修改,甚至无状态组件 会造成性能浪费 React.memo 是 React 官方提供的一个高阶组件,用于缓存我们的需要优化的组件 React 中的组件被设计为在状态或 props 值

    2024年02月14日
    浏览(26)
  • React性能优化之memo缓存函数

    React是一个非常流行的前端框架,但是在处理大型应用程序时,性能可能会成为一个问题。为了解决这个问题,React提供了一个称为memo的功能,它可以缓存函数并避免不必要的重新渲染。 memo是React中的一个高阶组件(HOC),它接收一个组件并返回一个新的组件。这个新组件具

    2024年02月11日
    浏览(30)
  • React Hook之useCallback 性能优化

    上文 对比之前的组件优化说明React.memo的作用我们说了 React.memo的妙用 但是 它却并非万能 我们来看这个情况 我们子组件代码编写如下 这里 我们接收了父组件 props中的一个 dom1funt 函数 然后点击dom1funt按钮 触发这个dom1funt 然后 父组件代码编写如下 父组件 我们定义了这个传给

    2024年02月11日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包