Skip to content

Provide / Inject

این صفحه فرض می‌کند شما قبلاً مبانی کامپوننت‌ها را خوانده‌اید. اگر با کامپوننت‌ها آشنایی ندارید، ابتدا آن را بخوانید.

انتقال پراپ به اعماق درخت کامپوننت‌ها | Prop Drilling

معمولاً وقتی می‌خواهیم داده‌ای را از والد به فرزند منتقل کنیم، از props استفاده می‌کنیم. اما تصور کنید موردی داشته باشیم که کامپوننت‌های تو در توی زیادی داریم و یک کامپوننت در عمق درخت نیاز به داده‌ای از یک کامپوننت اجدادی دوردست دارد. در این حالت، با استفاده از props باید همان پراپ را در تمام زنجیره والدین انتقال دهیم:

prop drilling diagram

ملاحظه می‌کنید اگرچه کامپوننت <Footer> شاید نیازی به این پراپ‌ها ندارد، ولی باز هم باید آن‌ها را تعریف و منتقل کند تا <DeepChild> بتواند به آن‌ها دسترسی داشته باشد. اگر زنجیره والدین‌ها طولانی‌تر باشد، کامپوننت‌های بیشتری در مسیر تاثیر می‌گیرند. این موضوع را "props drilling" (انتقال پراپ به اعماق درخت کامپوننت‌ها) می‌نامند و حتماً کار لذت‌بخشی نیست.

می‌توانیم با استفاده از provide و inject این مشکل را حل کنیم. کامپوننت والد می‌تواند به عنوان یک ارائه‌دهنده dependencyها برای تمام فرزندانش عمل کند. هر کامپوننتی در درخت فرزندان، صرف‌نظر از عمق آن، می‌تواند dependencyهای ارائه شده توسط کامپوننت‌های بالاتر در زنجیره والدین را وارد (inject) کند.

Provide/inject scheme

Provide | ارائه

برای ارائه دادن داده به فرزندان یک کامپوننت، از تابع provide()‎ استفاده کنید:

vue
<script setup>
import { provide } from 'vue'

provide(/* key */ 'message', /* value */ 'hello!')
</script>

اگر از <script setup> استفاده نمی‌کنید، اطمینان حاصل کنید که provide()‎ به صورت همگام (synchronous) درون setup()‎ صدا زده می‌شود:

js
import { provide } from 'vue'

export default {
  setup() {
    provide(/* key */ 'message', /* value */ 'hello!')
  }
}

تابع provide()‎ دو آرگومان دریافت می‌کند. آرگومان اول injection key نام دارد که می‌تواند یک رشته یا یک Symbol باشد. injection key توسط کامپوننت‌های فرزند برای پیدا کردن value مورد نظر استفاده می‌شود. یک کامپوننت می‌تواند provide()‎ را چندین بار با injection key های متفاوت صدا بزند تا مقادیر مختلفی را ارائه دهد.

آرگومان دوم value ارائه شده است. این value می‌تواند از هر نوعی باشد، از جمله stateهای reactive مثل refs:

js
import { ref, provide } from 'vue'

const count = ref(0)
provide('key', count)

ارائه مقادیر reactive به کامپوننت‌های فرزند این امکان را می‌دهد تا ارتباط reactive با کامپوننت ارائه‌دهنده برقرار کنند.

برای ارائه داده به فرزندان یک کامپوننت، از آپشن provide استفاده کنید:

js
export default {
  provide: {
    message: 'hello!'
  }
}

برای هر پراپرتی در شیء provide، کلید برای پیدا کردن مقدار صحیح جهت inject توسط کامپوننت‌های فرزند استفاده می‌شود، در حالی که مقدار آن، همان چیزی است که در نهایت inject می‌شود.

اگر نیاز داریم تا stateهایی خاص را ارائه دهیم، مثلاً داده‌های تعریف شده توسط data()‎، آنگاه provide باید از تابع استفاده کند:

js
export default {
  data() {
    return {
      message: 'hello!'
    }
  },
  provide() {
    // دسترسی داشته باشیم `this` از سینتکس تابع استفاده می‌کنیم تا به
    return {
      message: this.message
    }
  }
}

