استفاده مجدد از کدبیس Vue.js در اپلیکیشن های مختلف — به زبان ساده

۷۳ بازدید
آخرین به‌روزرسانی: ۰۳ مهر ۱۴۰۲
زمان مطالعه: ۱۴ دقیقه
استفاده مجدد از کدبیس Vue.js در اپلیکیشن های مختلف — به زبان ساده

یکی از مهم‌ترین خصوصیات کدنویسی خوب، قابلیت استفاده مجدد از کد است. حال چه بهتر که قادر باشیم از کل کدبیس یک پروژه در موقعیت‌های مختلف بهره بگیریم. در این مقاله در خصوص روش استفاده مجدد از کدبیس Vue.js در اپلیکیشن‌های مختلف صحبت خواهیم کرد.

یک اپلیکیشن Vue.js صرف نظر از این که چه کاری انجام می‌دهد، معمولاً به چند مورد نیاز دارد:

  • پیاده‌سازی کامل «منطق تجاری» (Business Logic)
  • پیشنهاد مجموعه‌ای از کامپوننت‌ها که به منطق تجاری فوق‌الذکر معنی ببخشند.
  • استایل‌دهی به اپلیکیشن و طراحی یک UI زیبا و UX مناسب.

فرض کنید می‌خواهیم به جای یک اپلیکیشن خاص، اپلیکیشنی طراحی کنیم که بتواند برای کاربردهای مختلف از سوی شرکت‌های متفاوت مورد استفاده قرار گیرد. در واقع تصور کنید ما اپلیکیشنی برای مشتری خود طراحی می‌کنیم که وی آن را بسته به نیازهای مشتریانش ویرایش می‌کند. این مشتریانِ مشتریِ خودمان را «مصرف‌کننده» می‌نامیم. در این سناریوی خاص چند پیش‌نیاز دیگر هم لازم است تا امکان استفاده مجدد از اپلیکیشن برای مصرف‌کنندگان متفاوت فراهم آید:

  1. ارائه مجموعه کارکردهای پایه (Core)
  2. امکان هماهنگی با هویت شرکت‌های مختلف به خصوص هویت بصری آن‌ها.
  3. ارائه چندین قالب (Theme) محدود تا مصرف‌کننده بتواند از بین آن‌ها انتخاب کند. قالب‌ها ممکن است کارکردهای اندکی متفاوت داشته باشند، اما اساساً حس و حال متفاوتی برای آن مجموعه کارکردهای پایه ارائه می‌کنند.
  4. امکان بسط‌پذیری اپلیکیشن بر مبنای کارکردهای پایه و یا مستقل از آن برای هر مصرف‌کننده.
  5. امکان تغییر دادن بخشی از کارکردهای پایه برای هر مصرف‌کننده بدون از کار افتادن یا تأثیر گذاردن بر بخش‌های باقیمانده.

بدین ترتیب شاید متوجه شده باشید که ما در حال صحبت از یک «محصول بدون عنوان» (white-label product) هستیم که برخی شرایط اضافی نیز دارد. عملیاتی ساختن همه موارد فوق یک چالش هیجان‌انگیز به حساب می‌آید. خیلی زود متوجه می‌شویم که توسعه اپلیکیشن به منظور فراهم ساختن امکان استفاده مجدد، بار کاری را تا حد چشمگیری افزایش می‌دهد. این وضعیت در مورد راه‌اندازی اولیه زیرساخت پروژه کلی بیشتر صدق می‌کند. اما به هر حال این چیزی نیست که نتوان پیش‌بینی کرد. نقشه راه این پروژه، نمایانگر یک مرحله آغازین با شیب تند است، اما در مراحل بعدی و تغییر اپلیکیشن برای هر مصرف‌کننده، به انرژی و کار کمتری نیاز خواهد داشت. چنان که امپراتور رومی، آگوستوس، چند سال پیش از میلاد مسیح، اشاره کرده است: «آرام بشتاب» (Festina lente) باید شعار ما در طراحی این پروژه باشد.

کارکردهای پایه

نمودار معماری کارکردهای پایه اپلیکیشن ما به صورت زیر خواهد بود:

استفاده مجدد از کدبیس Vue.js
برای نمایش در اندازه بزرگتر روی تصویر کلیک کنید.

