Hook'lar

Carats, @carats/hooks paketi üzerinden size durum ve istemci tarafı yaşam döngüsü sağlar.

Carats'ta useEffect ve bağımlılık dizisi yoktur. Bir hook gövdesi sunucuda da istemcide de çalışır; bu yüzden DOM'a veya ağa dokunan her şey bir hook geri çağrısının içinde bulunmalıdır.

use() Hook'u

use fonksiyonu durum yönetimi sağlar. React hook'larının aksine reaktif değildir: bir değer atamak hiçbir şeyi yeniden işlemez, dolayısıyla sayfadaki hiçbir şey kendiliğinden güncellenmez. İkinin diğer yarısı olan subscribe, bir değişikliğin nasıl görünür hâle geldiğidir. İşlerken değeri okuyun, DOM'u bir aboneden yazın ve değeri her yerden değiştirin.

Temel Kullanım

import { use } from '@carats/hooks';

export default function Counter(props: { start?: number }) {
    const count = use(props.start ?? 0);

    // get() is a plain read, safe on the server and in the browser
    return <p class="counter">{count.get()}</p>;
}

Yeniden işleme olmadığı için değeri işlerken okuyun ve DOM'u bir aboneden yazın:

import { use, afterMount } from '@carats/hooks';

export default function Counter() {
    const count = use(0);

    afterMount(() => {
        const inc = document.getElementById('inc');
        const dec = document.getElementById('dec');
        const label = document.getElementById('count');

        const unsub = count.subscribe((value) => {
            if (label) label.textContent = String(value);
        });

        const increment = () => count.set((n) => n + 1);
        const decrement = () => count.set((n) => n - 1);

        inc?.addEventListener('click', increment);
        dec?.addEventListener('click', decrement);

        return () => {
            unsub();
            inc?.removeEventListener('click', increment);
            dec?.removeEventListener('click', decrement);
        };
    });

    return (
        <div class="counter">
            <p id="count">{count.get()}</p>
            <button id="inc">+</button>
            <button id="dec">-</button>
        </div>
    );
}

State Arayüzü

use fonksiyonu get, set ve subscribe metotları olan bir State nesnesi döndürür. set ya yeni bir değeri ya da mevcut olanı alan bir fabrikayı kabul eder.

type Getter<T> = () => T;
type Factory<T> = (currentValue: T) => T;
type Setter<T> = {
    (fn: Factory<T>): T;
    (value: T): T;
};
type Subscriber<T> = (value: T) => void;
type Subscribe<T> = (subscriber: Subscriber<T>) => () => void;
type State<T> = {
    get: Getter<T>;
    set: Setter<T>;
    subscribe: Subscribe<T>;
};
declare function use<T>(): State<T>;
declare function use<T>(initialState: T): State<T>;

set yeni değeri döndürür; yani güncellenmiş sayıya hemen ihtiyacınız olduğunda sonucu doğrudan kullanabilirsiniz.

`use` bir bileşenin dışından da çağrılabilir; bu, sayfalar arasında tek bir durum parçasını paylaşmanın yerleşik yoludur.

Subscribe Metodu

import { use } from '@carats/hooks';

const state = use(0);

// Returns the unsubscribe function
const unsub = state.subscribe((value) => {
    console.log('Count changed to:', value);
});

subscribe bir abonelikten çıkma fonksiyonu döndürür. Bunu bir afterMount veya beforeMount hook'unun clear geri çağrısından çağırın, aksi halde sayfa değiştikten sonra abone kayıtlı kalır.

İki kaygıyı birbirinden ayırın. Bir işleyici durumu değiştirir, bir abone DOM'u yazar. DOM'u da yazan bir işleyici öğeye iki doğruluk kaynağı verir ve bunlardan biri bir an kaçırıldığında sayfa kendisiyle çelişir.

// Wrong: two places write to the DOM, so they can disagree
const onClick = () => {
    count.set((n) => n + 1);
    if (label) label.textContent = String(count.get());
};

// Right: the handler only changes state, the subscriber only writes the DOM
const unsub =  count.subscribe((value) => {
    if (label) label.textContent = String(value);
});
const onClick = () => count.set((n) => n + 1);

