React 整体感知

当我们由浅入深地认知一样新事物的时候,往往需要遵循 Why > What > How 这样一个认知过程。它们是相辅相成、缺一不可的。而了解了具体的 What 和 How 之后,往往能够更加具象地回答理论层面的 Why,因此,在进入 Why 的探索之前,我们先整体感知一下 What 和 How 两个过程。
What
打开 React 官网,第一眼便能看到官方给出的回答。
React 是用于构建用户界面的 JavaScript 库。
不知道你有没有想过,构建用户界面的方式有千百种,为什么 React 会突出?站长交易同样,我们可以从 React 哲学里得到回应。
我们认为, React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。
可见,关键是实现了 快速响应 ,那么制约 快速响应 的因素有哪些呢?React 是如何解决的呢?
How
让我们带着上面的两个问题,在遵循真实的React代码架构的前提下,实现一个包含时间切片、fiber、Hooks的简易 React,并舍弃部分优化代码和非必要的功能,将其命名为 HuaMu。
注意:为了和源码有点区分,函数名首字母大写,源码是小写。
CreateElement 函数
在开始之前,我们先简单的了解一下JSX,如果你感兴趣,可以关注下一篇《JSX背后的故事》。
JSX会被工具链Babel编译为React.createElement(),接着React.createElement()返回一个叫作React.Element的JS对象。
这么说有些抽象,通过下面demo看下转换前后的代码:
// JSX 转换前const el =

HuaMu

;
// 转换后的 JS 对象const el = {
type:“h1”,
props:{
title:“el_title”,
children:“HuaMu”,
}
}
可见,元素是具有 type 和 props 属性的对象,而 CreateElement 函数的主要任务就是创建该对象。
/**

  • @param {string} type HTML标签类型
  • @param {object} props 具有JSX属性中的所有键和值
  • @param {string | array} children 元素树
    */function CreateElement(type, props, …children) {
    return {
    type,
    props:{
    …props,
    children,
    }
    }
    }
    说明:我们将剩余参数赋予children,扩展运算符用于构造字面量对象props,对象表达式将按照 key-value 的方式展开,从而保证 props.children 始终是一个数组。接下来,我们一起看下 demo:
    CreateElement(“h1”, {title:“el_title”}, ‘hello’, ‘HuaMu’)
    // 返回的 JS 对象
    {
    “type”: “h1”,
    “props”: {
    “title”: “el_title” // key-value
    “children”: [“hello”, “HuaMu”] // 数组类型
    }
    }
    注意:当 …children 为空或为原始值时,React 不会创建 props.children,但为了简化代码,暂不考虑性能,我们为原始值创建特殊的类型TEXT_EL。
    function CreateElement(type, props, …children) {
    return {
    type,
    props:{
    …props,
    children: children.map(child => typeof child === “object” ? child : CreateTextElement(child))
    }
    }
    }
    function CreateTextElement(text) {
    return {
    type: “TEXT_EL”,
    props: {
    nodeValue: text,
    children: []
    }
    }
    }
    Render 函数
    CreateElement 函数将标签转化为对象输出,接着 React 进行一系列处理,Render 函数将处理好的节点根据标记进行添加、更新或删除内容,最后附加到容器中。下面简单的实现 Render 函数是如何实现添加内容的:

首先创建对应的DOM节点,然后将新节点附加到容器中,并递归每个孩子节点做同样的操作。

将元素的 props 属性分配给节点。

function Render(el,container) {
// 创建节点
const dom = el.type === 'TEXT_EL'? document.createTextNode("") : document.createElement(el.type);
el.props.children.forEach(child => Render(child, dom))
// 为节点分配 props 属性
const isProperty = key => key !== 'children';
const setProperty = name => dom[name] = el.props[name];
Object.keys(el.props).filter(isProperty).forEach(setProperty)
container.appendChild(dom);
}

注意:文本节点使用textNode而不是innerText,是为了保证以相同的方式对待所有的元素 。

到目前为止,我们已经实现了一个简易的用于构建用户界面的 JavaScript 库。现在,让 Babel 使用自定义的 HuaMu 代替 React,将 /** @jsx HuaMu.CreateElement */ 添加到代码中,打开 codesandbox看看效果吧。
并发模式
在继续向下探索之前,我们先思考一下上面的代码中,有哪些代码制约 快速响应 了呢?
是的,在Render函数中递归每个孩子节点,即这句代码el.props.children.forEach(child => Render(child, dom))存在问题。一旦开始渲染,便不会停止,直到渲染了整棵元素树,我们知道,GUI渲染线程与JS线程是互斥的,JS脚本执行和浏览器布局、绘制不能同时执行。如果元素树很大,JS脚本执行时间过长,可能会阻塞主线程,导致页面掉帧,造成卡顿,且妨碍浏览器执行高优作业。
那如何解决呢?
通过时间切片的方式,即将任务分解为多个工作单元,每完成一个工作单元,判断是否有高优作业,若有,则让浏览器中断渲染。下面通过requestIdleCallback模拟实现:
简单说明一下:

