IVI adalah perpustakaan Web UI deklaratif yang dapat disembuhkan dengan ringan.
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 ( ) ,
) ;Ukuran contoh yang telah dikompilasi di atas hanya 2.7kb (Minified+Brotli). Ini termasuk seluruh runtime untuk rendering UI deklaratif. Templat yang dikompilasi dioptimalkan untuk ukuran kode dan kinerja awal dingin.
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)Templat IVI akan bekerja tanpa kompilasi apa pun, tetapi sangat disarankan untuk menggunakan prekompilasi untuk meningkatkan kinerja dan mengurangi ukuran kode.
Paket "@ivi/vite-plugin" menyediakan plugin vite.
// vite.config.mjs
import { defineConfig } from "vite" ;
import { ivi } from "@ivi/vite-plugin" ;
export default defineConfig ( {
plugins : [ ivi ( ) ] ,
} ) ; Paket "@ivi/rollup-plugin" menyediakan plugin rollup.
// rollup.config.mjs
import { ivi } from "@ivi/rollup-plugin" ;
export default {
input : "src/main.js" ,
output : {
file : "bundle.js" ,
} ,
plugins : [ ivi ( ) ]
} ; IVI Template Language memiliki sintaks seperti HTML dengan sintaks tambahan untuk properti DOM, acara dan penghapusan whitespace.
html membuat templat dengan node htmlelement.svg membuat template dengan node SVGELEMENT. import { html } from "ivi" ;
const Example = component ( ( c ) => {
// ...
return ( ) => html `
< div class =" app " >
< div > ${ count ( ) } </ div >
< button @click = ${ inc } > Increment </ button >
</ div >
` ;
} ) ;Templat dapat memiliki beberapa node root.
html `
< div > </ div >
${ expr }
text
< div > </ div >
` Elemen tanpa anak dapat ditutup sendiri dengan sintaks 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 karakter mencegah dari menghilangkan semua whitespace di sekitar baru: < div >
< b > 1 </ b >
v item left
< div > < div > < b > 1 </ b > item left </ div >Dalam template IVI, Anda dapat memasukkan konten dinamis yang disebut ekspresi. Ekspresi hanyalah sepotong kode JavaScript yang dievaluasi ketika template diterjemahkan. Nilai apa pun yang dihasilkan ekspresi pada waktu itu akan dimasukkan dalam template final yang diberikan.
html `
< div attr = ${ attributeValueExpr } >
${ childExpr }
</ div > ` ;Bahasa Template IVI mendukung sintaks tambahan untuk bekerja dengan properti DOM, acara, dll.
<div name="value" /> - atribut statis.<div name /> - Atribut Statis.<div name=${expr} /> - Dynamic Atribut element.setAttribute(name, expr) .<div .name=${expr} /> - element[name] = expr .<div *name=${expr} /> - element[name] = expr , diffs terhadap nilai DOM.<div ~name="value" /> - statis states <div style="name:value;"> .<div ~name=${expr} /> - Dynamic style element.style.setProperty(name, expr) .<div @name=${expr} /> - event element.addEventListener(name, expr) .<div ${directive} /> - Arahan Elemen Sisi Klien directive(element) .<div .textContent=${expr} /> - Konten teks. <div name="value" /> - atribut statis dengan nilai <div name="value"> .<div name /> - Atribut Statis Tanpa Nilai <div name> .<div name=${expr} /> - Dynamic Atribut element.setAttribute(name, expr) . Atribut DOM ditugaskan dengan Element.setAttribute(..) .
Ketika atribut dinamis memiliki nilai undefined , null atau false , itu akan dihapus dari elemen DOM dengan metode Element.removeAttribute(..) .
<div .name=${expr} /> - element[name] = expr .<div *name=${expr} /> - element[name] = expr , diffs terhadap nilai DOM. Properti ditugaskan dengan Element.name = value .
Berbeda dengan nilai DOM berguna dalam kasus penggunaan ketika kita menggunakan nilai <input> untuk menghindari memicu peristiwa input yang tidak perlu.
<div ~name="value" /> - Static Style <div style="value"> .<div ~name=${expr} /> - Dynamic style element.style.setProperty(name, expr) . Gaya statis secara otomatis digabungkan dengan :style="value" .
Gaya dinamis ditugaskan dengan metode CSSStyleDeclaration.setProperty(..) .
Ketika gaya memiliki nilai undefined , null atau false , itu akan dihapus dengan metode CSSStyleDeclaration.removeProperty(..) .
<div @name=${expr} /> - event element.addEventListener(name, expr) . Acara ditugaskan dengan metode EventTarget.addEventListener(..) .
Ketika acara memiliki nilai undefined , null atau false , itu akan dihapus dengan metode EventTarget.removeEventListener(..) .
<div .textContent=${expr} /> - Elemen konten element.textContent = expr . Properti konten teks dapat digunakan sebagai optimasi yang sedikit mengurangi konsumsi memori untuk elemen dengan anak teks. Ini akan membuat node teks dengan properti Node.textContent dan tidak akan memiliki node stateful yang terkait dengan node teks.
Nilai konten teks harus memiliki jenis undefined , null , false , string atau number .
<div ${directive} /> - Element Directive directive(element) .Petunjuk adalah fungsi yang dipanggil setiap templat waktu diperbarui dan menerima elemen DOM yang terkait dengan arahan:
type ElementDirective = < E extends Element > (
element : E ,
) => void ;Fungsi arahan hanya dipanggil ketika template dibuat dengan fungsi yang berbeda, jadi jika kita akan menggunakan kembali fungsi yang sama, itu dapat digunakan sebagai elemen dom yang dibuat callback:
const Example = component ( ( c ) => {
const onCreated = ( innerElement ) => {
// ..
} ;
return ( ) => html `
< div >
< div class =" Inner " ${ onCreated } />
</ div >
` ;
} ) ;Arahan dapat digunakan bukan hanya sebagai panggilan balik yang dibuat DOM sederhana, tetapi juga sebagai arahan yang stateful. Misalnya
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 ) } />
` ;
} ) ;Anda dapat menggunakan ekspresi JavaScript biasa di templat Anda, yang berarti Anda dapat menggunakan konstruksi aliran kontrol JavaScript seperti operator bersyarat, panggilan fungsi, dan jika atau beralih pernyataan untuk menghasilkan konten dinamis berdasarkan kondisi runtime.
Ini berarti Anda dapat membuat templat dengan logika kompleks yang secara kondisional membuat konten yang berbeda berdasarkan apa yang terjadi dalam aplikasi Anda. Anda dapat menyarangkan ekspresi templat satu sama lain untuk membangun templat yang lebih kompleks, dan Anda dapat menyimpan hasil templat dalam variabel untuk menggunakannya nanti dalam kode Anda.
const Example = component ( ( c ) => {
// ...
return ( show ) => html `
< div >
${ show && html ` < span > Show </ span > ` }
</ div >
` ;
} ) ;Jika ekspresi digunakan dalam posisi anak dari elemen HTML dan mengembalikan array, IVI akan membuat semua item dalam array itu sebagai node terpisah.
const Example = ( ) => html `
< div >
${ [
"Text Node 1" ,
"Text Node 2" ,
] }
</ div >
` ;IVI memungkinkan komponen untuk mengembalikan array elemen sebagai simpul root mereka. Ini berarti bahwa komponen dapat mengembalikan beberapa elemen tingkat atas, bukan hanya satu.
Misalnya, komponen dapat mengembalikan array elemen <li> yang membentuk daftar. Ketika komponen ini diterjemahkan, IVI akan memperlakukan array elemen <li> sebagai satu set elemen tingkat atas, seperti halnya dengan elemen akar tunggal.
Fitur ini memberikan lebih banyak fleksibilitas saat membangun komponen UI yang kompleks, karena memungkinkan Anda untuk membuat komponen yang menghasilkan sejumlah elemen tingkat atas yang dinamis tergantung pada inputnya.
const Example = component ( ( c ) => {
return ( entries ) => entries . map ( ( e ) => html `
< li > ${ e } </ li >
` ) ;
) ;
// Example([1, 2, 3])Ketika array diperbarui, node pohon tanpa kewarganegaraan dipetakan ke simpulnya dengan posisinya di array.
Ketika array berisi ekspresi bersyarat yang mengembalikan nilai "lubang" ( null , undefined atau false ), lubang itu akan menempati slot di pohon yang stateful, sehingga semua node akan dipetakan dengan node stateful mereka.
[
conditional ? "text" : null ,
StatefulComponent ( ) ,
] Dalam contoh di atas, ketika ekspresi conditional berubah dari teks ke "lubang" dan sebaliknya, StatefulComponent akan mempertahankan keadaan internalnya.
Ketika array tumbuh atau menyusut dalam ukuran, node stateful akan dibuat atau dilepas pada akhir array.
Di IVI, Anda dapat membuat daftar item menggunakan fungsi List() yang meluncur melalui berbagai data dan mengembalikan daftar elemen. Namun, ketika daftar diperbarui, penting untuk memetakan item yang diberikan dengan benar ke pandangan mereka. Ini berarti bahwa jika suatu item diberikan sebagai komponen yang memiliki keadaan internal yang dapat berubah sebagai akibat dari tindakan pengguna atau peristiwa eksternal, itu harus dipetakan ke instance komponen yang sama.
Untuk membuat daftar dinamis, IVI menyediakan fungsi 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 ;Ini membuat daftar dinamis dengan array kunci yang secara unik mengidentifikasi setiap item dalam daftar. Saat daftar diperbarui, IVI menggunakan kunci untuk memetakan item ke node stateful mereka.
Penting untuk dicatat bahwa ketika merender daftar dinamis, Anda harus selalu menggunakan pengidentifikasi unik sebagai kunci. Ini membantu IVI mengidentifikasi setiap elemen dalam daftar dan menghindari kesalahan rendering. Jika Anda menggunakan indeks atau nilai acak sebagai kunci, IVI mungkin tidak dapat mengidentifikasi elemen yang benar dalam daftar, yang dapat menyebabkan kesalahan.
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 menggunakan algoritma optimal untuk daftar dinamis yang menggunakan jumlah minimum Node.insertBefore() operasi untuk mengatur ulang node DOM.
Mengurangi Operasi Node.insertBefore() penting bukan hanya karena itu membatalkan keadaan DOM internal, tetapi juga karena setiap kali salah satu node DOM yang melekat pada dokumen dipindahkan, dapat menghasilkan pemberitahuan mutationObserver. Dan banyak ekstensi populer menggunakan pengamat mutasi untuk mengamati seluruh subtree dokumen, sehingga setiap operasi insertBefore dapat menjadi sangat mahal ketika digunakan di luar kotak pasir benchmarking.
Komponen bisa stateful atau stateless. Komponen stateful digunakan ketika Anda perlu mengelola keadaan yang berubah dari waktu ke waktu, seperti input pengguna, permintaan jaringan, atau animasi.
Komponen stateful dinyatakan dengan fungsi component() . Ini menciptakan fungsi pabrik yang menghasilkan node komponen.
// `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" } ) ,
) ;Komponen stateful menggunakan penutupan javascript untuk menyimpan keadaan internal.
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 >
` ;
} ) ; Ketika keadaan internal bermutasi, itu tidak memicu pembaruan komponen secara otomatis dan harus secara manual tidak valid dengan fungsi invalidate() .
Ada API tingkat tinggi seperti useState() atau useReducer() yang menggunakan fungsi invalidate() tingkat rendah di belakang layar untuk secara otomatis membatalkan komponen ketika keadaan internal bermutasi.
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 > `
) ;
} ) ;Komponen stateless di IVI hanyalah fungsi JavaScript dasar. Mereka lebih cepat dan lebih ringan daripada komponen stateful, yang menjadikannya pilihan yang baik untuk komponen sederhana dan dapat digunakan kembali yang tidak memiliki keadaan internal.
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 ;Node root adalah simpul paling atas dalam pohon stateful, dari mana semua node lainnya diterjemahkan. Ini mewakili titik masuk untuk algoritma rendering IVI dan menyimpan posisi di pohon DOM.
createRoot() createRoot membuat simpul root yang menggunakan antrian microtask untuk menjadwalkan pembaruan.
function createRoot (
parentElement : Element ,
nextNode : Node | null = null ,
) : Root ;parentElement - Elemen DOM induk.nextNode - NODE DOM NEXT. dirtyCheck() dirtyCheck melakukan algoritma pemeriksaan kotor di subtree root dan memperbarui semua komponen kotor.
function dirtyCheck (
root : Root ,
forceUpdate : boolean = false ,
) : void ;root - Root Node.forceUpdate - Paksa semua komponen untuk diperbarui, bahkan ketika mereka menggunakan petunjuk optimasi untuk mengurangi pembaruan. update() update pembaruan subtree root dengan representasi baru.
function update (
root : Root ,
v : VAny ,
forceUpdate : boolean = false ,
) : void ;root - Root Node.v - Representasi baru.forceUpdate - Paksa semua komponen untuk diperbarui, bahkan ketika mereka menggunakan petunjuk optimasi untuk mengurangi pembaruan. unmount() unmount unmount Subtree root dari DOM dan memicu kait unmount dalam komponen.
function unmount (
root : Root ,
detach : boolean ,
) : void ;root - Root Node.detach - Lepaskan node DOM paling atas dari subtree DOM. defineRoot() defineRoot membuat pabrik node root yang menggunakan kait OnRootInvalidated khusus.
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 Hook yang menerima simpul root dan keadaan kustom yang terkait dengan node root itu.component() component menciptakan pabrik yang menghasilkan node komponen.
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 - Fungsi yang menghasilkan fungsi render komponen stateful.areEqual - Fungsi opsional yang memeriksa properti input untuk perubahan dan digunakan sebagai petunjuk optimasi untuk mengurangi pembaruan yang tidak perlu ketika properti tidak berubah. Ketika subtree root diperbarui dengan opsi forceUpdate , petunjuk areEqual diabaikan dan semua komponen diperbarui.
getProps() getProps mendapatkan alat peraga komponen saat ini dari instance komponen.
function getProps = < P > ( component : Component < P > ) : P ;component - Component Instance. invalidate() invalidate membatalkan komponen dan menjadwalkan pembaruan.
function invalidate ( component : Component ) : void ;component - Component Instance. useUnmount()Menambahkan kait yang tidak ada gunanya.
function useUnmount (
component : Component ,
hook : ( ) => void ,
) : void ;component - Component Instance.hook - Hook Unmount.useMemo() useMemo menciptakan fungsi yang dimoized.
function useMemo < T , U > (
areEqual : ( prev : T , next : T ) => boolean ,
fn : ( props : T ) => U ,
) : ( props : T ) => U ;areEqual - Memeriksa properti input untuk perubahan untuk menghindari komputasi.fn - Fungsi untuk Memoize. useState() useState menciptakan keadaan komponen reaktif.
function useState < S > (
component : Component ,
state : S ,
) : [
get : ( ) => S ,
set : ( s : S ) => void ,
] ;component - Component Instance.state - keadaan awal.Mengembalikan Fungsi Pengambil Negara dan State Setter.
useReducer() useReducer menciptakan reduser status komponen reaktif.
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 - Component Instance.state - keadaan awal.reducer - Fungsi peredam status.Mengembalikan Fungsi Pengambil dan Tindakan Negara.
Efek samping memungkinkan Anda menentukan bagaimana komponen Anda harus berperilaku dengan sistem eksternal seperti panggilan API imperatif, manipulasi pengatur waktu, atau interaksi DOM langsung.
Anda dapat menganggapnya sebagai kombinasi dari mount , update dan pengait siklus hidup unmount .
useEffect() useEffect menciptakan efek samping yang dieksekusi segera setelah simpul root menyelesaikan pembaruan.
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 - Component Instance.effect - Efek Hook.areEqual - Fungsi opsional yang memeriksa properti input untuk perubahan dan digunakan untuk mengontrol kapan efek harus diperbarui.Mengembalikan fungsi efek samping yang harus dipanggil dalam fungsi render.
useLayoutEffect() useLayoutEffect menciptakan efek samping yang dieksekusi sebelum bingkai animasi.
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 - Component Instance.effect - Efek Hook.areEqual - Fungsi opsional yang memeriksa properti input untuk perubahan dan digunakan untuk mengontrol kapan efek harus diperbarui.Mengembalikan fungsi efek samping yang harus dipanggil dalam fungsi render.
useIdleEffect() useIdleEffect menciptakan efek samping yang dieksekusi saat browser menganggur.
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 - Component Instance.effect - Efek Hook.areEqual - Fungsi opsional yang memeriksa properti input untuk perubahan dan digunakan untuk mengontrol kapan efek harus diperbarui.Mengembalikan fungsi efek samping yang harus dipanggil dalam fungsi render.
List() List membuat daftar dinamis.
function List < E , K > (
entries : E [ ] ,
getKey : ( entry : E , index : number ) => K ,
render : ( entry : E ) => VAny ,
) : VList ;entries - Input Data.getKey - Fungsi yang harus mengembalikan kunci unik untuk setiap entri data.render - Fungsi yang membuat entri.context() context menciptakan fungsi pengambil konteks dan penyedia konteks.
function context = < T > ( ) : [
get : ( component : Component ) => T | undefined ,
provider : ( value : T , children : VAny ) => VContext < T > ,
] Mengembalikan fungsi get yang menemukan nilai konteks terdekat, dan fungsi provider yang menciptakan node konteks.
// 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 adalah lubang palka yang memungkinkan memperpanjang algoritma rendering IVI.
type ElementDirective = < E extends Element > (
element : E ,
) => void ;eventDispatcher() eventDispatcher membuat dispatcher acara yang menemukan node dom anak terdekat dan memancarkan metode CustomEvent dengan 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 - Tipe Acara.options - Opsi acara yang akan digunakan saat acara dikirim.Dispatcher acara memanggil penangan acara secara serempak. Semua penangan acara dipanggil sebelum pengembalian pengirim acara.
findDOMNode() findDOMNode menemukan anak node terdekat yang dimiliki oleh subtree simpul yang stateful.
function findDOMNode < T extends Node | Text > (
node : SNode | null ,
) : T | null ;node - Node Stateful. containsDOMElement() Berisi pemeriksaan containsDOMElement jika simpul stateful berisi elemen DOM di subtree -nya.
function containsDOMElement (
node : SNode ,
element : Element ,
) : boolean ;node - Node Stateful.element - Elemen DOM. hasDOMElement() hasDOMElement memeriksa apakah simpul stateful memiliki elemen DOM sebagai anaknya.
function hasDOMElement (
node : SNode ,
child : Element ,
) : boolean ;node - Node Stateful.child - Dom Element.preventUpdates() preventUpdates adalah fungsi NOOP yang selalu mengembalikan nilai true .
function preventUpdates < T > ( a : T , b : T ) : true ; strictEq() strictEq memeriksa nilai untuk kesetaraan dengan operator kesetaraan yang ketat === .
function strictEq < T > ( a : T , b : T ) : boolean ; shallowEq() shallowEq memeriksa objek dengan algoritma kesetaraan dangkal dan menggunakan operator kesetaraan yang ketat untuk memeriksa nilai individu untuk kesetaraan.
function shallowEq < T extends object > ( a : T , b : T ) : boolean ; shallowEqArray() shallowEqArray memeriksa array dengan algoritma kesetaraan dangkal dan menggunakan operator kesetaraan yang ketat untuk memeriksa nilai -nilai individu untuk kesetaraan.
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 ( ) ,
) ; Algoritma Invalidasi Komponen diimplementasikan dengan menandai komponen sebagai kotor dan menandai semua node induknya dengan bendera yang memiliki subtree kotor. Saat menandai algoritma mencapai node root, ia memohon kait OnRootInvalidated() yang dapat digunakan untuk mengimplementasikan penjadwal khusus.
Dirty .DirtySubtree .DirtySubtree , OnRootInvalidated() Hook dipanggil.Dirty , orang tua sudah ditandai dengan bendera DirtySubtree .Ketika penjadwal memutuskan untuk memperbarui node root dengan subtree kotor, itu memulai algoritma pemeriksaan kotor. Algoritma ini menjadi top-down dalam urutan kanan-ke-kiri, mengunjungi semua node dengan bendera subtree kotor sampai mencapai komponen kotor dan memperbaruinya.
DirtySubtree , mulai memeriksa anak -anaknya.Dirty , memicu pembaruan.Dirty , memicu pembaruan. Salah satu alasan mengapa perpustakaan inti sangat kecil adalah karena algoritma pembaruan diimplementasikan dalam urutan RTL. Algoritma yang melakukan pembaruan dalam pesanan RTL menyederhanakan banyak masalah kompleks dengan pembaruan DOM. Masalah utama dengan pembaruan DOM adalah bahwa ketika kita mulai memperbarui struktur pohon DOM, kita perlu memiliki referensi ke induk dan simpul DOM berikutnya, sehingga kita dapat menggunakan parent.insertBefore(newNode, nextNode) . Dalam kebanyakan kasus, mudah untuk mengambil simpul DOM berikutnya, tetapi ada kasus -kasus tepi seperti ketika kita memiliki dua ekspresi bersyarat yang berdekatan dan salah satu dari negara bagian mereka adalah bahwa itu benar -benar menghilangkan simpul DOM dari pohon, atau dua komponen yang berdekatan dengan kondisionalitas pada akar mereka, dll.
Mayoritas perpustakaan berurusan dengan kasus -kasus tepi ini dengan memperkenalkan node dom penanda (komentar atau node teks kosong). Misalnya, untuk mengimplementasikan ekspresi bersyarat, kami dapat menambahkan simpul teks kosong ketika bersyarat tidak membuat simpul DOM dan ketika bersyarat masuk ke keadaan ketika perlu menambahkan simpul DOM, ia akan menggunakan node penanda sebagai referensi simpul DOM berikutnya. Algoritma pembaruan RTL di IVI tidak menggunakan node penanda.
Algoritma RTL yang digunakan dalam IVI juga membuatnya jauh lebih mudah untuk mengimplementasikan perpindahan node tanpa memperkenalkan jalur kode tambahan, fragmen, dan hampir semua yang melibatkan memperbarui struktur DOM.
Setiap situs panggilan yang menciptakan template memiliki identitas yang unik, sehingga bahkan templat yang identik yang dibuat dari situs panggilan yang berbeda tidak akan dapat berdiferensi satu sama lain.
function TemplateUniqueIdentity ( condition , text ) {
return ( condition )
? html `div ${ text } `
: html `div ${ text } ` ;
} Dalam contoh di atas, ketika condition diubah, alih -alih memperbarui node teks, perbarui algoritma akan menggantikan seluruh elemen Div dengan yang baru.
Ada beberapa kasus penggunaan yang membutuhkan banyak bacaan yang sering dari variabel reaktif. Dan setiap kali variabel ini berubah, itu mempengaruhi banyak node UI, seperti beralih antara tema terang/gelap.
Alih -alih membuat banyak langganan untuk variabel ini, disarankan untuk menggunakan nilai JavaScript sederhana dan rerender seluruh subtree UI dengan dirtyCheck(root, true) ketika nilai ini diubah.
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 ( ) ) ; Kloning template adalah optimasi yang digunakan untuk mengkloning templat HTML dengan metode Node.cloneNode() .
Secara default, kloning template diaktifkan untuk semua templat. Tetapi kadang -kadang akan sia -sia untuk membuat templat untuk kloning dan instantiate dari itu ketika templat ini diterjemahkan hanya sekali.
Untuk menonaktifkan kloning, template harus memiliki komentar terkemuka /* preventClone */ . Misalnya
const Example = ( ) => /* preventClone */ html `
< div class =" Title " > ${ text } </ div >
` ; Templat dengan hanya satu elemen yang tidak memiliki sifat statis akan dibuat dengan document.createElement() .
html ` < div attr = ${ 0 } > ${ 1 } </ div > ` ;Secara default, penangan acara (ekspresi fungsi panah) secara otomatis diangkat ke ruang lingkup terluar.
const Example = component ( ( c ) => {
const [ count , setCount ] = useState ( c , 0 ) ;
return ( ) => html `
< div @click = ${ ( ) => { setCount ( count ( ) + 1 ) ; } } > ${ count ( ) } </ div >
` ;
} ) ;Setelah event handler mengangkat, itu akan diubah menjadi:
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 >
` ;
} ) ; Untuk menonaktifkan penangan acara yang diangkat, template harus memiliki komentar terkemuka /* preventHoist */ . Misalnya
const Example = component ( ( c ) => {
const [ count , setCount ] = useState ( c , 0 ) ;
return ( ) => /* preventHoist */ html `
< div @click = ${ ( ) => { setCount ( count ( ) + 1 ) ; } } > ${ count ( ) } </ div >
` ;
} ) ; Beberapa anotasi dapat dinyatakan dengan memisahkan mereka dengan | Operator, misalnya /* preventClone | preventHoist */
Untuk mendapatkan perkiraan kasar penggunaan memori, penting untuk memahami struktur data internal.
Dalam deskripsi di bawah ini kita akan menghitung penggunaan memori dalam mesin berbasis kromium dengan kompresi pointer di V8.
UI Tree diimplementasikan dengan SNode pohon stateful dan VAny tanpa kewarganegaraan yang tidak berubah.
Stateless Tree memiliki struktur data sederhana:
// 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 [ ] ,
} Untuk setiap node tanpa kewarganegaraan VAny ada node stateful SNode yang memiliki antarmuka:
// 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 > ;Struktur data ini dirancang dengan cermat untuk memiliki overhead memori kecil dan menghindari banyak situs panggilan polimorfik/megamorfik yang mengakses struktur data ini.
Untuk memahami mengapa situs panggilan monomorfik penting untuk kinerja, disarankan untuk membaca artikel yang bagus tentang topik ini: "Ada apa dengan monomorfisme?".
Template dikompilasi menjadi bagian statis yang disimpan dalam objek yang TemplateDescriptor dan serangkaian ekspresi dinamis.
const Example = ( attr , child ) => html `div :attr= ${ attr } span ${ child } ` ;Disusun ke:
// _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 > ;Kompiler template tidak hanya menghilangkan langkah kompilasi selama runtime, tetapi juga mengangkat atribut statis dan pendengar acara, mendeduplikasi opcodes, string dan fungsi pabrik template. Misalnya
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 >
` ;Akan menghasilkan dua templat yang berbeda dengan struktur data bersama:
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 ] ) ;Cukup sering, opcode yang digunakan untuk tujuan yang berbeda (alat peraga, anak, negara) akan memiliki nilai yang sama, jadi ketika opcode dideduplikasi mereka diperlakukan sebagai array sederhana dengan bilangan bulat yang dapat digunakan untuk tujuan yang berbeda.
Shared Strrings (kunci atribut, nama acara, dll) dideduklikasi menjadi satu array ( __IVI_STRINGS__ ) yang dibagikan di antara semua templat.
IVI dirancang sebagai solusi yang dapat disematkan, sehingga dapat diintegrasikan ke dalam kerangka kerja atau komponen web yang ada. Node root dasar yang dipakai dengan fungsi createRoot() menggunakan antrian microtask untuk menjadwalkan pembaruan. Node root dengan algoritma penjadwalan khusus dapat dibuat dengan mendefinisikan pabrik root baru dengan fungsi 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 > ;Sebagai contoh, untuk menghapus batching apa pun dan segera memperbarui subtree root saat dibatalkan kita dapat menentukan node akar berikut:
import { defineRoot } from "ivi" ;
const createSyncRoot = defineRoot ( ( root ) => {
// Immediately triggers dirty checking.
dirtyCheck ( root ) ;
} ) ; requestAnimationFrame() untuk menjadwalkan pembaruan UI Algoritma penjadwalan dengan batching rAF memiliki beberapa footgun potensial dengan kondisi balapan.
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 ( ) ,
) ; Dalam contoh ini, jika pengguna mengetik dengan sangat cepat dan menekan tombol [enter] , dimungkinkan untuk mendapatkan pesanan eksekusi seperti ini:
0 ke <input> .onChange() Event Handler dipicu, state.valid beralih ke keadaan false .[enter] .<input type="submit" .disabled={false} />rAF dipicu, tombol kirim masuk ke keadaan dinonaktifkan. Cara paling sederhana untuk menghindari masalah seperti ini adalah dengan menggunakan microtasks untuk batching. Tetapi jika Anda benar -benar ingin menambahkan penjadwalan rAF , dimungkinkan untuk menyelesaikan masalah seperti ini dengan memperkenalkan beberapa primitif sinkronisasi:
import { uiReady } from "my-custom-scheduler" ;
const onSubmit = async ( ev ) => {
await uiReady ( ) ;
submit ( ) ;
} ; IVI Runtime tidak bergantung pada perpustakaan eksternal.
IVI Dev Tools memiliki serangkaian dependensi minimal:
@rollup/pluginutils digunakan dalam plugin rollup @ivi/rollup-plugin untuk memfilter modul keluar. Mit