تصویر فوق ایده اساسی معماری کارکردهای پایه اپلیکیشن را نشان می‌دهد.

این نمودار با همراهی مشتری طراحی شده و هر یک از الزامات تجاری، مورد ارزیابی قرار گرفته تا مطمئن شویم که تنها شامل کارکردهای پایه‌ای است که برای هر کدام از مصرف‌کننده‌ها مفید خواهد بود. در ادامه هر الزام را در معماری خود به «دامنه تجاری» (Business Domain) ترجمه کرده‌ایم. در این مثال، این موارد به صورت صفحه اصلی، پروفایل و تنظیمات ظاهر شده‌اند:

استفاده مجدد از کدبیس Vue.js

اگر روی لایه UI صفحه اصلی با برش‌های عمودی بزرگنمایی کنیم، به طور معمول چیزی مانند آن چه در ادامه توضیح می‌دهیم، خواهیم دید. کامپوننت تک فایلی Homepage.vue به خوبی با <template> ،<script> و <style> همبسته است. در ادامه به بررسی این نمادگذاری کلاسیک به صورت یک «کامپوننت تک فایلی» (SFC) مناسب نیازهای خاص خود می‌پردازیم. صرفاً به خاطر داشته باشید که فعلاً این یک اپلیکیشن با کارکردهای کامل است و در اغلب موارد نیازی به تغییر دادن چیزی وجود ندارد. آن چه فعلاً وجود ندارد، ابزار منفردسازی است. در طول این راهنما نمودار معماری را طوری بهبود می‌بخشیم که همه الزامات فوق‌الذکر به دست آید.

هویت شرکت

هر شرکتی در طی زمان به نقطه‌ای می‌رسد که لازم می‌شود راهنماهایی برای هویت شرکتی خود طراحی کند. این راهنماها ممکن است منتج به نوعی «سیستم طراحی» (design system) شوند. صرف وقت برای چنین راهنماهایی ارزشش را دارد. شرکت‌ها بدین ترتیب به بررسی جامع این موضوع می‌پردازند که در هر شرایطی برندشان باید چطور به نظر برسد و دسترس‌پذیری و همچنین شمایل‌نگاری برند تعریف می‌شود. موارد زیادی در این خصوص وجود دارند که باید در نظر داشت.

سفارشی‌سازی

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

  • لوگوهای برند
  • یک تصویر قهرمان ترجیحاً به صورت SVG
  • شمایل‌نگاری به صورت SVG
  • بین 1 تا 2 فونت که باید در سراسر اپلیکیشن مورد استفاده قرار گیرند.
  • ترجمه‌ها برای همه زبان‌هایی که در اپلیکیشن وجود دارند.
  • یک theme که می‌تواند به صورت یک شیء جاوا اسکریپت مانند زیر ارائه شود. توجه کنید که این یکی از قالب‌هایی که در بخش قبلی اشاره کردیم نیست. متأسفانه در اینجا یک تصادم نام وجود دارد که در ادامه آن را بیشتر مورد بررسی قرار می‌دهیم.
1const theme = {
2  colors: {
3    main: '#abcdef',
4    secondary: '#001122',
5    gray: '#666',
6  },
7  spacings: {
8    tiny: '8px',
9    small: '16px',
10    base: '24px',
11    large: '48px',
12    xLarge: '64px',
13  },
14  fontSizes: {
15    title: '44px',
16    large: '19px',
17    refular: '17px',
18  },
19};

در کد فوق مثال مختصری از بلوک‌های بنیادی تشکیل‌دهنده نمای بصری می‌بینید که به شکل یک شیء جاوا اسکریپت نمایش یافته است.

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

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

CSS در جاوا اسکریپت

احتمالاً متوجه شده‌اید که تعریف کردن متغیرهای CSS در جاوا اسکریپت که عموماً به نام فناوری CSS-in-JS نامیده می‌شود در این چارچوب نیازهای ما را مرتفع می‌سازد. CSS-in-JS منشأ بحث‌های داغ زیادی بوده است، ما قصد نداریم وارد این بحث‌ها بشویم، زیرا از حیطه موضوع این مقاله خارج است.

