IVI هي مكتبة واجهة مستخدم ويب تصريحية خفيفة الوزن.
f(state) => UI import { createRoot , update , component , useState , html } from "ivi" ;
const Example = component ( ( c ) => {
const [ count , setCount ] = useState ( c , 0 ) ;
const inc = ( ) => { setCount ( count ( ) + 1 ) ; } ;
return ( ) => html `
< div class =" app " >
< div > ${ count ( ) } </ div >
< button @click = ${ inc } > Increment </ button >
</ div >
` ;
} ) ;
update (
createRoot ( document . body ) ,
Example ( ) ,
) ;حجم المثال المسبق أعلاه هو 2.7 كيلو بايت فقط (Minified+Brotli). ويشمل وقت تشغيل كامل لتقديم واجهة المستخدم التعريفية. تم تحسين القوالب المسبقة لحجم الرمز وأداء البدء البارد.
createRoot(parentElement, nextNode)dirtyCheck(root, forceUpdate)update(root, v, forceUpdate)unmount(root, detach)defineRoot(onInvalidate)component(factory, areEqual)getProps(component)invalidate(component)useUnmount(component, hook)useMemo(areEqual, fn)useState(component, value)useReducer(component, value, reducer)useEffect(component, effect, areEqual)useLayoutEffect(component, effect, areEqual)useIdleEffect(component, effect, areEqual)List(entries, getKey, render)context()eventDispatcher(eventType, options)findDOMNode(node)containsDOMElement(node, element)hasDOMElement(node, element)preventUpdates(a, b)strictEq(a, b)shallowEq(a, b)shallowEqArray(a, b)ستعمل قوالب IVI دون أي مواجهة مسبقة ، ولكن يوصى بشدة باستخدام premompilation لتحسين الأداء وتقليل حجم الرمز.
توفر حزمة "@ivi/vite-plugin" البرنامج المساعد Vite.
// vite.config.mjs
import { defineConfig } from "vite" ;
import { ivi } from "@ivi/vite-plugin" ;
export default defineConfig ( {
plugins : [ ivi ( ) ] ,
} ) ; يوفر حزمة "@ivi/rollup-plugin" البرنامج المساعد Rollup.
// rollup.config.mjs
import { ivi } from "@ivi/rollup-plugin" ;
export default {
input : "src/main.js" ,
output : {
file : "bundle.js" ,
} ,
plugins : [ ivi ( ) ]
} ; تحتوي لغة قالب IVI على بناء جملة تشبه HTML مع بناء جملة إضافي لخصائص DOM والأحداث وإزالة المسافة البيضاء.
html ينشئ قالب مع العقد htmlelement.svg ينشئ قالب مع عقد svgelement. import { html } from "ivi" ;
const Example = component ( ( c ) => {
// ...
return ( ) => html `
< div class =" app " >
< div > ${ count ( ) } </ div >
< button @click = ${ inc } > Increment </ button >
</ div >
` ;
} ) ;يمكن أن تحتوي القوالب على عقد جذر متعددة.
html `
< div > </ div >
${ expr }
text
< div > </ div >
` يمكن إغلاق العناصر غير الطفولية مع بناء الجملة A /> .
html `
< div
class =" a "
/>
` ; < div >
< p > </ p >
ab
< p > </ p >
</ div > < div > < p > </ p > ab < p > </ p > </ div > < div > < span > a b </ span > </ div > < div > < span > a b </ span > </ div > < div >
ab
cd
</ div > < div > ab cd </ div >v من إزالة جميع المساحات البيضاء حول الخطوط الجديدة: < div >
< b > 1 </ b >
v item left
< div > < div > < b > 1 </ b > item left </ div >في قوالب IVI ، يمكنك تضمين محتوى ديناميكي يسمى التعبيرات. التعبير هو مجرد قطعة من رمز JavaScript الذي يتم تقييمه عند تقديم القالب. مهما كانت القيمة التي ينتجها التعبير في ذلك الوقت سيتم تضمينها في القالب النهائي المقدم.
html `
< div attr = ${ attributeValueExpr } >
${ childExpr }
</ div > ` ;تدعم لغة قالب IVI بناء جملة إضافية للعمل مع خصائص DOM والأحداث وما إلى ذلك.
<div name="value" /> - سمة ثابتة.<div name /> - سمة ثابتة.<div name=${expr} /> - element element.setAttribute(name, expr) .<div .name=${expr} /> - element[name] = expr .<div *name=${expr} /> - element[name] = expr ، Diffs مقابل قيمة DOM.<div ~name="value" /> - النمط الثابت <div style="name:value;"> .<div ~name=${expr} /> - element element.style.setProperty(name, expr)<div @name=${expr} /> - Event element.addEventListener(name, expr) .<div ${directive} /> - directive(element) .<div .textContent=${expr} /> - محتوى النص. <div name="value" /> - سمة ثابتة ذات قيمة <div name="value"> .<div name /> - سمة ثابتة بدون قيمة <div name> .<div name=${expr} /> - element element.setAttribute(name, expr) . يتم تعيين سمات DOM باستخدام Element.setAttribute(..) .
عندما يكون للسمة الديناميكية قيمة undefined أو null أو false ، ستتم إزالتها من عنصر DOM باستخدام طريقة Element.removeAttribute(..) .
<div .name=${expr} /> - element[name] = expr .<div *name=${expr} /> - element[name] = expr ، Diffs مقابل قيمة DOM. يتم تعيين الخصائص مع عنصر مشغل الواجب. Element.name = value .
يعد الاختلاف مع قيمة DOM مفيدًا في حالات الاستخدام عندما نستخدم <input> القيم لتجنب تشغيل أحداث input غير الضرورية.
<div ~name="value" /> - النمط الثابت <div style="value"> .<div ~name=${expr} /> - element element.style.setProperty(name, expr) يتم دمج الأنماط الثابتة تلقائيًا مع سمة :style="value" .
يتم تعيين الأنماط الديناميكية باستخدام طريقة CSSStyleDeclaration.setProperty(..) .
عندما يكون للأناقة قيمة undefined أو null أو false ، ستتم إزالتها باستخدام طريقة CSSStyleDeclaration.removeProperty(..) .
<div @name=${expr} /> - Event element.addEventListener(name, expr) . يتم تعيين الأحداث باستخدام EventTarget.addEventListener(..) .
عندما يكون للحدث قيمة undefined أو null أو false ، ستتم إزالتها باستخدام طريقة EventTarget.removeEventListener(..) .
<div .textContent=${expr} /> - element content element.textContent = expr يمكن استخدام خاصية محتوى النص كتحسين يقلل قليلاً من استهلاك الذاكرة للعناصر مع طفل نص. سيقوم بإنشاء عقدة نصية مع خاصية Node.textContent ولن يكون لها أي عقد دولة مرتبطة بعقدة نصية.
يجب أن تحتوي قيمة محتوى النص على نوع undefined ، لا ، null ، false ، string أو نوع number .
<div ${directive} /> - directive(element) .التوجيه هو وظيفة يتم استدعاؤها في كل مرة يتم فيها تحديث قالب ويتلقى عنصر DOM مرتبط بتوجيه:
type ElementDirective = < E extends Element > (
element : E ,
) => void ;يتم استدعاء وظيفة التوجيه فقط عند إنشاء القالب بوظيفة مختلفة ، لذلك إذا كنا سنعيد استخدام نفس الوظيفة ، يمكن استخدامه كعنصر DOM الذي تم إنشاؤه: رد اتصال:
const Example = component ( ( c ) => {
const onCreated = ( innerElement ) => {
// ..
} ;
return ( ) => html `
< div >
< div class =" Inner " ${ onCreated } />
</ div >
` ;
} ) ;يمكن استخدام التوجيهات ليس فقط كإجراءات اتصال بسيطة تم إنشاؤها ، ولكن أيضًا كتوجيهات دولة. على سبيل المثال
function createStatefulDirective ( ) {
// Internal state that stores previous value.
let prev ;
// Returns a factory that creates directive functions.
return ( next ) => ( element ) => {
// Check if previous value has been changed.
if ( prev !== next ) {
prev = next ;
// Updates textContent only when input value is changed.
element . textContent = next ;
}
} ;
}
const Example = component ( ( c ) => {
const directive = createStatefulDirective ( ) ;
return ( i ) => htm `
<div ${ directive ( i ) } />
` ;
} ) ;يمكنك استخدام تعبيرات JavaScript العادية في القوالب الخاصة بك ، مما يعني أنه يمكنك استخدام أي بنيات تدفق تحكم JavaScript مثل العوامل الشرطية ومكالمات الوظائف ، وإذا أو تبديل البيانات لإنشاء محتوى ديناميكي بناءً على ظروف وقت التشغيل.
هذا يعني أنه يمكنك إنشاء قوالب ذات منطق معقد يجعل محتوى مختلفًا بشكل مشروط بناءً على ما يحدث في التطبيق الخاص بك. يمكنك تعبيرات القالب داخل بعضها البعض لإنشاء قوالب أكثر تعقيدًا ، ويمكنك تخزين نتائج القوالب في المتغيرات لاستخدامها لاحقًا في الكود الخاص بك.
const Example = component ( ( c ) => {
// ...
return ( show ) => html `
< div >
${ show && html ` < span > Show </ span > ` }
</ div >
` ;
} ) ;إذا تم استخدام تعبير في موضع الطفل لعنصر HTML وأرجع صفيفًا ، فسيقوم IVI بتقديم جميع العناصر الموجودة في تلك الصفيف كعقد منفصلة.
const Example = ( ) => html `
< div >
${ [
"Text Node 1" ,
"Text Node 2" ,
] }
</ div >
` ;يسمح IVI للمكونات بإرجاع صفائف العناصر كعقد الجذر الخاصة بها. هذا يعني أنه يمكن للمكون إعادة عناصر متعددة من المستوى الأعلى بدلاً من واحد فقط.
على سبيل المثال ، يمكن للمكون إعادة مجموعة من العناصر <li> التي تشكل قائمة. عند تقديم هذا المكون ، سيتعامل IVI مع مجموعة العناصر <li> كمجموعة من العناصر ذات المستوى الأعلى ، تمامًا كما هو مع عنصر جذر واحد.
توفر هذه الميزة مزيدًا من المرونة عند إنشاء مكونات واجهة المستخدم المعقدة ، لأنها تتيح لك إنشاء مكونات تنشئ عددًا ديناميكيًا من العناصر ذات المستوى الأعلى اعتمادًا على مدخلاتها.
const Example = component ( ( c ) => {
return ( entries ) => entries . map ( ( e ) => html `
< li > ${ e } </ li >
` ) ;
) ;
// Example([1, 2, 3])عند تحديث المصفوفات ، يتم تعيين عقد الأشجار عديمة الجنسية على العقد الحكومية من خلال موقفها في الصفيف.
عندما تحتوي الصفيف على تعبير مشروط يرجع قيمة "ثقب" ( null أو undefined أو false ) ، فإن الثقب سوف يشغل فتحة في شجرة مثيرة ، بحيث يتم تعيين جميع العقد على العقد الحكومية.
[
conditional ? "text" : null ,
StatefulComponent ( ) ,
] في المثال أعلاه ، عندما ينتقل التعبير conditional من نص إلى "ثقب" والعكس بالعكس ، ستحافظ StatefulComponent على حالتها الداخلية.
عندما ينمو الصفيف أو يتقلص في الحجم ، سيتم إنشاء العقد أو إزالتها في نهاية الصفيف.
في IVI ، يمكنك تقديم قوائم العناصر باستخدام وظيفة List() التي تحلق من خلال مجموعة من البيانات وإرجاع قائمة العناصر. ومع ذلك ، عندما يتم تحديث القائمة ، من المهم أن تقوم بتخطيط العناصر المقدمة بشكل صحيح على وجهات نظرهم الهادئة. هذا يعني أنه إذا تم تقديم عنصر ما كمكون له حالة داخلية يمكن أن تتغير نتيجة لإجراءات المستخدم أو الأحداث الخارجية ، فيجب تعيينه على نفس مثيل المكون.
لتقديم قوائم ديناميكية ، يوفر IVI وظيفة List() .
function List < E , K > (
// Input Entries.
entries : E [ ] ,
// Function that retrieves unique key from an entry.
getKey : ( entry : E , index : number ) => K ,
// Function that renders an entry.
render : ( entry : E ) => VAny ,
) : VList ;إنه ينشئ قائمة ديناميكية مع مجموعة من المفاتيح التي تحدد كل عنصر بشكل فريد في القائمة. عند تحديث القائمة ، يستخدم IVI مفاتيح لرسم خريطة للعقد في العقد الحكومية.
من المهم أن نلاحظ أنه عند تقديم قائمة ديناميكية ، يجب عليك دائمًا استخدام معرف فريد كمفتاح. هذا يساعد IVI على تحديد كل عنصر في قائمة وتجنب تقديم الأخطاء. إذا كنت تستخدم فهرسًا أو قيمة عشوائية كمفتاح ، فقد لا تتمكن IVI من تحديد العناصر الصحيحة في القائمة ، والتي يمكن أن تسبب أخطاء.
interface DataEntry {
key : number ;
text : string ;
}
const getEntryKey = ( entry : DataEntry ) => entry . key ;
const EntryView = ( entry : DataEntry ) => (
html ` < li > ${ entry . text } </ li > `
) ;
const ListView = ( data : DataEntry [ ] ) => html `
< ul > ${ List ( data , getEntryKey , EntryView ) } </ ul >
` ; تستخدم IVI خوارزمية مثالية لقوائم ديناميكية تستخدم الحد الأدنى لعدد Node.insertBefore() لإعادة ترتيب عقد DOM.
تقليل العمليات Node.insertBefore() مهمة ليس فقط لأنها تبطل حالة DOM الداخلية ، ولكن أيضًا لأنه في كل مرة يتم نقل إحدى عقد DOM المرتبطة بالوثيقة ، قد تنتج إخطار MutationObserver. والكثير من الامتدادات الشائعة تستخدم مراقبي الطفرة لمراقبة الشجرة الفرعية للوثيقة بأكملها ، بحيث يمكن أن تصبح كل عملية insertBefore مكلفة تمامًا عند استخدامها خارج صناديق الرمل القياسية.
يمكن أن تكون المكونات إما حكومية أو عديمة الجنسية. يتم استخدام مكونات الحالة عندما تحتاج إلى إدارة الحالة التي تتغير بمرور الوقت ، مثل إدخال المستخدم أو طلبات الشبكة أو الرسوم المتحركة.
يتم الإعلان عن المكونات الحكومية مع وظيفة component() . يخلق وظيفة المصنع التي تنتج العقد المكونة.
// `component()` function creates a factory function for component
// nodes of this type.
const Example = component ( ( c ) => {
// When component state is initialized, it should return a render
// function.
return ( props ) => (
html ` < div > ${ props . value } </ div > `
) ;
} ) ;
update (
document . body ,
Example ( { value : "Hello World" } ) ,
) ;تستخدم المكونات الحكومية إغلاق JavaScript لتخزين الحالة الداخلية.
const Example = component ( ( c ) => {
// Internal state.
let _counter = 0 ;
// Event handler.
const increment = ( ) => {
// Mutate internal state.
_counter ++ ;
// Invalidate component and schedule an update.
invalidate ( c ) ;
} ;
// Render function.
return ( ) => html `
< div >
< p > Count: ${ _counter } </ p >
< button @click = ${ increment } > Increment </ button >
</ div >
` ;
} ) ; عندما يتم تحور الحالة الداخلية ، فإنها لا تؤدي إلى تحديثات المكون تلقائيًا ويجب إبطالها يدويًا باستخدام وظيفة invalidate() .
هناك واجهات برمجة التطبيقات عالية المستوى مثل useState() أو useReducer() التي تستخدم وظيفة invalidate() وراء الكواليس لإبطال المكونات تلقائيًا عند تحور الحالة الداخلية.
const Example = component ( ( c ) => {
// Internal state.
const [ counter , setCounter ] = useState ( c , 0 ) ;
const increment = ( ) => {
// Automatically invalidates component when counter value is mutated.
setCounter ( counter ( ) + 1 ) ;
} ;
// Render function.
return ( ) => (
html `
< div >
< p > Count: ${ counter ( ) } </ p >
< button @click = ${ increment } > Increment </ button >
</ div > `
) ;
} ) ;المكونات عديمة الجنسية في IVI هي مجرد وظائف JavaScript الأساسية. إنها أسرع وأكثر خفيفة من المكونات الحكومية ، مما يجعلها خيارًا جيدًا للمكونات البسيطة والقابلة لإعادة الاستخدام التي لا تحتوي على أي حالة داخلية.
const Button = ( text , onClick ) => html `
< button @click = ${ onClick } > ${ text } </ button >
` ; type SNode = Opaque ;
type Root < State > = Opaque < State > ;
type Component < Props > = Opaque < Props > ; type VAny =
| null // Hole
| undefined // Hole
| false // Hole
| string // Text
| number // Text
| VRoot // Root
| VTemplate // Template
| VComponent // Component
| VContext // Context Provider
| VList // Dynamic List with track by key algo
| VAny [ ] // Dynamic List with track by index algo
;
type VRoot = Opaque ;
type VTemplate = Opaque ;
type VComponent = Opaque ;
type VContext = Opaque ;
type VList = Opaque ;العقدة الجذرية هي العقدة العليا في شجرة دولة ، يتم من خلالها تقديم جميع العقد الأخرى. إنه يمثل نقطة دخول لخوارزمية عرض IVI وتخزن موقعًا في شجرة DOM.
createRoot() يقوم createRoot بإنشاء عقدة الجذر التي تستخدم قائمة انتظار Microtass لجدولة التحديثات.
function createRoot (
parentElement : Element ,
nextNode : Node | null = null ,
) : Root ;parentElement - Parent Dom Element.nextNode - Next Dom Node. dirtyCheck() يقوم dirtyCheck بإجراء خوارزمية الفحص القذرة في شجرة فرعية للجذر وتحديث جميع المكونات القذرة.
function dirtyCheck (
root : Root ,
forceUpdate : boolean = false ,
) : void ;root - عقدة الجذر.forceUpdate - إجبار جميع المكونات على التحديث ، حتى عندما يستخدمون تلميحات التحسين لتقليل التحديثات. update() update تحديث الشجرة الفرعية الجذر مع تمثيل جديد.
function update (
root : Root ,
v : VAny ,
forceUpdate : boolean = false ,
) : void ;root - عقدة الجذر.v - تمثيل جديد.forceUpdate - إجبار جميع المكونات على التحديث ، حتى عندما يستخدمون تلميحات التحسين لتقليل التحديثات. unmount() unmount unmounts شجاع فرعي من الجذر من DOM ويؤدي إلى خطافات غير محتوية في المكونات.
function unmount (
root : Root ,
detach : boolean ,
) : void ;root - عقدة الجذر.detach - فصل العقد العلوية DOM من الشجرة الفرعية DOM. defineRoot() يقوم defineRoot بإنشاء مصنع عقدة الجذر الذي يستخدم خطافًا OnRootInvalidated مخصصًا.
function defineRoot (
onInvalidate : ( root : Root < undefined > ) => void ,
) : ( parentElement : Element , nextNode : Node | null ) => Root < undefined > ;
function defineRoot < S > (
onInvalidate : ( root : Root < S > , state : S ) => void ,
) : ( parentElement : Element , nextNode : Node | null , state : S ) => Root < S > ;onInvalidate - خطاف OnRootInvalidated الذي يتلقى عقدة الجذر وحالة مخصصة مرتبطة بعقدة الجذر تلك.component() component يخلق مصنعًا ينتج العقد المكونة.
function component (
factory : ( c : Component ) => ( ) => VComponent < undefined > ,
areEqual ?: ( ) => boolean
) : ( ) => VComponent < undefined > ;
function component < P > (
factory : ( c : Component ) => ( props : P ) => VAny ,
areEqual ?: ( prev : P , next : P ) => boolean
) : ( props : P ) => VComponent < P > ;factory - الوظيفة التي تنتج وظائف عرض مكونة.areEqual - وظيفة اختيارية تتحقق من خصائص الإدخال للتغييرات ويتم استخدامها كتلميح تحسين لتقليل التحديثات غير الضرورية عندما لا تتغير الخصائص. عندما يتم تحديث الشجرة الفرعية للجذر مع خيار forceUpdate ، يتم تجاهل تلميح areEqual وتحديث جميع المكونات.
getProps() getProps تحصل على الدعائم المكونة الحالية من مثيل المكون.
function getProps = < P > ( component : Component < P > ) : P ;component - مثيل مكون. invalidate() invalidate مكون المكون وجدولة التحديث.
function invalidate ( component : Component ) : void ;component - مثيل مكون. useUnmount()يضيف خطافا غير محتدم.
function useUnmount (
component : Component ,
hook : ( ) => void ,
) : void ;component - مثيل مكون.hook - الخطاف غير الجذاب.useMemo() useMemo يخلق وظيفة مذكرات.
function useMemo < T , U > (
areEqual : ( prev : T , next : T ) => boolean ,
fn : ( props : T ) => U ,
) : ( props : T ) => U ;areEqual - يتحقق من خصائص الإدخال للتغييرات لتجنب عمليات إعادة الحواسب.fn - وظيفة إلى المذكرات. useState() useState يخلق حالة مكون تفاعلي.
function useState < S > (
component : Component ,
state : S ,
) : [
get : ( ) => S ,
set : ( s : S ) => void ,
] ;component - مثيل مكون.state - الحالة الأولية.إرجاع وظائف Getter State و STATE STERTER.
useReducer() useReducer ينشئ مخفض حالة المكون التفاعلي.
type Dispatch < A > = ( action : A ) => void ;
function useReducer < S , A > (
component : Component ,
state : S ,
reducer : ( state : S , action : A ) => S ,
) : [
get : ( ) => S ,
dispatch : Dispatch < A > ,
] ;component - مثيل مكون.state - الحالة الأولية.reducer - وظيفة المخفض الدولة.إرجاع وظائف Getter State و Action Dispatcher.
تتيح لك الآثار الجانبية تحديد كيفية تصرف مكوناتك مع أنظمة خارجية مثل مكالمات API الضرورية ، أو التلاعب الموقت ، أو تفاعلات DOM المباشرة.
يمكنك التفكير في الأمر على أنه مزيج من خطافات mount و update و unmount .
useEffect() ينشئ useEffect تأثيرًا جانبيًا يتم تنفيذه مباشرة بعد انتهاء عقدة الجذر من التحديث.
function useEffect (
component : Component ,
effect : ( ) => ( ( ) => void ) | void ,
) : ( ) => void ;
function useEffect < P > (
component : Component ,
effect : ( props : P ) => ( ( ) => void ) | void ,
areEqual ?: ( prev : P , next : P ) => boolean
) : ( props : P ) => void ;component - مثيل مكون.effect - خطاف التأثير.areEqual - وظيفة اختيارية تتحقق من خصائص الإدخال للتغييرات ويتم استخدامها للتحكم في متى يجب تحديث التأثير.إرجاع وظيفة التأثير الجانبي الذي يجب استدعاؤه في وظيفة تقديم.
useLayoutEffect() يخلق useLayoutEffect تأثيرًا جانبيًا يتم تنفيذه قبل إطار الرسوم المتحركة.
function useLayoutEffect (
component : Component ,
effect : ( ) => ( ( ) => void ) | void ,
) : ( ) => void ;
function useLayoutEffect < P > (
component : Component ,
effect : ( props : P ) => ( ( ) => void ) | void ,
areEqual ?: ( prev : P , next : P ) => boolean
) : ( props : P ) => void ;component - مثيل مكون.effect - خطاف التأثير.areEqual - وظيفة اختيارية تتحقق من خصائص الإدخال للتغييرات ويتم استخدامها للتحكم في متى يجب تحديث التأثير.إرجاع وظيفة التأثير الجانبي الذي يجب استدعاؤه في وظيفة تقديم.
useIdleEffect() يخلق useIdleEffect تأثيرًا جانبيًا يتم تنفيذه عندما يكون المتصفح خاملاً.
function useIdleEffect (
ccomponent : Component ,
effect : ( ) => ( ( ) => void ) | void ,
) : ( ) => void ;
function useIdleEffect < P > (
ccomponent : Component ,
effect : ( props : P ) => ( ( ) => void ) | void ,
areEqual ?: ( prev : P , next : P ) => boolean
) : ( props : P ) => void ;component - مثيل مكون.effect - خطاف التأثير.areEqual - وظيفة اختيارية تتحقق من خصائص الإدخال للتغييرات ويتم استخدامها للتحكم في متى يجب تحديث التأثير.إرجاع وظيفة التأثير الجانبي الذي يجب استدعاؤه في وظيفة تقديم.
List() List تنشئ قوائم ديناميكية.
function List < E , K > (
entries : E [ ] ,
getKey : ( entry : E , index : number ) => K ,
render : ( entry : E ) => VAny ,
) : VList ;entries - بيانات الإدخال.getKey - وظيفة يجب أن تُرجع مفتاحًا فريدًا لكل إدخال بيانات.render - وظيفة التي تجعل إدخال.context() context يخلق وظائف Getter و Context Provider.
function context = < T > ( ) : [
get : ( component : Component ) => T | undefined ,
provider : ( value : T , children : VAny ) => VContext < T > ,
] إرجاع وظيفة get التي تجد أقرب قيمة سياق ، ودالة provider تنشئ عقد سياق.
// Creates a getter and provider functions.
const [ getContextValue , contextValueProvider ] = context ( ) ;
const Example = component ( ( c ) => {
return ( ) => html `
< h1 > Hello ${ getContextValue ( c ) } </ h1 >
` ;
} ) ;
update (
createRoot ( document . body ) ,
contextValueProvider (
"World" ,
Example ( ) ,
) ,
) ; ElementDirective هي فتحة الهروب التي تسمح بتمديد خوارزمية عرض IVI.
type ElementDirective = < E extends Element > (
element : E ,
) => void ;eventDispatcher() ينشئ eventDispatcher مرسلًا للمناسبات يجد أقرب عقدة DOM للأطفال وينبعث منه CustomEvent مع طريقة EventTarget.dispatchEvent() .
interface DispatchEventOptions {
// Option indicating whether the event bubbles. The default
// is `true`.
bubbles ?: boolean ;
// Option indicating whether the event can be cancelled. The
// default is `false`.
cancelable ?: boolean ;
// Option indicating whether the event will trigger listeners
// outside of a shadow root. The default is `false`.
composed ?: boolean ;
}
type EventDispatcher = {
( component : Component ) : boolean ;
< T > ( component : Component , value : T ) : boolean ;
} ;
function eventDispatcher = < T > (
eventType : string ,
options ?: DispatchEventOptions ,
) : EventDispatcher ;eventType - نوع الحدث.options - خيارات الأحداث التي سيتم استخدامها عند إرسال الحدث.يستدعي مرسل الأحداث معالجات الأحداث بشكل متزامن. يتم استدعاء جميع معالجات الأحداث قبل عودة مرسل الأحداث.
findDOMNode() يجد findDOMNode أقرب طفل عقدة DOM الذي ينتمي إلى الشجرة الفرعية للعقدة.
function findDOMNode < T extends Node | Text > (
node : SNode | null ,
) : T | null ;node - العقدة الحكومية. containsDOMElement() يتحقق containsDOMElement إذا كانت العقدة الهادئة تحتوي على عناصر DOM في الشجرة الفرعية.
function containsDOMElement (
node : SNode ,
element : Element ,
) : boolean ;node - العقدة الحكومية.element - عنصر دوم. hasDOMElement() يتحقق hasDOMElement إذا كان لدى العقدة الحكومية عنصر DOM كطفلها.
function hasDOMElement (
node : SNode ,
child : Element ,
) : boolean ;node - العقدة الحكومية.child - عنصر دوم.preventUpdates() preventUpdates هي وظيفة noop التي تُرجع دائمًا القيمة true .
function preventUpdates < T > ( a : T , b : T ) : true ; strictEq() يتحقق strictEq من القيم للمساواة مع مشغل المساواة الصارم === .
function strictEq < T > ( a : T , b : T ) : boolean ; shallowEq() يتحقق shallowEq من الكائنات مع خوارزمية المساواة الضحلة وتستخدم مشغل المساواة الصارم للتحقق من القيم الفردية للمساواة.
function shallowEq < T extends object > ( a : T , b : T ) : boolean ; shallowEqArray() يتحقق shallowEqArray من صفائف مع خوارزمية المساواة الضحلة وتستخدم مشغل المساواة الصارم للتحقق من القيم الفردية للمساواة.
function shallowEqArray < T > ( a : T [ ] , b : T [ ] ) : boolean ; const Example = component ( ( ) => {
const _onTouchDown = ( ev ) => { } ;
const addPassiveTouchDown = ( element ) => {
element . addEventListener (
"touchdown" ,
_onTouchDown ,
{ passive : true } ,
) ;
} ;
return ( ) => html `
< div ${ addPassiveTouchDown } > </ div >
` ;
} ) ; const useDynamicArg = ( ) => {
let prevKey ;
let prevValue ;
return ( key , value ) => ( element ) => {
if ( prevKey !== key ) {
if ( prevKey ) {
element . removeAttribute ( prevKey ) ;
}
element . setAttribute ( key , value ) ;
} else if ( prevValue !== value ) {
element . setAttribute ( key , value ) ;
}
} ;
} ;
const Example = component ( ( ) => {
const arg = useDynamicArg ( ) ;
return ( [ key , value ] ) => html `
< div ${ arg ( key , value ) } > </ div >
` ;
} ) ; import { createRoot , update , component , findDOMNode , useEffect , html } from "ivi" ;
import { EditorView , basicSetup } from "codemirror" ;
import { javascript } from "@codemirror/lang-javascript" ;
const CodeMirror = component ( ( c ) => {
let _editor ;
useEffect ( c , ( ) => {
_editor = new EditorView ( {
extensions : [ basicSetup , javascript ( ) ] ,
// findDOMNode finds the closest child DOM node.
parent : findDOMNode ( c ) ,
} ) ;
// Reset function will be invoked when component is unmounted.
return ( ) => {
_editor . destroy ( ) ;
} ;
} ) ( ) ;
// ^ When effect doesn't have any dependencies, it can be executed just
// once in the outer scope. Effect will run when its DOM tree is mounted.
return ( ) => html `
< div class =" CodeMirror " > </ div >
` ;
} ) ;
update (
createRoot ( document . body ) ,
CodeMirror ( ) ,
) ; يتم تنفيذ خوارزمية إبطال المكونات عن طريق وضع علامة على مكون على أنه قذر ووضع علامة على جميع العقد الأم مع العلامة التي لديها شجرة فرعية قذرة. عندما تصل علامات الخوارزمية إلى عقدة الجذر ، فإنها تستدعي الخطاف OnRootInvalidated() الذي يمكن استخدامه لتنفيذ جدولة مخصصة.
Dirty .DirtySubtree .DirtySubtree ، تم استدعاء خطاف OnRootInvalidated() .Dirty ، والآباء بالفعل وضع علامة على علم DirtySubtree .عندما يقرر Scheduler تحديث عقدة الجذر مع شاشة فرعية قذرة ، فإنها تبدأ خوارزمية فحص قذرة. هذه الخوارزمية تتجه إلى أسفل بترتيب من اليمين إلى اليسار ، وزيارة جميع العقد مع علامة شجاعة فرعية قذرة حتى تصل إلى مكون قذر ويقوم بتحديثه.
DirtySubtree ، تبدأ في التحقق من أطفالها.Dirty ، يؤدي إلى تحديث.Dirty ، يؤدي إلى تحديث. أحد الأسباب التي تجعل المكتبة الأساسية صغيرة جدًا هو أن خوارزمية التحديث يتم تنفيذها بترتيب RTL. الخوارزمية التي تنفذ التحديثات بترتيب RTL تبسط الكثير من المشكلات المعقدة مع تحديثات DOM. تكمن المشكلة الرئيسية في تحديثات DOM في أنه عندما نبدأ في تحديث بنية شجرة DOM ، نحتاج إلى إشارة إلى أحد الوالدين وعقدة DOM التالية ، حتى نتمكن من استخدام parent.insertBefore(newNode, nextNode) . في معظم الحالات ، من السهل استرداد عقدة DOM التالية ، ولكن هناك حالات حافة مثل عندما يكون لدينا تعبيرتان مشروطتان مجاوتتان وأن إحدى حالاتها هي أنها تزيل تمامًا عقدة DOM من الشجرة ، أو مكونين مجازين مع شرطية في جذورهم ، إلخ.
تتعامل غالبية المكتبات مع حالات الحافة هذه من خلال إدخال عقد DOM Marker (تعليق أو عقدة نصية فارغة). على سبيل المثال ، لتنفيذ تعبيرات مشروطة ، يمكننا إضافة عقدة نص فارغة عندما لا تقدم شرطية أي عقدة DOM وعندما تدخل الشرطية إلى حالة عندما تحتاج إلى إضافة عقدة DOM ، فإنها ستستخدم عقدة علامة كمرجع عقدة DOM التالية. لا تستخدم خوارزمية تحديث RTL في IVI أي عقد علامة.
تجعل خوارزمية RTL التي يتم استخدامها في IVI أيضًا تنفيذ عمليات إزاحة العقدة دون تقديم أي مسارات رمز إضافية وشظايا وكل ما يتضمن تحديث بنية DOM.
كل موقع مكالمة ينشئ قالبًا له هوية فريدة ، لذلك حتى أن القوالب المتطابقة التي تم إنشاؤها من مواقع الاتصال المختلفة لن تتمكن من الاختلاف ضد بعضها البعض.
function TemplateUniqueIdentity ( condition , text ) {
return ( condition )
? html `div ${ text } `
: html `div ${ text } ` ;
} في المثال أعلاه ، عندما يتم تغيير condition ، بدلاً من تحديث عقدة النص ، سيحل خوارزمية تحديث عن عنصر DIV بالكامل بعنصر جديد.
هناك بعض حالات الاستخدام التي تتطلب الكثير من القراءات المتكررة من متغير تفاعلي. وكلما تغير هذا المتغير ، فإنه يؤثر على الكثير من عقد واجهة المستخدم ، مثل التبديل بين السمات الخفيفة/الظلام.
بدلاً من إنشاء الكثير من الاشتراكات في هذه المتغيرات ، يوصى باستخدام قيم JavaScript البسيطة وإعادة شجرة UI كاملة مع dirtyCheck(root, true) عند تغيير هذه القيم.
const root = createRoot ( document . getElementById ( "app" ) ) ;
let theme = "Light" ;
function setTheme ( t ) {
if ( theme !== t ) {
theme = t ;
dirtyCheck ( root , true ) ;
}
}
const App = component ( ( c ) => {
const toggleTheme = ( ) => {
setTheme ( ( theme === "Light" ) ? "Dark" : "Light" ) ;
} ;
return ( ) => html `
div
div = ${ theme }
button @click= ${ toggleTheme } 'Toggle Theme'
` ;
} ) ;
update ( root , App ( ) ) ; استنساخ القالب هو تحسين يستخدم لاستنساخ قوالب HTML باستخدام طريقة Node.cloneNode() .
بشكل افتراضي ، يتم تمكين استنساخ القالب لجميع القوالب. ولكن في بعض الأحيان يكون من المقلق إنشاء قالب للاستنساخ والتزييب عنه عندما يتم تقديم هذا القالب مرة واحدة فقط.
لتعطيل الاستنساخ ، يجب أن يكون لدى القالب تعليق رائد /* preventClone */ . على سبيل المثال
const Example = ( ) => /* preventClone */ html `
< div class =" Title " > ${ text } </ div >
` ; سيتم إنشاء قوالب ذات عنصر واحد فقط لا يحتوي على أي خصائص ثابتة باستخدام document.createElement() .
html ` < div attr = ${ 0 } > ${ 1 } </ div > ` ;بشكل افتراضي ، يتم رفع معالجات الأحداث (تعبيرات وظيفة السهم) تلقائيًا إلى النطاق الخارجي.
const Example = component ( ( c ) => {
const [ count , setCount ] = useState ( c , 0 ) ;
return ( ) => html `
< div @click = ${ ( ) => { setCount ( count ( ) + 1 ) ; } } > ${ count ( ) } </ div >
` ;
} ) ;بعد رفع معالج الأحداث ، سيتم تحويله إلى:
const Example = component ( ( c ) => {
const [ count , setCount ] = useState ( c , 0 ) ;
const __ivi_hoist_1 = ( ) => { setCount ( count ( ) + 1 ) ; } ;
return ( ) => html `
< div @click = ${ __ivi_hoist_1 } > ${ count ( ) } </ div >
` ;
} ) ; لتعطيل معالجات الأحداث ، يجب أن يكون لدى القالب تعليق رائد /* preventHoist */ . على سبيل المثال
const Example = component ( ( c ) => {
const [ count , setCount ] = useState ( c , 0 ) ;
return ( ) => /* preventHoist */ html `
< div @click = ${ ( ) => { setCount ( count ( ) + 1 ) ; } } > ${ count ( ) } </ div >
` ;
} ) ; يمكن إعلان التعليقات التوضيحية المتعددة عن طريق فصلها بـ | مشغل ، على سبيل المثال /* preventClone | preventHoist */
للحصول على تقدير تقريبي لاستخدام الذاكرة ، من المهم فهم هياكل البيانات الداخلية.
في الوصف أدناه ، سنحسب استخدام الذاكرة في محركات قائمة على الكروم مع ضغط المؤشر في V8.
يتم تنفيذ شجرة واجهة المستخدم مع شجرة شجاعة SNode VAny شجرة عديمة الجنسية غير قابلة للتغيير.
الشجرة عديمة الجنسية لديها بنية بيانات بسيطة:
// 20 bytes
interface VNode < D extends VDescriptor , P > {
// Descriptors are reused for all VNodes with the same type and its memory
// usage can be ignored during estimation.
readonly d : D ;
// Prop value is used for storing the results of template expressions in an
// array, prop value for Components, or VRoot and VList props.
readonly p : P ;
}
type VArray = VAny [ ] ;
type VAny =
| null // empty slot
| undefined // empty slot
| false // empty slot
| string // text
| number // text
| VRoot // VNode<RootDescriptor, RootProps>
| VTemplate // VNode<TemplateDescriptor, P>
| VComponent // VNode<ComponentDescriptor, P>
| VContext // VNode<ContextDescriptor, ContextProps<T>>
| VList // VNode<ListDescriptor, ListProps<K>>
| VArray // VAny[]
;
// 20 bytes
// Root Props stores a location where its children should be rendered.
interface RootProps {
// Parent Element
p : Element ,
// Next Node
n : Node | null ,
}
// 20 bytes
// Context Props stores a context value and stateless child node.
interface ContextProps < T > {
// Context value
v : T ;
// Stateless child
c : VAny ;
}
// 20 bytes
interface ListProps < K > {
// Keys that uniquely identify each stateless node in a dynamic list.
k : K [ ] ,
// Stateless nodes
v : VAny [ ] ,
} لكل عقدة عديمة الجنسية VAny هناك عقدة SNode تحتوي على واجهة:
// 32 bytes
interface SNode1 < V extends VAny , S1 > {
// Stateless node associated with the current state.
v : V ;
// Bitflags
f : Flags ; // SMI value - Small Integer
// Children nodes.
c : SNode | ( SNode | null ) [ ] | null ;
// Parent node.
p : SNode | null ,
// State Slot #1.
s1 : S1 ;
}
// 36 bytes
interface SNode2 < V = VAny , S1 = any , S2 = any > extends SNode1 < V , S1 > {
// State slot #2.
s2 : S2 ;
}
// Stateful Nodes are using two different shapes. Call-sites that accessing its
// flags to determine node type will be in a polymorphic state. In this case it
// is perfectly fine to use polymorphic call-sites to reduce memory usage.
type SNode < V = VAny > = SNode1 < V > | SNode2 < V > ;
// Additional state size of the root nodes depends on the implementation of
// root nodes. Default root implementation doesn't use any additional state and
// stores `null` value in the additional state slot.
type SRoot < S > = SNode1 < VRoot , S > ;
// Text nodes are storing a reference to a Text DOM node.
type SText = SNode1 < string | number , Text > ;
// Template nodes are storing a reference to a root DOM node, DOM nodes with
// dynamic properties and DOM nodes that will be used as a reference for
// `parent.insertBefore(node, nextNode)` operations. Slots for DOM nodes with
// dynamic properties that also used as a reference for insertBefore operation
// will share the same slots, there won't be any duplicated references.
type STemplate = SNode1 < VTemplate , Node [ ] > ;
// Dynamic lists doesn't have any additional state.
type SList = SNode1 < VList , null > ;
// Components are using State Nodes with 2 state slots.
type SComponent = SNode2 <
VComponent ,
// Render function.
//
// Stateless components will share the same function.
// Stateful components will create closures and its memory usage will depend
// on the size of the closure context.
null | ( ( props : any ) => VAny ) ,
// Unmount hooks.
//
// Usually components don't have any unmount hooks, or they have just one
// unmount hook.
//
// When there is one hook, it will be stored without any additional arrays.
// If we add one more hook, array will be preallocated with exactly two
// slots `[firstHook, newHook]`. And when it grows even more, javascript
// engine will preallocate internal storage using a growth factor[1][2].
//
// 1. https://en.wikipedia.org/wiki/Dynamic_array#Growth_factor
// 2. https://github.com/v8/v8/blob/1e6775a539a3b88b25cc0ffdb52529c68aad2be8/src/objects/js-objects.h#L584-L590
null | ( ( ) => void ) | ( ( ) => void ) [ ]
> ;
// Contexts doesn't have any additional state.
type SContext = SNode1 < null , null > ;تم تصميم هياكل البيانات هذه بعناية للحصول على النفقات العامة للذاكرة الصغيرة وتجنب الكثير من مواقع المكالمات المتعددة الأشكال/Megamorphic التي تصل إلى هياكل البيانات هذه.
لفهم لماذا تعتبر مواقع المكالمات أحادية الشكل مهمة للأداء ، يوصى بقراءة مقالة رائعة حول هذا الموضوع: "ما هو الأمر مع أحادي الشكل؟".
يتم تجميع القوالب في جزء ثابت يتم تخزينه في كائن TemplateDescriptor ومجموعة من التعبيرات الديناميكية.
const Example = ( attr , child ) => html `div :attr= ${ attr } span ${ child } ` ;يتم تجميعها في:
// _T() creates TemplateDescriptor
const _tpl_1 = _T (
// _h() creates a template factory that uses Node.cloneNode(true) to
// instantiate static template structure.
_h ( "<div><span></span></div>" ) ,
// SMI (Small Integer) value that packs several values:
// struct Data {
// stateSize:6; // The number of state slots
// childrenSize:6; // The number of children slots
// svg:1; // Template with SVG elements
// }
// stateSize and childrenSize are used for preallocating arrays with
// exact number to avoid dynamic growth and reduce memory consumption.
1026 ,
// propOpCodes is an array of SMI values that stores opCodes for updating
// element properties.
[ 2 ] ,
// childOpCodes is an array of SMI values that stores opCodes for updating
// children nodes.
[ 7 , 4 ] ,
// stateOpCodes is an array of SMI values that stores opCodes for traversing
// DOM nodes and saving references to DOM nodes into internal state when
// template is instantiated.
[ 4 ] ,
// An array of string values that stores attribute name, event names, etc.
[ "attr" ] ,
) ;
// _t() creates stateless tree node VTemplate with shared TemplateDescriptor
// and an array of dynamic expressions.
const Example = ( attr , child ) => _t ( _tpl_1 , [ attr , child ] ) ; // Descriptor with TemplateData and template factory function.
type TemplateDescriptor = VDescriptor < TemplateData , ( ) => Element > ;
interface TemplateData {
// stateSize / childrenSize / svg flag
f : number ,
// Prop OpCodes
p : PropOpCode [ ] ,
// Child OpCodes
c : ChildOpCode [ ] ,
// State OpCodes
s : StateOpCode [ ] ,
// Strings
d : string [ ] ,
}
// Stateless tree node VTemplate.
type VTemplate < P = any > = VNode < TemplateDescriptor , P > ;لا يقوم برنامج التحويل البرمجي القالب بإزالة خطوة التجميع فقط أثناء وقت التشغيل ، بل إنه يرفع أيضًا سمات ثابتة ومستمعي الأحداث ، ومواد الرموز ، والسلاسل ، ووظائف مصنع القالب. على سبيل المثال
import { className } from "styles.css" ;
const a = ( id ) => html `
< div class = ${ className } id = ${ id } > </ div >
` ;
const b = ( id ) => html `
< div class = ${ className } id = ${ id } > </ div >
` ;سيقوم بإنشاء قالبين مختلفين بهياكل بيانات مشتركة:
import { className } from "styles.css" ;
import { _h , _T , _t } from "ivi" ;
const EMPTY_ARRAY = [ ] ;
const __IVI_STRINGS__ = [ "id" ] ;
const ELEMENT_FACTORY_1 = _h ( '<div class="' + className + '"></div>' ) ;
const SHARED_OP_CODES_1 = [ /*..*/ ] ;
const _tpl_a = _T (
/* factory */ ELEMENT_FACTORY_1 ,
/* flags */ 0 ,
/* propOpCodes */ SHARED_OP_CODES_1 ,
/* childOpCodes */ EMPTY_ARRAY ,
/* stateOpCodes */ EMPTY_ARRAY ,
/* shared strings */ __IVI_STRINGS__ ,
) ;
const _tpl_b = _T (
/* factory */ ELEMENT_FACTORY_1 ,
/* flags */ 0 ,
/* propOpCodes */ SHARED_OP_CODES_1 ,
/* childOpCodes */ EMPTY_ARRAY ,
/* stateOpCodes */ EMPTY_ARRAY ,
/* shared strings */ __IVI_STRINGS__ ,
) ;
const a = ( id ) => _t ( _tpl_a , [ id ] ) ;
const b = ( id ) => _t ( _tpl_b , [ id ] ) ;في كثير من الأحيان ، سيكون لدى الرموز البكبية التي يتم استخدامها لأغراض مختلفة (الدعائم ، الطفل ، الحالة) قيمًا مماثلة ، لذلك عندما يتم تكريس الرموز البصرية ، يتم التعامل معها على أنها صفائف بسيطة مع أعداد صحيحة يمكن استخدامها لأغراض مختلفة.
يتم تكريس المشتركين (مفاتيح السمات ، أسماء الأحداث ، إلخ) في صفيف واحد ( __IVI_STRINGS__ ) الذي يتم مشاركته بين جميع القوالب.
تم تصميم IVI كحل قابل للتضمين ، بحيث يمكن دمجه في الأطر الحالية أو مكونات الويب. تستخدم عقدة الجذر الأساسية التي تم تأسيسها مع وظيفة createRoot() قائمة انتظار microtask لجدولة التحديثات. يمكن إنشاء العقد الجذرية مع خوارزمية الجدولة المخصصة عن طريق تحديد مصانع الجذر الجديدة باستخدام وظيفة defineRoot() .
function defineRoot ( onInvalidate : ( root : Root < undefined > ) => void )
: ( parentElement : Element , nextNode : Node | null ) => Root < undefined > ;
function defineRoot < S > ( onInvalidate : ( root : Root < S > ) => void )
: ( parentElement : Element , nextNode : Node | null , state : S ) => Root < S > ;على سبيل المثال ، لإزالة أي دفعات وتحديث الجذر على الفور عندما يتم إبطالها ، يمكننا تحديد عقدة الجذر التالية:
import { defineRoot } from "ivi" ;
const createSyncRoot = defineRoot ( ( root ) => {
// Immediately triggers dirty checking.
dirtyCheck ( root ) ;
} ) ; requestAnimationFrame() لجدولة تحديثات واجهة المستخدم خوارزمية جدولة مع تجديدات rAF لديها بعض النقاط القدم المحتملة مع ظروف السباق.
function formStateReducer ( state , action ) {
switch ( action . type ) {
case "update" :
return {
value : action . value ,
valid : / ^[a-z]+$ / . test ( action . value ) ,
} ;
}
return state ;
}
const Form = component ( ( c ) => {
const [ state , dispatch ] = useReducer ( c ,
{ value : "" , valid : false } ,
formStateReducer ,
) ;
const onInput = ( ev ) => {
dispatch ( { type : "update" , value : ev . target . value } ) ;
} ;
return ( ) => html `
< form >
< input
@input = ${ onInput }
*value = ${ state ( ) . value }
/>
< input
type =" submit "
value =" Submit "
.disabled = ${ ! state ( ) . valid }
/ >
</ form >
` ;
} ) ;
update (
createRoot ( document . getElementById ( "app" ) ! ) ,
Form ( ) ,
) ; في هذا المثال ، إذا كانت أنواع المستخدمين سريعة حقًا ودفع زر [enter] ، فمن الممكن الحصول على ترتيب تنفيذ مثل هذا:
0 إلى <input> .onChange() ، يتحول state.valid إلى حالة false .[enter] .<input type="submit" .disabled={false} />rAF ، وتدخل زر إرسال إلى حالة معطلة. أبسط طريقة لتجنب مشكلات كهذه هي استخدام المواد الدقيقة للتجميع. ولكن إذا كنت ترغب حقًا في إضافة جدولة rAF ، فمن الممكن حل مشكلات مثل هذه من خلال تقديم بعض بدائل التزامن:
import { uiReady } from "my-custom-scheduler" ;
const onSubmit = async ( ev ) => {
await uiReady ( ) ;
submit ( ) ;
} ; لا يعتمد وقت تشغيل IVI على أي مكتبات خارجية.
أدوات IVI Dev لديها مجموعة أقل من التبعيات:
@rollup/pluginutils يستخدم في مكون إضافي @ivi/rollup-plugin لتصفية الوحدات النمطية. معهد ماساتشوستس للتكنولوجيا