IVI เป็นห้องสมุด Web UI ที่มีน้ำหนักเบาที่ฝังได้
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.7KB (minified+brotli) มันรวมถึงรันไทม์ทั้งหมดสำหรับการแสดงผล UI ที่ประกาศ เทมเพลตที่คอมไพล์ Precompiled ได้รับการปรับให้เหมาะสมสำหรับขนาดรหัสและประสิทธิภาพการเริ่มต้นเย็น
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 จะทำงานได้โดยไม่ต้องมีการตรวจสอบล่วงหน้า แต่ขอแนะนำอย่างยิ่งให้ใช้ preompilation เพื่อปรับปรุงประสิทธิภาพและลดขนาดรหัส
แพ็คเกจ "@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.config.mjs
import { ivi } from "@ivi/rollup-plugin" ;
export default {
input : "src/main.js" ,
output : {
file : "bundle.js" ,
} ,
plugins : [ ivi ( ) ]
} ; ภาษาเทมเพลต IVI มีไวยากรณ์เหมือน HTML พร้อมไวยากรณ์เพิ่มเติมสำหรับคุณสมบัติ DOM เหตุการณ์และการกำจัดช่องว่าง
html สร้างเทมเพลตที่มีโหนด HTMLELEMENTsvg สร้างเทมเพลตที่มีโหนด 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.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.style.setProperty(name, expr)<div @name=${expr} /> - Event element.addEventListener(name, expr)<div ${directive} /> - Directive Element -Side directive(element)<div .textContent=${expr} /> - เนื้อหาข้อความ <div name="value" /> - แอตทริบิวต์คงที่ด้วยค่า <div name="value"><div name /> - แอตทริบิวต์คงที่โดยไม่มีค่า <div name><div name=${expr} /> - 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.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.textContent = expr คุณสมบัติเนื้อหาข้อความสามารถใช้เป็นการเพิ่มประสิทธิภาพที่ลดการใช้หน่วยความจำเล็กน้อยสำหรับองค์ประกอบที่มีเด็กข้อความ มันจะสร้างโหนดข้อความด้วยคุณสมบัติ Node.textContent และจะไม่มีโหนดสถานะใด ๆ ที่เกี่ยวข้องกับโหนดข้อความ
ค่าเนื้อหาข้อความควรมีประเภท undefined null false string หรือจำนวน number
<div ${directive} /> - directive(element)Directive เป็นฟังก์ชั่นที่เรียกใช้ในแต่ละครั้งเทมเพลตได้รับการปรับปรุงและได้รับองค์ประกอบ DOM ที่เกี่ยวข้องกับคำสั่ง:
type ElementDirective = < E extends Element > (
element : E ,
) => void ;ฟังก์ชั่น Directive จะถูกเรียกใช้เฉพาะเมื่อเทมเพลตถูกสร้างขึ้นด้วยฟังก์ชั่นที่แตกต่างกันดังนั้นหากเราจะนำฟังก์ชั่นเดียวกันกลับมาใช้ใหม่มันสามารถใช้เป็นองค์ประกอบ DOM ที่สร้างการเรียกกลับ:
const Example = component ( ( c ) => {
const onCreated = ( innerElement ) => {
// ..
} ;
return ( ) => html `
< div >
< div class =" Inner " ${ onCreated } />
</ div >
` ;
} ) ;คำสั่งสามารถใช้ไม่เพียง แต่เป็น DOM ที่สร้างขึ้นมาอย่างง่าย ๆ แต่ยังเป็นคำสั่งของรัฐ เช่น
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> เป็นชุดขององค์ประกอบระดับบนสุดเช่นเดียวกับองค์ประกอบรูทเดียว
คุณลักษณะนี้ให้ความยืดหยุ่นมากขึ้นเมื่อสร้างส่วนประกอบ UI ที่ซับซ้อนเนื่องจากช่วยให้คุณสร้างส่วนประกอบที่สร้างองค์ประกอบระดับบนสุดแบบไดนามิกขึ้นอยู่กับอินพุตของพวกเขา
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 ที่แนบมากับเอกสารจะถูกย้ายมันอาจสร้างการแจ้งเตือนการกลายพันธุ์ และส่วนขยายที่ได้รับความนิยมจำนวนมากกำลังใช้ผู้สังเกตการณ์การกลายพันธุ์เพื่อสังเกตทรีย่อยเอกสารทั้งหมดดังนั้นการทำงานแต่ละ 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()
มี API ระดับสูงเช่น 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 สร้างโหนดรูทที่ใช้คิว Microtask สำหรับการอัปเดตการตั้งเวลา
function createRoot (
parentElement : Element ,
nextNode : Node | null = null ,
) : Root ;parentElement - องค์ประกอบ DOM หลัก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 mounts ทรีย่อยรูทจาก DOM และทริกเกอร์เบ็ด unmount ในส่วนประกอบ
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 - hook 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()เพิ่มเบ็ด unmount
function useUnmount (
component : Component ,
hook : ( ) => void ,
) : void ;component - อินสแตนซ์ส่วนประกอบhook - unmount hookuseMemo() 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 และสถานะผู้ตั้งค่าสถานะ
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 - State Reducerส่งคืนฟังก์ชั่น Getter และ Action Dispatcher
ผลข้างเคียงช่วยให้คุณระบุว่าส่วนประกอบของคุณควรทำงานกับระบบภายนอกเช่นการโทร API ที่จำเป็นการจัดการตัวจับเวลาหรือการโต้ตอบ DOM โดยตรง
คุณสามารถคิดได้ว่าเป็นการผสมผสานระหว่าง mount update และ unmount งาน hooks
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 บริบทและผู้ให้บริการบริบท
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 - ตัวเลือกเหตุการณ์ที่จะใช้เมื่อจัดส่งเหตุการณ์Event Dispatcher เรียกใช้ตัวจัดการเหตุการณ์แบบซิงโครนัส ตัวจัดการเหตุการณ์ทั้งหมดจะถูกเรียกใช้ก่อนที่จะส่งคืนเหตุการณ์
findDOMNode() findDOMNode พบเด็ก Dom Node ที่ใกล้เคียงที่สุดซึ่งเป็นของทรีย่อยโหนดสถานะ
function findDOMNode < T extends Node | Text > (
node : SNode | null ,
) : T | null ;node - โหนดสถานะ containsDOMElement() มีการตรวจสอบว่ามีการตรวจ containsDOMElement ว่าโหนดสถานะมีองค์ประกอบ DOM ในทรีย่อยหรือไม่
function containsDOMElement (
node : SNode ,
element : Element ,
) : boolean ;node - โหนดสถานะelement - องค์ประกอบ DOM hasDOMElement() hasDOMElement ตรวจสอบว่าโหนดสถานะมีองค์ประกอบ DOM เป็นลูกหรือไม่
function hasDOMElement (
node : SNode ,
child : Element ,
) : boolean ;node - โหนดสถานะchild - องค์ประกอบ DOMpreventUpdates() 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 ( ) ,
) ; อัลกอริทึมการทำให้เป็นโมฆะของคอมโพเนนต์ถูกนำมาใช้โดยการทำเครื่องหมายส่วนประกอบเป็นสิ่งสกปรกและทำเครื่องหมายโหนดหลักทั้งหมดด้วยธงที่พวกเขามีทรีย่อยสกปรก เมื่อการทำเครื่องหมายอัลกอริทึมถึงโหนดรูทจะเรียกใช้ hook OnRootInvalidated() ที่สามารถใช้ในการใช้กำหนดตารางเวลาที่กำหนดเอง
DirtyDirtySubtreeDirtySubtree , OnRootInvalidated() hook เรียกใช้Dirty ผู้ปกครองทำเครื่องหมายด้วยธง DirtySubtree แล้วเมื่อ Scheduler ตัดสินใจที่จะอัปเดตโหนดรูทด้วยทรีย่อยสกปรกมันจะเริ่มอัลกอริทึมการตรวจสอบสกปรก อัลกอริทึมนี้ไปจากบนลงล่างตามลำดับจากขวาไปซ้ายเยี่ยมชมโหนดทั้งหมดด้วยธงทรี subtree สกปรกจนกว่าจะถึงส่วนประกอบที่สกปรกและอัปเดต
DirtySubtree เริ่มตรวจสอบลูก ๆDirty เกอร์การอัปเดตDirty เกอร์การอัปเดต หนึ่งในเหตุผลที่ไลบรารีหลักมีขนาดเล็กมากเพราะอัลกอริทึมการอัปเดตถูกนำไปใช้ตามลำดับ RTL อัลกอริทึมที่ดำเนินการอัปเดตในการสั่งซื้อ RTL ช่วยลดความซับซ้อนของปัญหาที่ซับซ้อนจำนวนมากด้วยการอัปเดต DOM ปัญหาหลักของการอัปเดต DOM คือเมื่อเราเริ่มอัปเดตโครงสร้างต้นไม้ DOM เราจำเป็นต้องมีการอ้างอิงถึงพาเรนต์และโหนด DOM ถัดไปเพื่อให้เราสามารถใช้ parent.insertBefore(newNode, nextNode) ในกรณีส่วนใหญ่มันเป็นเรื่องง่ายที่จะดึงโหนด DOM ถัดไป แต่มีกรณีขอบเช่นเมื่อเรามีการแสดงออกตามเงื่อนไขสองอย่างที่อยู่ติดกันและหนึ่งในสถานะของพวกเขาคือมันจะลบโหนด DOM ออกจากต้นไม้อย่างสมบูรณ์หรือสององค์ประกอบที่อยู่ติดกันที่มีเงื่อนไขที่รากของพวกเขา ฯลฯ
ห้องสมุดส่วนใหญ่กำลังติดต่อกับกรณีขอบนี้โดยการแนะนำเครื่องหมาย DOM ของเครื่องหมาย (ความคิดเห็นหรือโหนดข้อความว่าง) ตัวอย่างเช่นในการใช้นิพจน์ตามเงื่อนไขเราสามารถเพิ่มโหนดข้อความที่ว่างเปล่าเมื่อเงื่อนไขไม่แสดงโหนด DOM ใด ๆ และเมื่อมีเงื่อนไขเข้าสู่สถานะเมื่อจำเป็นต้องเพิ่มโหนด DOM มันจะใช้โหนดเครื่องหมายเป็นการอ้างอิงโหนด DOM ถัดไป อัลกอริทึมการอัปเดต RTL ใน IVI ไม่ได้ใช้โหนดเครื่องหมายใด ๆ
อัลกอริทึม RTL ที่ใช้ใน IVI ยังช่วยให้การใช้งาน Node Displacements ง่ายขึ้นโดยไม่ต้องแนะนำเส้นทางรหัสเพิ่มเติมชิ้นส่วนและทุกอย่างที่เกี่ยวข้องกับการอัปเดตโครงสร้าง DOM
แต่ละไซต์โทรที่สร้างเทมเพลตมีเอกลักษณ์เฉพาะดังนั้นแม้แต่เทมเพลตที่เหมือนกันที่สร้างขึ้นจากไซต์โทรที่แตกต่างกันก็จะไม่สามารถแตกต่างกันได้
function TemplateUniqueIdentity ( condition , text ) {
return ( condition )
? html `div ${ text } `
: html `div ${ text } ` ;
} ในตัวอย่างด้านบนเมื่อมีการเปลี่ยนแปลง condition แทนที่จะอัปเดตโหนดข้อความอัลกอริทึมอัปเดตจะแทนที่องค์ประกอบ DIV ทั้งหมดด้วยใหม่
มีบางกรณีการใช้งานที่ต้องอ่านบ่อย ๆ จากตัวแปรปฏิกิริยา และเมื่อใดก็ตามที่ตัวแปรนี้เปลี่ยนไปมันจะส่งผลกระทบต่อโหนด UI จำนวนมากเช่นการสลับระหว่างธีมแสง/มืด
แทนที่จะสร้างการสมัครสมาชิกจำนวนมากไปยังตัวแปรนี้ขอแนะนำให้ใช้ค่า JavaScript อย่างง่ายและ Rerender Subtree 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 >
` ;
} ) ;หลังจาก Handler Event Handing มันจะถูกเปลี่ยนเป็น:
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
ต้นไม้ UI ถูกนำไปใช้กับ 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 > ;โครงสร้างข้อมูลนี้ได้รับการออกแบบอย่างระมัดระวังเพื่อให้มีหน่วยความจำขนาดเล็กและหลีกเลี่ยงไซต์โทร polymorphic/megamorphic จำนวนมากที่เข้าถึงโครงสร้างข้อมูลนี้
เพื่อให้เข้าใจว่าทำไมไซต์การโทรแบบ monomorphic จึงมีความสำคัญต่อการแสดงขอแนะนำให้อ่านบทความที่ยอดเยี่ยมในหัวข้อนี้: "เกิดอะไรขึ้นกับ monomorphism?"
เทมเพลตจะถูกคอมไพล์ไว้ล่วงหน้าเป็นส่วนคงที่ที่เก็บไว้ในวัตถุ 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 > ;คอมไพเลอร์เทมเพลตไม่เพียง แต่กำจัดขั้นตอนการรวบรวมในระหว่างการรันไทม์เท่านั้น แต่ยังยกคุณลักษณะคงที่และผู้ฟังเหตุการณ์การหักล้าง opcodes สตริงและฟังก์ชั่นโรงงานเทมเพลต เช่น
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 ] ) ;บ่อยครั้งที่ opcodes ที่ใช้เพื่อวัตถุประสงค์ที่แตกต่างกัน (อุปกรณ์ประกอบฉาก, เด็ก, รัฐ) จะมีค่าที่คล้ายกันดังนั้นเมื่อ opcodes ถูกหักสำรองพวกเขาจะได้รับการปฏิบัติเป็นอาร์เรย์ง่าย ๆ ที่มีจำนวนเต็มที่สามารถใช้เพื่อวัตถุประสงค์ที่แตกต่างกัน
strrings ที่ใช้ร่วมกัน (คีย์แอตทริบิวต์ชื่อเหตุการณ์ ฯลฯ ) จะถูกหักสำรองไว้ในอาร์เรย์เดียว ( __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() สำหรับการจัดตารางการอัปเดต UI อัลกอริทึมการจัดตารางเวลาที่มีการแบตช์ rAF มี footguns ที่มีศักยภาพบางอย่างที่มีเงื่อนไขการแข่งขัน
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 ถูกเรียกใช้ปุ่มส่งเข้าสู่สถานะปิดใช้งาน วิธีที่ง่ายที่สุดในการหลีกเลี่ยงปัญหาเช่นนี้คือการใช้ microtasks สำหรับการแบตช์ แต่ถ้าคุณต้องการเพิ่มการกำหนดเวลา rAF เป็นไปได้ที่จะแก้ปัญหาเช่นนี้โดยการแนะนำเบื้องต้นการซิงโครไนซ์บางอย่าง:
import { uiReady } from "my-custom-scheduler" ;
const onSubmit = async ( ev ) => {
await uiReady ( ) ;
submit ( ) ;
} ; IVI Runtime ไม่ได้ขึ้นอยู่กับไลบรารีภายนอกใด ๆ
เครื่องมือ ivi dev มีชุดการพึ่งพาน้อยที่สุด:
@rollup/pluginutils ใช้ในปลั๊กอินโรลอัพ @ivi/rollup-plugin เพื่อกรองโมดูล มิกซ์