با اینحال، توجه داشته باشید این کار به طور reactive تزریق را انجام نمی‌دهد. در ادامه در مورد reactive کردن injection ها بحث خواهیم کرد.

ارائه در سطح اپلیکیشن | App-level Provide

علاوه بر ارائه داده در یک کامپوننت، می‌توانیم در سطح اپلیکیشن هم ارائه کنیم:

js
import { createApp } from 'vue'

const app = createApp({})

app.provide(/* key */ 'message', /* value */ 'hello!')

ارائه‌های سطح اپلیکیشن برای تمام کامپوننت‌های رندر شده در اپلیکیشن در دسترس هستند. این مورد به خصوص زمانی مفید است که پلاگین‌ها را می‌نویسیم، چرا که پلاگین‌ها معمولاً قادر نیستند از طریق کامپوننت‌ها مقادیری را ارائه کنند.

Inject | تزریق

برای تزریق داده‌های ارائه شده توسط یک کامپوننت اجدادی، از تابع inject()‎ استفاده کنید:

vue
<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

اگر مقدار ارائه شده یک ref باشد، بدون تغییر تزریق می‌شود و به طور خودکار unwrap نمی‌شود. این امکان اتصال reactive به کامپوننت ارائه‌دهنده را برای کامپوننت تزریق‌کننده حفظ می‌کند. به عبارت دیگر، مقدار به صورت "as-is" و بدون تغییر تزریق می‌شود.

نمونه کامل از provide و inject با reactive

دوباره تاکید می‌کنیم اگر از <script setup> استفاده نمی‌کنید، inject()‎ باید فقط به صورت همگام درون setup()‎ صدا زده شود:

js
import { inject } from 'vue'

export default {
  setup() {
    const message = inject('message')
    return { message }
  }
}

برای تزریق داده‌های ارائه شده توسط یک کامپوننت اجدادی، از آپشن inject استفاده کنید:

js
export default {
  inject: ['message'],
  created() {
    console.log(this.message) // مقدار تزریق شده
  }
}

تزریق‌ها قبل از state خود کامپوننت انجام می‌شوند، بنابراین می‌توانید در data()‎ به خاصیت‌های تزریق شده دسترسی داشته باشید:

js
export default {
  inject: ['message'],
  data() {
    return {
      // مقدار دهی اولیه داده بر اساس مقدار تزریق شده
      fullMessage: this.message
    }
  }
}

نمونه کامل از provide + inject

Injection Aliasing

وقتی از سینتکس آرایه برای inject استفاده می‌کنیم، پراپرتی‌های تزریق شده با همان کلید در نمونه کامپوننت در دسترس قرار می‌گیرند. در مثال بالا، پراپرتی با کلید "message" ارائه شده بود و به عنوان this.message تزریق شد. local key همان injection key است.

اگر می‌خواهیم خاصیت را با local key متفاوتی تزریق کنیم، نیاز است از سینتکس شیء برای آپشن inject استفاده کنیم:

js
export default {
  inject: {
    /* local key */ localMessage: {
      from: /* injection key */ 'message'
    }
  }
}

در اینجا، کامپوننت یک پراپرتی ارائه شده با کلید "message" را پیدا می‌کند و سپس آن را به عنوان this.localMessage در اختیار قرار می‌دهد.

Injection Default Values

به طور پیش‌فرض، inject فرض می‌کند injection key در جایی در زنجیره والدین ارائه شده است. در صورتی که کلید ارائه نشده باشد، یک هشدار در زمان اجرا نمایش داده می‌شود.

اگر می‌خواهیم یک پراپرتی تزریق شده با optional provider ها کار کند، نیاز است مقدار پیش‌فرضی را مشابه props تعریف کنیم:

js
// خواهد بود "default value" مقدار `value`
// ارائه نشده باشد "message" اگر هیچ داده‌ای با کلید
const value = inject('message', 'default value')

در برخی موارد، مقدار پیش‌فرض نیاز به صدا زدن تابع یا مقداردهی اولیه یک کلاس جدید دارد. برای جلوگیری از محاسبات یا عوارض جانبی غیرضروری در صورتی که مقدار اختیاری استفاده نشود، می‌توانیم از factory function برای ساخت مقدار پیش‌فرض استفاده کنیم:

js
const value = inject('key', () => new ExpensiveClass(), true)

