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.
Berikut adalah beberapa perkara kecil- bukan pepijat sepenuhnya, tetapi lebih baik untuk membetulkannya.
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 }
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.
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.
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.
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!