تفاوت‌های یک برنامه مشابه در React و Vue

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 25 شهریور 1397
دسته بندی ها : جاوا اسکریپت

با استفاده از Vue در فضای کاری فعلی خود، درک خوبی از نحوه کار آن به دست آورده‌ام. گرچه، همچنان کنجکاو بودم که در سمت دیگر ماجرا یعنی React، اوضاع چگونه بود. من آموزش‌های زیادی را دیده بودم با این که بسیار خوب بودند، می‌خواستم بدانم که React چگونه از Vue متفاوت است. منظور من از تفاوت این نیست که آیا هر دو DOM مجازی دارند یا این که چگونه صفحات را رندر می‌کنند. من می‌خواستم این تفاوت‌ها به گونه‌ای بیان شوند که یک شخص تازه وارد در Vue یا React، (یا به کلی توسعه وب) بتواند درک بهتری از آن‌ها به دست بیاورد. پس تصمیم گرفتم که خودم چنین مقاله‌ای را بنویسم تا بتوانم شباهت‌ها و تفاوت‌ها را ببینم.

من تصمیم گرفتم که یک برنامه یادداشت ساده بسازم که کاربر به سادگی بتواند آیتم‌هایی را به آن اضافه کرده، و حذف کند.

هر دو برنامه با استفاده از CLIهای پیشفرض (create-react-app برای React و vue-cli برای Vue) ساخته شده‌اند. در ضمن CLI مخفف Command Line Interface (رابط خط دستوری) است.

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

کد CSS برای این دو برنامه دقیقا یکی است، اما تفاوتی در محل قرار گیری آن‌ها وجود دارد. با توجه به این مسئله، حال بیایید نگاهی به ساختار فایل این دو برنامه داشته باشیم:

در اینجا می‌بینید که ساختار آن‌ها نیز تقریبا یکی است. تنها تفاوت موجود این است که React سه فایل CSS دارد، در حالیکه Vue هیچ فایل CSSای ندارد. علت آن این است که در create-react-app،‌ هر کامپوننت React یک فایل همراه برای نگهداری استایل‌هایش خواهد داشت، درحالیکه در Vue استایل‌ها در فایل اصلی کامپوننت تعریف می‌شوند.

در نهایت، هر دوی آن‌ها یک چیز مشترک را به دست می‌آورند، و هیچ مانعی برای ساختاربندی متفاوت CSS در React یا Vue وجود ندارد. همه چیز به ترجیحات شخصی شما بر می‌گردد. فعلا ما ساختارهای موجود در CLIها را دنبال می‌کنیم.

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

حال که از این بخش هم گذشتیم،‌ بیایید به جزئیات وارد شویم.

چگونه داده‌ها را جهش دهیم؟

در ابتدا،‌ منظور ما از «جهش داده‌ها» چیست؟ این عبارت کمی فنی به نظر می‌رسد. اساسا معنی آن،‌ تغییر دادن داده‌هایی است که ذخیره کرده‌ایم. پس اگر ما مقدار نام یک شخص را از John به Mark تغییر دهیم، در واقع داده‌ها را «جهش داده‌ایم». پس در اینجاست که یک تفاوت کلیدی میان React و Vue وجود دارد. Vue اساسا یک آبجکت داده می‌سازد که در آن داده‌ها می‌توانند آزادانه تغییر داده شوند، اما React یک آبجکت state می‌سازد که در آن کمی تلاش برای تغییر دادن داده‌ها نیاز است. حال React این تلاش اضافی را با دلیل مناسبی قرار داده است، که کمی جلوتر به آن خواهیم رسید. اما در ابتدا بیایید نگاهی به آبجکت داده‌ها در Vue و آبجکت state در React داشته باشیم:

می‌بینید که ما داده مشابهی را به این دو منتقل کرده‌ایم و فقط برچسب‌های مختلفی دارند. پس منتقل کردن داده‌های اولیه به کامپوننت‌های خود، بسیار مشابه است. اما همانطور که اشاره کردیم، نحوه تغییر دادن داده‌ها بین دو فریم‌وورک متفاوت است.

فرض کنید که ما یک عنصر داده به نام name: ‘Sunil’ داریم.

در Vue، می‌توانیم با فراخوانی this.name به این عنصر ارجاع کنیم. همچنین می‌توانیم با استفاده از this.name = ‘John’ آن را بروزرسانی کنیم. این کد نام شخص را به John تغییر خواهد داد.

در React، با فراخوانی this.state.name به همین تکه داده ارجاع می‌کنیم. حال تفاوت کلیدی در اینجا این است که نمی‌توانیم به سادگی بنویسیم this.state.name =’John’، زیرا React محدودیت‌هایی برای جلوگیری از این گونه جهش‌های ساده و بدون احتیاط دارد. پس در React، چیزی به مانند this.setState({name: ‘John’}) را می‌نویسیم.

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