window.requestIdleCallback(cb[, options]) :浏览器将在主线程空闲时运行回调。函数会接收到一个IdleDeadline的参数,这个参数可以获取当前空闲时间(timeRemaining)以及回调是否在超时前已经执行的状态(didTimeout)。

React 已不再使用requestIdleCallback,目前使用 scheduler package。但在概念上是相同的。

依据上面的分析,代码结构如下:
// 当浏览器准备就绪时,它将调用 WorkLoop
requestIdleCallback(WorkLoop)
let nextUnitOfWork = null;
function PerformUnitOfWork(nextUnitOfWork) {
// TODO
}
function WorkLoop(deadline) {
// 当前线程的闲置时间是否可以在结束前执行更多的任务
let shouldYield = false;
while(nextUnitOfWork && !shouldYield) {
nextUnitOfWork = PerformUnitOfWork(nextUnitOfWork) // 赋值下一个工作单元
shouldYield = deadline.timeRemaining() < 1; // 如果 idle period 已经结束,则它的值是 0
}
requestIdleCallback(WorkLoop)
}
我们在 PerformUnitOfWork 函数里实现当前工作的执行并返回下一个执行的工作单元,可下一个工作单元如何快速查找呢?让我们初步了解 Fibers 吧。
Fibers
为了组织工作单元,即方便查找下一个工作单元,需引入fiber tree的数据结构。即每个元素都有一个fiber,链接到其第一个子节点,下一个兄弟姐妹节点和父节点,且每个fiber都将成为一个工作单元。
// 假设我们要渲染的元素树如下const el = (

)
function UseState(initial) {
const oldHook =
	wipFiber.alternate &&
	wipFiber.alternate.hooks &&
	wipFiber.alternate.hooks[hookIndex]
const hook = {
	state: oldHook ? oldHook.state : initial,
}

wipFiber.hooks.push(hook)
hookIndex++
return [hook.state]
}

UseState还需返回一个可更新状态的函数,因此,需要定义一个接收action的setState函数。

将action添加到队列中,再将队列添加到fiber。

在下一次渲染时,获取old hook的action队列,并代入new state逐一执行,以保证返回的状态是已更新的。

在setState函数中,执行跟Render函数类似的操作,将currentRoot设置为下一个工作单元,以便开始新的渲染。

function UseState(initial) {
...
const hook = {
	state: oldHook ? oldHook.state : initial,
	queue: [],
}
const actions = oldHook ? oldHook.queue : []
actions.forEach(action => {
	hook.state = action(hook.state)
})
const setState = action => {
	hook.queue.push(action)
	wipRoot = {
	dom: currentRoot.dom,
	props: currentRoot.props,
	alternate: currentRoot,
	}
	nextUnitOfWork = wipRoot
	deletions = []
}

wipFiber.hooks.push(hook)
hookIndex++
return [hook.state, setState]
}

现在,我们已经实现一个包含时间切片、fiber、Hooks 的简易 React。打开codesandbox看看效果吧。
结语
到目前为止,我们从 What > How 梳理了大概的 React 知识链路,后面的章节我们对文中所提及的知识点进行 Why 的探索,相信会反哺到 What 的理解和 How 的实践。

*** 开发的一个用于构建用户界面的 JavaScript 库。它采用组件化的开发模式,使得构建复杂的 UI 变得简单、高效。 要学习 React,你可以按照以下步骤进行: 1. 掌握 JavaScript:React 是基于 JavaScript 的,所以你需要熟悉 JavaScript 的语法、特性和基本原理。 2. 学习 HTML 和 CSS:React 主要用于构建用户界面,所以你需要对 HTML 和 CSS 有一定的了解。 3. 了解 React 的基本概念:学习 React 的前提是要了解其核心概念,如组件、状态、属性等。 4. 安装和配置 React:在开始使用 React 之前,需要先安装和配置开发环境,可以使用 create-react-app 脚手架工具快速搭建一个 React 项目。 5. 学习 JSX:JSX 是一种类似于 HTML 的语法扩展,用于在 JavaScript 中编写 React 组件。学习 JSX 将帮助你更好地理解和编写 React 组件。 6. 编写 React 组件:掌握 React 组件的生命周期、状态管理和事件处理等方面的知识,开始编写自己的 React 组件。 7. 理解 React 路由:React Router 是一个流行的第三方库,用于实现前端路由。了解和使用 React Router 将帮助你构建复杂的单页应用。 8. 掌握 React 生态系统:学习并使用一些常用的 React 相关库,如 Redux(状态管理)、Axios(网络请求)、React Bootstrap(UI 组件库)等,可以提高开发效率。 9. 实践项目:通过实践项目,将学到的知识应用到实际中,加深对 React 的理解和掌握。 10. 持续学习和探索:React 生态系统在不断发展,新的技术和工具层出不穷。要保持学习的状态,关注最新的 React 动态,并不断进行实践和探索。 希望以上步骤对你的 React 学习有所帮助!如果有更多关于 React 的问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值