SolidJS
إطار عمل JavaScript تفاعلي دقيق لواجهات المستخدم بدون DOM افتراضي وأداء استثنائي.
التثبيت
Section titled “التثبيت”إنشاء مشروع جديد
Section titled “إنشاء مشروع جديد”| الأمر | الوصف |
|---|---|
npx degit solidjs/templates/ts my-app | إنشاء مشروع SolidJS بـ TypeScript |
npx degit solidjs/templates/js my-app | إنشاء مشروع SolidJS بـ JavaScript |
npm create solid@latest | إنشاء مشروع باستخدام SolidStart CLI |
npm install solid-js | تثبيت SolidJS في مشروع موجود |
npm install solid-start | تثبيت إطار SolidStart الشامل |
أوامر التطوير
Section titled “أوامر التطوير”| الأمر | الوصف |
|---|---|
npm run dev | تشغيل خادم التطوير مع إعادة التحميل التلقائي |
npm run build | بناء للإنتاج |
npm run serve | معاينة بناء الإنتاج محلياً |
npm run start | تشغيل خادم SolidStart |
قوالب المشاريع
Section titled “قوالب المشاريع”# SolidJS أساسي (Vite)
npx degit solidjs/templates/ts my-app
cd my-app && npm install
# SolidStart (إطار عمل شامل)
npm create solid@latest my-app
# اختر قالباً: bare, with-auth, with-mdx, with-prisma, with-tailwindcss
# قوالب المجتمع
npx degit solidjs/templates/ts-windicss my-app # WindiCSS
npx degit solidjs/templates/ts-unocss my-app # UnoCSS
npx degit solidjs/templates/ts-bootstrap my-app # Bootstrap
npx degit solidjs/templates/ts-sass my-app # Sass
المكونات و JSX
Section titled “المكونات و JSX”المكونات الأساسية
Section titled “المكونات الأساسية”| الأمر | الوصف |
|---|---|
function App() { return <div>Hello</div> } | تعريف مكون أساسي |
<MyComponent name="value" /> | تمرير الخصائص لمكون |
props.children | الوصول لمحتوى الأبناء |
<div class={styles.container}> | تطبيق فئة CSS (استخدم class وليس className) |
<div classList={{ active: isActive() }}> | فئات CSS شرطية |
<div style={{ color: 'red', 'font-size': '14px' }}> | تطبيق أنماط مضمنة |
<div innerHTML={htmlString} /> | تعيين محتوى HTML خام |
<div ref={myRef}> | إرفاق مرجع DOM |
معالجة الأحداث
Section titled “معالجة الأحداث”| الأمر | الوصف |
|---|---|
<div onClick={handler}> | إرفاق حدث مفوّض |
<div on:click={handler}> | إرفاق حدث DOM أصلي (يتجاوز التفويض) |
<div onInput={(e) => setValue(e.target.value)}> | معالجة أحداث الإدخال |
<div on:keydown={(e) => handleKey(e)}> | معالجة أحداث لوحة المفاتيح أصلياً |
<div onClick={[handler, data]}> | تمرير بيانات مع معالج الحدث |
أنماط المكونات
Section titled “أنماط المكونات”import { type Component, type ParentComponent } from 'solid-js'
// مكون بأنواع محددة مع خصائص
interface UserProps {
name: string
age: number
role?: string
}
const UserCard: Component<UserProps> = (props) => {
return (
<div class="card">
<h2>{props.name}</h2>
<p>العمر: {props.age}</p>
<p>الدور: {props.role ?? 'عضو'}</p>
</div>
)
}
// مكون أب (يقبل أبناء)
const Layout: ParentComponent = (props) => {
return (
<div class="layout">
<header>تطبيقي</header>
<main>{props.children}</main>
<footer>التذييل</footer>
</div>
)
}
// تقسيم الخصائص لإعادة التوجيه
import { splitProps } from 'solid-js'
const Button: Component<ButtonProps> = (props) => {
const [local, rest] = splitProps(props, ['variant', 'size'])
return (
<button
class={`btn btn-${local.variant} btn-${local.size}`}
{...rest}
/>
)
}
// دمج الخصائص الافتراضية
import { mergeProps } from 'solid-js'
const Alert: Component<AlertProps> = (rawProps) => {
const props = mergeProps({ type: 'info', dismissible: false }, rawProps)
return <div class={`alert alert-${props.type}`}>{props.children}</div>
}
الأوليات التفاعلية
Section titled “الأوليات التفاعلية”الإشارات (Signals)
Section titled “الإشارات (Signals)”| الأمر | الوصف |
|---|---|
const [count, setCount] = createSignal(0) | إنشاء إشارة تفاعلية |
count() | قراءة قيمة الإشارة (يجب استدعاؤها كدالة) |
setCount(5) | تعيين الإشارة لقيمة محددة |
setCount(prev => prev + 1) | تحديث الإشارة بالقيمة السابقة |
const [name, setName] = createSignal<string>() | إنشاء إشارة بنوع محدد |
التأثيرات والقيم المحسوبة
Section titled “التأثيرات والقيم المحسوبة”| الأمر | الوصف |
|---|---|
createEffect(() => console.log(count())) | تشغيل تأثير جانبي عند تغير الإشارة |
createEffect(on(count, (v) => log(v))) | تتبع إشارة محددة بشكل صريح |
createEffect(on([a, b], ([a, b]) => ...)) | تتبع إشارات متعددة بشكل صريح |
const double = createMemo(() => count() * 2) | إنشاء قيمة مشتقة/محسوبة |
createRenderEffect(() => ...) | تأثير يعمل قبل رسم DOM |
createComputed(() => ...) | تأثير متزامن أثناء التصيير |
دورة الحياة
Section titled “دورة الحياة”| الأمر | الوصف |
|---|---|
onMount(() => { ... }) | تشغيل مرة واحدة عند تحميل المكون |
onCleanup(() => { ... }) | تشغيل التنظيف عند إزالة المكون |
batch(() => { setA(1); setB(2) }) | تجميع تحديثات إشارات متعددة |
untrack(() => value()) | قراءة إشارة دون تتبع التبعية |
الأنماط التفاعلية
Section titled “الأنماط التفاعلية”import { createSignal, createEffect, createMemo, on, batch, untrack } from 'solid-js'
function Counter() {
const [count, setCount] = createSignal(0)
const [step, setStep] = createSignal(1)
// قيمة مشتقة (محفوظة، تُعاد حسابها فقط عند تغير count)
const doubled = createMemo(() => count() * 2)
const isEven = createMemo(() => count() % 2 === 0)
// تأثير: يعمل عند تغير أي إشارة يتم الوصول إليها
createEffect(() => {
console.log(`العدد الآن: ${count()}`)
// يتتبع count() كتبعية تلقائياً
})
// تتبع صريح باستخدام on()
createEffect(on(count, (value, prev) => {
console.log(`تغير من ${prev} إلى ${value}`)
}, { defer: true })) // defer: تخطي التشغيل الأولي
// تجميع التحديثات (إعادة تصيير واحدة)
const reset = () => batch(() => {
setCount(0)
setStep(1)
})
// القراءة دون إنشاء تبعية
const logWithoutTracking = () => {
const current = untrack(() => count())
console.log('لقطة:', current)
}
return (
<div>
<p>العدد: {count()} (مضاعف: {doubled()}, زوجي: {String(isEven())})</p>
<button onClick={() => setCount(c => c + step())}>
أضف {step()}
</button>
<button onClick={reset}>إعادة تعيين</button>
</div>
)
}
التحكم في التدفق
Section titled “التحكم في التدفق”المكونات المدمجة
Section titled “المكونات المدمجة”| الأمر | الوصف |
|---|---|
<Show when={loggedIn()} fallback={<Login/>}> | التصيير الشرطي |
<For each={items()}>{(item) => <li>{item}</li>}</For> | تصيير قائمة (بمفتاح المرجع) |
<Index each={items()}>{(item, i) => <li>{item()}</li>}</Index> | تصيير قائمة (بمفتاح الفهرس) |
<Switch><Match when={a()}>A</Match></Switch> | تصيير Switch/case |
<ErrorBoundary fallback={err => <p>{err}</p>}> | التقاط أخطاء التصيير |
<Suspense fallback={<Loading/>}> | عرض بديل أثناء التحميل غير المتزامن |
<Dynamic component={MyComp} /> | تصيير مكون ديناميكي |
<Portal mount={document.body}> | التصيير في عقدة DOM مختلفة |
أمثلة التحكم في التدفق
Section titled “أمثلة التحكم في التدفق”import { Show, For, Index, Switch, Match, Suspense, ErrorBoundary } from 'solid-js'
function Dashboard() {
const [user, setUser] = createSignal(null)
const [items, setItems] = createSignal([
{ id: 1, name: 'ألفا', status: 'نشط' },
{ id: 2, name: 'بيتا', status: 'غير نشط' },
])
const [view, setView] = createSignal('list')
return (
<div>
{/* Show: تصيير شرطي مع بديل */}
<Show when={user()} fallback={<p>يرجى تسجيل الدخول</p>}>
{(u) => <p>مرحباً، {u().name}!</p>}
</Show>
{/* For: بمفتاح المرجع (الأفضل للكائنات) */}
<For each={items()}>
{(item, index) => (
<div>
<span>{index() + 1}. {item.name}</span>
<span> ({item.status})</span>
</div>
)}
</For>
{/* Switch/Match: شروط متعددة */}
<Switch fallback={<p>عرض غير معروف</p>}>
<Match when={view() === 'list'}>
<ListView />
</Match>
<Match when={view() === 'grid'}>
<GridView />
</Match>
<Match when={view() === 'table'}>
<TableView />
</Match>
</Switch>
{/* ErrorBoundary: التقاط الأخطاء في الشجرة الفرعية */}
<ErrorBoundary fallback={(err, reset) => (
<div>
<p>خطأ: {err.message}</p>
<button onClick={reset}>حاول مرة أخرى</button>
</div>
)}>
<RiskyComponent />
</ErrorBoundary>
</div>
)
}
المخازن والحالة
Section titled “المخازن والحالة”عمليات المخزن
Section titled “عمليات المخزن”| الأمر | الوصف |
|---|---|
const [store, setStore] = createStore({}) | إنشاء مخزن تفاعلي |
setStore('name', 'Alice') | تحديث خاصية المخزن بالمسار |
setStore('users', 0, 'name', 'Bob') | تحديث خاصية مخزن متداخلة |
setStore('list', l => [...l, item]) | إضافة عنصر لمصفوفة المخزن |
setStore('list', i => i.id === 1, 'done', true) | تحديث عناصر المصفوفة المطابقة |
produce(s => { s.count++ }) | تحديثات المخزن بأسلوب قابل للتغيير |
reconcile(newData) | استبدال بيانات المخزن بكفاءة |
unwrap(store) | الحصول على البيانات الخام غير الملفوفة بـ proxy |
أنماط المخزن
Section titled “أنماط المخزن”import { createStore, produce, reconcile, unwrap } from 'solid-js/store'
interface Todo {
id: number
text: string
completed: boolean
}
interface AppState {
todos: Todo[]
filter: 'all' | 'active' | 'completed'
user: { name: string; settings: { theme: string } }
}
function TodoApp() {
const [state, setState] = createStore<AppState>({
todos: [],
filter: 'all',
user: { name: 'Alice', settings: { theme: 'dark' } },
})
// إضافة عنصر
const addTodo = (text: string) => {
setState('todos', todos => [
...todos,
{ id: Date.now(), text, completed: false }
])
}
// تبديل عنصر محدد بالمطابقة
const toggleTodo = (id: number) => {
setState('todos', todo => todo.id === id, 'completed', c => !c)
}
// حذف عنصر (التصفية تنتج مصفوفة جديدة)
const deleteTodo = (id: number) => {
setState('todos', todos => todos.filter(t => t.id !== id))
}
// تحديثات بأسلوب قابل للتغيير باستخدام produce
const clearCompleted = () => {
setState(produce((s) => {
s.todos = s.todos.filter(t => !t.completed)
}))
}
// تحديث متداخل عميق
const setTheme = (theme: string) => {
setState('user', 'settings', 'theme', theme)
}
// استبدال بيانات المخزن بالكامل (reconcile يقارن بكفاءة)
const loadFromServer = async () => {
const data = await fetch('/api/todos').then(r => r.json())
setState('todos', reconcile(data))
}
return (/* ... */)
}
الموارد وجلب البيانات
Section titled “الموارد وجلب البيانات”عمليات الموارد
Section titled “عمليات الموارد”| الأمر | الوصف |
|---|---|
const [data] = createResource(fetchFn) | إنشاء مورد غير متزامن |
const [data] = createResource(id, fetchFn) | مورد مع إشارة مصدر تفاعلية |
data() | الوصول لبيانات المورد |
data.loading | التحقق مما إذا كان المورد قيد التحميل |
data.error | الوصول لخطأ المورد |
data.latest | الحصول على آخر قيمة محلولة (تبقى بعد إعادة الجلب) |
data.state | الحصول على حالة المورد (‘unresolved’, ‘pending’, ‘ready’, ‘errored’) |
const { refetch } = data | إعادة الجلب يدوياً |
createResource(source, fetcher, { initialValue }) | مورد بقيمة أولية |
أنماط جلب البيانات
Section titled “أنماط جلب البيانات”import { createResource, createSignal, Suspense, ErrorBoundary } from 'solid-js'
interface User {
id: number
name: string
email: string
}
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) throw new Error(`المستخدم ${id} غير موجود`)
return res.json()
}
function UserProfile() {
const [userId, setUserId] = createSignal(1)
// المورد يعيد الجلب تلقائياً عند تغير userId()
const [user, { refetch, mutate }] = createResource(userId, fetchUser)
// تحديث متفائل
const updateName = async (newName: string) => {
const prev = user()
mutate({ ...prev!, name: newName }) // متفائل
try {
await fetch(`/api/users/${userId()}`, {
method: 'PATCH',
body: JSON.stringify({ name: newName }),
})
} catch {
mutate(prev) // التراجع عند الخطأ
}
}
return (
<ErrorBoundary fallback={<p>فشل تحميل المستخدم</p>}>
<Suspense fallback={<p>جاري تحميل المستخدم...</p>}>
<div>
<h2>{user()?.name}</h2>
<p>{user()?.email}</p>
<p>الحالة: {user.state}</p>
<button onClick={refetch} disabled={user.loading}>
تحديث
</button>
<button onClick={() => setUserId(id => id + 1)}>
المستخدم التالي
</button>
</div>
</Suspense>
</ErrorBoundary>
)
}
التوجيه
Section titled “التوجيه”@solidjs/router
Section titled “@solidjs/router”| الأمر | الوصف |
|---|---|
npm install @solidjs/router | تثبيت موجه SolidJS |
<Router><Route path="/" component={Home}/></Router> | تعريف مسار أساسي |
<Route path="/users/:id" component={User}/> | مسار بمعامل |
<Route path="/*all" component={NotFound}/> | مسار شامل |
const params = useParams() | الوصول لمعاملات المسار |
const [searchParams, setSearchParams] = useSearchParams() | الوصول لمعاملات الاستعلام |
const navigate = useNavigate() | التنقل البرمجي |
navigate('/dashboard') | التنقل إلى مسار |
<A href="/about">حول</A> | مكون رابط التنقل |
<A href="/about" activeClass="active"> | رابط بتنسيق نشط |
إعدادات الموجه
Section titled “إعدادات الموجه”import { Router, Route, A, useParams, useNavigate, useSearchParams } from '@solidjs/router'
import { lazy } from 'solid-js'
// مسارات محملة بتأخير
const Dashboard = lazy(() => import('./pages/Dashboard'))
const UserProfile = lazy(() => import('./pages/UserProfile'))
const Settings = lazy(() => import('./pages/Settings'))
function App() {
return (
<Router>
<nav>
<A href="/" activeClass="active" end>الرئيسية</A>
<A href="/dashboard" activeClass="active">لوحة التحكم</A>
<A href="/settings" activeClass="active">الإعدادات</A>
</nav>
<Route path="/" component={Home} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/users/:id" component={UserProfile} />
<Route path="/settings" component={Settings} />
<Route path="/*all" component={NotFound} />
</Router>
)
}
// مسارات متداخلة
function App() {
return (
<Router>
<Route path="/admin" component={AdminLayout}>
<Route path="/" component={AdminDashboard} />
<Route path="/users" component={AdminUsers} />
<Route path="/users/:id" component={AdminUserDetail} />
</Route>
</Router>
)
}
السياق وحقن التبعيات
Section titled “السياق وحقن التبعيات”واجهة السياق (Context API)
Section titled “واجهة السياق (Context API)”import { createContext, useContext, type ParentComponent } from 'solid-js'
import { createStore } from 'solid-js/store'
interface AuthState {
user: { name: string; role: string } | null
token: string | null
}
interface AuthContextValue {
state: AuthState
login: (token: string, user: AuthState['user']) => void
logout: () => void
isAuthenticated: () => boolean
}
const AuthContext = createContext<AuthContextValue>()
const AuthProvider: ParentComponent = (props) => {
const [state, setState] = createStore<AuthState>({
user: null,
token: null,
})
const value: AuthContextValue = {
state,
login: (token, user) => {
setState({ token, user })
},
logout: () => {
setState({ token: null, user: null })
},
isAuthenticated: () => state.token !== null,
}
return (
<AuthContext.Provider value={value}>
{props.children}
</AuthContext.Provider>
)
}
function useAuth() {
const context = useContext(AuthContext)
if (!context) throw new Error('يجب استخدام useAuth داخل AuthProvider')
return context
}
// الاستخدام
function UserMenu() {
const auth = useAuth()
return (
<Show when={auth.isAuthenticated()} fallback={<LoginButton />}>
<p>مرحباً، {auth.state.user?.name}</p>
<button onClick={auth.logout}>تسجيل الخروج</button>
</Show>
)
}
التنسيق
Section titled “التنسيق”خيارات CSS
Section titled “خيارات CSS”| الأمر | الوصف |
|---|---|
import styles from './App.module.css' | استيراد وحدات CSS |
<div class={styles.container}> | تطبيق فئة وحدة CSS |
npm install solid-styled-components | تثبيت المكونات المنسقة لـ Solid |
const Btn = styled('button')\color: red“ | إنشاء مكون منسق |
<div class="static-class"> | تطبيق فئة CSS ثابتة |
تكامل Tailwind CSS
Section titled “تكامل Tailwind CSS”# تثبيت Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
// tailwind.config.js
export default {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: { extend: {} },
plugins: [],
}
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
SolidStart (الإطار الشامل)
Section titled “SolidStart (الإطار الشامل)”ميزات SolidStart
Section titled “ميزات SolidStart”| الأمر | الوصف |
|---|---|
npm create solid@latest | إنشاء مشروع SolidStart |
"use server" directive | تحديد الدالة كخادم فقط |
createServerAction$() | إنشاء إجراء خادم |
createRouteData() | تحميل بيانات المسار على الخادم |
<Title>عنوان الصفحة</Title> | تعيين عنوان الصفحة (من @solidjs/meta) |
<Meta name="description" content="..." /> | تعيين وسوم meta |
دوال وإجراءات الخادم
Section titled “دوال وإجراءات الخادم”// src/routes/todos.tsx
import { createAsync, query, action, redirect } from '@solidjs/router'
// استعلام الخادم (تحميل البيانات)
const getTodos = query(async () => {
'use server'
const db = await getDatabase()
return db.todos.findMany()
}, 'todos')
// إجراء الخادم (التعديلات)
const addTodo = action(async (formData: FormData) => {
'use server'
const text = formData.get('text') as string
const db = await getDatabase()
await db.todos.create({ data: { text, completed: false } })
throw redirect('/todos') // يعيد التحقق
})
export default function TodosPage() {
const todos = createAsync(() => getTodos())
return (
<div>
<form action={addTodo} method="post">
<input name="text" placeholder="مهمة جديدة" required />
<button type="submit">إضافة</button>
</form>
<Suspense fallback={<p>جاري التحميل...</p>}>
<For each={todos()}>
{(todo) => <p>{todo.text}</p>}
</For>
</Suspense>
</div>
)
}
الاختبار
Section titled “الاختبار”إعداد Vitest
Section titled “إعداد Vitest”npm install -D vitest @solidjs/testing-library @testing-library/jest-dom jsdom
npm install -D vite-plugin-solid
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import solid from 'vite-plugin-solid'
export default defineConfig({
plugins: [solid()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test-setup.ts',
transformMode: { web: [/\.[jt]sx?$/] },
},
})
اختبار المكونات
Section titled “اختبار المكونات”import { render, screen, fireEvent } from '@solidjs/testing-library'
import { describe, it, expect } from 'vitest'
import Counter from './Counter'
describe('Counter', () => {
it('يصيّر العدد الأولي', () => {
render(() => <Counter initialCount={5} />)
expect(screen.getByText('Count: 5')).toBeInTheDocument()
})
it('يزداد عند النقر', async () => {
render(() => <Counter initialCount={0} />)
const button = screen.getByRole('button', { name: /increment/i })
fireEvent.click(button)
expect(screen.getByText('Count: 1')).toBeInTheDocument()
})
})
أفضل الممارسات
Section titled “أفضل الممارسات”-
استدعِ الإشارات دائماً كدوال —
count()وليسcount. نسيان الأقواس هو الخطأ الأكثر شيوعاً في SolidJS؛ بدونها تمرر دالة الحصول بدلاً من القيمة. -
لا تفكك الخصائص — التفكيك يكسر التفاعلية لأنه يقرأ القيمة مرة واحدة. استخدم
props.nameمباشرة أو استخدمsplitProps()/mergeProps()للتعامل مع الخصائص. -
استخدم
<For>لمصفوفات الكائنات و<Index>للقيم البسيطة —<For>يفهرس بالمرجع (أفضل للكائنات التي قد تُعاد ترتيبها) بينما<Index>يفهرس بالموضع (أفضل لقوائم القيم البسيطة). -
فضّل المخازن للحالة المعقدة — الإشارات رائعة للقيم البسيطة، لكن للكائنات والمصفوفات المتداخلة، يوفر
createStoreتفاعلية دقيقة دون استبدال الكائن بالكامل. -
استخدم
on()للتتبع الصريح — عندما تريد أن يتتبع التأثير إشارات محددة فقط (وليس كل إشارة تُقرأ بداخله)، غلّفه بـon(signal, callback). -
استفد من
SuspenseوErrorBoundary— غلّف المكونات غير المتزامنة بـSuspenseلحالات التحميل وErrorBoundaryلمعالجة الأخطاء بأناقة. -
حمّل المسارات بتأخير — استخدم
lazy(() => import('./Page'))لمكونات المسارات لتمكين تقسيم الشفرة وتقليل حجم الحزمة الأولي. -
استخدم
batch()للتحديثات المتعددة — عند تعيين عدة إشارات دفعة واحدة، غلّفها بـbatch()لتشغيل إعادة تصيير واحدة بدلاً من عدة. -
اجعل المكونات صغيرة — مكونات SolidJS تشغل جسمها مرة واحدة فقط (ليس عند كل تصيير مثل React)، لذا قسّم المكونات الكبيرة لتحديد نطاق التحديثات التفاعلية.
-
استخدم أنواع TypeScript العامة مع الإشارات —
createSignal<string | null>(null)يوفر أماناً أفضل للأنواع وإكمالاً تلقائياً لحالتك التفاعلية. -
افهم نموذج الترجمة — يترجم SolidJS كود JSX إلى عمليات DOM حقيقية في وقت البناء. لا يوجد DOM افتراضي، وهذا سبب سرعته ولكنه أيضاً سبب أهمية قواعد التفاعلية (عدم التفكيك، استدعاء الإشارات).
-
استخدم
untrack()باعتدال — قراءة إشارة داخلuntrack()تمنع تتبع التبعيات. هذا مفيد للتسجيل أو القراءات لمرة واحدة لكنه قد يسبب أخطاء إذا أُفرط في استخدامه.