afterMount() Geri Çağrısı

afterMount geri çağrısı yalnızca istemci tarafında, load olayı tetiklendikten sonra çalışır. Sayaç etiketi veya düğme gibi aynı bileşenin oluşturduğu DOM öğelerine erişmek için kullanın. Bir clear geri çağrısı döndürebilir; bu, sonraki hidrasyon geçişinden önce çalışır.

import { afterMount } from '@carats/hooks';

export default function MyComponent() {
    afterMount(() => {
        const button = document.getElementById('my-button');
        const onClick = () => console.log('Button clicked!');
        button?.addEventListener('click', onClick);

        return () => button?.removeEventListener('click', onClick);
    });

    return <button id="my-button">Click me</button>;
}

Kaydettiğiniz işleyicinin tam referansını saklayın. `removeEventListener`'a taze bir ok fonksiyonu geçirmek yeni bir fonksiyon kimliği oluşturur; bu yüzden dinleyici aslında hiç kaldırılmaz.

beforeMount() Geri Çağrısı

beforeMount geri çağrısı, load olayını beklemeden istemci tarafında hemen çalışır. Zamanlayıcılar, tuş dinleyicileri veya abonelikler gibi bileşenin kendi işaretlemesine ihtiyaç duymayan işler için kullanın. afterMount gibi bir clear geri çağrısı döndürebilir.

import { beforeMount } from '@carats/hooks';

export default function MyComponent() {
    beforeMount(() => {
        const timer = setInterval(() => console.log('tick'), 1000);
        return () => clearInterval(timer);
    });

    return <div>My Component</div>;
}

İş, bu bileşenin ürettiği işaretlemeye bağlıysa afterMount'a ulaşın; çünkü load olayından sonra çalışır ve belge o noktada eksiksiz olduğu garanti edilir. Zamanlayıcı veya tuş dinleyicisi gibi iş marklemeye bağlı değilse beforeMount'a ulaşın, böylece iş daha erken başlar.

clearHydrations()

Kayıtlı tüm hidrasyon geri çağrılarının clear geri çağrılarını çalıştırarak temizler. Bu esas olarak testlerde ve bir uygulamayı elle sökerken kullanışlıdır.

import { clearHydrations } from '@carats/hooks';

// Runs every registered clear callback and empties the registry
await clearHydrations();

Tam Örnek

import { use, beforeMount, afterMount } from '@carats/hooks';
import './counter.sass';

interface CounterProps {
    initialValue?: number;
}

export default function Counter(props: CounterProps) {
    const count = use(props.initialValue ?? 0);

    // beforeMount runs immediately and does not need this component's markup
    beforeMount(() => {
        const onKey = (e: KeyboardEvent) => {
            if (e.key === 'ArrowUp') count.set((n) => n + 1);
            if (e.key === 'ArrowDown') count.set((n) => n - 1);
        };
        document.addEventListener('keydown', onKey);
        return () => document.removeEventListener('keydown', onKey);
    });

    afterMount(() => {
        const label = document.getElementById('count');
        const inc = document.getElementById('inc');
        const dec = document.getElementById('dec');

        const unsub = count.subscribe((value) => {
            if (label) label.textContent = String(value);
        });

        const increment = () => count.set((n) => n + 1);
        const decrement = () => count.set((n) => n - 1);

        inc?.addEventListener('click', increment);
        dec?.addEventListener('click', decrement);

        return () => {
            unsub();
            inc?.removeEventListener('click', increment);
            dec?.removeEventListener('click', decrement);
        };
    });

    return (
        <div class="counter">
            <h2>Counter: <span id="count">{count.get()}</span></h2>
            <button id="inc" type="button">+</button>
            <button id="dec" type="button">-</button>
        </div>
    );
}

Tip Tanımları

type MaybePromise<T> = T | Promise<T>;
type ClearCallback = () => MaybePromise<void>;
type HydrationCallback = () => MaybePromise<ClearCallback | void>;

declare function afterMount(callback: HydrationCallback): void;
declare function beforeMount(callback: HydrationCallback): void;
declare function clearHydrations(): Promise<void>;
declare function use<T>(): State<T>;
declare function use<T>(initialState: T): State<T>;