علت آن این است که React می‌خواهد برخی چرخه‌ها مانند componentWillRecieveProps، shouldComponentUpdate، componentWillUpdate، render، componentDidUpdate را هر زمان که state تغییر می‌کند، مجددا اجرا کند. اگر مستقیما داده‌ها را تغییر دهیم، React مجبور می‌شود که کار بسیار بیشتری انجام دهد تا چرخه‌های مورد نیاز را پیدا کند. پس برای ساده‌سازی این مسئله،‌ React از setState استفاده می‌کند.

حال که بحث جهش داده‌ها را گذرانده‌ایم، بیایید به بخش اضاضه کردن آیتم به هر دو برنامه نگاهی داشته باشیم.

چگونه‌ آیتم‌های جدیدی برای یادداشت بسازیم؟

React:

createNewToDoItem = () => {
    this.setState( ({ list, todo }) => ({
      list: [
          ...list,
        {
          todo
        }
      ],
      todo: ''
    })
  );
};

React چگونه این کار را انجام داد؟

در React، فیلد ورودی ما صفتی به نام value بر روی خود دارد. این مقدار به طور خودکار در طی استفاده از برخی توابع بروزرسانی می‌شود، که گرد هم می‌آیند تا چیزی بسازند که بسیار شبیه به اتصال دو طرفه یا two-way binding است. (اگر این اصطلاح را نشنیده‌اید، در ادامه و در بخش «Vue چگونه این کار را انجام داد؟» توضیحی به همراه جزئیات وجود دارد) ما با داشتن یک listener از نوع onChange که به فیلد ورودی متصل شده است، این نوع اتصال دو طرفه را می‌سازیم. بیایید نگاهی سریع به فیلد ورودی داشته باشیم تا بتوانید ببینید که چه اتفاقی می‌افتد:

<input type="text"
       value={this.state.todo}
       onChange={this.handleInput}/>

تابع handleInput هر زمان که مقدار فیلد ورودی تغییر کند، اجرا می‌شود. این تابع لیست یادداشتی که داخل آبجکت sate وجود دارد را بروزرسانی می‌کند و هر مقداری که در فیلد ورودی وجود داشته باشد را در آن قرار می‌دهد. این تابع به مانند این کد است:

handleInput = e => {
  this.setState({
    todo: e.target.value
  });
};

حال، هر زمان که کاربری دکمه «+» را بر روی صفحه بفشارد تا یک آیتم جدید را اضافه کند، تابع createNewToDoItem اساسا this.setState را اجرا می‌کند و تابع را منتقل می‌کند. این تابع دو پارامتر را می‌گیرد که اولین مورد آرایه کل لیست از آبجکت state است، و دومین مورد هم آیتم جدید است که توسط تابع handleInput بروزرسانی می‌شود. سپس این تابع یک آبجکت جدید را بر می‌گرداند، که کل لیست آیتم‌ها را از پیش دارد و سپس آیتم جدید را در انتهای آن اضافه می‌کند.

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

Vue:

createNewToDoItem() {
    this.list.push(
        {
            'todo': this.todo
        }
    );
    this.todo = '';
}

Vue چگونه این کار را انجام داد؟

در Vue، فیلد ورودی ما یک handle به نام v-model بر روی خود دارد. این ما را قادر می‌سازد تا کاری به نام اتصال دو طرفه را انجام دهید. بیایید اول نگاهی سریع به فیلد ورودی خود داشته باشیم و سپس آن را توضیح دهیم:

<input type="text" v-model="todo"/>

V-Model ورودی این فیلد را به یک کلید که در آبجکت داده‌های خود به نام toDoItem داریم، اتصال می‌دهد. وقتی که صفحه بارگذاری می‌شود، ما toDoItem را برابر با یک رشته خالی داریم؛ به صورت: todo: ‘’. اگر toDoItem از قبل مقداری داده در خود داشت، مثلا todo: ‘text’، فیلد ورودی ما به همراه متن «text» در داخل خود بارگذاری می‌شود. به هر حال، هر مقداری که در فیلد ورودی خود قرار دهیم به مقدار todo اتصال داده می‌شود. این مسئله،‌ عملا «اتصال دو طرفه» نام دارد. (فیلد ورودی می‌تواند آبجکت داده را بروزرسانی کند و آبجکت داده می‌تواند فیلد ورودی را بروزرسانی کند)

چگونه آیتم‌ها را از لیست حذف کنیم؟

React:

deleteItem = indexToDelete => {
    this.setState(({ list }) => ({
      list: list.filter((toDo, index) => index !== indexToDelete)
    }));
};

React چگونه این کار را انجام داد؟

با این که تابع deleteItem داخل فایل ToDo.js قرار دارد، به سادگی توانستم با ابتدا منتقل کردن تابع deleteItem() بر روی <ToDoItem/> به عنوان یک prop، به این صورت در فایل ToDoItem.js به آن اشاره کنم:

<ToDoItem deleteItem={this.deleteItem.bind(this, key)}/>

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

<div className=”ToDoItem-Delete” onClick={this.props.deleteItem}>-</div>

تنها کاری که باید برای ارجاع به یک تابع که در کامپوننت والد قرار دارد انجام دهیم، این است که به this.props.deleteItem ارجاع کنیم.

Vue:

onDeleteItem(todo){
  this.list = this.list.filter(item => item !== todo);
}

Vue چگونه این کار را انجام داد؟

در Vue یک روش کمی متفاوت مورد نیاز است. اساسا باید در اینجا سه کار را انجام دهیم:

در ابتدا بر روی عنصری که می‌خواهیم تابع را بر روی آن فراخوانی کنیم:

<div class=”ToDoItem-Delete” @click=”deleteItem(todo)”>-</div>

سپس باید یک تابع emit را به عنوان یک متد داخل کامپوننت فرزند (که در این مورد ToDoItem.vue‌ است) بسازیم، که چنین ظاهری دارد:

deleteItem(todo) {
    this.$emit('delete', todo)
}

به همراه این، خواهید دید که ما در واقع وقتی که یک ToDoItem.vue را داخل ToDo.vue اضافه می‌کنیم، به یک تابع ارجاع می‌کنیم:

<ToDoItem v-for="todo in list"
          :todo="todo"
          @delete="onDeleteItem" // <-- this :)
          :key="todo.id" />

این چیزی است که با نام «listener رویداد سفارشی» شناخته می‌شود. این listener منتظر هر اتفاقی می‌ماند که در آن یک emit با رشته «emit» فعال شده است. اگر این listener‌ چنین چیزی را مشاهده کند، تابعی به نام onDeleteItem را فراخونی می‌کند. این تابع داخل فایل ToDo.vue قرار دارد، نه فایل ToDoItem.vue. این تابع همانطور که پیش‌تر لیست شد، به سادگی آرایه todo را داخل آبجکت داده فیلتر می‌کند تا آیتمی که بر روی آن کلیک شده بود را حذف کند.

همچنین در مثال Vue، می‌توانستم به سادگی به این صورت بخش $emit را داخل @click بنویسم:

<div class=”ToDoItem-Delete” @click=”$emit(‘delete’, todo)”>-</div> 

این کار تعداد قدم‌های مورد نیاز را از ۳ به ۲ کاهش می‌داد، و این مسئله کاملا به ترجیح شخصی شما بستگی دارد.

به طور خلاصه، کامپوننت‌های فرزند در React مجبورند از طریق this.props به توابع والد دسترسی داشته باشند، در حالیکه در Vue باید رویدادها را از کامپوننت فرزند emit کنیم که معمولا در کامپوننت والد جمع‌آوری می‌شوند.

چگونه listenerهای رویداد را منتقل کنیم؟

React:

Listenerهای رویداد برای موارد ساده مانند رویدادهای کلیک ساده هستند. در اینجا، مثالی را می‌بینید که در آن برای یک دکمه که یک آیتم جدید برای یادداشت‌ها می‌سازد، یک رویداد کلیک ساخته‌ایم:

<div className=”ToDo-Add” onClick={this.createNewToDoItem}>+</div>.

کاملا ساده، و بسیار شبیه به نحوه مدیریت رویدادهای خطی با استفاده از Vanilla JS. همانطور که در بخش Vue اشاره شد، راه‌اندازی یک listener برای مدیریت هر زمان که دکمه enter فشرده می‌شود، کمی بیشتر زمان برد. در جهت انجام این کار، رویداد onKeyPress باید توسط تگ ورودی مدیریت شود. به مانند:

<input type=”text” onKeyPress={this.handleKeyPress}/>.

این تابع اساسا تابع createNewToDoitem را هر زمان که تشخیص می‌داد کلید enter فشرده شده است، فعال می‌کرد. به مانند:

handleKeyPress = (e) => {

if (e.key === ‘Enter’) {

this.createNewToDoItem();

}

};

Vue:

این کار در Vue به شدت ساده است. به سادگی از علامت @ استفاده می‌کنیم، و سپس نوع listernerای که می‌خواهیم را می‌نویسیم. برای مثال، برای اضافه کردن یک listener کلیک می‌توانیم این کار را انجام دهیم:

<div class=”ToDo-Add” @click=”createNewToDoItem()”>+</div> 

