前端跳槽突围课:React18底层源码深入剖析

2024-05-06 14:26:45 浏览数 (2)

在当下就业环境下,只会框架的使用是不够的,这是很多初级工程师面临的困境。想要脱颖而出,成为具备手写框架、源码贡献等技能的高手,才能拥有更多的机会。想要这种摆脱框架黑盒状态。本文不仅可以深入学习React工作原理,还可以掌握源码调试技巧,手写框架的实践,以及成为源码Contributor的方法论,为你提供方位的指导和实践,助力成为一个真正有实力的高级技术人才 。

一、什么是 React

官方定义:用于构建用户界面的 JavaScript 库

基于函数式思想,践行代数效应(为了解决副作用)的 React,view = function(data) (react可能会产生副作用,函数里面的操作会影响外面)

原理:调度&VDOM (浏览器的资源是很稀缺的)

组件化

Learn Once, Write Anywhere

二、React 解决了什么问题

• CPU 的瓶颈

当有大量任务的时候,会造成一个卡顿。js线程和渲染线程的一个互斥,比如渲染3000个节点,使用js的情况下 比使用呢react 渲染慢,一个每秒 60 帧刷新率的设备,1帧的时间是 1000/60 ≈ 16.7ms。

三、React 为什么需要并发

我们都知道,js是单线程语言,同一时间只能执行一件事情。这样就会导致一个问题,如果有一个耗时任务占据了线程,那么后续的执行内容都会被阻塞。比如下面这个例子:

<button id="btn" onclick="handle()">点击按钮</button>

<script> // 用户点击事件回调

function handle() {

console.log('click 事件触发 ')

}

// 耗时任务,一直占用线程,阻塞了后续的用户行为

function render() {

for (let i = 0; i < 10 ** 5; i ) {

console.log(i)

}

}

window.onload = function () {

render()

} </script>

当我们点击按钮时,由于render函数一直在执行,所以handle回调迟迟没有执行。对于用户来讲,界面是卡死且无法交互的。

如果我们把这个例子中的render函数类比成React的更新过程:即setState触发了一次更新,而这次更新耗时非常久,比如200ms。那么在这200ms的时间内界面是卡死的,用户无法进行交互,非常影响用户的使用体验。如下图所示,200ms内浏览器的渲染被阻塞,且用户的click事件回调也被阻塞。

四、Demo

我们使用 create-react-app 这个官方脚手架创建一个 React 项目,然后将 index.js 这个文件修改为以下代码

import { createRoot } from 'react-dom/client';

function App() {

return <h1>Hello dan!!!</h1>

}

const root = createRoot(document.getElementById('root'))

root.render(<App></App>)

执行 npm start 这个脚本,如果你看到这个非常简单(丑陋)的页面显示出来,那证明项目已经可以正常运行起来了

我们可以将react更新看作一个任务,click事件看作一个任务。在并发的情况下,react更新到一半的时候,进来了click任务,这个时候先去执行click任务。等click任务执行完成后,接着继续执行剩余的react更新。这样就保证了即使在耗时更新的情况下,用户依旧是可以进行交互的(interactive)。

虽然这个想法看上去非常不错,但是实现起来就有点困难了。比如更新到一半时怎么中断?更新中断了又怎么恢复呢?如果click又触发了react更新不就同时存在了两个更新了吗,它们的状态怎么区分?等等各种问题。

虽然很困难,但React18确实做到了这一点:

Concurrency is not a feature, per se. It’s a new behind-the-scenes mechanism that enables React to prepare multiple versions of your UI at the same time.

正如官网中描述的:并发是一种新的幕后机制,它允许在同一时间里,准备多个版本的UI,即多个版本的更新,也就是前面我们提到的并发。

五、客户端渲染 API 的更新

当你第一次安装 React 18 的时候,控制台会出现如下警告:

ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it’s running React 17.

React 18 引入了一个新的 root API,它提供了更好的操作根节点的方式。新的 root API 还启用了新的并发渲染器,使开发者能够选择使用并发特性。

// 之前

import { render } from 'react-dom';

const container = document.getElementById('app');

render(<App tab="home" />, container);

// 现在

import { createRoot } from 'react-dom/client';

const container = document.getElementById('app');

const root = createRoot(container); // 如果你使用 TypeScript,请使用 createRoot(container!)

root.render(<App tab="home" />);

我们也已经将 unmountComponentAtNode 修改为 root.unmount:

// 之前

unmountComponentAtNode(container);

// 现在

root.unmount();

我们从 render 中移除了回调函数,因为当使用 Suspense 的时候通常不是预期的结果:

// 之前

const container = document.getElementById('app');

render(<App tab="home" />, container, () => {

console.log('rendered');

});

// 现在

function AppWithCallbackAfterRender() {

useEffect(() => {

console.log('rendered');

});

return <App tab="home" />

}

const container = document.getElementById('app');

const root = createRoot(container);

root.render(<AppWithCallbackAfterRender />);

0 人点赞