پارامتر سوم نشان می‌دهد مقدار پیش‌فرض باید به عنوان factory function در نظر گرفته شود.

js
export default {
  // سینتکس شیء الزامی است
  // هنگام تعریف مقادیر پیش‌فرض برای تزریق‌ها
  inject: {
    message: {
      from: 'message', // اختیاری است اگر همان کلید تزریق استفاده شود
      default: 'default value'
    },
    user: {
      // non-primitive برای مقادیر factory function از
      // یا مواردی که باید برای هر کامپوننت منحصربفرد باشند، استفاده کنید
      default: () => ({ name: 'John' })
    }
  }
}

کار با Reactive

هنگام استفاده از مقادیر reactive برای provide / inject ، توصیه می‌شود هر جا امکان دارد تغییرات reactive state را درون provider نگه دارید. این اطمینان می‌دهد که state ارائه شده و تغییرات احتمالی آن در یک کامپوننت قرار دارند تا نگهداری آن‌ها در آینده آسان‌تر باشد.

ممکن است مواردی باشد که نیاز داشته باشیم داده را از یک کامپوننت تزریق‌کننده به‌روزرسانی کنیم. در چنین مواردی، توصیه می‌کنیم تابعی را ارائه کنیم که مسئول تغییر دادن state باشد:

vue
<!-- inside provider component -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
</script>
vue
<!-- in injector component -->
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>

در نهایت، می‌توانید مقدار ارائه شده را داخل readonly()‎ قرار دهید تا اطمینان حاصل کنید داده‌های انتقال داده شده از طریق provide توسط کامپوننت تزریق‌کننده قابل تغییر نیستند.

vue
<script setup>
import { ref, provide, readonly } from 'vue'

const count = ref(0)
provide('read-only-count', readonly(count))
</script>

برای ایجاد لینک reactive بین injections و provider، نیاز است یک پراپرتی computed با استفاده از تابع computed()‎ ارائه کنیم:

js
import { computed } from 'vue'

export default {
  data() {
    return {
      message: 'hello!'
    }
  },
  provide() {
    return {
      // explicitly provide a computed property
      message: computed(() => this.message)
    }
  }
}

نمونه کامل از provide + inject همراه با Reactivity

تابع computed()‎ معمولاً در کامپوننت‌های Composition API استفاده می‌شود، اما می‌تواند برای پوشش برخی کاربردها در Options API نیز کمک کننده باشد. می‌توانید بیشتر در مورد نحوه استفاده از آن با مطالعه مبانی Reactivity و Computed Properties با تنظیم API Preference به Composition API بیاموزید.

نیاز به پیکربندی موقت

استفاده بالا نیازمند تنظیم app.config.unwrapInjectedRef = true است تا تزریق‌ها به طور خودکار refs computed را تنظیم کنند. این رفتار بطور پیش‌فرض در Vue 3.3 انجام خواهد شد و این پیکربندی به صورت موقت برای جلوگیری از خرابی معرفی شده است. پس از 3.3 دیگر نیازی به آن نخواهد بود.

کار با کلیدهای Symbol

تا اینجا، در مثال‌ها از کلیدهای تزریق رشته‌ای استفاده کرده‌ایم. اگر در یک برنامه بزرگ با بسیاری ارائه‌دهنده‌های وابستگی کار می‌کنید، یا قصد دارید کامپوننت‌هایی بنویسید که توسط سایر توسعه‌دهندگان استفاده خواهند شد، بهتر است از کلیدهای تزریق Symbol برای جلوگیری از برخورد احتمالی استفاده کنید.

توصیه می‌شود Symbolsها را در یک فایل جداگانه export کنید:

js
// keys.js
export const myInjectionKey = Symbol()
js
// in provider component
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'

provide(myInjectionKey, {
  /* data to provide */
})
js
// in injector component
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'

const injected = inject(myInjectionKey)

See also: Typing Provide / Inject

js
// in provider component
import { myInjectionKey } from './keys.js'

export default {
  provide() {
    return {
      [myInjectionKey]: {
        /* data to provide */
      }
    }
  }
}
js
// in injector component
import { myInjectionKey } from './keys.js'

export default {
  inject: {
    injected: { from: myInjectionKey }
  }
}
Provide / Inject has loaded