Heim  >  Artikel  >  Web-Frontend  >  Erstellen Sie einen Tiny React ChHooks

Erstellen Sie einen Tiny React ChHooks

Linda Hamilton
Linda HamiltonOriginal
2024-10-20 18:31:02756Durchsuche

Build a Tiny React ChHooks

Dieses Tutorial basiert auf diesem Tutorial, jedoch mit JSX, Typoskript und einem einfacheren Ansatz zur Implementierung. Sie können sich die Notizen und den Code in meinem GitHub-Repo ansehen.

Okay, bevor wir uns in die Hooks stürzen, müssen wir das letzte Kapitel kurz zusammenfassen – es gibt noch etwas zu reparieren, aber das letzte Kapitel war zu viel, also, nun, hier ist es.

Korrektur des letzten Kapitels

Hier sind einige kleinere Dinge – nicht nur Fehler, aber es ist besser, sie zu beheben.

vDOM-Vergleich

In Javascript sind zwei Funktionen nur dann gleich, wenn sie gleich sind. Sie bleiben ungleich, auch wenn sie die gleiche Prozedur haben, d. h.

const a = () => 1;
const b = () => 1;
a === b; // false

Wenn es also um den vDOM-Vergleich geht, sollten wir den Funktionsvergleich überspringen. Hier ist die Lösung,

for (let i = 0; i < aKeys.length; i++) {
    const key = aKeys[i]
    if (key === 'key') continue
    if (aProps[key] instanceof Function && bProps[key] instanceof Function) continue
    if (aProps[key] !== bProps[key]) return false
}
for (let i = 0; i < bKeys.length; i++) {
    const key = bKeys[i]
    if (key === 'key') continue
    if (aProps[key] instanceof Function && bProps[key] instanceof Function) continue
    if (aProps[key] !== bProps[key]) return false
}

Umgang mit CSS

Style sollte als spezielle Eigenschaft behandelt werden, die einem Element mit der Eigenschaft .style zugewiesen wird. Hier ist die Lösung,

export type VDomAttributes = { 
    key?: string | number
    style?: object
    [_: string]: unknown | undefined
}

export function createDom(vDom: VDomNode): HTMLElement | Text {
    if (isElement(vDom)) {
        const element = document.createElement(vDom.tag)
        Object.entries(vDom.props ?? {}).forEach(([name, value]) => {
            if (value === undefined) return
            if (name === 'key') return
            if (name === 'style') {
                Object.entries(value as Record<string, unknown>).forEach(([styleName, styleValue]) => {
                    element.style[styleName as any] = styleValue as any
                })
                return
            }
            if (name.startsWith('on') && value instanceof Function) {
                element.addEventListener(name.slice(2).toLowerCase(), value as EventListener)
            } else {
                element.setAttribute(name, value?.toString() ?? "")
            }
        })
        return element
    } else {
        return document.createTextNode(vDom)
    }
}

Da diese Nebenkorrekturen nun abgeschlossen sind, wenden wir uns dem Hauptthema dieses Kapitels zu – Hooks.

Kapselung der vDOM-Erstellung

Wir haben zuvor explizit render(vDom, app!) aufgerufen, was die Erstellung von vDOM durch den Benutzer erfordert. Hier ist eine bessere Möglichkeit, dies zu tun.

import { mount, useState, type FuncComponent } from "./runtime";
import { createElement, fragment, VDomAttributes, VDomNode } from "./v-dom";

const App: FuncComponent = (props: VDomAttributes, __: VDomNode[]) => {
    const [cnt, setCnt] = useState(0)
    return <div>
        <button onClick={() => setCnt(cnt() + 1)}>Click me</button>
        <p>Count: {cnt()}</p>
    </div>
}
const app = document.getElementById('app')
mount(App, {}, [], app!)
let reRender: () => void = () => {}
export function mount(app: FuncComponent, props: VDomAttributes, children: VDomNode[], parent: HTMLElement) {
    reRender = () => {
        const vDom = app(props, children) as unknown as VDomNode
        render(vDom, parent)
    }
    reRender()
}