Vue.js در عمل راه‌حل خاص خود را برای CSS-in-JS به صورت «کامپوننت‌های تک فایلی» ارائه کرده است. در سوی دیگر، جامعه ری‌اکت ایده‌های خاص خود را برای حل این مشکل دارد. این همان جایی است که اختلاف زیادی بین React و Vue شاهد هستیم. با این که Vue یک راه‌حل جامع ارائه کرده است، جامعه ری‌اکت به طور عمده روی توسعه اکوسیستم در حال شکوفایی متمرکز شده است، چون ری‌اکت در حال حاضر صرفاً یک کتابخانه UI محسوب می‌شود. گزینه‌های محبوبی که ظاهر شده‌اند شامل کامپوننت‌های استایل‌دار و emotion و چند مورد دیگر هستند. همچنین یک آداپتر emotion (+) برای VUE نیز وجود دارد که در نهایت به ابزار منتخب ما تبدیل شده است.

بدین ترتیب استایل‌ها دیگر به کلاس‌ها محدود نیستند، بلکه به شکل کامپوننت‌های واقعی Vue ظاهر می‌شوند و از این رو کامپوننت‌های استایل‌دار نام دارند.

استفاده مجدد از کدبیس Vue.js

در تصویر فوق لایه UI مربوط به Homepage را می‌بینید که اندکی متفاوت است. بدین ترتیب از شر تگ <style> به کلی رها شده‌ایم و استایل‌های خود را به درون کد واقعی جاوا اسکریپت که در <script> تعریف شده می‌بریم. توجه کنید که آن‌ها را به خارج از لایه UI نبرده‌ایم و همچنان درون آن قرار دارد. در ادامه در مورد مزیت این بازسازی بیشتر صحبت خواهیم کرد.

طراحی قالب برای اپلیکیشن

همچنان که پیش‌تر اشاره کردیم، کتابخانه‌های CSS-in-JS از همان قواعد نام‌گذاری برای themes سراسری‌شان استفاده می‌کنند که ما برای نامیدن قالب‌های اپلیکیشن بهره می‌گیریم. اما در عمل تفاوت زیادی وجود دارد.

زمانی که از قالب صحبت می‌کنیم، منظورمان CSS اپلیکیشن و کامپوننت‌های متمایزی است که موجب ایجاد تفاوت بین قالب‌های مختلف می‌شوند. در تصویر زیر نمای به‌روز‌شده‌ای از نمودار معماری را می‌بینید. در این تصویر مفهوم جدیدی به نام «کانتینر» (Container) را وارد کرده‌ایم. اینک باید با مفهوم کارکردهای پایه (Core) آشنا باشید که در کانتینر خاص خودش قرار می‌گیرد. به علاوه یک کانتینر به نام Theme نیز وجود دارد که همه قالب‌های اپلیکیشن درون آن جای می‌گیرند.

استفاده مجدد از کدبیس Vue.js
برای نمایش در اندازه بزرگتر روی تصویر کلیک کنید.

استایل‌ها

در نخستین بسط نمودار معماری که در تصویر فوق ملاحظه کردید، style از لایه UI هر برش کارکردی به لایه UI قالب‌ها انتقال یافته است. از این‌رو Core بدون هیچ استایلی مانده است. تنها کارکرد Core تعریف کردن آنچه کامپوننت‌ها باید انجام دهند و چگونگی نمایش هر کامپوننت صرف نظر از ساختار معناشناختی‌اش است. علاوه بر موارد بنیادی مانند رنگ، فاصله‌بندی و غیره، اینک می‌توانیم استایل‌بندی‌های خاصی را برای هر قالب با کمک گرفتن از قدرت و انعطاف‌پذیری CSS طراحی کنیم. کل نمود بصری در معرض تغییر شکل قرار دارد و بدین ترتیب می‌توانیم هر قالب را به شیوه‌ای کاملاً متفاوت طراحی کنیم و این تجربه‌ای متفاوت از صرفاً تغییر رنگ است. همزمان با این تغییرات در قالب همه کارکردها نیز پابرجا باقی می‌مانند، زیرا همه کارکردهای در Core تعریف شده‌اند.

بهترین جنبه این معماری آن است که اکنون طیف گسترده‌ای از مفاهیم بصری مختلف را همراه با رنگ‌های برند و هویت بصری دیگر با کمترین تلاش در اختیار داریم. بدین ترتیب مصرف‌کننده‌ها می‌توانند یک مبنای بصری را بر مبنای نیازهای فردی خود برگزینند.