نکته: @click در واقع کوتاه شده v-on:click است. نکته خوب در listernerهای رویداد Vue این است که می‌توانید چیزهای زیادی را به آن‌ها زنجیر کنید؛ مانند .once که از فعال شدن بیش از یک بار listener مربوطه جلوگیری می‌کند. همچنین وقتی که به نوشتن listenerهای خاصی برای مدیریت key strokeها می‌رسیم، تعدادی میان‌بر وجود دارند. من پی بردم که ساختن یک listener در React برای ساخت آیتم‌های یادداشت در زمانی که دکمه enter فشرده می‌شد، کمی بیشتر زمان می‌برد. اما در Vue به سادگی می‌توانیم بنویسیم: 

<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>

چگونه داده‌ها را به کامپوننت‌های فرزند منتقل کنیم؟

React:

در React، در جایی که propها ساخته شده‌اند، آن‌ها را به کامپوننت فرزند منتقل می‌کنیم. به مانند:

<ToDoItem key={key} item={todo} />

در اینجا می‌بینیم که دو prop به کامپوننت ToDoItem منتقل شده‌اند. از حالا به بعد، می‌توانیم از طریق this.props در کامپوننت فرزند به آن‌ها ارجاع کنیم. پس برای دسترسی به item.todo، به سادگی this.props.item را فراخوانی می‌کنیم.

Vue:

در Vue، propها را در جایی که ساخته شده‌اند به کامپوننت فرزند منتقل می‌کنیم. به مانند:

<ToDoItem v-for="todo in list"
            :todo="todo"
            :key="todo.id"
            @delete="onDeleteItem" />

پس از این که این کار انجام شد، آن‌ها را به آرایه props در کامپوننت فرزند منتقل می‌کنیم. به مانند: props: [‘todo’}. با استفاده از نام این موارد، می‌توان در کامپوننت فرزند به آن‌ها ارجاع کرد.

چگونه داده‌ها را به یک کامپوننت والد بر گردانیم؟

React:

در ابتدا با ارجاع به تابع به عنوان یک prop در جایی که کامپوننت فرزند را فراخوانی می‌کنیم، آن را به کامپوننت فرزند منتقل می‌کنیم. سپس فراخوانی به تابع را با هر چیزی که لازم است، مانند onClick و با ارجاع به this.props.function اضافه می‌کنیم. این کار تابعی که در کامپوننت والد قرار دارد را فعال می‌کند. می‌توانید در بخش «چگونه آیتم‌ها را از لیست حذف کنیم؟» یک مثال کامل از این مسئله را ببینید.

Vue:

در کامپوننت فرزند خود، به سادگی یک تابع را می‌نویسیم که یک مقدار را به کامپوننت والد emit می‌کند. در کامپوننت والد خود، تابعی را می‌نویسیم که منتظر مقداری که emit شده است می‌ماند، که خود بعدا می‌تواند یک تابع را فعال کند. می‌توانید در بخش «چگونه آیتم‌ها را از لیست حذف کنیم؟» یک مثال کامل از این مسئله را ببینید.

و در اینجا کار ما به پایان می‌رسد!

ما نحوه اضافه کردن، حذف کردن و تغییر داده‌ها، انتقال داده‌ها در قالب prop از والد به فرزند، و فرستادن برخی داده‌ها از فرزند به والد در قالب listenerهای رویداد را دیدیم. گرچه همچنان تفاوت‌های بیشتری میان React و Vue وجود دارد، اما امیدوارم که محتویات این مقاله به عنوان اساسی برای درک این دو فریم‌وورک عمل کرده باشند.

منبع

آموزش های مرتبط راکت :

مقالات پیشنهادی

ReactJS ، Angular 5 و Vue.js علیه یکدیگر

فریم‌وورک‌های JavaScript با سرعتی بسیار بالایی در حال توسعه هستند، که یعنی امروزه نسخه‌های در حال بروز شدن Angular، ReactJS و بازیکن جدید، یعنی Vue.js...

آموزش ساخت برنامه موبایل توسط React Native – قسمت دوم

در قسمت قبل با ساختار و نحوه راه اندازی React Native آشنا شدیم, در این قسمت میخواهیم بیشتر روی کار عملی تمرکز کنیم و پروژه رو جلو ببریم.

آموزش ساخت برنامه موبایل توسط React Native – قسمت اول

در این سری آموزش‌ها میخوایم درباره ساخت اپلیکیشن های native برای ios و اندروید یاد بگیریم. اما این کار رو توسط فریمورک محبوب React Native Javascript ا...

تجربه استفاده از React Native در Airbnb - توسعه اپلیکیشن به صورت native

این مطلب پنجمین مطلب از مجموعه «تجربه استفاده از React Native در Airbnb» است که ما در آن به بررسی تجربه استفاده از ری‌اکت نیتیو و آینده اپلیکیشن موبای...