从 Preact 源码一窥 React 原理(三):组件

本文是《从Preact源码一窥React原理》系列的第三篇,主要探讨了Preact中的组件,包括Component类、组件渲染、forceUpdate和setState函数,以及Context API。文章详细解释了Component类的属性、组件的创建与渲染过程,以及如何通过forceUpdate和setState进行组件更新。此外,还介绍了Legacy Context和New Context的使用与实现原理。
摘要由CSDN通过智能技术生成

系列文章:

  1. 从 Preact 源码一窥 React 原理(一):JSX 渲染
  2. 从 Preact 源码一窥 React 原理(二):Diff 算法
  3. 从 Preact 源码一窥 React 原理(三):组件(本文)

前言

本文大抵是《从 Preact 源码一窥 React 原理》系列的收尾篇了,算上这一篇的组件原理 Preact 的核心源码中的部分也已经基本涵盖了。Preact 中的诸多细节自然是无法面面俱到的,还请诸位自行阅读罢。
剩余还有 Hook 机制、Preact 新增的 Linked State 特性等等,由于其未被 Preact 的核心所包含,因此可能会放在后续进行介绍(有缘再见吧 )。

组件

React 组件是一部分独立的、且可复用的 UI 单元,根据应用方式的不同可以分为类组件以及函数组件。
以下是 Preact 中组件渲染的一个示例:

import {
    h, render, Component } from 'preact';

// 类组件
class Clock extends Component {
   
	render() {
   
		let time = new Date();
		return <time datetime={
   time.toISOString()}>{
    time.toLocaleTimeString() }</time>;
	}
}

// 函数组件
function Clock () {
   
	let time = new Date();
	return <time datetime={
   time.toISOString()}>{
    time.toLocaleTimeString() }</time>;
}

// render an instance of Clock into <body>:
render(<Clock />, document.body);

如果你曾使用过 React 进行开发,那么你对上述的代码应当很熟悉了。让我们首先着眼于类组件,可以看到,所有的类组件都必须由 Component 基类派生而来。
这也就引出了我们所讨论的第一个重点:Component 类。

Component 类

Preact 中 Component 类的定义没有使用 ES6 语法的 class (尽管在其他代码中使用了 ES6 的相关语法),而是使用了 ES5 的方式。
由于 JavaScript 的灵活性,Component 中的属性在构造函数以外动态添加,因此 Preact 贴心的为 Component 中的几个属性提供了注释,其代码如下所示:

export function Component(props, context) {
   
	this.props = props;
	this.context = context;
	// this.constructor // When component is functional component, this is reset to functional component
	// if (this.state==null) this.state = {};
	// this.state = {};
	// this._dirty = true;
	// this._renderCallbacks = []; // Only class components

	// Other properties that Component will have set later,
	// shown here as commented out for quick reference
	// this.base = null;
	// this._context = null;
	// this._ancestorComponent = null; // Always set right after instantiation
	// this._vnode = null;
	// this._nextState = null; // Only class components
	// this._prevVNode = null;
	// this._processingException = null; // Always read, set only when handling error
}

其中,各个属性值各自的含义为:

  • props:组件接收的 props 值;
  • context:组件获得的 context 值,根据其使用的 API 不同可能是 Legacy Context 的值或是 New Context 的值;
  • state:组件内部状态;
  • _dirty:标记组件内部状态是否发生改变,也即是组件是否需要进行重新 Diff;
  • _renderCallbacks:组件渲染完成的回调函数;
  • base:调用 render 函数渲染之后得到的真实 DOM;
  • _context:保存了自父组件获得的 Legacy Context 值;
  • _ancestorComponent:最近的父组件;
  • _vnode:组件实例所对应的 VNode;
  • _nextState:在 Diff 执行前,暂时保存组件 state 变化后的值;
  • _prevVNode:使用 render 函数之后得到的 VNode
  • _processingExecption:需要由组件实例处理的上抛的错误;
  • _depth:在 Component 类中未提及,但在 diff 函数中被使用,代表组件的深度;
  • _parentDom:同样在 Component 类中未提及,但在 diff 函数中被使用,代表组件的父真实 DOM。

组件渲染

在上文《从 Preact 源码一窥 React 原理(二):Diff 算法》中我们了解了 Diff 算法的原理以及具体实现,但是略过了 diff 函数中关于组件的部分代码。现在我们就将这部分代码补上:

export function diff(dom, parentDom, newVNode, oldVNode, context, isSvg, excessDomChildren, mounts, ancestorComponent, force, oldDom) {
   
	// If the previous type doesn't match the new type we drop the whole subtree
	// 如果 oldVnode 与 newVnode 的类型或者 key 值不匹配则将整个原先的 DOM 树抛弃
	if (oldVNode==null || newVNode==null || oldVNode.type!==newVNode.type || oldVNode.key!==newVNode.key) {
   
		if (oldVNode!=null) unmount(oldVNode, ancestorComponent);
		if (newVNode==null) return null;
		dom = null;
		oldVNode = EMPTY_OBJ;
	}

	if (options.diff) options.diff(newVNode);

	let c, p, isNew = false, oldProps, oldState, snapshot,
		newType = newVNode.type;

	/** @type {import('../internal').Component | null} */
	let clearProcessingException;

	try {
   
		// outer: 是标签语法,较为少见,详见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/label
		// VNode 类型为 Fragment ,其为子节点的聚合,无需向 DOM 添加额外节点
		outer: if (oldVNode.type===Fragment || newType===Fragment) {
   
			balabala...
		}
		// VNode 类型为类组件或者函数组件
		else if (typeof newType==='function') {
   
			// PART 1
			// 判断组件是否使用了 New Context API,当使用了 New Context API 则从 provider 获取 context 值,
			// 否则从 diff 函数的参数中获取从父组件传递的 context 值。
			let cxType = newType.contextType;
			let provider = cxType && context[cxType._id];
			let cctx = cxType != null ? (provider ? provider.props.value : cxType._defaultValue) : context;

			// PART 2
			// 当 oldVNode 中已经存在 Component 的实例时,将该实例赋值给 newVNode
			if (oldVNode._component) {
   
				
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值