انتساب استایل‌های صحیح به کامپوننت‌های صحیح

این آن جایی است که بخش جاوا اسکریپت CSS-in-JS به کار می‌آید. از آنجا که انتزاع emotion بر روی CSS به شکل کامپوننت‌های Vue.js قابل اکسپورتِ ES6 ساده ارائه شده است، می‌توانیم هر کاری که جاوا اسکریپت به ما اجازه می‌دهد با استایل‌ها انجام دهیم. به طور خاص می‌توانیم آن‌ها را هر کجا که دست داریم قرار دهیم و دیگر محدود به تگ <style> کامپوننت تک فایلی نیستیم.

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

اینک سؤال این است که پیوند بین Core و هر یک از قالب‌ها در کجا صورت می‌گیرد. برای این که این اتفاق بیفتد، باید از یک مفهوم اشتراک کد به نام «کامپوننت‌های مرتبه بالاتر» (Higher Order Components) یا به اختصار HOG و همچنین کارکرد require.context در webpack بهره بگیریم.

بهترین راه برای درک کامپوننت‌های مرتبه بالاتر، توصیف ریاضیاتی آن‌ها است. فرض کنید دو تابع f: x => x و g: x => 2x را داریم. پس از مدتی لازم می‌شود که تابع‌ها تغییر یابند، چون نتیجه تابع‌ها باید یک واحد افزایش یابد. بدیهی است که تعریف تابع‌های قبلی را به ترتیب به صورت زیر تغییر می‌دهیم: f': x => x + و g': x => 2x + 1. اما به جای این که این کار را به صورت دستی انجام بدهیم، می‌توانستیم یک تابع جدید به صورت h: x => x + 1 نیز بنویسیم و آن را روی تابع‌های f و g به صورت زیر فراخوانی کنیم:

g: f': h(f(x)) => x + 1
g': h(g(x)) => 2x + 1

در حالتی که به جای 2 تابع، با 1000 تابع مواجه باشیم، اهمیت پیاده‌سازی آن تابع جدید h بیشتر نمایان می‌شود. در واقع می‌توانیم همین مفهوم را در مورد کامپوننت‌های Vue نیز اعمال کنیم. می تونیم تابعی بنویسیم که یک کامپوننت بگیرد، آن را به صورت مناسب ویرایش کند و کامپوننت جدیدی با ویرایش‌های موصوف بازگشت دهد.

در مورد require.context باید اشاره کنیم که بخشی از استاندارد ECMAScript محسوب نمی‌شود، اما به هر حال همراه با webpack عرضه شده است. معنی این حرف آن است که این تابع درزمان build-time اجرا می‌شود، اما در زمان runtime نخواهد شد.

require.context برای require کردن بازگشتی دسته کاملی از وابستگی‌ها به صورت یکباره گزینه مناسبی است و می‌توان از تطبیق با RegExp بهره گرفت و کار را از یک ریشه از پیش تعریف شده آغاز کرد. در این مورد به تعریف یک چارچوب برای قالب خود به صورت زیر می‌پردازیم:

const themeContext = require.context(THEME_ROOT_PATH, true, /.styles.js$/);

کد فوق همه فایل‌ها را با آغاز از THEME_ROOT_PATH و پایان ‎.styles.js به صورت require می‌کند. THEME_ROOT_PATH ثابتی است که با پلاگین DefinePlugin وب پک تعریف شده است.

1const path = require('path');
2
3const { THEME } = process.env;
4
5module.exports = {
6  // ...
7  plugins: [
8    new webpack.DefinePlugin({
9      THEME_ROOT_PATH: JSON.stringify(path.resolve(__dirname, `../theme/${THEME}/src`)),
10    }),
11  ],
12};

این پلاگین به نوبه خود به یک متغیر به نام THEME دسترسی می‌یابد که با cross-env در package.json تعریف شده است و نقطه منفردی در همه کد است که امکان کنترل ظاهر بصری را به یک مصرف‌کننده می‌دهد. در واقع آن کار به سادگی فشردن یک کلید است.

1{
2  "scripts": {
3    "build": "cross-env THEME=theme1 webpack"
4  }
5}

اگر بخواهیم همه موارد مطرح‌شده را کنار هم قرار دهیم، در نهایت به یک کامپوننت مرتبه بالاتر به نام withStyles می‌رسیم که یک کامپوننت Vue به نام MyComponent.vue می‌گیرد، به دنبال پیاده‌سازی استایل آن یعنی MyComponent.styles.js قالب THEME بر مبنای name مربوط به MyComponent.vue می‌گردد و در نهایت components رجیستر شده آن را به وسیله همه کامپوننت‌های استایل‌دار که در پیاده‌سازی استایلش بیابد، بسط می‌دهد. به همین دلیل است که در مثال زیر از Wrapper و Box علاوه بر کامپوننت Text که به صورت معمولی ثبت شده بهره گرفته‌ایم.

MyComponent.vue که درون پوشش withStyles قرار گرفته است:

1<template>
2  <Wrapper>
3    <Text value="Hello, world!" />
4    <Box color="hotpink" />
5  </Wrapper>
6</template>
7
8<script>
9import withStyles from 'core/util/withStyles.js';
10import Text from 'core/app/ui/Text.vue';
11const MyComponent = {
12  name: 'MyComponent',
13  styleInterface: ['Wrapper', 'Box'],
14  components: {
15     Text,
16  },
17};
18export default withStyles(MyComponent);
19</script>

پیاده‌سازی استایل برای MyComponent.vue

1import { styled } from '@egoist/vue-emotion';
2
3const Wrapper = styled('div')`
4  display: flex;
5  flex-direction: column;
6`;
7
8const Box = styled('div')`
9  width: 150px;
10  height: 150px;
11  background-color: ${({ color }) => color};
12`;
13
14const styles = {
15  Wrapper,
16  Box,
17};
18
19export default styles;

علاوه بر این یک کلید سفارشی به نام styleInterface نیز ایجاد کردیم که هدفی مشابه یک رجیستر لوکال کامپوننت‌ها دارد. این کلید در واقع آرایه‌ای از رشته‌ها است که نشان می‌دهد کدام کامپوننت استایل‌دار باید از سوی هر قالب پیاده‌سازی شود. از این کلید برای صدور هشدارهایی در طی فرایند توسعه در قالب در زمان مفقود بودن پیاده‌سازی‌های استایل و همچنین داشتن مروری درون فایلی از این که کدام کامپوننت‌ها در وهله نخست در دسترسی ما قرار دارند، استفاده می‌کنیم.

نقاط بسط

قالب‌های مختلف ممکن است کارکردهایی داشته باشند که اندکی با پیاده‌سازی Core تفاوت داشته باشند. برخی اوقات، با مشکلاتی مواجه می‌شویم که CSS به تنهایی نمی‌تواند حل کند. برای نمونه اگر یک کامپوننت که برخی آیتم‌ها را نمایش می‌دهد در نظر بگیرید، برخی قالب‌ها ممکن است آن‌ها را در یک grid نمایش دهند، در حالی که برخی دیگر یک اسلایدر برای اسکرول کردن روی آن‌ها داشته باشند و هر بار یک آیتم را هایلایت کنند.

اولین پیاده‌سازی که به ذهن می‌رسد این است که یک prop بولی به نام useSlider داشته باشیم که بین نمایش gird و نمایش اسلایدر سوئیچ کند. همچنین شاید بهتر باشد یک prop به نام display داشته باشیم که با آن صرفاً بین حالت‌های نمایشی مختلف انتخاب کنیم. یکی از مشکلات این پیاده‌سازی آن است که در نهایت با کامپوننت‌های پیچیده‌ای مواجه می‌شویم که کارهای مختلفی انجام می‌دهند. بنابراین به جای این که بپرسیم، «چطور می‌توانم کاری کنم که این کامپوننت یک کار دیگر انجام دهد؟» در اغلب موارد بهتر است بپرسیم: «آیا کامپوننت دیگری می‌تواند این مشکل را حل کند؟»

در این مورد خاص، مشکل بزرگ‌تر آن است که چگونه با یک Core تعریف شده که همه تعاریف کامپوننت را در خود جای داده برخورد کنیم. توجه کنید که تنها پیاده‌سازی‌های استایل هستند که در هر قالب متفاوت هستند. اگر prop پیشنهادی useSlider را به true تغییر بدهید، این تغییر روی همه قالب‌ها اعمال خواهد شد. به این ترتیب همه قالب‌ها به جای صرفاً قالب موردنظر، دارای یک اسلایدر خواهند شد.

برای حل این مشکل از مفهومی به نام «نقاط بسط» (Extension Points) استفاده می‌کنیم که اصالتاً از سوی UML معرفی شده است. در این مثال دو کامپوننت به نام‌های GridView.vue و SliderView.vue ایجاد می‌کنیم. فرض ما بر این است که نمایش grid نمایش پیش‌فرض است و باید آن کامپوننت را در Core قرار دهیم. با این حال SliderView.vue جایگاه خود را در لایه UI قالب متناظر می‌یابد. اکنون هر قالب یک فایل به نام extensionPoints.js پیاده‌سازی می‌کند که کامپوننت‌های تشکیل‌دهنده را به تعدادی از کامپوننت‌های نقطه بسط خوش‌تعریف شده مانند زیر انتساب می‌دهد:

1// Header.vue gets imported from the core
2import Header from 'core/app/ui/Header.vue'
3
4// SliderView.vue gets imported from theme1
5import SliderView from 'theme/theme1/ui/SliderView.vue'
6
7// This particular theme ...
8const extensionPoints = {
9  // ... uses the regular Header
10  'Header-ExtensionPoint': Header,
11  // ... but also uses a special, non-standard ItemView
12  'ItemView-ExtensionPoint': SliderView,
13};
14  
15export default extensionPoints;

همه قالب‌های دیگر نیز می‌توانند تصمیم بگیرند تا از GridView.vue استفاده کنند. حتی می‌توانیم پا را فراتر گذارده و مقادیر پیش‌فرض Core را برای هر نقطه بسط تعریف کنیم و سپس آن را با یک زیرمجموعه از override-ها که در هر قالب تعیین‌شده ادغام نماییم.

اکنون تنها کاری که باقی مانده این است که روی شیء extensionPoints حلقه‌ای تعریف کنیم و این کامپوننت‌ها را به صورت سراسری ثبت کنیم:

1import Vue from 'vue';
2
3import extensionPoints from './extensionPoints';
4
5Object.entries(extensionPoints).forEach(([name, component]) => {
6  // Global component registration
7  Vue.component(name, component);
8})

بدین ترتیب در همه جا در قالب‌های اپلیکیشن با شناسه ژنریک خودشان یعنی </ ItemView-ExtensionPoint/> در دسترس ما خواهند بود. توجه کنید که این رویکرد چگونه به رفع معضل داشتن یک Core ثابت که نباید ویرایش پیدا کند، فائق می‌آید.

