DOM مجازی (vnode)
یک DOM مجازی سبک به سبک Snabbdom. رابط کاربری تو را با شیءهای ساده جاوااسکریپت (همان VNodeها) نشان میدهد، درخت کهنه را با درخت نو میسنجد و فقط تفاوتها را بر DOM واقعی مینشاند.
init()سازوکار همسنجی را برپا میکند و یک تابعpatchبرمیگرداند. ماژولهای همراه (class / props / attrs / style / events) خودبهخود ثبت میشوند.patch(oldVnode, newVnode)اگرoldVnodeعنصری واقعی از DOM باشد درخت را سوار میکند، وگرنه دو درخت vnode را میسنجد و DOM را همانجا بهروز میکند.h(sel, dataOrChildren?, children?)همان یاریرسان hyperscript است کهVNodeمیسازد.
وارد کردن
import { init, h, classModule, propsModule, styleModule, eventListenersModule } from 'ranuts/vnode';نکته: در این پیادهسازی
init()هیچ آرگومانی نمیگیرد: مجموعهٔ ماژولها ثابت است و در درون ثبت میشود، پس لازم نیست تکتک صادراتهای*Moduleرا بهinitبدهی. آنها برای مراجعه و وارسی صادر شدهاند.
نمونه
آغاز سریع
import { init, h } from 'ranuts/vnode';
// init() یک تابع `patch` برمیگرداند.
// ماژولهای همراه (class، props، attrs، style، events) خودبهخود ثبت میشوند.
const patch = init();
const container = document.getElementById('app');
// ساختن یک درخت vnode
let vnode = h('div#app.container', { style: { color: 'red' } }, [
h('h1', 'Hello vnode'),
h('button', { on: { click: () => console.log('clicked') } }, 'Click me'),
]);
// رسم نخست: بهجای vnode کهنه یک عنصر واقعی DOM بده تا درخت درون آن سوار شود
patch(container, vnode);
// بعدها: درخت بهروزشده را بساز و vnode پیشین را روی آن patch کن.
// تنها تفاوتها (متن، سبک، شنوندهها) بر DOM مینشیند.
const newVnode = h('div#app.container', { style: { color: 'green' } }, [
h('h1', 'Hello again'),
h('button', { on: { click: () => console.log('clicked') } }, 'Updated'),
]);
patch(vnode, newVnode);
vnode = newVnode; // تازهترین درخت را برای patch بعدی نگه دارساختن گره با h
// فقط برچسب
h('div');
// برچسب + data
h('div', { class: { active: true } });
// برچسب + یک فرزند متنی
h('span', 'hello');
// برچسب + آرایهای از فرزندان
h('ul', [h('li', 'one'), h('li', 'two')]);
// برچسب + data + فرزندان
h('a', { attrs: { href: '/home' } }, 'Home');
// گزینشگرهای به سبک CSS، شناسه و کلاسها را تعیین میکنند
h('div#main.card.large', 'content'); // <div id="main" class="card large">content</div>
// اگر گزینشگر با "svg" آغاز شود، فضاینام SVG خودبهخود اعمال میشود
h('svg', { attrs: { width: 100, height: 100 } }, [h('circle', { attrs: { cx: 50, cy: 50, r: 40 } })]);API
init()
سازوکار همسنجی را میسازد و یک تابع patch برمیگرداند. ماژولهای همراه در درون ثبت میشوند و خودش آرگومانی نمیگیرد.
بازگشت
| مقدار | توضیح | نوع |
|---|---|---|
patch | درختهای vnode را بر DOM واقعی سوار یا با آن همسنجی میکند | (oldVnode: VNode | Element, vnode: VNode) => VNode |
patch(oldVnode, vnode)
همان چیزی است که init() برمیگرداند. در نخستین فراخوان، بهجای oldVnode یک Element واقعی از DOM بده تا درخت درون آن سوار شود. در فراخوانهای بعدی، VNode پیشین را بده تا همانجا سنجیده و بهروز شود. VNode تازه را برمیگرداند که آن را بهعنوان مقدار «کهنه» برای فراخوان بعدی نگه میداری.
پارامترها
| پارامتر | توضیح | نوع |
|---|---|---|
oldVnode | vnode پیشین، یا در نخستین سوار شدن یک عنصر DOM | VNode | Element |
vnode | درخت vnode تازهای که باید رسم شود | VNode |
h(sel, dataOrChildren?, children?)
یاریرسان hyperscript که یک VNode میسازد. چند شکل دارد:
| امضا | توضیح |
|---|---|
h(sel) | عنصری که فقط از یک گزینشگر ساخته میشود |
h(sel, data) | عنصری همراه با VNodeData (که data میتواند null باشد) |
h(sel, children) | عنصری همراه با فرزندان: یک متن یا عدد، یک VNode، یا یک آرایه |
h(sel, data, children) | عنصری همراه با داده و فرزندان |
پارامترها
| پارامتر | توضیح | نوع |
|---|---|---|
sel | گزینشگر به سبک CSS: tag، tag#id، tag.class و ترکیب آنها (div#id.a.b). با svg… فضاینام SVG خودبهخود افزوده میشود | string |
data | دادهٔ گره: class / props / attrs / style / شنوندهها / key / hook. میتواند null باشد | VNodeData | null |
children | یک متن یا عدد (که گرهٔ متنی میشود)، یک VNode تنها، یا آرایهای از آنها | VNodeChildren |
فیلدهای VNodeData
| میدان | توضیح | نوع | اعمالکننده |
|---|---|---|---|
props | ویژگیهای DOM که با elm[key] = value گذاشته میشوند | Record<string, any> | propsModule |
attrs | صفتهای HTML که با setAttribute گذاشته میشوند (true و false صفت را میگذارند و برمیدارند) | Record<string, string | number | boolean> | attributesModule |
class | کلاسهای شرطی: نگاشتی از نوع name → boolean | Record<string, boolean> | classModule |
style | سبکهای درونخطی: نگاشتی از نوع name → value (کلیدهای --var به متغیرهای CSS بدل میشوند) | Record<string, any> | styleModule |
on | شنوندههای رویداد: event → handler (یا آرایهای از دستگیرهها) | Record<string, Function | Function[]> | eventListenersModule |
key | نشانی پایدار که الگوریتم همسنجی برای جفت کردن و جابهجا کردن فرزندان به کار میبرد | string | number | (هستهٔ همسنجی) |
ns | نشانی فضاینام (در زیردرختهای SVG خودبهخود گذاشته میشود) | string | (هستهٔ همسنجی) |
hook | قلابهای چرخهٔ عمر برای هر vnode (Hooks) | Hooks | (فقط در سطح نوع: نکته را ببین) |
نکته:
hookو نوعHooksبخشی از سطح عمومی نوعها هستند. اما این پیادهسازی پیراسته، DOM را از راه چرخهٔ عمر ماژولها (create/update/destroy) میگرداند؛ فراخوانهایdata.hookکه به هر vnode بستهاند، در حلقهٔ کنونیpatchصدا زده نمیشوند.
ماژولها
هر ماژول متولی یک تکه از VNodeData است. init() همهٔ آنها را ثبت میکند؛ ضمناً تکتک هم صادر شدهاند.
| صادرات | متولی | توضیح |
|---|---|---|
classModule | data.class | بر پایهٔ نگاشت name → boolean کلاسها را میگذارد و برمیدارد |
propsModule | data.props | ویژگیهای DOM را یکراست نسبت میدهد (elm[key] = value) |
attributesModule | data.attrs | صفتهای HTML را با setAttribute میگذارد و برمیدارد (از جمله xml و xlink) |
styleModule | data.style | سبکهای درونخطی و ویژگیهای سفارشی CSS را میگذارد |
eventListenersModule | data.on | شنوندههای رویداد را میچسباند و برمیدارد |
modules | — | شیء ثبت پیشفرض که نام هر ماژول را به خودِ ماژول مینگارد |
صادراتهای لایهٔ پایینتر
| صادرات | نوع | توضیح |
|---|---|---|
vnode | (sel, data, children, text, elm) => VNode | کارخانهٔ سطحپایین VNode که h در درون به کار میبرد. در کد برنامه h را ترجیح بده. |
addNS | (data, children, sel) => void | فضاینام SVG را بازگشتی بر یک زیردرخت اعمال میکند. برای گزینشگرهای svg… خودِ h آن را صدا میزند. |
htmlDomApi | DOMAPI | مبدل پیشفرض DOM مرورگر که patch در درون به کار میبرد (ساختن، درج، حذف، گرههای متنی و…). |
is | { array, isStr, primitive, isVnode } | یاریرسانهای کوچک پاسداری از نوع که در سراسر درون vnode به کار میروند. |
Chain | class Chain | سازندهٔ دستوری و زنجیرهپذیر DOM (setAttribute، append، setTextContent و…). به همسنجی vnode ربطی ندارد. |
create | (tagName, options?) => Chain | کارخانهای برای راحتی کار که یک Chain تازه برمیگرداند. |
نوعها
| نوع | شکل و معنا |
|---|---|
VNode | { sel, data, children, elm, text, key, listener? }: یک گرهٔ مجازی |
VNodeData | { props?, attrs?, class?, style?, on?, key?, ns?, hook? }: فیلدها را در بالا ببین |
VNodes | VNode[] |
VNodeChildElement | VNode | string | number |
VNodeChildren | VNodeChildElement | VNodeChildElement[] |
ArrayOrElement<T> | T | T[] |
Key | string | number |
Hooks | { pre?, init?, create?, insert?, prepatch?, update?, postpatch?, destroy?, remove?, post? } |
DOMAPI | واسطی که عملیات DOM مورد استفادهٔ patch را وصف میکند (htmlDomApi را ببین) |
Fragment | گسترشی بر DocumentFragment برای کار با قطعهها |
Modules | Record<string, Record<string, ModuleHook>>: شکل دفتر ثبت ماژولها |
ModuleHook | یک فراخوان تنها از چرخهٔ عمر یک ماژول |
یادداشتها
- فقط مرورگر.
ranuts/vnodeبهdocumentو APIهای DOM دست میزند؛ آن را در کد مرورگر وارد کن، نه در Node. - آخرین vnode را نگه دار.
patchهمانVNodeتازه را برمیگرداند. آن را ذخیره کن و در بهروزرسانی بعدی بهعنوانoldVnodeبده تا تفاوتها نسبت به درخت کنونی سنجیده شود. - در یک
VNode،textوchildrenبا هم جمع نمیشوند: هر گره یا گرهٔ متنی است یا عنصری با فرزند. - برای فهرستها
keyبگذار. وقتی فهرستی پویا را میکشی، به همنیاها مقدارهایkeyپایدار بده تا همسنجی بتواند گرهها را جفت و جابهجا کند بهجای آنکه از نو بسازد.