Rumah  >  Artikel  >  hujung hadapan web  >  Bina ChHooks Tiny React

Bina ChHooks Tiny React

Linda Hamilton
Linda Hamiltonasal
2024-10-20 18:31:02822semak imbas

Build a Tiny React ChHooks

Tutorial ini berdasarkan tutorial ini, tetapi dengan JSX, skrip taip dan pendekatan yang lebih mudah untuk dilaksanakan. Anda boleh menyemak nota dan kod pada repo GitHub saya.

Baiklah, sebelum menyelam ke dalam mata kail, kita perlu menutup sedikit bab terakhir- masih ada yang perlu diperbaiki, tetapi bab terakhir terlalu banyak, jadi, inilah dia.

Membetulkan bab terakhir

Berikut adalah beberapa perkara kecil- bukan pepijat sepenuhnya, tetapi lebih baik untuk membetulkannya.

vDOM Perbandingan

Dalam javascript, dua fungsi adalah sama hanya jika ia adalah sama, kekal tidak sama walaupun mempunyai prosedur yang sama, iaitu,

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

Jadi, apabila bercakap tentang Perbandingan vDOM, kita harus melangkau perbandingan fungsi. Inilah penyelesaiannya,

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
}

Mengendalikan CSS

Gaya harus dianggap sebagai sifat istimewa yang dikaitkan dengan elemen dengan sifat .style. Inilah penyelesaiannya,

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

Sekarang pembaikan sampingan ini telah selesai, mari kita beralih kepada topik utama bab ini- Cangkuk.

Penciptaan vDOM kapsul

Kami sebelum ini secara eksplisit memanggil render(vDom, app!), yang memerlukan pembuatan vDOM oleh pengguna, berikut ialah cara yang lebih baik untuk melakukannya.

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

Kelihatan lebih kurang baik. Sekarang mari kita beralih kepada topik utama bab ini- Cangkuk.

useState

Baiklah, mari kita ke cangkuk. Cangkuk pertama yang akan kami laksanakan ialah useState. Ia adalah cangkuk yang membolehkan kita menguruskan keadaan komponen. Kita boleh mempunyai tandatangan berikut untuk useState,

Perhatikan bahawa pelaksanaan kami berbeza sedikit daripada React yang asal. Kami akan mengembalikan fungsi getter dan setter, bukannya mengembalikan keadaan secara langsung.

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

Jadi di manakah kita akan mengaitkan nilainya? Jika kita hanya menyembunyikannya dalam penutupan itu sendiri, nilai akan hilang apabila komponen itu dipaparkan semula. Jika anda berkeras untuk berbuat demikian, anda perlu mengakses ruang fungsi luar, yang tidak boleh dilakukan dalam javascript.

Jadi cara kami ialah menyimpannya, anda rasa, dalam serat. Jadi, mari tambah medan pada gentian.

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

Dan kami hanya memasang cangkuk pada gentian akar, jadi kami boleh menambah baris berikut pada fungsi pelekap.

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
}

Indeks cangkuk akan mula digunakan kemudian. Kini, indeks cangkuk ditetapkan semula setiap kali komponen dipaparkan semula, tetapi cangkuk lama dibawa ke atas.

Sila ambil perhatian bahawa, kami memaparkan komponen vDOM, hanya gentian lama boleh diakses, jadi kami hanya boleh memanipulasi pembolehubah itu. Walau bagaimanapun, ia adalah batal pada awalnya, jadi mari kita sediakan dummy.

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

Sekarang kita akan mempunyai masa otak yang besar- memandangkan susunan setiap panggilan cangkuk ditetapkan (anda tidak boleh menggunakan cangkuk dalam gelung atau keadaan, peraturan React asas, anda tahu mengapa ia sekarang), jadi kami boleh menggunakan dengan selamat gunakan hookIndex untuk mengakses cangkuk.

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
}

Nah, jom cuba,

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

Ia agak berkesan- kiraan meningkat daripada sifar kepada satu, tetapi ia tidak meningkat lagi.

Nah... pelik kan? Mari lihat apa yang berlaku, masa nyahpepijat.

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

Anda akan melihatnya, ia sentiasa log 1. Tetapi halaman web memberitahu kami bahawa ia adalah 1, jadi ia sepatutnya 2. Apa yang sedang berlaku?

Untuk jenis asli, javascript melepasi nilai, jadi nilai disalin, bukan dirujuk. Dalam komponen kelas React, ia memerlukan anda mempunyai objek keadaan untuk menangani isu tersebut. Dalam komponen berfungsi, React menangani dengan, penutupan. Tetapi jika kita menggunakan yang terakhir, ia memerlukan perubahan besar dalam kod kita. Jadi cara mudah untuk mendapatkan pas ialah, menggunakan fungsi untuk mendapatkan keadaan, supaya fungsi itu akan sentiasa mengembalikan keadaan terkini.

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

Dan sekarang, di sini kita mendapatnya! Ia berkesan! Kami mencipta cangkuk useState untuk React kecil kami.

Ringkasan

Baiklah, anda mungkin percaya bahawa bab ini terlalu pendek- cangkuk penting untuk bertindak balas, jadi mengapa kami hanya melaksanakan useState?

Pertama, banyak cangkuk hanyalah variasi useState. Cangkuk sedemikian tidak relevan dengan komponen yang dipanggil, sebagai contoh, useMemo. Perkara sebegini hanyalah kerja remeh dan kami tiada masa untuk disia-siakan.

Tetapi, kedua, sebab yang paling penting, ialah, untuk cangkuk seperti useEffect, di bawah bingkai berasaskan kemas kini akar semasa kami, ia hampir mustahil untuk dilakukan. Apabila gentian dinyahlekap, anda tidak boleh memberi isyarat, kerana kami hanya mengambil vDOM global dan mengemas kini keseluruhan vDOM, sedangkan, dalam React sebenar, ia tidak begitu.

Dalam React sebenar, komponen berfungsi dikemas kini oleh komponen induk, jadi komponen induk boleh memberi isyarat kepada komponen anak untuk dinyahlekap. Tetapi dalam kes kami, kami hanya mengemas kini komponen akar, jadi kami tidak boleh memberi isyarat kepada komponen anak untuk dinyahlekap.

Walau bagaimanapun, projek kecil semasa pada asasnya telah menunjukkan cara tindak balas berfungsi dan saya harap anda dapat membantu dalam mendapatkan pemahaman yang lebih baik tentang rangka kerja tindak balas.

Atas ialah kandungan terperinci Bina ChHooks Tiny React. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn