توسعه فرانت‌اند

Vue Composition API: راهنمای جامع

تفاوت Composition و Options API، State و اثر جانبی، Cleanup و قرارداد Composable؛ نمونه کامل Vue 3 با روش اجرا و وابستگی روشن.

1404/11/12 3 دقیقه مطالعه 1,678 بازدید

Composition API در Vue راهی برای تعریف State، مقدار مشتق‌شده و رفتارهای وابسته به چرخه عمر با توابع است. وقتی یک قابلیت میان data، methods و بخش‌های دیگر Options API پراکنده می‌شود، جمع کردن منطق مرتبط در یک محدوده می‌تواند خواندن آن را آسان‌تر کند. این API جایگزین اجباری Options API نشده و انتخاب آن تضمین نمی‌کند کد هر پروژه کوتاه‌تر یا بهتر شود. سازمان‌دهی باید با اندازه جزء و تجربه تیم هماهنگ باشد.

برای مثال فرضی یک کنترل تعداد، مقدار جاری، حد افزایش و فرمان Reset به یک قابلیت مربوط‌اند. می‌توان آن‌ها را در یک Composable نگه داشت و در چند Component استفاده کرد. منطق دیگری مانند اندازه پنجره، مسئولیت متفاوتی دارد و بهتر است در تابع جدا بماند. هدف، تقسیم فایل به توابع بیشتر نیست؛ هر تابع باید قرارداد روشن و دلیل استفاده مجدد داشته باشد. تابعی که همه Stateهای صفحه را می‌گیرد و تغییر می‌دهد مرز مفیدی ایجاد نمی‌کند.

انتخاب میان دو API را با پروژه توضیح دهید

FAQ رسمی Composition API ادامه پشتیبانی از Options API و دلایل استفاده از Composition را توضیح می‌دهد. برای جزء کوچک، Options API ممکن است همچنان خوانا باشد. در قابلیت پیچیده با چند مسئولیت مرتبط، Composition می‌تواند گروه‌بندی بر اساس قابلیت را آسان‌تر کند. هیچ کدام به تنهایی مشکل نام‌گذاری، وابستگی پنهان یا طراحی نامناسب State را حل نمی‌کنند. قبل از مهاجرت، مشکل مشخص کد را بنویسید تا نتیجه با همان مشکل سنجیده شود.

ترکیب دو API در یک Component هم ممکن است، اما قرارداد دسترسی و مسئولیت باید معلوم باشد. مهاجرت تدریجی می‌تواند منطقی‌تر از بازنویسی همه فایل‌ها باشد. در Code Review، صرف حضور setup نشانه بهتر بودن کد نیست. بررسی کنید منطق مرتبط واقعاً کنار هم قرار گرفته، داده غیرضروری حذف شده و مصرف‌کننده می‌فهمد کدام مقدار قابل تغییر است. مقایسه با React Hooks نیز باید با تفاوت مدل اجرا انجام شود؛ برچسب «قدرتمندتر» معیار عملی برای انتخاب تیم نیست.

ref و reactive را با رفتارشان بشناسید

ref یک مقدار را در ظرف دارای value نگه می‌دارد. در JavaScript برای دسترسی به مقدار از value استفاده می‌شود، در حالی که Template در بسیاری از کاربردهای معمول آن را باز می‌کند. reactive برای object یک Proxy واکنش‌گرا ایجاد می‌کند. راهنمای Reactivity رفتار و محدودیت‌ها را توضیح می‌دهد. جدا کردن یک property ساده از object واکنش‌گرا و نگه داشتن آن در متغیر معمولی می‌تواند رابطه به‌روزرسانی را از دست بدهد.

در انتخاب، ثبات قرارداد مهم است. بازگرداندن object معمولی دارای refها از Composable، به مصرف‌کننده اجازه می‌دهد آن‌ها را با حفظ واکنش‌پذیری جدا کند. object معمولی در اینجا به معنی غیرواکنش‌گرا بودن مقدار داخل ref نیست. اگر می‌خواهید تغییر فقط از فرمان‌های خاص انجام شود، State را با همین محدودیت ارائه کنید. برای نمونه آموزشی count بازگردانده می‌شود، اما قرارداد محصول می‌تواند مقدار فقط‌خواندنی و توابع تغییر را جدا کند تا قوانین دامنه آسان دور زده نشوند.

computed برای مقدار مشتق‌شده است