هر نقطه بسط در واقع می‌تواند تقریباً هر چیزی را پیاده‌سازی کند. تنها باید از چند مسئله آگاه باشید:

  • هر چه نقاط بسط افزایش یابند، سردرگمی نیز افزایش می‌یابد. بنابراین باید در مورد این که آیا به یک نقطه بسط دیگر نیاز داریم یا نه، با دقت تصمیم‌گیری کنیم. همواره این احتمال وجود دارد که کارکرد موجود به قدر کافی خوب باشد.
  • در مورد اینترفیس عمومی هر نقطه بسط مراقب باشید. به عبارت دیگر باید در مورد props هوشیار باشیم. زمانی که یک پیاده‌سازی جدید، prop-های جدیدی اضافه می‌کند، هر یک از فراخوانی‌های آن باید مورد بررسی قرار گیرند‌. در نهایت این prop-های جدید باید جایی اضافه شوند، چون در غیر این صورت کاملاً بی‌فایده خواهند بود. همه پیاده‌سازی‌های دیگر در مورد این prop-ها اطلاعی ندارند و ممکن است بدون هیچ تأثیری به آن‌ها ارسال شوند. دیر یا زود این وضعیت به یک سردرگمی بزرگ منجر می‌شود. بنابراین بهتر است اینترفیس بین همه پیاده‌سازی‌های مختلف ثابت بماند. به این ترتیب هر یک از آن‌ها همان مجموعه prop-ها را دریافت می‌کنند.
  • توجه داشته باشید که در همه موارد یک گزینه قطعی برای محل درج یک نقطه بسط وجود ندارد. فرض کنید یک کامپوننت A وجود دارد که به صورت داخلی از کامپوننت‌های B ،C و D استفاده می‌کند. شما می‌توانید به جای D از D’ در اعماق درخت کامپوننت استفاده کنید تا به طور خاص تنها یک برگ را تغییر دهید و یا این که می‌توانید یک نقطه بسط را در مراتب بالاتر با عوض کردن A با A’ ایجاد کنید. در این صورت A’ دقیقاً از همان B و C استفاده می‌کند و بر مبنای آن‌ها از کامپوننت خصوصی جدید D’’ نیز بهره می‌گیرد که صرفاً از سوی A’ ایمپورت شده و هرگز در جایی به صورت سراسری ثبت نشده است. در نگاه نخست رویکرد قبلی معقول‌تر به نظر می‌رسد، چون مشکل را به شیوه جامع‌تر حل می‌کند و عوارض جانبی کمتری دارد. با این که این ارزیابی نادرستی نیست، اما در برخی موارد بهتر است از رویکرد دوم استفاده کنیم. بدین ترتیب انعطاف‌پذیری بیشتری برای تغییرات آتی به دست می‌آید. بنابراین برای کامپوننت‌هایی که احتمال دارد از سوی مصرف‌کننده‌های مختلف به روش‌های گوناگون تغییر یابند، بهتر است از رویکرد ایجاد یک branch در مراتب بالاتر درخت کامپوننت بهره بگیرید.

بسط اپلیکیشن و ویرایش Core

در این بخش یک بار دیگر نمودار معماری اپلیکیشن را به‌روزرسانی می‌کنیم:

استفاده مجدد از کدبیس Vue.js
شکل نهایی معماری - برای نمایش در اندازه بزرگتر روی تصویر کلیک کنید.

چنان که می‌بینید کانتینر جدیدی به نام Consumer ظاهر شده است. اکنون ما همه ایده‌هایی که تا به اینجا بررسی شد را روی این کانتینر جدید پیاده‌سازی می‌کنیم. مصرف‌کننده‌ها نیز می‌توانند ‎.styles.js را تعیین کنند و نقاط بسط خاص خود را تعریف نمایند. در نهایت با کانتینرهای آبشاری مواجه هستیم. Consumer نسبت به Theme و آن نیز نسبت به Core تقدم دارند. بدین ترتیب می‌توانیم نیازهای منفرد خاص هر مصرف‌کننده را برآورده سازیم. الزام پنجم ما در ابتدای این مقاله آن بود که بتوانیم بخش‌هایی از Core را برای هر مصرف‌کننده بدون از کار افتادن یا تأثیر گذاردن بر بخش‌های دیگر ویرایش کنیم که اینک حاصل آمده است.

علاوه بر آن این نقطه جایی است که راه‌حل‌های یک‌بارمصرف را که تنها یک مصرف‌کننده تقاضا کرده است، پیاده‌سازی می‌کنیم. شاید Consumer 2 از ما درخواست یک امکان پشتیبانی چت زنده کرده باشد که در گوشه صفحه وب‌سایت ظاهر می‌شوند. در این حالت می‌توانیم یک «دامنه تجاری» (Business Domain) جدید به نام LiveChat با استفاده از لایه‌های UI ،Model و Connector درون برش مصرف‌کننده عمودی Consumer 2 ایجاد کنیم. ضمناً بخش scripts را در پکیج package.json به‌روزرسانی می‌کنیم:

1{
2  "scripts": {
3    "build:consumer1": "cross-env CONSUMER=consumer1 THEME=theme2 webpack",
4    "build:consumer2": "cross-env CONSUMER=consumer2 THEME=theme1 webpack"
5  }
6}

در کد فوق یک اسکریپت build متفاوت برای هر مصرف‌کننده می‌سازیم. علاوه بر متغیر THEME متغیر دیگری به نام CONSUMER تعیین می‌کنیم که اساساً همان منظور را تأمین می‌کند و این بار فقط برای Consumers عمل خواهد کرد.

سخن پایانی

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

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

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
bauer-kirch
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *