IVI ist eine leichte eingebettete deklarative Web -UI -Bibliothek.
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 ( ) ,
) ;Die Größe des obigen vorkompilierten Beispiels beträgt nur 2,7 KB (Minified+Brotli). Es beinhaltet die gesamte Laufzeit für die deklarative UI -Rendering. Vorkompilierte Vorlagen sind für die Codegröße und die Leistung des Kaltstarts optimiert.
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 -Vorlagen funktionieren ohne Vorkompilierung, es wird jedoch dringend empfohlen, die Vorkompilierung zu verwenden, um die Leistung zu verbessern und die Codegröße zu verringern.
"@ivi/vite-plugin" -Paket bietet Vite-Plugin.
// vite.config.mjs
import { defineConfig } from "vite" ;
import { ivi } from "@ivi/vite-plugin" ;
export default defineConfig ( {
plugins : [ ivi ( ) ] ,
} ) ; "@ivi/rollup-plugin" -Paket bietet Rollup-Plugin.
// rollup.config.mjs
import { ivi } from "@ivi/rollup-plugin" ;
export default {
input : "src/main.js" ,
output : {
file : "bundle.js" ,
} ,
plugins : [ ivi ( ) ]
} ; Die IVI-Vorlagensprache hat eine HTML-ähnliche Syntax mit zusätzlicher Syntax für DOM-Eigenschaften, Ereignisse und Whitespace-Entfernung.
html erstellt eine Vorlage mit HTMLelement -Knoten.svg erstellt eine Vorlage mit Svgelement -Knoten. import { html } from "ivi" ;
const Example = component ( ( c ) => {
// ...
return ( ) => html `
< div class =" app " >
< div > ${ count ( ) } </ div >
< button @click = ${ inc } > Increment </ button >
</ div >
` ;
} ) ;Vorlagen können mehrere Root -Knoten haben.
html `
< div > </ div >
${ expr }
text
< div > </ div >
` Kinderlose Elemente können mit A /> -Syntax selbst geschlossen werden.
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 -Zeichen verhindert, dass alle Whitespaces rund um Newlines entfernt werden: < div >
< b > 1 </ b >
v item left
< div > < div > < b > 1 </ b > item left </ div >In IVI -Vorlagen können Sie dynamische Inhalte, die als Ausdrücke bezeichnet werden, einbeziehen. Ein Ausdruck ist nur ein Stück JavaScript -Code, das bewertet wird, wenn die Vorlage gerendert wird. Welcher Wert, der ein Ausdruck zu diesem Zeitpunkt erzeugt, in die endgültige gerenderte Vorlage aufgenommen wird.
html `
< div attr = ${ attributeValueExpr } >
${ childExpr }
</ div > ` ;Die IVI -Vorlagesprache unterstützt zusätzliche Syntax für die Arbeit mit DOM -Eigenschaften, Ereignissen usw.
<div name="value" /> - statisches Attribut.<div name /> - statisches Attribut.<div name=${expr} /> - Dynamisches element.setAttribute(name, expr) .<div .name=${expr} /> - element[name] = expr .<div *name=${expr} /> - element[name] = expr , diffs gegen einen DOM -Wert.<div ~name="value" /> - statischer Stil <div style="name:value;"> .<div ~name=${expr} /> - Dynamisches element.style.setProperty(name, expr) .<div @name=${expr} /> - element.addEventListener(name, expr) .<div ${directive} /> - Client -Seite -Element directive(element) .<div .textContent=${expr} /> - Textinhalt. <div name="value" /> - statisches Attribut mit einem Wert <div name="value"> .<div name /> - statisches Attribut ohne Wert <div name> .<div name=${expr} /> - Dynamisches element.setAttribute(name, expr) . DOM -Attribute werden mit Element.setAttribute(..) zugewiesen.
Wenn dynamisches Attribut einen undefined , null oder false Wert hat, wird es aus dem DOM -Element mit Element.removeAttribute(..) Methode entfernt.
<div .name=${expr} /> - element[name] = expr .<div *name=${expr} /> - element[name] = expr , diffs gegen einen DOM -Wert. Eigenschaften werden mit einem Zuordnungsoperatorelement zugewiesen.Name Element.name = value .
Das Differing mit einem DOM -Wert ist in Anwendungsfällen nützlich, wenn wir <input> -Werte verwenden, um zu vermeiden, dass unnötige input ausgelöst werden.
<div ~name="value" /> - statischer Stil <div style="value"> .<div ~name=${expr} /> - Dynamisches element.style.setProperty(name, expr) . Statische Stile werden automatisch mit :style="value" -attribut verschmolzen.
Dynamische Stile werden mit einer CSSStyleDeclaration.setProperty(..) -Methode zugeordnet.
Wenn der Stil einen undefined , null oder false Wert hat, wird er mit CSSStyleDeclaration.removeProperty(..) Methode entfernt.
<div @name=${expr} /> - element.addEventListener(name, expr) . Ereignisse werden mit einer EventTarget.addEventListener(..) -Methode zugewiesen.
Wenn das Ereignis einen undefined , null oder false Wert hat, wird es mit EventTarget.removeEventListener(..) -Methode entfernt.
<div .textContent=${expr} /> - Textinhaltselement.textContent element.textContent = expr . Die Eigenschaft in der Textinhalte kann als Optimierung verwendet werden, die den Speicherverbrauch für Elemente mit einem Textkind geringfügig verringert. Es erstellt einen Textknoten mit einem Node.textContent .
Der Textinhaltswert sollte einen undefined null , false , string oder eine number haben.
<div ${directive} /> - Element -Direktive directive(element) .Die Richtlinie ist eine Funktion, die jedes Mal aufgerufen wird, wenn die Vorlage aktualisiert wird und ein DOM -Element empfängt, das einer Richtlinie zugeordnet ist:
type ElementDirective = < E extends Element > (
element : E ,
) => void ;Die Anweisungsfunktion wird nur aufgerufen, wenn die Vorlage mit einer anderen Funktion erstellt wird. Wenn wir also dieselbe Funktion wiederverwenden, kann sie als DOM -Element erstellt werden.
const Example = component ( ( c ) => {
const onCreated = ( innerElement ) => {
// ..
} ;
return ( ) => html `
< div >
< div class =" Inner " ${ onCreated } />
</ div >
` ;
} ) ;Direktiven können nicht nur als einfache DOM -erstellte Rückrufe verwendet werden, sondern auch als staatliche Richtlinien. Z.B
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 ) } />
` ;
} ) ;Sie können regelmäßige JavaScript -Ausdrücke in Ihren Vorlagen verwenden, sodass Sie alle JavaScript -Steuerungsstromkonstrukte wie bedingte Operatoren, Funktionsaufrufe und Anweisungen verwenden können, um dynamische Inhalte basierend auf Laufzeitbedingungen zu generieren.
Dies bedeutet, dass Sie Vorlagen mit komplexer Logik erstellen können, die unterschiedliche Inhalte basierend auf dem, was in Ihrer Anwendung geschieht, bedingungsvoll rendert. Sie können Vorlagenausdrücke ineinander nisten, um komplexere Vorlagen aufzubauen, und die Ergebnisse von Vorlagen in Variablen speichern, um sie später in Ihrem Code zu verwenden.
const Example = component ( ( c ) => {
// ...
return ( show ) => html `
< div >
${ show && html ` < span > Show </ span > ` }
</ div >
` ;
} ) ;Wenn in der untergeordneten Position eines HTML -Elements ein Ausdruck verwendet wird und ein Array zurückgibt, rendert IVI alle Elemente in diesem Array als separate Knoten.
const Example = ( ) => html `
< div >
${ [
"Text Node 1" ,
"Text Node 2" ,
] }
</ div >
` ;IVI ermöglicht es Komponenten, Elementarrays als Wurzelknoten zurückzugeben. Dies bedeutet, dass eine Komponente anstelle von nur einem mehrere Elemente der obersten Ebene zurückgeben kann.
Beispielsweise könnte eine Komponente eine Reihe von <li> Elementen zurückgeben, aus denen eine Liste besteht. Wenn diese Komponente gerendert wird, behandelt IVI die Auswahl von <li> Elementen als eine Reihe von Elementen der obersten Ebene, genau wie es mit einem einzelnen Wurzelelement wäre.
Diese Funktion bietet mehr Flexibilität beim Erstellen komplexer UI-Komponenten, da Sie je nach Eingabe Komponenten erstellen können, die eine dynamische Anzahl von Elementen auf höchstem Niveau erzeugen.
const Example = component ( ( c ) => {
return ( entries ) => entries . map ( ( e ) => html `
< li > ${ e } </ li >
` ) ;
) ;
// Example([1, 2, 3])Wenn Arrays aktualisiert werden, werden staatenlose Baumknoten durch ihre Position im Array auf ihre staatlichen Knoten abgebildet.
Wenn Array einen bedingten Ausdruck enthält, der einen "Loch" -Wert ( null , undefined oder false ) zurückgibt, besetzt das Loch einen Schlitz in einem Zustandsbaum, so dass alle Knoten auf ihren Zustandsknoten korrekt sind.
[
conditional ? "text" : null ,
StatefulComponent ( ) ,
] Im obigen Beispiel, wenn conditional Ausdruck von einem Text zu einem "Loch" wechselt und umgekehrt, wird StatefulComponent seinen inneren Zustand bewahren.
Wenn das Array wächst oder in der Größe schrumpft, werden am Ende eines Arrays staatliche Knoten erstellt oder entfernt.
In IVI können Sie Listen von Elementen mithilfe der List() -Funktion, die eine Reihe von Daten durchschlägt und eine Liste von Elementen zurückgibt, rendern. Wenn die Liste jedoch aktualisiert wird, ist es wichtig, dass die gerendeten Elemente auf ihre staatlichen Ansichten korrekt zugeordnet werden. Dies bedeutet, dass, wenn ein Element als Komponente mit internem Zustand gerendert wird, der sich aufgrund von Benutzeraktionen oder externen Ereignissen ändern kann, auf dieselbe Komponenteninstanz abgebildet werden sollte.
Um dynamische Listen zu rendern, stellt IVI die Funktion List() bereit.
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 ;Es erstellt eine dynamische Liste mit einer Reihe von Schlüssel, die jedes Element in der Liste eindeutig identifizieren. Wenn die Liste aktualisiert wird, verwendet IVI Schlüssel, um Elemente auf ihren Stateful -Knoten abzubilden.
Es ist wichtig zu beachten, dass Sie bei der Rendern einer dynamischen Liste immer einen eindeutigen Kenner als Schlüssel verwenden sollten. Dies hilft IVI, jedes Element in einer Liste zu identifizieren und Rendering -Fehler zu vermeiden. Wenn Sie einen Index oder einen zufälligen Wert als Schlüssel verwenden, kann IVI möglicherweise keine korrekten Elemente in einer Liste identifizieren, was zu Fehlern führen kann.
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 verwendet einen optimalen Algorithmus für dynamische Listen, der die minimale Anzahl von Node.insertBefore() verwendet.
Reduzierung Node.insertBefore() -Operationen ist wichtig, nicht nur, weil er den internen Dom -Zustand ungültig macht, sondern auch, wenn eines der an das Dokument angehängten Dom -Knoten eine mutationObserver -Benachrichtigung erzeugt. Und viele beliebte Erweiterungen verwenden Mutationsbeobachter, um den gesamten Dokument -Subtree zu beobachten, sodass jeder insertBefore vor der Operation durch Benchmarking -Sandboxen ziemlich kostspielig werden kann.
Komponenten können entweder staatlich oder staatenlos sein. Staatliche Komponenten werden verwendet, wenn Sie den Status verwalten müssen, der sich im Laufe der Zeit ändert, z. B. Benutzereingaben, Netzwerkanforderungen oder Animationen.
Stateful -Komponenten werden mit component() -Funktion deklariert. Es erstellt eine Fabrikfunktion, die Komponentenknoten erzeugt.
// `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" } ) ,
) ;Stateful -Komponenten verwenden JavaScript -Schließungen, um den internen Zustand zu speichern.
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 >
` ;
} ) ; Wenn der interne Zustand mutiert ist, löst er keine Aktualisierungen der Komponenten automatisch aus und sollte manuell mit invalidate() -Funktion ungültig werden.
Es gibt hochrangige APIs wie useState() oder useReducer() , die die Funktion Low-Level invalidate() hinter den Kulissen verwenden, um Komponenten automatisch ungültig zu machen, wenn der interne Zustand mutiert wird.
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 > `
) ;
} ) ;Staatenlose Komponenten in IVI sind nur grundlegende JavaScript -Funktionen. Sie sind schneller und leichter als staatliche Komponenten, was sie zu einer guten Wahl für einfache und wiederverwendbare Komponenten macht, die keinen internen Zustand haben.
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 ;Ein Wurzelknoten ist der oberste Knoten in einem Zustand, aus dem alle anderen Knoten gerendert werden. Es stellt einen Einstiegspunkt für den IVI -Rendering -Algorithmus dar und speichert eine Position im DOM -Baum.
createRoot() createRoot erstellt einen Root -Knoten, bei dem die Warteschlange zum Planen von Updates verwendet wird.
function createRoot (
parentElement : Element ,
nextNode : Node | null = null ,
) : Root ;parentElement - übergeordnetes Dom -Element.nextNode - Nächster DOM -Knoten. dirtyCheck() dirtyCheck führt den Dirty Checking -Algorithmus in einem Root -Subtree durch und aktualisiert alle schmutzigen Komponenten.
function dirtyCheck (
root : Root ,
forceUpdate : boolean = false ,
) : void ;root .forceUpdate - Erzwingen Sie alle Komponenten zum Aktualisieren, auch wenn sie Optimierungshinweise verwenden, um Updates zu reduzieren. update() update Aktualisierung eines Root -Unterbaums mit einer neuen Darstellung.
function update (
root : Root ,
v : VAny ,
forceUpdate : boolean = false ,
) : void ;root .v - Neue Darstellung.forceUpdate - Erzwingen Sie alle Komponenten zum Aktualisieren, auch wenn sie Optimierungshinweise verwenden, um Updates zu reduzieren. unmount() unmount entfaltet einen Root -Subtree aus dem DOM und löst Unmontschen in Komponenten aus.
function unmount (
root : Root ,
detach : boolean ,
) : void ;root .detach - Abnehmen die obersten Dom -Knoten vom DOM -Subtree. defineRoot() defineRoot erstellt eine Root -Knotenfabrik, die einen benutzerdefinierten OnRootInvalidated Haken verwendet.
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, der einem Root -Knoten und einem benutzerdefinierten Zustand empfängt, der diesem Root -Knoten zugeordnet ist.component() component erstellt eine Fabrik, die Komponentenknoten erzeugt.
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 - Funktion, die staatliche Komponenten -Renderfunktionen erzeugt.areEqual - Optionale Funktion, die die Eingabeeigenschaften auf Änderungen überprüft und als Optimierungshinweise verwendet wird, um unnötige Aktualisierungen zu reduzieren, wenn sich die Eigenschaften nicht geändert haben. Wenn das Root -Subtree mit forceUpdate -Option aktualisiert wird, wird areEqual Hint ignoriert und alle Komponenten aktualisiert.
getProps() getProps Ruft aktuelle Komponenten -Requisiten von der Komponenteninstanz ab.
function getProps = < P > ( component : Component < P > ) : P ;component - Komponenteninstanz. invalidate() Invalidated -Komponenten invalidate und plant ein Update.
function invalidate ( component : Component ) : void ;component - Komponenteninstanz. useUnmount()Fügt einen unmontalen Haken hinzu.
function useUnmount (
component : Component ,
hook : ( ) => void ,
) : void ;component - Komponenteninstanz.hook - UNMOUND HAKE.useMemo() useMemo erstellt eine meierierte Funktion.
function useMemo < T , U > (
areEqual : ( prev : T , next : T ) => boolean ,
fn : ( props : T ) => U ,
) : ( props : T ) => U ;areEqual - Überprüft die Eingabeeigenschaften auf Änderungen, um Neuberechnungen zu vermeiden.fn - Funktion zum Memoisieren. useState() useState erstellt einen reaktiven Komponentenzustand.
function useState < S > (
component : Component ,
state : S ,
) : [
get : ( ) => S ,
set : ( s : S ) => void ,
] ;component - Komponenteninstanz.state - Anfangszustand.Gibt die Funktionen des staatlichen Getters und der staatlichen Setzer zurück.
useReducer() useReducer erstellt einen reaktiven Komponentenzustandsreduzierer.
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 - Komponenteninstanz.state - Anfangszustand.reducer - Zustandsreduziererfunktion.Gibt den staatlichen Getter- und Aktions -Dispatcher -Funktionen zurück.
Mit Nebenwirkungen können Sie angeben, wie sich Ihre Komponenten mit externen Systemen wie imperativen API -Aufrufen, Timermanipulationen oder direkten DOM -Wechselwirkungen verhalten sollten.
Sie können es als eine Kombination aus mount , update und unmount Lebenszyklen -Haken betrachten.
useEffect() useEffect erstellt einen Nebeneffekt, der unmittelbar nach Abschluss eines Updates ausgeführt wird.
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 - Komponenteninstanz.effect - Effekthaken.areEqual - Optionale Funktion, die die Eingabeeigenschaften auf Änderungen überprüft und zur Steuerung verwendet wird, wenn ein Effekt aktualisiert werden sollte.Gibt eine Nebeneffektfunktion zurück, die in einer Renderfunktion aufgerufen werden sollte.
useLayoutEffect() useLayoutEffect erstellt einen Nebeneffekt, der vor dem Animationsrahmen ausgeführt wird.
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 - Komponenteninstanz.effect - Effekthaken.areEqual - Optionale Funktion, die die Eingabeeigenschaften auf Änderungen überprüft und zur Steuerung verwendet wird, wenn ein Effekt aktualisiert werden sollte.Gibt eine Nebeneffektfunktion zurück, die in einer Renderfunktion aufgerufen werden sollte.
useIdleEffect() useIdleEffect erzeugt einen Nebeneffekt, der ausgeführt wird, wenn der Browser im Leerlauf ist.
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 - Komponenteninstanz.effect - Effekthaken.areEqual - Optionale Funktion, die die Eingabeeigenschaften auf Änderungen überprüft und zur Steuerung verwendet wird, wenn ein Effekt aktualisiert werden sollte.Gibt eine Nebeneffektfunktion zurück, die in einer Renderfunktion aufgerufen werden sollte.
List() List erstellt eine dynamische Listen.
function List < E , K > (
entries : E [ ] ,
getKey : ( entry : E , index : number ) => K ,
render : ( entry : E ) => VAny ,
) : VList ;entries - Eingabedaten.getKey - Funktion, die für jede Dateneingabe einen eindeutigen Schlüssel zurückgeben sollte.render - Funktion, die einen Eintrag macht.context() context erstellt Kontextfunktionen und Kontextanbieterfunktionen.
function context = < T > ( ) : [
get : ( component : Component ) => T | undefined ,
provider : ( value : T , children : VAny ) => VContext < T > ,
] Gibt eine get -Funktion zurück, die den engsten Kontextwert findet, und eine provider , die Kontextknoten erstellt.
// 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 ist eine Fluchtluke, die es ermöglicht, den IVI -Rendering -Algorithmus zu erweitern.
type ElementDirective = < E extends Element > (
element : E ,
) => void ;eventDispatcher() eventDispatcher erstellt einen Event -Dispatcher, der den nächstgelegenen Kinderdom -Knoten findet und mit EventTarget.dispatchEvent() -Methode ein CustomEvent ausgibt.
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 - Ereignisart.options - Ereignisoptionen, die beim Versand von Ereignissen verwendet werden.Der Event -Dispatcher ruft Event -Handler synchron auf. Alle Event -Handler werden vor dem Rückkehr von Event -Dispatcher aufgerufen.
findDOMNode() findDOMNode findet das engste Dom -Knoten -Kind, das zu einem staatlichen Knoten -Subtree gehört.
function findDOMNode < T extends Node | Text > (
node : SNode | null ,
) : T | null ;node - Stateful Knoten. containsDOMElement() containsDOMElement -Überprüfungen, wenn ein Zustandsknoten in seinem Unterbaum ein Dom -Elemente enthält.
function containsDOMElement (
node : SNode ,
element : Element ,
) : boolean ;node - Stateful Knoten.element - DOM -Element. hasDOMElement() hasDOMElement prüft, ob ein staatlicher Knoten ein DOM -Element als Kind hat.
function hasDOMElement (
node : SNode ,
child : Element ,
) : boolean ;node - Stateful Knoten.child - Dom -Element.preventUpdates() preventUpdates ist eine Noop -Funktion, die immer true Wert zurückgibt.
function preventUpdates < T > ( a : T , b : T ) : true ; strictEq() strictEq überprüft die Werte für Gleichheit mit strengen Gleichstellungsoperator === .
function strictEq < T > ( a : T , b : T ) : boolean ; shallowEq() shallowEq überprüft Objekte mit einem flachen Gleichstellungsalgorithmus und verwendet strengen Gleichstellungsoperator, um die einzelnen Werte auf Gleichheit zu überprüfen.
function shallowEq < T extends object > ( a : T , b : T ) : boolean ; shallowEqArray() shallowEqArray prüft Arrays mit einem flachen Gleichheitsalgorithmus und verwendet strengen Gleichstellungsoperator, um die einzelnen Werte auf Gleichheit zu überprüfen.
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 ( ) ,
) ; Der Komponenten -Invalidierungsalgorithmus wird implementiert, indem die Komponente als schmutzig und alle seine übergeordneten Knoten mit einer Flagge markiert werden, die einen schmutzigen Unterbaum haben. Wenn das Markierungsalgorithmus einen Root -Knoten erreicht, ruft er OnRootInvalidated() Hook auf, mit dem ein benutzerdefinierter Zeitplaner implementiert werden kann.
Dirty Flagge markiert.DirtySubtree -Flagge markiert.DirtySubtree -Flag, OnRootInvalidated() -Haken aufgerufen.Dirty Flagge markiert, Eltern, die bereits mit DirtySubtree -Flagge gekennzeichnet sind.Wenn Scheduler beschließt, einen Root -Knoten mit einem schmutzigen Unterbaum zu aktualisieren, startet er einen schmutzigen Check -Algorithmus. Dieser Algorithmus geht in einer Reihenfolge von rechts nach links oben nach unten und besucht alle Knoten mit einem schmutzigen Subtree-Flag, bis er eine schmutzige Komponente erreicht und aktualisiert.
DirtySubtree -Flagge beginnt seine Kinder zu überprüfen.Dirty Flagge, löst ein Update aus.Dirty Flagge, löst ein Update aus. Einer der Gründe, warum die Kernbibliothek so klein ist, ist, dass der Aktualisierungsalgorithmus in RTL -Reihenfolge implementiert wird. Algorithmus, der Updates in RTL -Bestellung durchführt, vereinfacht viele komplexe Probleme mit DOM -Updates. Das Hauptproblem bei DOM -Updates besteht darin, dass wir, wenn wir eine DOM -Baumstruktur aktualisieren, einen Verweis auf einen übergeordneten und einen nächsten DOM -Knoten benötigen, damit wir parent.insertBefore(newNode, nextNode) verwenden können. In den meisten Fällen ist es einfach, einen nächsten DOM -Knoten abzurufen, aber es gibt Randfälle, wie wenn wir zwei benachbarte bedingte Ausdrücke haben, und einer ihrer Zustände ist, dass er einen DOM -Knoten vollständig aus dem Baum oder zwei benachbarte Komponenten mit Bedingungen an ihren Wurzeln usw. vollständig entfernt.
Die Mehrheit der Bibliotheken befasst sich mit diesen Kantenfällen, indem sie Marker -Dom -Knoten (Kommentar oder leerer Textknoten) einführen. Um bedingte Ausdrücke beispielsweise einen leeren Textknoten hinzuzufügen, wenn bedingt kein Dom -Knoten rendert. Wenn bedingt in einen Status geht, wenn er einen DOM -Knoten hinzufügen muss, verwendet er einen Markierungsknoten als nächster DOM -Knotenreferenz. Der RTL -Update -Algorithmus in IVI verwendet keine Markerknoten.
Der RTL -Algorithmus, der in IVI verwendet wird, erleichtert es auch viel einfacher, Knotenverschiebungen zu implementieren, ohne zusätzliche Codepfade, Fragmente und so ziemlich alles einzuführen, bei dem eine Dom -Struktur aktualisiert wird.
Jede Call-Site, die eine Vorlage erstellt, hat eine eindeutige Identität, sodass selbst identische Vorlagen, die aus verschiedenen Call-Sites erstellt wurden, nicht in der Lage sind, gegeneinander zu differenzieren.
function TemplateUniqueIdentity ( condition , text ) {
return ( condition )
? html `div ${ text } `
: html `div ${ text } ` ;
} Im obigen Beispiel ersetzt der Update -Algorithmus, wenn condition geändert wird, anstatt den Textknoten zu aktualisieren, anstatt das gesamte Div -Element durch einen neuen zu ersetzen.
Es gibt einige Anwendungsfälle, die viele häufige Lesevorgänge aus einer reaktiven Variablen erfordern. Und wenn sich diese Variable ändert, wirkt sich viele UI -Knoten aus, z. B. das Umschalten zwischen hellen/dunklen Themen.
Anstatt viele Abonnements für diese Variablen zu erstellen, wird empfohlen, einfache JavaScript -Werte zu verwenden und die gesamte UI -Subtree mit dirtyCheck(root, true) erneut zu übernehmen, wenn diese Werte geändert werden.
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 ( ) ) ; Das Klonieren der Vorlagenklone ist eine Optimierung, die zum Klonen von HTML -Vorlagen mit einem Node.cloneNode() -Methode.
Standardmäßig ist das Klonieren der Vorlagenklone für alle Vorlagen aktiviert. Aber manchmal wäre es verschwenderisch, eine Vorlage zum Klonen zu erstellen und daraus zu instanziieren, wenn diese Vorlage nur einmal gerendert wird.
Um das Klonieren zu deaktivieren, sollte die Vorlage einen führenden Kommentar haben /* preventClone */ . Z.B
const Example = ( ) => /* preventClone */ html `
< div class =" Title " > ${ text } </ div >
` ; Vorlagen mit nur einem Element, das keine statischen Eigenschaften hat, werden mit document.createElement() erstellt.
html ` < div attr = ${ 0 } > ${ 1 } </ div > ` ;Standardmäßig werden Ereignishandler (Arrow -Funktionsausdrücke) automatisch in den äußersten Bereich gehockt.
const Example = component ( ( c ) => {
const [ count , setCount ] = useState ( c , 0 ) ;
return ( ) => html `
< div @click = ${ ( ) => { setCount ( count ( ) + 1 ) ; } } > ${ count ( ) } </ div >
` ;
} ) ;Nach dem Heben von Event -Handler wird es verwandelt in:
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 >
` ;
} ) ; Um Ereignis -Handler zu deaktivieren, die Hebeblate heben, sollte die Vorlage einen führenden Kommentar haben /* preventHoist */ . Z.B
const Example = component ( ( c ) => {
const [ count , setCount ] = useState ( c , 0 ) ;
return ( ) => /* preventHoist */ html `
< div @click = ${ ( ) => { setCount ( count ( ) + 1 ) ; } } > ${ count ( ) } </ div >
` ;
} ) ; Mehrere Anmerkungen können deklariert werden, indem sie mit | getrennt werden Operator, /* preventClone | preventHoist */
Um eine grobe Schätzung der Speicherverwendung zu erhalten, ist es wichtig, interne Datenstrukturen zu verstehen.
In der folgenden Beschreibung berechnen wir den Speicherverbrauch in einem Chrombasis-Motoren mit Zeigerkomprimierung in V8.
Der UI -Baum wird mit einem staatlichen SNode und unveränderlichen Staatlosenbaum VAny implementiert.
Der staatenlose Baum hat eine einfache Datenstruktur:
// 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 [ ] ,
} Für jeden staatenlosen Knoten VAny gibt es einen Stateful -Knoten SNode mit einer Schnittstelle:
// 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 > ;Diese Datenstrukturen wurden sorgfältig so konzipiert, dass sie einen kleinen Speicheraufwand haben und viel polymorphe/megamorphe Anrufstellen vermeiden, die auf diese Datenstrukturen zugreifen.
Um zu verstehen, warum monomorphe Anrufstellen wichtig für die Leistung sind, wird empfohlen, einen großartigen Artikel zu diesem Thema zu lesen: "Was ist mit Monomorphismus los?".
Vorlagen werden in einen statischen Teil vorkompiliert, der in einem TemplateDescriptor -Objekt und einem Array dynamischer Ausdrücke gespeichert ist.
const Example = ( attr , child ) => html `div :attr= ${ attr } span ${ child } ` ;Wird zusammengestellt in:
// _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 > ;Der Template -Compiler eliminiert den Kompilierungsschritt während der Laufzeit nicht nur, sondern hebt auch statische Attribute und Ereignishörer, deduplicates Opcodes, Zeichenfolgen und Template -Fabrikfunktionen. Z.B
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 >
` ;Generiert zwei verschiedene Vorlagen mit gemeinsamen Datenstrukturen:
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 ] ) ;Sehr oft werden Opcodes, die für verschiedene Zwecke verwendet werden (Requisiten, Kind, Zustand), ähnliche Werte haben. Wenn Opcodes dedupliziert werden, werden sie als einfache Arrays mit Ganzzahlen behandelt, die für verschiedene Zwecke verwendet werden können.
Freigegebene Streitigkeiten (Attributschlüssel, Ereignisnamen usw.) werden in ein Array ( __IVI_STRINGS__ ) eingeleitet, das zwischen allen Vorlagen geteilt wird.
IVI ist als eingebettbare Lösung ausgelegt, sodass sie in vorhandene Frameworks oder Webkomponenten integriert werden kann. Der mit createRoot() -Funktion instanziierte Grundstammknoten verwendet die Microtask -Warteschlange, um Updates zu planen. Root -Knoten mit benutzerdefiniertem Planungsalgorithmus können erstellt werden, indem neue Root -Fabriken mit defineRoot() -Funktion definiert werden.
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 > ;Zum Beispiel können wir den folgenden Stammknoten definieren, um das Batching zu entfernen und sofort zu aktualisieren, wenn sie ungültig sind:
import { defineRoot } from "ivi" ;
const createSyncRoot = defineRoot ( ( root ) => {
// Immediately triggers dirty checking.
dirtyCheck ( root ) ;
} ) ; requestAnimationFrame() zur Planung der UI -Updates Das Planungsalgorithmus mit rAF -Batching hat einige potenzielle Fußgunen mit Rennbedingungen.
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 ( ) ,
) ; In diesem Beispiel ist es möglich, eine [enter] wie folgt zu erhalten:
0 in <input> .onChange() Event -Handler wird ausgelöst, state.valid umschaltet in einen false Zustand.[enter] -Taste.<input type="submit" .disabled={false} />rAF -Ereignis wird ausgelöst, die Sendel -Taste wird in einen deaktivierten Status eingeleitet. Der einfachste Weg, um Probleme wie diese zu vermeiden, besteht darin, Mikrotasks für die Charge zu verwenden. Wenn Sie jedoch rAF -Planung hinzufügen möchten, können Sie Probleme wie diese lösen, indem Sie einige Synchronisation -Primitive einführen:
import { uiReady } from "my-custom-scheduler" ;
const onSubmit = async ( ev ) => {
await uiReady ( ) ;
submit ( ) ;
} ; Die IVI -Laufzeit hängt nicht von externen Bibliotheken ab.
IVI Dev Tools hat einen minimalen Satz von Abhängigkeiten:
@rollup/pluginutils wird in einem Rollup-Plugin @ivi/rollup-plugin verwendet, um Module herauszufiltern. MIT