Vue vs React: دليل عملي للانتقال من إطار إلى آخر
رغم الظهور المستمر لأطر Front-End جديدة، ما يزال كل من React وVue.js في صدارة الخيارات الأكثر استخداماً بين المطورين. كلاهما سريع، مرن، وقابل للتعلم، لكن لكل إطار فلسفته الخاصة في بناء الواجهات، وإدارة الحالة، والتعامل مع المكونات والتفاعلات اليومية.
إذا كنت تعمل على مشروع جديد أو انتقلت من فريق يعتمد Vue إلى آخر يستخدم React، فغالباً ستلاحظ أن الفكرة نفسها يمكن تنفيذها بطرائق مختلفة تماماً. هذا المقال يقدم لك دليلاً عملياً لفهم الأنماط المشتركة بين الإطارين، حتى يصبح الانتقال بينهما أكثر سلاسة وإنتاجية.

يعتمد هذا الشرح على React الحديثة باستخدام Hooks، وعلى Vue Options API (Vue 2). كما يُستحسن تجربة الأمثلة عملياً لفهم الفروق الدقيقة في السلوك والتركيب.
https://github.com/yigiterinc/VueVsReact
محاور المقارنة بين Vue و React
- بنية المكونات
- إدارة State
- تمرير Props
- إنشاء Methods وFunctions
- خيارات التنسيق Styling
- ربط حقول النماذج بالبيانات
- التعامل مع الأحداث
- التنسيق الشرطي
- العرض الشرطي
- عرض القوائم
- التواصل من الابن إلى الأب
- مراقبة تغيّر البيانات
- Computed Properties مقابل useMemo
- Vue Slots مقابل Render Props
بنية المكونات في Vue و React
كيف تُبنى المكونات في Vue
في Vue، يتكوّن Single File Component عادة من ثلاثة أقسام رئيسية: template وscript وstyle. هذا التنظيم يجعل كل ما يخص المكوّن موجوداً في ملف واحد، وهو ما يسهّل القراءة والصيانة.
<template>
<div id="structure">
<h1>Hello from Vue</h1>
</div>
</template>
<script>
export default {}
</script>
<style>
#structure {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
ولعرض هذا المكوّن داخل التطبيق، يمكن إضافته إلى App.vue بالشكل التالي:
<template>
<div id="app">
<Structure />
</div>
</template>
<script>
import Structure from './Structure/Structure.vue'
export default {
name: 'App',
components: {
Structure,
},
}
</script>
كيف تُبنى المكونات في React
في React، المكوّن الوظيفي هو دالة تُعيد JSX، وهي صيغة تسمح بكتابة عناصر شبيهة بـ HTML داخل JavaScript. الفكرة هنا أبسط من حيث الشكل: دالة في الداخل، وواجهة مستخدم في الخارج.
import React from 'react'
function Structure() {
return <div>Render me App</div>
}
export default Structure
import './App.css'
import Structure from './Structure/Structure'
function App() {
return (
<div className="App">
<Structure />
</div>
)
}
export default App
الفرق الجوهري هنا أن Vue تفصل بين القالب والمنطق والتنسيق داخل الملف نفسه، بينما React تميل إلى دمج منطق العرض مع JavaScript عبر JSX.
إدارة State في Vue و React
استخدام State في Vue
في Vue Options API، تُعرَّف الحالة عبر الخاصية data على شكل دالة تعيد كائناً يحتوي على المتغيرات المطلوبة.
<template>
<div>
<h1>Hello {{ currentFramework }}</h1>
</div>
</template>
<script>
export default {
data() {
return {
currentFramework: 'Vue!',
alternative: 'React!',
}
},
}
</script>
أي تغيير على هذه البيانات سيؤدي إلى إعادة العرض إذا كانت مستخدمة داخل template.
استخدام State في React مع useState
في React الحديثة، يتم استخدام useState لتخزين الحالة داخل المكوّن.
import React, { useState } from 'react';
function App() {
const [stateName, setStateName] = useState('default value');
}
يتم تعريف اسم المتغير ودالة التحديث داخل مصفوفة، ثم تمرير القيمة الابتدائية إلى useState.
import { React, useState } from 'react'
function TestUseState() {
const [frameworkName, setFrameworkName] = useState('React')
return (
<div>
<h1>useState API</h1>
<p>Current Framework: {frameworkName}</p>
</div>
)
}
export default TestUseState
لاحظ أن Vue تستخدم {{ }} داخل القالب، بينما React تستخدم { } داخل JSX.
تمرير Props في Vue و React
تعريف Props في Vue
في Vue، يمكن تعريف props بشكل صريح مع النوع والقيمة الافتراضية وكونها مطلوبة أم لا، ما يمنح المطور ضبطاً أكبر وسلوكاً أوضح أثناء التطوير.
<template>
<div class="address">
<p>City: {{ city }}</p>
<p>Street: {{ street }}</p>
<p>House No: {{ houseNumber }}</p>
<p>Postal Code: {{ postalCode }}</p>
</div>
</template>
<script>
export default {
data() {
return {}
},
props: {
city: {
type: String,
default: 'Munich',
},
street: {
type: String,
required: true,
},
houseNumber: {
type: Number,
required: true,
},
postalCode: {
type: Number,
required: true,
},
},
}
</script>
ويمكن تمريرها من المكوّن الأب كما يلي:
<template>
<div class="address">
<p>Name: Yigit</p>
<Address street="randomStrasse" :postalCode="80999" :houseNumber="32">
</Address>
</div>
</template>
<script>
import Address from '@/components/Address.vue'
export default {
data() {
return {}
},
components: {
Address,
},
}
</script>
استخدام : هنا هو اختصار لـ v-bind، ويُستخدم عند تمرير قيم ليست نصية فقط مثل الأرقام والكائنات والمصفوفات.
تعامل React مع Props
في React، لا تحتاج إلى تعريف props مسبقاً بالطريقة نفسها. يمكن استقبالها مباشرة عبر destructuring أو من خلال الكائن props.
import React from 'react'
function Address({ city, street, postalCode, houseNumber }) {
return (
<div>
<p>City: {city}</p>
<p>Street: {street}</p>
<p>Postal Code: {postalCode}</p>
<p>House Number: {houseNumber}</p>
</div>
)
}
export default Address
import React from 'react'
function UserInfo() {
return (
<div>
<p>Name: Yigit</p>
<Address
city="Istanbul"
street="Ataturk Cad."
postalCode="34840"
houseNumber="92"
></Address>
</div>
)
}
export default UserInfo
إنشاء Methods وFunctions
Methods في Vue
في Vue، تُعرَّف الدوال داخل الخاصية methods، ويمكن استدعاؤها من القالب والوصول من خلالها إلى بيانات المكوّن باستخدام this.
<template>
<div>
{{ sayHello() }}
</div>
</template>
<script>
export default {
data() {
return {
to: 'Methods',
}
},
methods: {
sayHello() {
return 'Hello ' + this.to
},
},
}
</script>
Functions في React
في React، الأمر أبسط: هي مجرد دوال JavaScript داخل المكوّن.
import React from 'react'
function HelloFunctions() {
const to = 'Functions'
function sayHello() {
return 'Hello ' + to
}
const sayHelloModern = () => 'Hello ' + to
return (
<div>
{sayHello()}
<br />
{sayHelloModern()}
</div>
)
}
export default HelloFunctions
خيارات التنسيق Styling في Vue و React
التنسيق في Vue
يُعد التنسيق في Vue مباشراً وواضحاً، إذ يمكن كتابة CSS داخل وسم style، مع دعم scoped لعزل الأنماط داخل المكوّن نفسه.
<template>
<div class="main-container">
<h3 class="label">I am a styled label</h3>
</div>
</template>
<script>
export default {
data() {
return {}
},
}
</script>
<style scoped>
.main-container {
position: absolute;
left: 50%;
top: 45%;
margin: 0;
transform: translate(-50%, -50%);
text-align: center;
}
.label {
font-size: 30px;
font-weight: 300;
letter-spacing: 0.5rem;
text-transform: uppercase;
}
</style>
خيارات التنسيق في React
توفّر React عدة أساليب للتنسيق، ويعتمد الاختيار غالباً على طبيعة المشروع وتفضيل الفريق.
- ملفات CSS عادية
import React from 'react'
import './styles.css'
function Styled() {
return (
<div>
<h3 class="title">I am red</h3>
</div>
)
}
export default Styled
.title {
color: red;
font-size: 30px;
}
- استخدام Material UI مع makeStyles
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
const useStyles = makeStyles({
root: {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
border: 0,
borderRadius: 3,
boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
color: 'white',
height: 48,
padding: '0 30px',
},
});
export default function Hook() {
const classes = useStyles();
return <Button className={classes.root}>Hook</Button>;
}
- استخدام Styled Components
import React from 'react'
import styled, { css } from 'styled-components'
const Title = styled.h1`
font-size: 2em;
text-align: center;
color: palevioletred;
`
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
height: 100vh;
`
function StyledComponent() {
return (
<Wrapper>
<Title>Hello World!</Title>
</Wrapper>
)
}
export default StyledComponent
عملياً، تمنح React حرية أكبر، لكن هذه الحرية قد تؤدي إلى تفاوت كبير في بنية المشروع إن لم يكن هناك نمط عمل واضح داخل الفريق.
ربط Form Input بالبيانات في Vue و React
ربط الحقول عبر v-model في Vue
في Vue، تُستخدم v-model لربط حقول الإدخال بالبيانات بشكل مباشر وسلس.
<template>
<div>
<input v-model="inputState" type="text" />
<br />
{{ inputState }}
<br />
<button @click="changeInputState()">Click to say goodbye</button>
</div>
</template>
<script>
export default {
data() {
return {
inputState: 'Hello',
}
},
methods: {
changeInputState: function () {
this.inputState = 'Goodbye'
},
},
}
</script>
الميزة المهمة هنا أن v-model يوفّر 2-way data binding، أي أن تغيير الحقل يحدّث البيانات، وتغيير البيانات ينعكس فوراً على الحقل.
ربط الحقول في React
في React، يتم الربط يدوياً عبر الأحداث وتحديث الحالة.
import { React, useState } from 'react'
function FormInputBinding() {
const [userInput, setUserInput] = useState('Hello')
return (
<div>
<input type="text" onChange={(e) => setUserInput(e.target.value)} />
<button onClick={() => setUserInput('Goodbye')}>Click to say goodbye</button>
{userInput}
</div>
)
}
export default FormInputBinding
يعكس هذا أسلوب 1-way binding في React، حيث لا يتغير محتوى الحقل تلقائياً إلا إذا ربطته صراحة بالقيمة عبر الخاصية value.
التعامل مع الأحداث User Input
التعامل مع الأحداث في Vue
يستخدم Vue التوجيه v-on أو اختصاره @ للتعامل مع أحداث المستخدم مثل النقر والإدخال.
<template>
<div>
<input v-model="username" id="outlined-basic" label="Username" variant="outlined" />
<input v-model="password" id="outlined-basic" type="password" label="Password" variant="outlined" />
<input v-model="termsAccepted" id="outlined-basic" type="checkbox" label="Password" variant="outlined" />
<Button variant="contained" color="primary" @click="submitForm">Submit</Button>
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: '',
termsAccepted: false,
}
},
methods: {
submitForm: function () {
console.log(this.username, this.password, this.termsAccepted)
},
},
}
</script>
التعامل مع الأحداث في React
import { React, useState } from 'react'
import {
TextField,
Checkbox,
FormControlLabel,
Button,
} from '@material-ui/core'
function EventHandling() {
let [username, setUsername] = useState('')
let [password, setPassword] = useState('')
let [termsAccepted, setTermsAccepted] = useState(false)
const submitForm = () => {
console.log(username, password, termsAccepted)
}
const formContainer = {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '20px',
}
return (
<div style={formContainer}>
<TextField onInput={(e) => setUsername(e.target.value)} id="outlined-basic" label="Username" variant="outlined" />
<TextField onInput={(e) => setPassword(e.target.value)} id="outlined-basic" type="password" label="Password" variant="outlined" />
<FormControlLabel
control={
<Checkbox
type="checkbox"
checked={termsAccepted}
onChange={(e) => setTermsAccepted(e.target.checked)}
/>
}
label="Accept terms and conditions"
/>
<Button variant="contained" color="primary" onClick={() => submitForm()}>Submit</Button>
</div>
)
}
export default EventHandling
التنسيق الشرطي Conditional Styling
التنسيق الشرطي في Vue
<template>
<div>
<button @click="toggleApplyStyles"></button>
<p :class="{ textStyle: stylesApplied }">
Click the button to {{ stylesApplied ? 'unstyle' : 'style' }} me
</p>
</div>
</template>
<script>
export default {
data() {
return {
stylesApplied: false,
}
},
methods: {
toggleApplyStyles: function () {
this.stylesApplied = !this.stylesApplied
},
},
}
</script>
<style>
.textStyle {
font-size: 25px;
color: red;
letter-spacing: 120%;
}
</style>
هنا يتم تطبيق الصنف textStyle فقط إذا كانت القيمة stylesApplied تساوي true.
التنسيق الشرطي في React
import { React, useState } from 'react'
import './styles.css'
function ConditionalStyling() {
let [stylesApplied, setStylesApplied] = useState(false)
return (
<div>
<button onClick={() => setStylesApplied(!stylesApplied)}>Click me</button>
<p style={{ color: stylesApplied ? 'red' : 'green' }}>Red or Green</p>
<p className={stylesApplied ? 'styleClass' : ''}>Red with class</p>
</div>
)
}
export default ConditionalStyling
العرض الشرطي Conditional Rendering
العرض الشرطي في Vue
<template>
<div>
<h2 v-if="condition1">condition1 is true</h2>
<h2 v-else-if="condition2">condition2 is true</h2>
<h2 v-else>all conditions are false</h2>
</div>
</template>
<script>
export default {
data() {
return {
condition1: false,
condition2: false,
}
},
}
</script>
العرض الشرطي في React
import React from 'react'
function ConditionalRendering() {
const condition1 = false
const condition2 = false
function getMessage() {
let message = ''
if (condition1) {
message = 'condition1 is true'
} else if (condition2) {
message = 'condition2 is true'
} else {
message = 'all conditions are false'
}
return <h1>{message}</h1>
}
return <>{getMessage()}</>
}
export default ConditionalRendering
عرض القوائم Rendering Arrays
عرض القوائم في Vue باستخدام v-for
<template>
<div>
<h1>Names</h1>
<ul>
<li v-for="(person, index) in people" :key="index">
{{ person.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
people: [
{ id: 0, name: 'Yigit' },
{ id: 1, name: 'Gulbike' },
{ id: 2, name: 'Mete' },
{ id: 3, name: 'Jason' },
{ id: 4, name: 'Matt' },
{ id: 5, name: 'Corey' },
],
}
},
}
</script>
عرض القوائم في React باستخدام map
import React from 'react'
function RenderingLists() {
const cities = [
'Istanbul',
'München',
'Los Angeles',
'London',
'San Francisco',
]
return (
<div>
<h1>Cities</h1>
{cities.map((city, index) => (
<h4 key={index}>{city}</h4>
))}
</div>
)
}
export default RenderingLists
التواصل من الابن إلى الأب Child to Parent Communication
في Vue باستخدام $emit
<template>
<div>
<button @click="buttonClicked">Submit</button>
</div>
</template>
<script>
export default {
methods: {
buttonClicked: function () {
this.$emit('buttonClicked')
},
},
}
</script>
<template>
<form action="#">
<input v-model="username" type="text" />
<child @buttonClicked="handleButtonClicked" />
</form>
</template>
<script>
import Child from './Child.vue'
export default {
components: { Child },
data() {
return {
username: '',
}
},
methods: {
handleButtonClicked: function () {
console.log(this.username)
},
},
}
</script>
في React عبر تمرير دالة من الأب
import React from 'react'
function Child({ handleButtonClicked }) {
return (
<div>
<button onClick={() => handleButtonClicked()}>Submit</button>
</div>
)
}
export default Child
import { React, useState } from 'react'
import Child from './Child.js'
function Parent() {
const [username, setUsername] = useState('')
const submitForm = () => {
console.log(username)
}
return (
<div>
<input onChange={(e) => setUsername(e.target.value)} type="text" />
<Child handleButtonClicked={submitForm} />
</div>
)
}
export default Parent
مراقبة تغيّر البيانات State Changes
المراقبة في Vue باستخدام watch
<template>
<div>
<input v-model="number1" type="number" name="number 1" />
<input v-model="number2" type="number" name="number 2" />
{{ sum }}
</div>
</template>
<script>
export default {
data() {
return {
number1: 0,
number2: 0,
sum: 0,
}
},
watch: {
number1: function (val) {
this.sum = parseInt(val) + parseInt(this.number2)
},
number2: function (val) {
this.sum = parseInt(this.number1) + parseInt(val)
},
},
}
</script>
المراقبة في React باستخدام useEffect
import { React, useState, useEffect } from 'react'
function ReactToDataChanges() {
const [number1, setNumber1] = useState(0)
const [number2, setNumber2] = useState(0)
const [sum, setSum] = useState(0)
useEffect(() => {
console.log('I am here!')
setSum(parseInt(number1) + parseInt(number2))
}, [number1, number2])
return (
<div>
<input onChange={(e) => setNumber1(e.target.value)} type="number" name="number 1" />
<input onChange={(e) => setNumber2(e.target.value)} type="number" name="number 2" />
{sum}
</div>
)
}
export default ReactToDataChanges
كما يمكن استخدام useEffect لتنفيذ عمليات مثل جلب البيانات من API عند تحميل المكوّن:
useEffect(() => {
fetchUserData()
}, [])
const fetchUserData = async () => {
const url = '';
const response = await axios.get(url);
const user = response.data;
setUser(user);
}
أما في Vue، فيمكن تنفيذ هذا السلوك داخل created أو mounted.
Computed Properties في Vue مقابل useMemo في React
Computed Properties في Vue
تُستخدم computed في Vue لحساب قيم مشتقة مع الاستفادة من التخزين المؤقت، ما يساعد في إبقاء القوالب نظيفة وتجنب إعادة الحساب غير الضرورية.
<template>
<div>
<h3>Yigit's Favorite Cities are:</h3>
<p v-for="city in favCities" :key="city">{{ city }}</p>
<h3>Yigit's Favorite Cities in US are:</h3>
<p v-for="town in favCitiesInUS" :key="town">{{ town }}</p>
<button @click="addBostonToFavCities">Why is Boston not in there? Click to add</button>
</div>
</template>
<script>
export default {
computed: {
favCitiesInUS: function () {
return this.favCities.filter((city) => this.usCities.includes(city))
},
},
data() {
return {
favCities: [
'Istanbul',
'München',
'Los Angeles',
'Rome',
'Florence',
'London',
'San Francisco',
],
usCities: [
'New York',
'Los Angeles',
'Chicago',
'Houston',
'Phoenix',
'Arizona',
'San Francisco',
'Boston',
],
}
},
methods: {
addBostonToFavCities() {
if (this.favCities.includes('Boston')) return
this.favCities.push('Boston')
},
},
}
</script>
useMemo في React
import { React, useMemo, useState } from 'react'
function UseMemoTest() {
const [favCities, setFavCities] = useState([
'Istanbul',
'München',
'Los Angeles',
'Rome',
'Florence',
'London',
'San Francisco',
]),
[usCities, setUsCities] = useState([
'New York',
'Los Angeles',
'Chicago',
'Houston',
'Phoenix',
'Arizona',
'San Francisco',
'Boston',
])
const favCitiesInUs = useMemo(() => {
return favCities.filter((city) => usCities.includes(city))
}, [favCities, usCities])
return (
<div>
<h3>Yigit's Favorite Cities are:</h3>
{favCities.map((city) => (
<p key={city}>{city}</p>
))}
<h3>Yigit's Favorite Cities in US are:</h3>
{favCitiesInUs.map((town) => (
<p key={town}>{town}</p>
))}
<button onClick={() => setFavCities([...favCities, 'Boston'])}>Click me to add</button>
</div>
)
}
export default UseMemoTest
Vue Slots مقابل Render Props في React
استخدام Slots في Vue
تُستخدم Slots في Vue لبناء مكونات مرنة تقبل محتوى من الخارج مع الحفاظ على هيكل داخلي محدد.
<template>
<div>
<h3>Component in slot 1:</h3>
<slot name="slot1"></slot>
<h3>Hello slot1</h3>
<slot name="slot2"></slot>
<h3>Component in slot 3:</h3>
<slot name="slot3"></slot>
</div>
</template>
<script>
export default {}
</script>
<template>
<div>
<Consumer>
<custom-button text="I am a button in slot 1" slot="slot1"></custom-button>
<h1 slot="slot2">I am in slot 2, yayyy</h1>
<custom-button text="I am in slot 3" slot="slot3"></custom-button>
</Consumer>
</div>
</template>
<script>
import CustomButton from './CustomButton.vue'
import Consumer from './Consumer.vue'
export default {
components: {
CustomButton,
Consumer,
},
}
</script>
<template>
<button>{{ text }}</button>
</template>
<script>
export default {
props: {
text: {
type: String,
default: 'I am a button component',
},
},
}
</script>
النهج المقابل في React
في React، يمكن تمرير JSX كمحتوى أو كخاصية بسهولة، وهو ما يجعل هذا النوع من التركيب أكثر مباشرة في كثير من الحالات.
import React from 'react'
import Child from './Child'
function Parent() {
const compToBeRendered = (
<div>
<h1>Hello</h1>
<button>Im button</button>
</div>
)
return (
<div>
<Child compToBeRendered={compToBeRendered}>
</Child>
</div>
)
}
export default Parent
import React from 'react'
function Child({ compToBeRendered }) {
return (
<div>
<h1>In child:</h1>
{compToBeRendered}
</div>
)
}
export default Child
أيّهما أسهل: Vue أم React؟
من منظور عملي، تعتمد الإجابة على الخلفية التقنية وأسلوب الفريق. Vue يقدّم بنية أكثر توجيهاً، مع توجيهات جاهزة مثل v-if وv-for وv-model، ما يساعد في كتابة كود منظم بسرعة. في المقابل، React يمنحك مرونة واسعة لأنه أقرب إلى JavaScript نفسها، لكن هذه المرونة قد تكون سلاحاً ذا حدين إذا غابت المعايير أو الخبرة الكافية.
كما أن مشاريع React غالباً ما تعتمد على مكتبات خارجية متعددة لتغطية جوانب مثل إدارة الحالة، التنسيق، التوجيه، أو بناء النماذج، وهو ما يرفع سقف التعلّم مقارنة ببعض السيناريوهات في Vue.
الخلاصة التقنية
إذا كنت تبحث عن إطار يمنحك نمطاً واضحاً ومباشراً في بناء الواجهات، فغالباً ستجد Vue أكثر راحة في البداية. أما إذا كنت تفضّل المرونة العالية والاقتراب من JavaScript مع منظومة ضخمة من الأدوات، فـReact خيار قوي للغاية. تقنياً، لا يدور الاختيار دائماً حول الأفضل مطلقاً، بل حول الأنسب لسياق المشروع، وخبرة الفريق، وسرعة التطوير المطلوبة. والأهم من ذلك أن فهم الأنماط المشتركة بين الإطارين يختصر كثيراً من وقت الانتقال بينهما ويزيد كفاءتك كمطور Front-End.