وقتی مقدار از State دیگر به دست می‌آید، computed رابطه را روشن می‌کند. در کنترل تعداد، دوبرابر مقدار و امکان افزایش از count محاسبه می‌شوند. نگه داشتن این‌ها به صورت State مستقل نیاز به هماهنگی دستی ایجاد می‌کند؛ یکی تغییر کند و دیگری فراموش شود، رابط ناسازگار می‌شود. تابع محاسبه بهتر است اثر جانبی نداشته باشد. دریافت شبکه یا تغییر عنوان صفحه، کار متفاوتی از محاسبه یک مقدار است و باید در مسیر مناسب خودش انجام شود.

computed نباید به محل انباشتن همه منطق صفحه تبدیل شود. شرط‌های بلند را با نام‌های روشن تقسیم کنید و حد مجاز را در قرارداد نگه دارید. disabled بودن دکمه در ظاهر، جای کنترل مقدار در تابع تغییر را نمی‌گیرد. در نمونه، increment خودش حد را بررسی می‌کند. این کار برای استفاده دوباره تابع هم مهم است، چون ممکن است مصرف‌کننده دیگری آن را بدون همان دکمه فراخوانی کند. قواعد رابط و قواعد تغییر داده باید از یک انتظار مشترک پیروی کنند.

watch برای اثر جانبی با منبع مشخص

مستندات Watchers تفاوت watch و watchEffect را توضیح می‌دهد. watch منبع مشخص دارد و برای واکنش به تغییر آن مناسب است؛ watchEffect وابستگی‌های واکنش‌گرای خوانده‌شده در اجرای اثر را دنبال می‌کند. انتخاب به معنی رفتار مورد انتظار است، نه کوتاه‌ترین Syntax. اگر فقط مقدار مشتق‌شده لازم است، ابتدا computed را بررسی کنید. Watcherهایی که برای همگام کردن دو State تکراری نوشته می‌شوند ممکن است نشانه مدل داده پیچیده‌تر از نیاز باشند.

در درخواست async، نتیجه قدیمی می‌تواند بعد از نتیجه جدید برسد. Watcher باید روش لغو یا کنار گذاشتن پاسخ قدیمی داشته باشد و Cleanup در زمان مناسب ثبت شود. گرفتن State از همه جای صفحه در یک اثر، فهم دلیل اجرا را دشوار می‌کند. برای منابع بزرگ هم Deep Watch بدون بررسی هزینه مناسب نیست. مقاله Web Workers درباره جدا کردن محاسبه سنگین مفید است؛ افزودن Watcher به یک محاسبه طولانی، هزینه آن را حذف نمی‌کند.

نمونه کامل بدون ابزار Build

فایل زیر را index.html ذخیره کنید. نمونه Vue 3 را از آدرس مشخص unpkg دریافت می‌کند و برای دریافت آن به اینترنت نیاز دارد؛ وابستگی پنهان دیگری ندارد. روش استفاده از ESM مرورگر در راهنمای شروع Vue معرفی شده است. برای مسیر محلی قابل پیش‌بینی، اگر Python نصب دارید، در پوشه فایل python -m http.server 8000 را اجرا و http://localhost:8000 را باز کنید. این نمونه آموزشی CDN جای تنظیم Build و نسخه ثابت یک پروژه محصولی را نمی‌گیرد.

<!doctype html><html lang="fa" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>نمونه Composition API</title><style>body{font:1rem/1.8 Tahoma,sans-serif;max-width:40rem;margin:2rem auto;padding:1rem}button{font:inherit;padding:.5rem;margin:.3rem}button:focus-visible{outline:3px solid #111;outline-offset:3px}[v-cloak]{display:none}</style></head><body>
<main id="app" v-cloak><h1>کنترل تعداد</h1><p role="status">مقدار: {{ count }}؛ دوبرابر: {{ doubled }}</p><button type="button" @click="increment" :disabled="!canIncrease">افزایش تا ۱۰</button><button type="button" @click="reset">صفر کردن</button><p>عرض پنجره: {{ width }} پیکسل</p></main><button id="stop" type="button">توقف نمونه و Cleanup</button><p id="notice" role="status"></p>
<script type="module">
import {createApp,ref,computed,watch,onMounted,onUnmounted} from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js';
function useCounter(){const count=ref(0);const doubled=computed(()=>count.value*2);const canIncrease=computed(()=>count.value<10);function increment(){if(canIncrease.value)count.value++}function reset(){count.value=0}return {count,doubled,canIncrease,increment,reset}}
function useViewportWidth(){const width=ref(0);const update=()=>{width.value=window.innerWidth};onMounted(()=>{update();window.addEventListener('resize',update)});onUnmounted(()=>window.removeEventListener('resize',update));return {width}}
const app=createApp({setup(){const counter=useCounter();const {width}=useViewportWidth();const originalTitle=document.title;watch(counter.count,value=>{document.title='مقدار نمونه: '+value});onUnmounted(()=>{document.title=originalTitle});return {...counter,width}}});
app.mount('#app');
document.getElementById('stop').onclick=e=>{app.unmount();e.currentTarget.disabled=true;document.getElementById('notice').textContent='نمونه متوقف شد و Listener برداشته شد؛ برای شروع دوباره صفحه را تازه کنید.'};
</script></body></html>

useCounter یک count مستقل برای هر بار فراخوانی می‌سازد و doubled و canIncrease از آن مشتق می‌شوند. دکمه افزایش تا حد ده کار می‌کند و Reset مقدار را به صفر برمی‌گرداند. useViewportWidth مسئول دیگری دارد: پس از Mount عرض پنجره را می‌خواند و listener مربوط را ثبت می‌کند. هنگام Unmount همان listener برداشته می‌شود. بازگرداندن width به صورت ref اجازه می‌دهد Component آن را در Template نمایش دهد، بدون اینکه از جزئیات listener خبر داشته باشد.

Watcher در Component با تغییر count عنوان سند را تغییر می‌دهد. این یک اثر جانبی مشخص است؛ محاسبه doubled چنین اثری ندارد. دکمه خارج از محدوده app می‌تواند برنامه را Unmount کند تا Cleanup اجرا شود و عنوان قبلی برگردد. این دکمه نمونه مدیریت چرخه عمر است، نه کنترل لازم همه رابط‌ها. پس از توقف، Component حذف می‌شود و برای اجرای دوباره می‌توانید صفحه را تازه کنید. داده‌ای ذخیره یا به Server ارسال نمی‌شود.

Composable باید مرز و Cleanup داشته باشد

راهنمای Composables استفاده مجدد از منطق Stateدار را توضیح می‌دهد. نام use یک قرارداد رایج است، اما نام به تنهایی تابع را Composable مفیدی نمی‌کند. ورودی، خروجی و زمان فراخوانی باید معلوم باشند. اگر تابع Hookهای چرخه عمر ثبت می‌کند، باید در زمینه مناسب Component فراخوانی شود. ثبت Listener یا Timer بدون Cleanup در استفاده مکرر می‌تواند منابع و رفتارهای تکراری باقی بگذارد.

State مشترک با State هر instance فرق دارد. قرار دادن ref بیرون تابع ممکن است آن را میان مصرف‌کنندگان مشترک کند؛ ساختن آن داخل تابع نمونه‌های مستقل می‌سازد. این تفاوت باید در مستندات تابع نوشته شود. در SSR، اشتراک ناخواسته داده میان درخواست‌ها مسئله مهم‌تری ایجاد می‌کند و APIهای مرورگر هم در محیط Server وجود ندارند. در نمونه، دسترسی به window در Mount انجام می‌شود، نه هنگام تعریف ماژول. انتقال همین کد به SSR نیازمند توجه به خروجی اولیه و تغییر بعدی آن است.

TypeScript و نگهداری را جدا از نام API ببینید

Composition API می‌تواند با TypeScript قراردادهای خوبی داشته باشد، اما استفاده از JavaScript به خودی خود Type-safe نمی‌شود. نوع Props، Emitها و داده بیرونی باید تعریف و در مرز لازم بررسی شوند. مقاله TypeScript پیشرفته درباره تفاوت بررسی Compile-time و Runtime توضیح دارد. استخراج منطق به تابع نباید اعتبارسنجی ورودی را حذف کند یا تصمیم دسترسی را به ظاهر Component بسپارد. نوع درست همچنان به رفتار درست نیاز دارد.

در تحویل، بنویسید هر Composable State مستقل دارد یا مشترک، چه منابعی ایجاد می‌کند و چگونه پاک می‌شوند. رابط مصرف‌کننده باید کوچک و قابل پیش‌بینی بماند. مقاله Atomic Design برای مرز جزء و صفحه مفید است. نقطه شروع عملی یک قابلیت مشخص است: State اصلی و مشتق‌شده را جدا کنید، اثر جانبی را با منبع روشن بنویسید و فقط وقتی استفاده مشترک وجود دارد آن را به قرارداد مستقل تبدیل کنید.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!