Preact核心源码剖析:从createElement到渲染引擎
【免费下载链接】preact 项目地址: https://gitcode.com/gh_mirrors/pre/preact
本文深入解析Preact的核心实现机制,从JSX编译后的createElement函数开始,详细剖析VNode创建、Diff算法、组件生命周期、状态管理、事件系统和Refs处理等关键环节。通过源码分析和流程图展示,揭示Preact如何在保持轻量级的同时实现高效的虚拟DOM渲染和更新机制。
createElement与VNode创建机制详解
在Preact的虚拟DOM体系中,createElement函数是整个渲染流程的起点,它负责将JSX语法转换为Preact能够理解的VNode(虚拟节点)数据结构。这个转换过程看似简单,实则包含了精妙的设计和性能优化考量。
createElement函数的核心作用
createElement函数是JSX编译后的目标函数,它接收三个主要参数:
type: 节点类型,可以是HTML标签名字符串或组件函数/类props: 属性对象,包含所有传递给元素的属性和事件处理器children: 子节点,可以是文本、其他VNode或它们的数组
让我们通过一个代码示例来理解其工作流程:
// JSX语法
const element = <div className="container" onClick={handleClick}>
Hello <span>World</span>
</div>;
// 编译后的createElement调用
const element = createElement(
'div',
{ className: 'container', onClick: handleClick },
'Hello ',
createElement('span', null, 'World')
);
VNode数据结构解析
VNode是Preact虚拟DOM的核心数据结构,它包含了渲染所需的所有信息。每个VNode对象具有以下关键属性:
| 属性名 | 类型 | 描述 |
|---|---|---|
type | string/Function | 节点类型,HTML标签名或组件构造函数 |
props | object | 包含所有属性和children的完整属性对象 |
key | string/number | 用于列表渲染的标识符,优化diff算法 |
ref | Function/object | 用于获取DOM节点或组件实例的引用 |
_children | Array | 内部使用的子节点数组 |
_dom | Node | 对应的实际DOM节点 |
_component | Component | 如果是组件节点,指向组件实例 |
createElement的内部实现机制
createElement函数的实现遵循了以下几个关键步骤:
- 属性规范化处理:从原始props中提取key和ref,确保它们不会传递给DOM元素
- 子节点处理:将多个children参数合并到props.children中
- defaultProps处理:如果是组件类型,合并默认属性
- VNode创建:调用
createVNode创建最终的虚拟节点
export function createElement(type, props, children) {
let normalizedProps = {}, key, ref, i;
// 提取key和ref
for (i in props) {
if (i == 'key') key = props[i];
else if (i == 'ref') ref = props[i];
else normalizedProps[i] = props[i];
}
// 处理子节点
if (arguments.length > 2) {
normalizedProps.children =
arguments.length > 3 ? slice.call(arguments, 2) : children;
}
// 处理defaultProps
if (typeof type == 'function' && type.defaultProps != null) {
for (i in type.defaultProps) {
if (normalizedProps[i] === undefined) {
normalizedProps[i] = type.defaultProps[i];
}
}
}
return createVNode(type, normalizedProps, key, ref, null);
}
createVNode的优化设计
createVNode函数负责创建最终的VNode对象,其设计考虑了多个性能优化点:
export function createVNode(type, props, key, ref, original) {
const vnode = {
type,
props,
key,
ref,
_children: null,
_parent: null,
_depth: 0,
_dom: null,
_nextDom: undefined,
_component: null,
constructor: undefined,
_original: original == null ? ++vnodeId : original,
_index: -1,
_flags: 0
};
if (original == null && options.vnode != null) options.vnode(vnode);
return vnode;
}
VNode创建流程的Mermaid流程图
关键特性与设计理念
1. 属性分离策略
Preact采用属性分离策略,将key和ref从普通props中提取出来,确保它们不会传递给实际的DOM元素。这种设计既符合React的约定,也避免了不必要的属性传递。
2. 子节点处理优化
createElement支持多种子节点传递方式:
- 作为单独的参数:
createElement('div', null, child1, child2) - 作为数组:
createElement('div', null, [child1, child2]) - 通过props.children:
createElement('div', { children: [child1, child2] })
3. 默认属性合并
对于组件类型的VNode,createElement会自动合并defaultProps,确保即使某些props未显式传递,组件也能获得默认值。
4. 性能优化措施
- V8优化:通过保持
createVNode调用位置固定,帮助V8引擎优化对象形状检测 - 内存重用:通过
_original字段支持VNode重用,减少内存分配 - 标志位系统:使用
_flags字段跟踪VNode状态,优化diff算法
实际应用示例
让我们通过一个复杂的例子来展示createElement的实际工作:
// 复杂组件的JSX表示
function UserList({ users }) {
return (
<div className="user-list">
<h2>用户列表</h2>
<ul>
{users.map(user => (
<li key={user.id} className="user-item">
<Avatar src={user.avatar} size="small" />
<span>{user.name}</span>
</li>
))}
</ul>
</div>
);
}
// 对应的createElement调用链
createElement(UserList, { users: userData },
createElement('div', { className: 'user-list' },
createElement('h2', null, '用户列表'),
createElement('ul', null,
users.map(user =>
createElement('li', {
key: user.id,
className: 'user-item'
},
createElement(Avatar, {
src: user.avatar,
size: 'small'
}),
createElement('span', null, user.name)
)
)
)
)
);
VNode类型系统
Preact的VNode支持多种类型,每种类型都有特定的处理逻辑:
| VNode类型 | type值类型 | 处理方式 |
|---|---|---|
| 原生元素 | string | 直接创建对应的DOM元素 |
| 函数组件 | Function | 调用函数获取返回的VNode |
| 类组件 | Class | 实例化组件并调用render方法 |
| Fragment | Symbol | 处理子节点而不创建包装元素 |
| 文本节点 | 无 | 创建文本节点,props为文本内容 |
这种类型系统的设计使得Preact能够高效地处理各种不同的渲染场景,同时保持代码的简洁性和可维护性。
通过深入理解createElement和VNode的创建机制,我们能够更好地掌握Preact的工作原理,编写出更高效、更可靠的组件代码。这种机制不仅提供了强大的抽象能力,还通过精心的优化设计确保了出色的运行时性能。
Diff算法实现与DOM更新优化
Preact作为React的轻量级替代方案,其核心优势之一就是高效的Diff算法实现。通过精心优化的虚拟DOM比较机制,Preact能够在最小化DOM操作的同时保持出色的性能表现。让我们深入剖析Preact的Diff算法实现原理及其DOM更新优化策略。
虚拟DOM Diff的核心机制
Preact的Diff算法主要包含三个层面的比较:组件级Diff、元素级Diff和子节点Diff。整个Diff过程遵循深度优先遍历原则,确保高效的内存使用和计算性能。
组件级Diff策略
对于函数组件和类组件,Preact采用智能的重用策略。当检测到组件类型相同时,会复用现有组件实例,避免不必要的重新创建:
// 组件复用逻辑
if (oldVNode._component) {
c = newVNode._component = oldVNode._component;
clearProcessingException = c._processingException = c._pendingError;
} else {
// 创建新组件实例
if (isClassComponent) {
newVNode._component = c = new newType(newProps, componentContext);
} else {
newVNode._component = c = new BaseComponent(newProps, componentContext);
c.constructor = newType;
c.render = doRender;
}
}
高效的子节点Diff算法
Preact的子节点Diff算法是其性能优化的核心所在。通过constructNewChildrenArray函数构建新的子节点数组,并采用基于Key的匹配策略:
function constructNewChildrenArray(newParentVNode, renderResult, oldChildren) {
let skew = 0;
newParentVNode._children = [];
for (let i = 0; i < renderResult.length; i++) {
let childVNode = renderResult[i];
// 处理各种节点类型:null、boolean、string、array等
const skewedIndex = i + skew;
const matchingIndex = findMatchingIndex(
childVNode,
oldChildren,
skewedIndex,
remainingOldChildren
);
childVNode._index = matchingIndex;
// ... 后续处理逻辑
}
}
Key匹配算法优化
Preact的findMatchingIndex函数实现了高效的Key匹配算法,优先匹配相同Key的节点,其次按位置匹配:
function findMatchingIndex(newVNode, oldChildren, start, remainingOldChildren) {
const key = newVNode.key;
const type = newVNode.type;
// 1. 优先匹配相同Key的节点
if (key != null) {
for (let i = start; i < oldChildren.length; i++) {
const oldVNode = oldChildren[i];
if (oldVNode && oldVNode.key === key && oldVNode.type === type) {
return i;
}
}
}
// 2. 按位置匹配相同类型的节点
for (let i = start; i < oldChildren.length; i++) {
const oldVNode = oldChildren[i];
if (oldVNode && (oldVNode._flags & MATCHED) === 0 &&
oldVNode.type === type) {
return i;
}
}
return -1; // 没有找到匹配的节点
}
DOM属性更新优化
Preact通过setProperty函数智能更新DOM属性,避免不必要的DOM操作:
// 属性比较更新逻辑
export function setProperty(dom, name, value, oldValue, namespace) {
if (value === oldValue) return; // 值未变化,跳过更新
if (name === 'style') {
// 样式属性特殊处理
if (typeof value == 'string') {
dom.style.cssText = value;
} else {
// 逐属性比较更新
for (let i in { ...oldValue, ...value }) {
let newVal = value == null || value[i] == null ? '' : value[i];
let oldVal = oldValue == null || oldValue[i] == null ? '' : oldValue[i];
if (newVal !== oldVal) {
dom.style[i] = newVal;
}
}
}
} else if (name[0] === 'o' && name[1] === 'n') {
// 事件处理器的优化处理
// ... 事件监听器的智能绑定和解绑
} else {
// 普通属性更新
dom[name] = value;
}
}
批量更新与异步渲染
Preact实现了高效的批量更新机制,通过commitQueue收集需要执行回调的组件,在Diff完成后统一处理:
// 提交队列处理
if (c._renderCallbacks.length) {
commitQueue.push(c);
}
// 在Diff完成后统一执行回调
function commitRoot(commitQueue, refQueue) {
for (let i = 0; i < commitQueue.length; i++) {
commitQueue[i]._renderCallbacks.forEach(callback => {
try {
callback();
} catch (e) {
options._catchError(e, commitQueue[i]._vnode);
}
});
}
// ... 处理ref队列
}
性能优化策略对比
下表总结了Preact Diff算法的主要优化策略:
| 优化策略 | 实现方式 | 性能收益 |
|---|---|---|
| Key匹配 | 优先匹配相同Key的节点 | 减少DOM移动操作 |
| 组件复用 | 复用现有组件实例 | 避免重新创建组件 |
| 属性比较 | 值相等时跳过更新 | 减少DOM属性操作 |
| 批量更新 | 统一执行回调函数 | 减少浏览器重排 |
| 异步渲染 | 可插拔的调度器 | 避免界面卡顿 |
实际应用示例
以下示例展示了Preact Diff算法在实际场景中的应用:
// 列表渲染优化示例
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
/>
))}
</ul>
);
}
// 由于使用了key属性,Preact能够高效地识别
// 哪些项目需要更新、移动或删除
通过这种基于Key的优化策略,Preact能够在列表重新排序时最小化DOM操作,只移动必要的DOM元素而不是重新创建整个列表。
Preact的Diff算法实现充分体现了性能与开发体验的平衡。通过精细化的优化策略和智能的DOM操作管理,Preact在保持小巧体积的同时提供了接近原生性能的渲染体验。这些优化技术不仅适用于Preact本身,也为理解虚拟DOM技术的核心原理提供了宝贵的实践参考。
组件生命周期与状态管理机制
Preact作为React的轻量级替代方案,在组件生命周期和状态管理方面提供了与React高度兼容的API。本文将深入剖析Preact中组件生命周期的执行机制、状态管理的内部实现原理,以及Hooks系统的工作方式。
组件生命周期执行流程
Preact的组件生命周期遵循经典的挂载、更新、卸载三个阶段。让我们通过一个流程图来理解完整的生命周期执行过程:
挂载阶段的生命周期
在组件挂载阶段,Preact按照以下顺序执行生命周期方法:
- constructor - 组件构造函数,初始化state和绑定方法
- getDerivedStateFromProps - 静态方法,在渲染前根据props计算state
- componentWillMount - 渲染前的最后准备(已不推荐使用)
- render - 渲染组件内容
- componentDidMount - 组件挂载完成后执行
更新阶段的生命周期
当组件状态或属性发生变化时,更新阶段的生命周期被触发:
- getDerivedStateFromProps - 根据新的props计算state
- shouldComponentUpdate - 决定是否继续更新流程
- componentWillUpdate - 更新前的准备工作
- render - 重新渲染组件
- getSnapshotBeforeUpdate - 获取DOM更新前的快照
- componentDidUpdate - 更新完成后执行
状态管理机制深度解析
Preact的状态管理基于setState方法,其内部实现采用了高效的批量更新策略。
setState的实现原理
BaseComponent.prototype.setState = function (update, callback) {
let s;
if (this._nextState != null && this._nextState !== this.state) {
s = this._nextState;
} else {
s = this._nextState = assign({}, this.state);
}
if (typeof update == 'function') {
update = update(assign({}, s), this.props);
}
if (update) {
assign(s, update);
}
if (update == null) return;
if (this._vnode) {
if (callback) {
this._stateCallbacks.push(callback);
}
enqueueRender(this);
}
};
批量更新与渲染队列
Preact使用渲染队列(rerenderQueue)来管理组件的更新,确保高效的批量处理:
let rerenderQueue = [];
export function enqueueRender(c) {
if ((!c._dirty && (c._dirty = true) && rerenderQueue.push(c) &&
!process._rerenderCount++) || prevDebounce !== options.debounceRendering) {
prevDebounce = options.debounceRendering;
(prevDebounce || defer)(process);
}
}
function process() {
let c;
rerenderQueue.sort(depthSort);
while ((c = rerenderQueue.shift())) {
if (c._dirty) {
let renderQueueLength = rerenderQueue.length;
renderComponent(c);
if (rerenderQueue.length > renderQueueLength) {
rerenderQueue.sort(depthSort);
}
}
}
process._rerenderCount = 0;
}
Hooks状态管理机制
Preact的Hooks系统提供了函数组件的状态管理能力,其核心实现基于链表结构和执行上下文。
Hooks执行流程
useState内部实现
export function useState(initialState) {
currentHook = 1;
return useReducer(invokeOrReturn, initialState);
}
export function useReducer(reducer, initialState, init) {
const hookState = getHookState(currentIndex++, 2);
hookState._reducer = reducer;
if (!hookState._component) {
hookState._value = [
!init ? invokeOrReturn(undefined, initialState) : init(initialState),
action => {
const currentValue = hookState._nextValue ? hookState._nextValue[0] : hookState._value[0];
const nextValue = hookState._reducer(currentValue, action);
if (currentValue !== nextValue) {
hookState._nextValue = [nextValue, hookState._value[1]];
hookState._component.setState({});
}
}
];
hookState._component = currentComponent;
}
return hookState._nextValue || hookState._value;
}
Hooks依赖管理
Preact使用参数比较来决定是否重新执行effect:
function argsChanged(oldArgs, newArgs) {
return !oldArgs || newArgs.some((arg, i) => arg !== oldArgs[i]);
}
export function useEffect(callback, args) {
const state = getHookState(currentIndex++, 3);
if (!options._skipEffects && argsChanged(state._args, args)) {
state._value = callback;
state._pendingArgs = args;
currentComponent.__hooks._pendingEffects.push(state);
}
}
生命周期与状态更新的协调
Preact通过精巧的设计确保了生命周期方法与状态更新的正确协调:
类组件的状态更新流程
| 阶段 | 方法 | 说明 |
|---|---|---|
| 更新前 | getDerivedStateFromProps | 根据props计算state |
| 决策 | shouldComponentUpdate | 决定是否继续更新 |
| 准备 | componentWillUpdate | 更新前的准备工作 |
| 渲染 | render | 生成新的虚拟DOM |
| 快照 | getSnapshotBeforeUpdate | 获取DOM快照 |
| 提交 | componentDidUpdate | 更新完成后的回调 |
函数组件的Hooks更新流程
| 阶段 | Hook | 说明 |
|---|---|---|
| 渲染 | useState/useReducer | 获取当前状态 |
| 提交前 | useLayoutEffect | DOM更新前同步执行 |
| 提交后 | useEffect | DOM更新后异步执行 |
| 清理 | 清理函数 | 组件卸载或依赖变化时执行 |
性能优化策略
Preact在生命周期和状态管理方面实现了多种性能优化:
- 批量更新:通过
enqueueRender将多个setState调用合并为一次渲染 - 深度排序:组件按深度排序,确保父组件先于子组件更新
- 短路优化:当
shouldComponentUpdate返回false时跳过整个更新流程 - Effect调度:异步执行useEffect,避免阻塞渲染
错误处理机制
Preact提供了完整的错误边界机制:
options._catchError = (e, vnode, oldVNode) => {
// 错误处理逻辑
};
// 在diff过程中捕获错误
try {
// 组件渲染逻辑
} catch (e) {
options._catchError(e, newVNode, oldVNode);
}
通过深入分析Preact的生命周期与状态管理机制,我们可以看到其精巧的设计和高效的实现。无论是类组件的经典生命周期,还是函数组件的Hooks系统,Preact都提供了强大而灵活的状态管理能力,同时在性能优化和错误处理方面做了充分的考虑。
事件系统与Refs处理原理
Preact作为React的轻量级替代方案,在事件系统和Refs处理方面实现了高度优化的机制。这两个核心功能共同构成了Preact与DOM交互的基础架构,既保持了与React API的兼容性,又通过精巧的设计实现了极致的性能优化。
事件系统架构与实现原理
Preact的事件系统采用了一种高效的代理模式,通过事件委托和智能的事件处理机制来减少内存占用并提升性能。整个事件处理流程可以概括为以下几个关键步骤:
事件属性识别与处理
在src/diff/props.js的setProperty函数中,Preact通过属性名前缀识别事件处理器:
// 识别on开头的属性作为事件处理器
if (name[0] === 'o' && name[1] === 'n') {
useCapture = name !== (name = name.replace(/(PointerCapture)$|Capture$/i, '$1'));
// 标准化事件名称
if (name.toLowerCase() in dom || name === 'onFocusOut' || name === 'onFocusIn')
name = name.toLowerCase().slice(2);
else name = name.slice(2);
// 存储事件监听器
if (!dom._listeners) dom._listeners = {};
dom._listeners[name + useCapture] = value;
// 添加或移除事件监听器
if (value) {
if (!oldValue) {
value._attached = eventClock;
dom.addEventListener(
name,
useCapture ? eventProxyCapture : eventProxy,
useCapture
);
} else {
value._attached = oldValue._attached;
}
} else {
dom.removeEventListener(
name,
useCapture ? eventProxyCapture : eventProxy,
useCapture
);
}
}
事件代理机制
Preact使用统一的事件代理函数来处理所有事件,这种设计避免了为每个事件处理器创建独立的函数实例:
function createEventProxy(useCapture) {
return function (e) {
if (this._listeners) {
const eventHandler = this._listeners[e.type + useCapture];
if (e._dispatched == null) {
e._dispatched = eventClock++;
} else if (e._dispatched < eventHandler._attached) {
return;
}
return eventHandler(options.event ? options.event(e) : e);
}
};
}
const eventProxy = createEventProxy(false);
const eventProxyCapture = createEventProxy(true);
事件时序控制与竞态条件防护
Preact引入了逻辑时钟机制来解决DOM事件处理中的竞态条件问题,特别是在事件冒泡过程中可能出现的时序问题:
// 逻辑时钟解决竞态条件问题
let eventClock = 0;
// 在事件处理中检查时序
if (e._dispatched < eventHandler._attached) {
return; // 跳过在DOM修补期间添加的元素上的事件处理
}
这种机制确保了即使在DOM更新过程中触发事件,也能正确处理事件的时序关系。
Refs处理机制深度解析
Preact的Refs系统提供了两种主要的使用方式:函数Refs和对象Refs(通过createRef创建)。Refs的处理贯穿整个渲染生命周期,从组件挂载到更新再到卸载。
Refs处理流程
applyRef函数实现
在src/diff/index.js中,applyRef函数负责具体的Ref处理逻辑:
export function applyRef(ref, value, vnode) {
try {
if (typeof ref == 'function') {
let hasRefUnmount = typeof ref._unmount == 'function';
if (hasRefUnmount) {
ref._unmount(); // 执行之前的清理函数
}
if (!hasRefUnmount || value != null) {
// 存储清理函数到函数实例本身
ref._unmount = ref(value);
}
} else ref.current = value;
} catch (e) {
options._catchError(e, vnode);
}
}
Refs队列处理机制
在diff过程中,所有需要更新的Refs都会被收集到refQueue数组中,然后在commit阶段统一处理:
// 在diffChildren中处理Refs
if (childVNode.ref && oldVNode.ref != childVNode.ref) {
if (oldVNode.ref) {
applyRef(oldVNode.ref, null, childVNode);
}
refQueue.push(
childVNode.ref,
childVNode._dom || childVNode._component,
childVNode
);
}
// 在commitRoot中处理所有Refs
export function commitRoot(commitQueue, root, refQueue) {
for (let i = 0; i < refQueue.length; i++) {
applyRef(refQueue[i], refQueue[++i], refQueue[++i]);
}
}
事件与Refs的协同工作
Preact的事件系统和Refs机制在设计上高度协同,共同为开发者提供了强大的DOM交互能力:
事件处理中的Refs使用
// 示例:在事件处理中使用Refs
class SearchComponent extends Component {
inputRef = createRef();
handleSearch = () => {
if (this.inputRef.current) {
const value = this.inputRef.current.value;
// 执行搜索逻辑
}
};
render() {
return (
<div>
<input ref={this.inputRef} onInput={this.handleSearch} />
<button onClick={this.handleSearch}>搜索</button>
</div>
);
}
}
性能优化策略
Preact在事件和Refs处理中采用了多项性能优化策略:
- 事件委托:使用统一的事件代理函数,减少内存占用
- 懒绑定:只在需要时才添加事件监听器
- 批量处理:在commit阶段统一处理所有Refs更新
- 时序控制:通过逻辑时钟避免竞态条件
高级特性与边界情况处理
捕获阶段事件支持
Preact支持事件捕获阶段处理,通过onClickCapture这样的命名约定:
// 捕获阶段事件处理
render(
<div onClickCapture={handleCaptureClick}>
<button onClick={handleBubbleClick}>点击我</button>
</div>,
container
);
Ref清理机制
对于函数Refs,Preact提供了完善的清理机制:
// 函数Ref的清理示例
<div ref={node => {
if (node) {
// 挂载时的设置
node.focus();
return () => {
// 清理函数
node.blur();
};
}
}} />
错误处理
Preact为Refs处理提供了完善的错误边界机制:
try {
// Ref处理逻辑
if (typeof ref == 'function') {
// 函数Ref处理
} else {
// 对象Ref处理
}
} catch (e) {
options._catchError(e, vnode); // 错误边界处理
}
实际应用场景与最佳实践
表单处理场景
// 使用Refs管理表单焦点
class FormComponent extends Component {
inputRefs = [];
focusNextInput = (currentIndex) => {
const nextInput = this.inputRefs[currentIndex + 1];
if (nextInput && nextInput.current) {
nextInput.current.focus();
}
};
render() {
return (
<form>
{[1, 2, 3, 4].map((_, index) => (
<input
key={index}
ref={ref => {
this.inputRefs[index] = { current: ref };
}}
onKeyPress={e => {
if (e.key === 'Enter') {
e.preventDefault();
this.focusNextInput(index);
}
}}
/>
))}
</form>
);
}
}
动画与交互场景
// 使用Refs控制动画
class AnimatedComponent extends Component {
boxRef = createRef();
handleClick = () => {
if (this.boxRef.current) {
this.boxRef.current.style.transform = 'scale(1.2)';
setTimeout(() => {
if (this.boxRef.current) {
this.boxRef.current.style.transform = 'scale(1)';
}
}, 300);
}
};
render() {
return (
<div>
<div ref={this.boxRef} style={{ transition: 'transform 0.3s' }} />
<button onClick={this.handleClick}>触发动画</button>
</div>
);
}
}
Preact的事件系统和Refs处理机制通过精心的设计和优化,在保持API兼容性的同时实现了卓越的性能表现。这些机制共同构成了Preact高效DOM交互的基础,为开发者提供了强大而灵活的工具来处理复杂的用户交互场景。
总结
Preact通过精巧的架构设计在轻量级体积下实现了完整的React特性支持。其核心优势体现在:createElement的高效VNode创建机制、智能的Diff算法优化、精细的生命周期管理、高效的Hooks状态系统,以及事件委托和Refs批量处理等性能优化策略。这些设计不仅保证了出色的运行时性能,还为开发者提供了与React高度兼容的开发体验,是现代Web开发中优秀的轻量级解决方案。
【免费下载链接】preact 项目地址: https://gitcode.com/gh_mirrors/pre/preact
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



