React18原理: 时间分片技术选择

渲染1w个节点的不同方式

1 )案例1:一次渲染1w个节点

html 复制代码
<div id='root'><div>

<script type="text/javascript">
	function randomHexColor() {
		return "#" + ("0000"+ (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
	}

	setTimeout(function() {
		var k = 0;
		var root = document.getElementById("root");
		for(var i = 0; i < 10000; i++) {
			k += new Date - 0 ;
			var el = document.createElement("div");
			el.innerHTML = k;
			root.appendChild(el);
			el.style.cssText = `background: ${randomHexColor()};height: 40px`;
		}
	},1000);
</script>

1 次完成 1w 个节点的渲染

可以看到,圈中的部分,明显被阻塞,这一块绝对会导致用户体验很差

2 )案例2: 1w 个节点分100次执行,每次执行100个

html 复制代码
<div id='root'><div>

<script type="text/javascript">
	var root = document.getElementById("root");

	function randomHexColor() {
		return "#" + ("0000"+ (Math.random() * 0x1000000 << 0).toString(16)).substr(-6);
	}

	function loop(n) {
		var k = 0;
		console.log(n);
		for(var i = 0; i < 100; i++) {
			k += new Date - 0 ;
			var el = document.createElement("div");
			el.innerHTML = k;
			root.appendChild(el);
			el.style.cssText = `background: ${randomHexColor()};height: 40px`;
		}
		if (n) {
			// 内部再调用 n-1次 loop,总计调用 n 次 loop
			setTimeout(function() {
				loop(n - 1);
			}, 40)
		}
	}

	// 1s 执行一次
	setTimeout(function() {
		loop(100)
	}, 1000);
</script>
  • 这里,40ms 执行一次,100次,共4s执行完成
  • 可以看到,没有明显的阻塞

为什么会有如此区别

  • 究其原因是因为浏览器是单线程,它将GUI描绘,时间器处理,事件处理,JS执行远程资源加载统统放在一起,当做某件事,只有将它做完才能做下一件事
  • 如果有足够的时间,浏览器是会对我们的代码进行编译优化(JIT) 及进行热代码优化,一些DOM操作,内部也会对reflow进行处理
  • reflow是一个性能黑洞,很可能让页面的大多数元素进行重新布局
  • 所以,浏览器歇一会儿跑一会儿,比一直跑性能更好

时间分片技术方案选择

1 )注意兼容问题

  • 注意,在 request18 版本并没有使用 requestIdleCalback, 仍旧因为兼容问题

2 ) 方案选择

  • 在浏览器环境中,常见的macro task有setTimeout、MessageChannel、postMessage
  • 而常见的 micro task 有 MutationObsever, Promise.then

2.1 微任务问题

  • 为什么不使用微任务呢?

  • 宏任务是在下一轮事件循环中执行,微任务是在当前时间循环中执行

  • 微任务将在页面更新前全部执行完,也就是是要在当前帧执行完的,所以达不到「将主线程还给浏览器」的目的

  • 使用微任务就会造成一个问题,在极端情况下,一直卡在当前帧,因为微任务是在当前帧执行完的(本轮事件循环内)

  • 所以在 setTimeout, MessageChannel, postMessage 三选一

  • 为什么不使用setTimeout(fn,0)呢?

    ts 复制代码
    function timer() {
      console.ltme('计时器')
      setTimeout(() => {
        console.timeEnd('计时器')
        timer()
      }, 0)
    }
    
    timer()
    • 递归的setTimeout()调用会使调用间隔变为4ms,导致浪费了4ms
    • 这是一个递归的定时器,在递归的时候,看下它的间隔时间
    • 在定时器递归大约4次以后,它的递归时间就变成了4ms~ 5ms 变得不准确
    • 明明我们指定的是 0ms, 这里差距有些大了,所以这个api有些问题,误差太大

2.2 宏任务问题

  • 为什么不使用 rAF() 呢?

    • 如果上次任务调度不是rAF()触发的,将导致在当前帧更新前进行两次任务调度
    • 页面更新的时间不确定,如果浏览器间隔了10ms才更新页面,那么这10ms就浪费了
  • React Scheduler使用MessageChannel的原因为:生成宏任务,实现:

    • 将主线程还给浏览器,以便浏览器更新页面

    • 浏览器更新页面后继续执行未完成的任务

      js 复制代码
      const { port1, port2 } = new MessageChannel();
      
      port1.onmessage = function (event){
        console.log('收到来自port2的消息:', event.data);//收到来自port2的消息: pong
      };
      
      port2.onmessage = function (event) {
        console.log('收到来自port1的消息:', event.data);//收到来自port1的消息: ping
        port2.postMessage('pong');
      };
      port1.postMessage('ping');
相关推荐
_OP_CHEN几秒前
【前端开发之CSS】(二)CSS 选择器终极指南:从基础到进阶,精准拿捏页面元素!
前端·css·html·网页开发·css选择器
ヤ鬧鬧o.1 分钟前
HTML安全密码备忘录
前端·javascript·css·html·css3
ヤ鬧鬧o.3 分钟前
小巧路径转换器优化
前端·javascript·css
阿宇爱吃鱼12 分钟前
uniapp input输入框,限制金额输入格式
前端·javascript·uni-app
Dreamy smile14 分钟前
JavaScript 实现 HTTPS SSE 连接
开发语言·javascript·https
coding随想15 分钟前
Web SQL Database API:一段被时代淘汰的浏览器存储技术
前端·数据库·sql
Marshmallowc16 分钟前
React 刷新页面 Token 消失?深度解析 Redux + LocalStorage 数据持久化方案与 Hook 避坑指南
javascript·react·数据持久化·redux·前端工程化
Dreamy smile20 分钟前
vue3 vite pinia实现动态路由,菜单权限,按钮权限
前端·javascript·vue.js
翱翔的苍鹰23 分钟前
智谱(Zhipu)大模型的流式使用 response.iter_lines() 逐行解析 SSE 流
服务器·前端·数据库
未来之窗软件服务28 分钟前
仙盟创梦IDE-集成开发测试:自动解析驱动的多线路自动化测试
前端·测试自动化·仙盟创梦ide·东方仙盟