Sieht mehr oder weniger besser aus. Kommen wir nun zum Hauptthema dieses Kapitels – Hooks.

useState

Okay, kommen wir zum Haken. Der erste Hook, den wir implementieren werden, ist useState. Es handelt sich um einen Hook, der es uns ermöglicht, den Zustand einer Komponente zu verwalten. Wir können die folgende Signatur für useState haben,

Beachten Sie, dass sich unsere Implementierung geringfügig vom ursprünglichen React unterscheidet. Wir werden eine Getter- und eine Setter-Funktion zurückgeben, anstatt den Status direkt zurückzugeben.

function useState<T>(initialValue: T): [() => T, (newValue: T) => void] {
    // implementation
}

Wo werden wir also den Wert ansetzen? Wenn wir es einfach im Abschluss selbst verstecken, geht der Wert verloren, wenn die Komponente erneut gerendert wird. Wenn Sie darauf bestehen, müssen Sie auf den Bereich der äußeren Funktion zugreifen, was in Javascript nicht möglich ist.

Unsere Art ist es also, es, Sie ahnen es schon, in Ballaststoffen zu speichern. Fügen wir also der Faser ein Feld hinzu.

interface Fiber {
    parent: Fiber | null
    sibling: Fiber | null
    child: Fiber | null
    vDom: VDomNode
    dom: HTMLElement | Text | null
    alternate: Fiber | null
    committed: boolean
    hooks?: {
        state: unknown[]
    },
    hookIndex?: {
        state: number
    }
}

Und wir montieren nur Haken an der Wurzelfaser, sodass wir der Mount-Funktion die folgende Zeile hinzufügen können.

export function render(vDom: VDomNode, parent: HTMLElement) {
    wip = {
        parent: null,
        sibling: null,
        child: null,
        vDom: vDom,
        dom: null,
        committed: false,
        alternate: oldFiber,
        hooks: oldFiber?.hooks ?? {
            state: []
        },
        hookIndex: {
            state: 0
        }
    }
    wipParent = parent
    nextUnitOfWork = wip
}

Hook-Index wird später verwendet. Jetzt wird der Hook-Index jedes Mal zurückgesetzt, wenn die Komponente erneut gerendert wird, aber alte Hooks werden übernommen.

Bitte beachten Sie, dass beim Rendern der vDOM-Komponente nur auf alte Glasfasern zugegriffen werden kann und wir daher nur diese Variable manipulieren können. Allerdings ist es ganz am Anfang null, also richten wir einen Dummy ein.

const a = () => 1;
const b = () => 1;
a === b; // false

Jetzt werden wir viel Zeit zum Nachdenken haben – da die Reihenfolge jedes Hook-Aufrufs festgelegt ist (Sie können Hooks nicht in einer Schleife oder einer Bedingung verwenden, grundlegende React-Regel, Sie wissen jetzt, warum das so ist), sodass wir sie sicher verwenden können Verwenden Sie HookIndex, um auf den Hook zuzugreifen.

for (let i = 0; i < aKeys.length; i++) {
    const key = aKeys[i]
    if (key === 'key') continue
    if (aProps[key] instanceof Function && bProps[key] instanceof Function) continue
    if (aProps[key] !== bProps[key]) return false
}
for (let i = 0; i < bKeys.length; i++) {
    const key = bKeys[i]
    if (key === 'key') continue
    if (aProps[key] instanceof Function && bProps[key] instanceof Function) continue
    if (aProps[key] !== bProps[key]) return false
}

Nun, lass es uns versuchen,

export type VDomAttributes = { 
    key?: string | number
    style?: object
    [_: string]: unknown | undefined
}

