المحتويات
الخطافات
يوفّر Carats لك إدارة الحالة ودورة حياة في جانب العميل عبر حزمة @carats/hooks.
لا يوجد useEffect ولا مصفوفة اعتماديات في Carats. جسم الخطاف يعمل على الخادم وفي العميل، لذا كل ما يلمس DOM أو الشبكة يجب أن يكون داخل استدعاء خطاف.
خطاف use()
توفّر الدالة use إدارة الحالة. وبخلاف خطافات React فهي ليست تفاعلية: تعيين قيمة لا يُعيد تصيير شيء، لذا لا يتحدّث شيء في الصفحة من تلقاء نفسه. النصف الآخر من الزوج هو subscribe، وهو كيف يصبح التغيير مرئيًا. اقرأ القيمة أثناء التصيير، واكتب DOM من مشترك، وغيّر القيمة من أي مكان.
الاستخدام الأساسي
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>;
}
لأنه لا إعادة تصيير، اقرأ القيمة أثناء التصيير واكتب DOM من مشترك:
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>
);
}
واجهة الحالة
تُعيد الدالة use كائن State فيه طرائق get وset وsubscribe. ويقبل set إما قيمة جديدة أو دالة مصنّعة تستقبل الحالية.
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 القيمة الجديدة، لذا يمكنك استخدام الناتج مباشرة حين تحتاج الرقم المحدَّث فورًا.
يمكن استدعاء `use` أيضًا خارج المكوّن، وهذه هي الطريقة المعتادة لمشاركة قطعة حالة واحدة بين الصفحات.
طريقة subscribe
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 دالة لإلغاء الاشتراك. استدعِها من استدعاء clear في خطاف afterMount أو beforeMount، وإلا بقي المشترك مسجلًا بعد استبدال الصفحة.
أبقِ الأمرين منفصلين. المعالج يغيّر الحالة والمشترك يكتب DOM. المعالج الذي يكتب DOM أيضًا يمنح العنصر مصدرَي حقيقة، وفي اللحظة التي يُفوَّت فيها أحدهما تتناقض الصفحة مع نفسها.
// 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()
يعمل استدعاء afterMount في جانب العميل فقط، بعد إطلاق حدث load. استخدمه للوصول إلى عناصر DOM التي أنشأها المكوّن نفسه، مثل تسمية عداد أو زر. ويمكنه إرجاع استدعاء clear يعمل قبل تمريرة الترطيب التالية.
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>;
}
احتفظ بإشارة إلى المعالج نفسه الذي سجّلته. تمرير دالة سهم جديدة إلى removeEventListener ينشئ هوية دالة جديدة، لذا لا يُزال المستمع فعليًا أبدًا.
استدعاء beforeMount()
يعمل استدعاء beforeMount فورًا في جانب العميل، دون انتظار حدث load. استخدمه للأعمال التي لا تحتاج ترميز المكوّن نفسه، مثل المؤقتات ومستمعي لوحة المفاتيح أو الاشتراكات. وكما في afterMount، يمكنه إرجاع استدعاء clear.
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>;
}
استخدم afterMount عندما يعتمد العمل على الترميز الذي أنتجه هذا المكوّن، لأنه يعمل بعد حدث load,此时 يكون المستند مكتملًا. واستخدم beforeMount عندما لا يعتمد، مثل مؤقت أو مستمع لوحة مفاتيح، كي يبدأ العمل في وقت أقرب.
clearHydrations()
يمسح كل استدعاءات الترطيب المسجَّلة بتشغيل استدعاءات clear الخاصة بها. وهذا مفيد أساسًا في الاختبارات وفي تفكيك تطبيق يدويًا.
import { clearHydrations } from '@carats/hooks';
// Runs every registered clear callback and empties the registry
await clearHydrations();
مثال كامل
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>
);
}
تعريفات الأنواع
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>;