Vue.js: provide / inject
آخر تحديث: 2026-08-26
provide وinject هما واجهات برمجة التطبيقات (APIs) للتواصل عبر المستويات في Vue 3 — حيث توفر المكونات السلفية البيانات، ويمكن لأي مكون تابع (مهما كان عمق التسلسل) إدخالها واستخدامها. وهذا يحل مشكلة «prop drilling».
يدعم provide في Vue 3 البيانات التفاعلية (انظر / reactive)، وستصبح المكونات الفرعية تفاعلية تلقائيًا بعد إدخالها. سيساعدك هذا الدرس على إتقان هذه الآلية وخمس حالات استخدام عملية.
1. ما ستتعلمه
provide/injectالقواعد الأساسية- نوع الرمز كمفتاح إدخال
- التزويد التفاعلي (المرجع / التفاعلي)
- طريقتان لتعديل
provide - مقارنة مع «بروبس» و«إيفينتو باص» و«بينيا»
- 5 سيناريوهات رئيسية من واقع الحياة (الموضوع / المستخدم / i18n)
- 3 أنماط خاطئة تتعلق بـ «provide» و«inject»
2. كابوس حفر الدعامة بلون «اللون المميز» عبر 5 مستويات
(1) نقطة الضعف: التطبيق → التخطيط → العنوان → قائمة المستخدم → الزر، تمرير عبر 5 طبقات
كان من الضروري أن تدعم لوحة التحكم الإدارية الخاصة بـ«أليس» إمكانية تبديل القوالب:
<!-- ❌ The "Broken" Version: 5-Layer prop drilling -->
<!-- App.vue -->
<Layout :theme="theme" :user="user" :locale="locale">
<router-view />
</Layout>
<!-- Layout.vue -->
<Header :theme="theme" :user="user" :locale="locale" />
<!-- Header.vue -->
<UserMenu :theme="theme" :user="user" :locale="locale" />
<!-- UserMenu.vue -->
<ThemeButton :theme="theme" />
<LocaleSelector :locale="locale" />
يضيف مدير المنتج تشارلي 3 إعدادات أخرى:
«أليس، نحتاج إلى إضافة إعدادات لتطيي الشريط الجانبي، وإعدادات الإشعارات، وعلامات الميزات. وهذا يعني 8 خصائص يجب تمريرها عبر 5 مكونات. هذا الأمر يصعب صيانته.»
(2) حل «توفير/إدخال» في Vue: يتم الإدخال في العناصر السلفية؛ وتتلقى العناصر التابعة ذلك
<!-- App.vue Ancestors -->
<script setup>
import { ref, provide } from 'vue'
const theme = ref('light')
const user = ref({ name: 'Alice' })
const locale = ref('zh-CN')
// ✅ Once provide,All descendants may inject
provide('theme', theme)
provide('user', user)
provide('locale', locale)
</script>
<!-- UserMenu.vue Any descendant at any depth -->
<script setup>
import { inject } from 'vue'
// ✅ Get it directly, No props passthrough needed
const theme = inject('theme')
const user = inject('user')
const locale = inject('locale')
</script>
<template>
<button :classe="theme">Toggle {{ user.name }}</button>
<p>{{ locale }}</p>
</template>
5 مستويات من التداخل → سطر واحد من provide + سطر واحد من inject.
(3) الإيرادات
بعد التزويد / الحقن:
- عمق تداخل المكونات: غير محدود (يمكن إجراء عملية الإدراج في أي مستوى من مستويات الشجرة)
- عدد المعلمات: 5+ → 0 (لم تعد تُمرَّر)
- التكوين الجديد: لا يلزم سوى وحدة واحدة من
provide - سهولة الصيانة: يقوم المكون الأصلي بإدارة الstatus العامة بشكل مركزي
3. القواعد الأساسية لـ provide و inject
(1) إدخال البيانات
<!-- App.vue Ancestor Components -->
<script setup>
import { ref, provide } from 'vue'
const theme = ref('light')
const user = { name: 'Alice' }
// 1. Inject a static value
provide('appName', 'My Admin')
// 2. Injecting Responsive Data(ref)
provide('theme', theme)
// 3. Injecting Responsive Data(reactive)
provide('user', user)
// 4. Injection Methods
provide('updateUser', (newUser) => {
user.value = newUser
})
</script>
(2) يقوم «inject» باستقبال البيانات
<!-- Child.vue Any descendant component -->
<script setup>
import { inject } from 'vue'
// 1. Basic Reception
const theme = inject('theme')
// 2. With default values(My ancestors are no longer with us provide When to Use)
const theme = inject('theme', 'light')
// 3. Include factory function defaults
const theme = inject('theme', () => 'light')
// 4. Type Assertion(TypeScript)
const theme = inject<string>('theme', 'light')
</script>
(3) مثال كامل
<!-- Ancestor.vue -->
<script setup>
import { ref, provide } from 'vue'
const count = ref(0)
const user = reactive({ name: 'Alice', age: 25 })
provide('count', count) // ref
provide('user', user) // reactive
provide('config', { theme: 'dark' }) // Object
provide('reset', () => { count.value = 0 }) // Methods
</script>
<!-- DeepChild.vue(Any depth) -->
<script setup>
import { inject } from 'vue'
const count = inject('count') // Responsive
const user = inject('user') // Responsive
const config = inject('config') // Ordinary Object
const reset = inject('reset') // Function
// Edit
function increment() {
count.value++ // ✅ Ancestor components will also respond
}
</script>
4. نوع «Symbol» كمفتاح إدخال
(1) لماذا نستخدم Symbol؟
المفاتيح النصية عرضة للتعارضات (فوجود عدة عبارات provide تحمل نفس المفتاح سيؤدي إلى استبدال بعضها البعض). أما الرموز فهي فريدة، مما يمنع حدوث التعارضات.
<!-- keys.js - Centrally manage all provide key -->
<script>
export const THEME_KEY = Symbol('theme')
export const USER_KEY = Symbol('user')
export const LOCALE_KEY = Symbol('locale')
export const CONFIG_KEY = Symbol('config')
</script>
<!-- Ancestor.vue Ancestor Components -->
<script setup>
import { provide, ref } from 'vue'
import { THEME_KEY, USER_KEY, LOCALE_KEY } from './keys'
provide(THEME_KEY, ref('light'))
provide(USER_KEY, ref({ name: 'Alice' }))
provide(LOCALE_KEY, ref('zh-CN'))
</script>
<!-- DeepChild.vue Any descendants -->
<script setup>
import { inject } from 'vue'
import { THEME_KEY } from './keys'
// ✅ Type Safety:Must use Symbol talent inject
const theme = inject(THEME_KEY)
const user = inject(USER_KEY)
</script>
(2) 5 مزايا رئيسية
| الميزة | الوصف |
|---|---|
| التفرد | الرمز فريد على مستوى العالم؛ ولا تحدث أي تعارضات |
| أمان الأنواع | يمكن لـ TypeScript استنتاج |
| الإدارة المركزية | يتم تخزين جميع المفاتيح في ملف واحد |
| ملائم لإعادة الهيكلة | تعديل مفتاح ما لا يؤثر على المفاتيح الأخرى |
| سهولة القراءة | يمكنك معرفة الغرض منه بمجرد النظر إلى اسم المفتاح |
5. شرح مفصل لـ provide التفاعلي
(1) 3 أنواع من أوضاع "التوفير"
<!-- Ancestor.vue -->
<script setup>
import { ref, reactive, provide } from 'vue'
// 1. Inject ref(Responsive)
const count = ref(0)
provide('count', count)
// 2. Inject reactive(Responsive)
const user = reactive({ name: 'Alice' })
provide('user', user)
// 3. Injecting Read-Only Objects(Non-responsive)
const config = { theme: 'dark' }
provide('config', config)
</script>
(2) طريقتان لإجراء التغييرات
<!-- Method 1:Directly Modify Ancestors(Recommendations) -->
<!-- Ancestor.vue -->
<script setup>
import { ref, provide } from 'vue'
const theme = ref('light')
provide('theme', theme)
function toggleTheme() {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
</script>
<!-- Method 2:Through their descendants inject Edit Citation -->
<!-- DeepChild.vue -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
function toggleTheme() {
theme.value = 'dark' // ✅ The change is ref,Both the ancestors and other descendants responded
}
</script>
(3) 5 سيناريوهات استجابة رئيسية
| السيناريو | الاستخدام |
|---|---|
| تغيير السمة | الرجوع + توفير |
| تسجيل دخول المستخدم | reactive + provide |
| الإعدادات المحلية (i18n) | المرجع + التوفير + طريقة التبديل |
| التحميل العالمي | ref(false) + توفير |
| إشعارات الرسائل العالمية | تفاعلية + توفير |
6. 5 سيناريوهات رئيسية من واقع الحياة
(1) المشهد الأول: تغيير الموضوع
// keys.js
export const THEME_KEY = Symbol('theme')
<!-- App.vue Ancestors -->
<script setup>
import { ref, provide } from 'vue'
import { THEME_KEY } from './keys'
const theme = ref('light')
provide(THEME_KEY, theme)
</script>
<!-- ThemeButton.vue Descendants -->
<script setup>
import { inject } from 'vue'
import { THEME_KEY } from './keys'
const theme = inject(THEME_KEY)
</script>
<template>
<button @click="theme.value = theme === 'light' ? 'dark' : 'light'">
{{ theme === 'light' ? '🌞' : '🌙' }}
</button>
</template>
(2) السيناريو 2: مصادقة المستخدم
// keys.js
export const AUTH_KEY = Symbol('auth')
<!-- App.vue -->
<script setup>
import { reactive, provide } from 'vue'
import { AUTH_KEY } from './keys'
const auth = reactive({
user: null,
isLoggedIn: false,
login(credentials) { /* API call */ },
logout() { this.user = null; this.isLoggedIn = false }
})
provide(AUTH_KEY, auth)
</script>
<!-- UserMenu.vue Descendants -->
<script setup>
import { inject } from 'vue'
import { AUTH_KEY } from './keys'
const auth = inject(AUTH_KEY)
</script>
<template>
<div v-if="auth.isLoggedIn">{{ auth.user.name }}</div>
<button v-else @click="auth.login(creds)">Login</button>
</template>
(3) السيناريو 3: i18n
// keys.js
export const I18N_KEY = Symbol('i18n')
<!-- App.vue -->
<script setup>
import { ref, provide, computed } from 'vue'
import { I18N_KEY } from './keys'
const messages = {
'zh-CN': { hello: 'Hello', welcome: 'Welcome' },
'en-US': { hello: 'Hello', welcome: 'Welcome' }
}
const locale = ref('zh-CN')
const t = computed(() => (key) => messages[locale.value][key])
provide(I18N_KEY, { locale, t })
</script>
<!-- Hello.vue Descendants -->
<script setup>
import { inject } from 'vue'
import { I18N_KEY } from './keys'
const { t } = inject(I18N_KEY)
</script>
<template>
<h1>{{ t('hello') }}, {{ t('welcome') }}</h1>
</template>
(4) السيناريو 4: التحميل العالمي
<!-- App.vue -->
<script setup>
import { ref, provide } from 'vue'
const loading = ref(false)
provide('loading', loading)
</script>
<!-- AnyComponent.vue Descendants -->
<script setup>
import { inject } from 'vue'
const loading = inject('loading')
async function fetchData() {
loading.value = true
await fetch('/api/data')
loading.value = false
}
</script>
<template>
<button @click="fetchData">Refresh</button>
</template>
(5) السيناريو 5: الموضوع + بيانات الأعمال
<!-- App.vue -->
<script setup>
import { ref, reactive, provide } from 'vue'
const appState = reactive({
theme: 'light',
user: { name: 'Alice' },
permissions: ['read', 'write'],
config: { sidebar: true }
})
provide('appState', appState)
</script>
7. التوفير / الإدخال مقابل الأساليب الأخرى
(1) مقارنة بين 5 طرق للتواصل
| الجانب | props/emit | provide/inject | ناقل الأحداث | Pinia | mitt |
|---|---|---|---|---|---|
| مراسلات بين الأب والابن | ⭐⭐⭐⭐⭐ | - | - | - | - |
| عبر المستويات | ❌ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Brothers Communications | ❌ (يتطلب ترحيلًا من الوالدين) | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| متجاوب | ✅ | ✅ | ❌ (يدوي) | ✅ | ❌ |
| أمان الأنواع | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| أدوات المطورين | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| منحنى التعلم | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
(2) 5 سيناريوهات رئيسية للاختيار
| السيناريو | الحل الموصى به |
|---|---|
| التواصل بين الوالدين والأبناء | props/emit |
| الstatus العامة المشتركة بين المستويات (السمة/المصادقة/i18n) | توفير/إدراج |
| الstatus الشاملة في التطبيقات المتوسطة إلى الكبيرة | Pinia (المرحلة 4.2) |
| ناقل الأحداث البسيط | mitt / tiny-emitter |
| إدارة الحالات التجارية المعقدة | Pinia + Vue Router |
8. مثال كامل: تبديل السمات في النظام الخلفي لموقع التجارة الإلكترونية
▶ مثال: 1. الإدارة المركزية للمفاتيح
// src/keys.js
export const THEME_KEY = Symbol('theme')
export const USER_KEY = Symbol('user')
export const LOCALE_KEY = Symbol('locale')
export const CART_KEY = Symbol('cart')
▶ مثال: 2. App.vue — مرة واحدة provide
<!-- src/App.vue -->
<script setup>
import { ref, reactive, provide, readonly } from 'vue'
import { THEME_KEY, USER_KEY, LOCALE_KEY, CART_KEY } from './keys'
// Topic
const theme = ref('light')
function setTheme(newTheme) {
theme.value = newTheme
localStorage.setItem('theme', newTheme)
}
// User
const user = reactive({
id: 1,
name: 'Alice',
role: 'admin'
})
// Internationalization
const locale = ref('zh-CN')
const messages = {
'zh-CN': { home: 'Home', cart: 'Shopping Cart' },
'en-US': { home: 'Home', cart: 'Cart' }
}
const t = (key) => messages[locale.value][key]
// Shopping Cart
const cart = reactive({ item: [], total: 0 })
// Disposable provide 4 Global state
provide(THEME_KEY, { theme, setTheme })
provide(USER_KEY, { user })
provide(LOCALE_KEY, { locale, t })
provide(CART_KEY, { cart })
</script>
<template>
<router-view />
</template>
▶ مثال: 3. الاستخدام من قبل أي سليل (5 طرق للاستخدام)
<!-- DeepChild.vue Any depth -->
<script setup>
import { inject } from 'vue'
import { THEME_KEY, USER_KEY, LOCALE_KEY, CART_KEY } from './keys'
// 1. Deconstruction
const { theme, setTheme } = inject(THEME_KEY)
const { user } = inject(USER_KEY)
const { locale, t } = inject(LOCALE_KEY)
const { cart } = inject(CART_KEY)
</script>
<template>
<div :class="['app', theme]">
<p>{{ user.name }} ({{ user.role }})</p>
<p>{{ t('home') }} | {{ t('cart') }}</p>
<p>{{ cart.items.length }} items, ${{ cart.total }}</p>
<button @click="setTheme('dark')">Dark Mode</button>
</div>
</template>
▶ مثال: 4. توضيح المزايا الخمس الرئيسية لمفاتيح إدخال الرموز
// src/keys.js
export const THEME_KEY = Symbol('theme') // The Only One
export const ANOTHER_THEME_KEY = Symbol('theme') // Also the only one(Different Symbol)
<!-- App.vue -->
<script setup>
import { provide, ref } from 'vue'
import { THEME_KEY, ANOTHER_THEME_KEY } from './keys'
const theme1 = ref('light')
const theme2 = ref('dark')
// ✅ There will be no conflict:Different Symbol Even if the names are the same
provide(THEME_KEY, theme1)
provide(ANOTHER_THEME_KEY, theme2)
</script>
▶ مثال: 5. مرجع سريع لـ 5 أخطاء شائعة
| الخطأ | الأعراض | الحل |
|---|---|---|
| تعارضات مفاتيح السلاسل | الكتابة فوق | استخدام الرمز |
| مصدر غير سلف | غير محدد | التحقق من التسلسل الهرمي للمكونات |
| تعديل قيمة "للقراءة فقط" | تحذير | تعديل السلف؛ أما الأحفاد فهي للقراءة فقط |
| تجاهل القيمة الافتراضية | غير محدد | inject(key, defaultValue) |
عدد كبير من عبارات provide |
صعوبة في الصيانة | مركزية في keys.js |
▶ مثال: 6. 5 مقارنات رئيسية في الأداء
| الوضع | الأداء | قابلية التطبيق |
|---|---|---|
provide('key', value) |
⭐⭐⭐⭐⭐ | قيمة ثابتة |
provide('key', ref) |
⭐⭐⭐⭐⭐ | التقييم التفاعلي |
provide('key', reactive) |
⭐⭐⭐⭐ | كائنات معقدة |
provide('key', computed) |
⭐⭐⭐⭐⭐ | القيمة المشتقة |
provide('key', function) |
⭐⭐⭐⭐ | كيفية الاستخدام |
❓ أسئلة شائعة
provide أم props؟props للاتصال المباشر بين المكونات الأصلية والتابعة (صريح وآمن من حيث النوع). استخدم provide للتواصل عبر المستويات (3 مستويات أو أكثر) لتجنب «prop drilling». استخدم Pinia (المرحلة 4.2) للstatus العامة في التطبيقات الكبيرة.provide('key', ref) تفاعلية، provide('key', {a: 1}) غير تفاعلية (كائن عادي). نوصي باستخدام provide ref أو provide reactive.provide في المستوى الأعلى من setup؟provide في المستوى الأعلى من <script setup> (لا يُسمح باستدعائه داخل خطافات دورة الحياة)؛ وإلا، فلن يسري مفعول provide.provide بعد inject؟ref.value، لكن يُنصح بالقيام بذلك فقط في المكونات السلفية (باستخدام طريقة provide). فقد يؤدي تعديل ref مباشرةً إلى عدم وضوح تدفق البيانات.src/keys.js، الذي يقوم بتصدير جميع الرموز. ويتم استيراد جميع المكونات المستخدمة من هذا الملف.provide/inject وPinia؟provide/inject. أما بالنسبة للمشاريع المتوسطة إلى الكبيرة (10 حالات عامة أو أكثر)، فيُفضل استخدام Pinia، لأنها توفر دعمًا أفضل لأدوات التطوير (DevTools).provide؟inject بالبحث مرة أخرى عن المكونات السلفية في الاتجاه الصاعد.📖 ملخص
provideوinjectهما واجهتا برمجة التطبيقات (API) الخاصة بالاتصال بين المستويات في Vue 3- provide (السلف)، inject (أي سليل)
- 5 أنواع من
provide: قيمة ثابتة / مرجع / تفاعلية / محسوبة / دالة - مفتاح الرموز: لتجنب التضارب، قم بتركيز الإدارة في ملف keys.js
- 5 سيناريوهات عملية: السمة / المصادقة / الترجمة الدولية (i18n) / التحميل / البيانات التجارية
- المقارنة مع «props»: استخدم «props» لعلاقات «الوالد-الابن»؛ واستخدم
provideللتواصل بين المستويات المختلفة - المقارنة مع Pinia: استخدم Provide للمشاريع الصغيرة، واستخدم Pinia للمشاريع الكبيرة
📝 تمارين
-
أسئلة أساسية (مستوى الصعوبة: ⭐)
تنفيذ أداة بسيطة لتبديل السمات:
- ملف keys.js يحدد THEME_KEY
- يوفر ملف App.vue السمة (ref) + طريقة setTheme
- ThemeButton.vue: إدراج السمة + تعيين السمة
- انقر على الزر للتبديل بين الوضع الفاتح والوضع الداكن
-
مسائل متقدمة (مستوى الصعوبة: ⭐⭐)
تنفيذ مصادقة المستخدم (توفير/إدخال):
- حقن AUTH_KEY: طرق user و login و logout
- يقوم ملف App.vue بتعيين المتغير user على القيمة null
- في ملف LoginPage.vue، استدعِ
login(creds)لمحاكاة عملية تسجيل الدخول - يعرض ملف Header.vue واجهة مستخدم مختلفة بناءً على قيمة isLoggedIn
-
مسألة التحدي (مستوى الصعوبة: ⭐⭐⭐)
تنفيذ نظام شامل لإدارة الstatus العامة للعمليات الخلفية للتجارة الإلكترونية:
- 4 مفاتيح: THEME / USER / CART / LOCALE
- يوفر ملف App.vue 4 حالات عامة في آن واحد
- استخدام 5 مكونات على مستويات مختلفة (الرأس / الشريط الجانبي / قائمة المنتجات / سلة التسوق / التذييل)
- التواصل بين المكونات: عند النقر على العنوان، يتم تسجيل الخروج → يتم تحديث الشريط الجانبي تلقائيًا
- تبديل السمة + تبديل الترجمة (i18n) + إضافة العناصر إلى سلة التسوق وحذفها منها
- استخدام TypeScript للتصنيف القوي
- 5 توفير الأنماط (القيمة/المرجعية/التفاعلية/المحسوبة/الوظيفية)