export function createDom(vDom: VDomNode): HTMLElement | Text {
    if (isElement(vDom)) {
        const element = document.createElement(vDom.tag)
        Object.entries(vDom.props ?? {}).forEach(([name, value]) => {
            if (value === undefined) return
            if (name === 'key') return
            if (name === 'style') {
                Object.entries(value as Record<string, unknown>).forEach(([styleName, styleValue]) => {
                    element.style[styleName as any] = styleValue as any
                })
                return
            }
            if (name.startsWith('on') && value instanceof Function) {
                element.addEventListener(name.slice(2).toLowerCase(), value as EventListener)
            } else {
                element.setAttribute(name, value?.toString() ?? "")
            }
        })
        return element
    } else {
        return document.createTextNode(vDom)
    }
}

Es funktioniert irgendwie – die Zählung stieg von null auf eins, aber sie erhöht sich nicht weiter.

Nun... seltsam, oder? Mal sehen, was los ist, Zeit zum Debuggen.

import { mount, useState, type FuncComponent } from "./runtime";
import { createElement, fragment, VDomAttributes, VDomNode } from "./v-dom";

const App: FuncComponent = (props: VDomAttributes, __: VDomNode[]) => {
    const [cnt, setCnt] = useState(0)
    return <div>
        <button onClick={() => setCnt(cnt() + 1)}>Click me</button>
        <p>Count: {cnt()}</p>
    </div>
}
const app = document.getElementById('app')
mount(App, {}, [], app!)

Sie werden das sehen, es wird immer 1 protokolliert. Aber die Webseite sagt uns, dass es 1 ist, also sollte es 2 sein. Was ist los?

Bei nativen Typen übergibt Javascript den Wert, sodass der Wert kopiert und nicht referenziert wird. In der React-Klassenkomponente ist ein Statusobjekt erforderlich, um die Probleme zu beheben. In der funktionalen Komponente adressiert die Reaktion „with“, „closure“. Wenn wir jedoch Letzteres verwenden würden, wäre eine große Änderung in unserem Code erforderlich. Eine einfache Möglichkeit, den Status zu erhalten, besteht darin, eine Funktion zu verwenden, um den Status abzurufen, sodass die Funktion immer den neuesten Status zurückgibt.

let reRender: () => void = () => {}
export function mount(app: FuncComponent, props: VDomAttributes, children: VDomNode[], parent: HTMLElement) {
    reRender = () => {
        const vDom = app(props, children) as unknown as VDomNode
        render(vDom, parent)
    }
    reRender()
}

Und jetzt haben wir es! Es funktioniert! Wir haben den useState-Hook für unseren kleinen React erstellt.

Zusammenfassung

Okay, Sie glauben vielleicht, dass dieses Kapitel zu kurz ist – Hooks sind wichtig, um zu reagieren. Warum haben wir also nur useState implementiert?

Erstens sind viele Hooks nur Variationen von useState. Ein solcher Hook ist für die Komponente, die er heißt, beispielsweise useMemo, irrelevant. Solche Dinge sind nur triviale Arbeiten und wir dürfen keine Zeit verlieren.

Aber der zweite, wichtigste Grund ist, dass Hooks wie useEffect in unserem aktuellen, auf Root-Updates basierenden Rahmen nahezu unmöglich sind. Wenn eine Glasfaser ausgehängt wird, können Sie kein Signal senden, da wir nur das globale vDOM abrufen und das gesamte vDOM aktualisieren, während dies in echtem React nicht der Fall ist.

In echtem React werden Funktionskomponenten von der übergeordneten Komponente aktualisiert, sodass die übergeordnete Komponente der untergeordneten Komponente signalisieren kann, die Bereitstellung aufzuheben. Aber in unserem Fall aktualisieren wir nur die Root-Komponente, sodass wir der untergeordneten Komponente nicht signalisieren können, die Bereitstellung aufzuheben.

Das aktuelle kleine Projekt hat jedoch im Wesentlichen gezeigt, wie React funktioniert, und ich hoffe, dass es Ihnen dabei hilft, ein besseres Verständnis des React-Frameworks zu erlangen.

Das obige ist der detaillierte Inhalt vonErstellen Sie einen Tiny React ChHooks. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn