Maison  >  Article  >  interface Web  >  Construire un petit ChHooks React

Construire un petit ChHooks React

Linda Hamilton
Linda Hamiltonoriginal
2024-10-20 18:31:02822parcourir

Build a Tiny React ChHooks

Ce tutoriel est basé sur ce tutoriel, mais avec JSX, dactylographié et une approche plus simple à mettre en œuvre. Vous pouvez consulter les notes et le code sur mon dépôt GitHub.

D'accord, avant de plonger dans les accroches, nous avons besoin de résumer un peu le dernier chapitre - encore quelque chose à corriger, mais le dernier chapitre était de trop, alors, eh bien, le voici.

Correction du dernier chapitre

Voici quelques choses mineures - pas entièrement des bugs, mais il vaut mieux les corriger.

Comparaison vDOM

En javascript, deux fonctions ne sont égales que si elles sont identiques, restent inégales même si elles ont la même procédure, c'est-à-dire

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

Donc, en ce qui concerne la comparaison vDOM, nous devrions ignorer la comparaison des fonctions. Voici le correctif,

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
}

Gestion du CSS

Le style doit être traité comme une propriété spéciale attribuée à l'élément avec la propriété .style. Voici le correctif,

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)
    }
}

Maintenant que ces corrections secondaires sont terminées, passons au sujet principal de ce chapitre : les crochets.

Capsulement de la création de vDOM

Nous avons précédemment appelé explicitement render(vDom, app!), ce qui nécessite la création de vDOM par l'utilisateur, voici une meilleure façon de le faire.

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()
}

Ça a l'air mieux plus ou moins. Passons maintenant au sujet principal de ce chapitre : les crochets.

utiliserÉtat

D'accord, passons au crochet. Le premier hook que nous allons implémenter est useState. C'est un hook qui nous permet de gérer l'état d'un composant. Nous pouvons avoir la signature suivante pour useState,

Notez que notre implémentation est légèrement différente du React original. Nous allons renvoyer une fonction getter et setter, au lieu de renvoyer l'état directement.

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

Alors, où allons-nous fixer la valeur ? Si nous la cachons simplement dans la fermeture elle-même, la valeur sera perdue lorsque le composant sera restitué. Si vous insistez pour le faire, vous devez accéder à l'espace de la fonction externe, ce qui n'est pas possible en javascript.

Notre façon de faire est donc de le stocker, vous l'aurez deviné, dans les fibres. Alors, ajoutons un champ à la fibre.

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
    }
}

Et nous montons uniquement des crochets sur la fibre racine, nous pouvons donc ajouter la ligne suivante à la fonction de montage.

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
}

L'index de crochet sera utilisé plus tard. Désormais, l'index du hook est réinitialisé à chaque fois que le composant est restitué, mais les anciens hooks sont conservés.

Veuillez noter que, lors du rendu du composant vDOM, seule l'ancienne fibre est accessible, nous ne pouvons donc manipuler que cette variable. Cependant, il est nul au tout début, alors créons un mannequin.

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

Maintenant, nous aurons un gros temps de réflexion - puisque l'ordre de chaque appel de hook est fixe (vous ne pouvez pas utiliser de hooks dans une boucle ou une condition, règle de base de React, vous savez pourquoi c'est maintenant), nous pouvons donc l'utiliser en toute sécurité utilisez hookIndex pour accéder au hook.

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
}

Eh bien, essayons,

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)
    }
}

Cela fonctionne en quelque sorte : le nombre est passé de zéro à un, mais il n'augmente pas davantage.

Eh bien... étrange, n'est-ce pas ? Voyons ce qui se passe, c'est l'heure du débogage.

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!)

Vous verrez ça, il enregistre toujours 1. Mais la page web nous dit que c'est 1, donc ça devrait être 2. Que se passe-t-il ?

Pour les types natifs, javascript passe par valeur, donc la valeur est copiée et non référencée. Dans le composant de classe React, vous devez disposer d'un objet d'état pour résoudre les problèmes. En composant fonctionnel, le React s'adresse avec, fermeture. Mais si nous devions utiliser cette dernière solution, cela nécessiterait un grand changement dans notre code. Donc, un moyen simple d'obtenir un laissez-passer consiste à utiliser la fonction pour obtenir l'état, afin que la fonction renvoie toujours le dernier état.

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()
}

Et maintenant, nous l’avons ! Ça marche! Nous avons créé le hook useState pour notre petit React.

Résumé

D'accord, vous pensez peut-être que ce chapitre est trop court - les hooks sont importants pour réagir, alors pourquoi avons-nous uniquement implémenté useState ?

Premièrement, de nombreux hooks ne sont que des variantes de useState. Un tel hook n'a aucun rapport avec le composant qu'il s'appelle, par exemple useMemo. De telles choses ne sont que des travaux insignifiants et nous n'avons pas de temps à perdre.

Mais la deuxième raison, la plus importante, est que, pour des hooks comme useEffect, dans notre cadre actuel basé sur la mise à jour racine, ils sont presque impossibles à faire. Lorsqu'une fibre est démontée, vous ne pouvez pas signaler, car nous récupérons uniquement le vDOM global et mettons à jour l'ensemble du vDOM, alors que, dans le vrai React, ce n'est pas comme ça.

Dans le vrai React, les composants fonctionnels sont mis à jour par le composant parent, afin que le composant parent puisse signaler au composant enfant de démonter. Mais dans notre cas, nous mettons à jour uniquement le composant racine, nous ne pouvons donc pas signaler au composant enfant de démonter.

Cependant, le petit projet actuel a essentiellement démontré comment fonctionne React, et j'espère que cela vous sera utile pour mieux comprendre le framework React.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn