Skip to content

دوال التصيير و JSX

نوصي باستخدام القوالب لبناء التطبيقات في معظم الحالات. ومع ذلك ، هناك حالات نحتاج فيها إلى القوة البرمجية الكاملة لـ JavaScript. هنا يمكننا استخدام دوال التصيير.

إذا كنت جديدًا على مفهوم الـDOM الافتراضي ودوال التصيير ، فتأكد من قراءة الفصل آلية التصيير أولاً.

استخدام أساسي

إنشاء عقد افتراضية

توفر Vue دالة ()h لإنشاء عقد افتراضية:

js
import { h } from 'vue'

const vnode = h(
  'div', // نوع العنصر
  { id: 'foo', class: 'bar' }, // خاصيات
  [
    /* العقد الأبناء */
  ]
)

()h هي اختصار لـ hyperscript - والذي يعني "JavaScript الذي ينتج HTML (لغة ترميز النص الفائق)". هذا الاسم موروث من الاصطلاحات المشتركة بين العديد من تنفيذات DOM الافتراضية. يمكن أن يكون الاسم الأكثر وصفًا هو ()createVnode ، ولكن الاسم الأقصر يساعد عندما تضطر إلى استدعاء هذه الدالة عدة مرات في دالة التصيير.

دالة ()h مصممة لتكون مرنة للغاية:

js
// كل الوسائط ما عدا النوع اختيارية
h('div')
h('div', { id: 'foo' })

//كل من الخاصيات الأصلية والسمات يمكن استخدامها في خاصيات Vue
// يقوم Vue تلقائيًا باختيار الطريقة الصحيحة لتعيينها
h('div', { class: 'bar', innerHTML: 'السلام عليكم' })

// يمكن إضافة معدلات الخاصيات مثل `.prop` و `.attr`
// بالبادئة `.` و `^` على التوالي  
h('div', { '.name': 'some-name', '^width': '100' })

// class و style لديهم نفس الدعم لقيمة الكائن / المصفوفة 
// التي لديهم في القوالب
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// يجب تمرير مستمعي الأحداث كـ onXxx
h('div', { onClick: () => {} })

// العقد الأبناء يمكن أن يكون سلسلة نصية
h('div', { id: 'foo' }, 'السلام عليكم')

// يمكن حذف الخاصيات عندما لا توجد خاصيات
h('div', 'السلام عليكم')
h('div', [h('span', 'السلام عليكم')])

// يمكن أن تحتوي مصفوفة العقد الأبناء على عقد افتراضي وسلاسل نصية
h('div', ['السلام عليكم', h('span', 'السلام عليكم')])

العقدة الافتراضية الناتجة لها الشكل التالي:

js
const vnode = h('div', { id: 'foo' }, [])

vnode.type // 'div'
vnode.props // { id: 'foo' }
vnode.children // []
vnode.key // null

ملاحظة

تحتوي واجهة VNode الكاملة على العديد من الخاصيات الداخلية الأخرى ، ولكن من المستحسن بشدة تجنب الاعتماد على أي خاصيات غير تلك المدرجة هنا. هذا يتجنب الكسر غير المقصود في حال تغيرت الخاصيات الداخلية.

التصريح بدوال التصيير #declaring-render-functions}

عند استخدام القوالب مع الواجهة التركيبية ، تستخدم قيمة إرجاع خطاف ()setup لعرض البيانات على القالب. عند استخدام دوال التصيير ، يمكننا إرجاع الدالة المصيّرة مباشرةً بدلاً من ذلك:

js
import { ref, h } from 'vue'

export default {
  props: {
    /* ... */
  },
  setup(props) {
    const count = ref(1)

    // إرجاع دالة التصيير
    return () => h('div', props.msg + count.value)
  }
}

دالة التصيير معلنة داخل ()setup لذلك لديها بشكل طبيعي الوصول إلى الخاصيات وأي حالة تفاعلية صرح بها في نفس النطاق.

بالإضافة إلى إرجاع عقدة واحدة ، يمكنك أيضًا إرجاع سلاسل نصية أو مصفوفات:

js
export default {
  setup() {
    return () => 'السلام عليكم!'
  }
}
js
import { h } from 'vue'

export default {
  setup() {
    // استخدام مصفوفة لإرجاع عقدة أبناء متعددة
    return () => [
      h('div'),
      h('div'),
      h('div')
    ]
  }
}

ملاحظة

تأكد من إرجاع دالة بدلاً من إرجاع القيم مباشرةً! تستدعى دالة ()setup مرة واحدة فقط لكل مكون ، بينما سستدعى دالة التصيير المرجعة عدة مرات.

يمكننا التصريح بدوال التصيير باستخدام خيار render:

js
import { h } from 'vue'

export default {
  data() {
    return {
      msg: 'السلام عليكم'
    }
  },
  render() {
    return h('div', this.msg)
  }
}

دالة ()render لديها الوصول إلى نسخة المكون عبر this.

بالإضافة إلى إرجاع عقدة واحدة ، يمكنك أيضًا إرجاع سلاسل نصية أو مصفوفات:

js
export default {
  render() {
    return 'السلام عليكم!'
  }
}
js
import { h } from 'vue'

export default {
  render() {
    // استخدام مصفوفة لإرجاع عقدة أبناء متعددة
    return [
      h('div'),
      h('div'),
      h('div')
    ]
  }
}

إذا لم تكن تحتاج دالة التصيير إلى أي حالة من نسخة المكون ، فيمكن أيضًا إعلانها مباشرةً كدالة لغرض الإيجاز:

js
function السلام عليكم() {
    return 'السلام عليكم!'
}

هذا صحيح، هذا مكون Vue صالح! اطلع على فصل المكونات الوظيفية لمزيد من التفاصيل حول هذه الصيغة.

العقد الافتراضية لابد أن تكون فريدة

جميع العقد في شجرة المكون يجب أن تكون فريدة. وهذا يعني أن دالة التصيير التالية غير صالحة:

js
function render() {
  const p = h('p', 'hi')
  return h('div', [ 
    // 😬 - عقدة مكررة!
    p,
    p
  ])
}

إذا كنت تريد حقًا تكرار نفس العنصر / المكون عدة مرات ، فيمكنك القيام بذلك باستخدام دالة منتِجة. على سبيل المثال ، فإن دالة التصيير التالية هي طريقة صالحة تمامًا لتصيير 20 فقرة متطابقة:

js
function render() {
  return h(
    'div',
    Array.from({ length: 20 }).map(() => {
      return h('p', 'hi')
    })
  )
}

JSX / TSX

JSX هو امتداد شبيه بـ XML لـ JavaScript يسمح لنا بكتابة شيفرة مثل هذه:

jsx
const vnode = <div>السلام عليكم</div>

داخل تعبيرات JSX ، استخدم الأقواس المنحنية لتضمين القيم الديناميكية:

jsx
const vnode = <div id={dynamicId}>السلام عليكم, {userName}</div>

create-vue و Vue CLI لديهما خيارات لاطلاق المشاريع بصيغة قاعدية مع دعم JSX معد مسبقًا. إذا كنت تقوم بتهيئة JSX يدويًا ، فيرجى الرجوع إلى توثيق vue/babel-plugin-jsx@ للحصول على التفاصيل.

على الرغم من أن React قدمته لأول مرة ، إلا أن JSX ليس لديها دلالات وقت التشغيل محددة ويمكن تصريفها إلى عديد من المخرجات المختلفة. إذا كنت قد عملت مع JSX من قبل ، فلاحظ أن تصريف JSX في Vue مختلف عن تصريف JSX في React ، لذلك لا يمكنك استخدام تحويل JSX في React في تطبيقات Vue. بعض الاختلافات الملحوظة عن JSX في React تشمل:

  • يمكنك استخدام سمات HTML مثل class و for كخاصيات - لا حاجة لاستخدام className أو htmlFor.
  • تمرير العناصر الأبناء إلى المكونات (أي المنافذ) يعمل بشكل مختلف.

توفر تعريفات النوع في Vue أيضًا استنباط النوع لاستخدام TSX. عند استخدام TSX ، تأكد من تحديد "jsx": "preserve" في tsconfig.json حتى يترك TypeScript بناء صيغة JSX سليمًا لتصريف JSX في Vue من أجل معالجته.

استنباط الأنواع في JSX

بشكل مماثل للتحويل ، تحتاج JSX في Vue أيضًا إلى تعريفات أنواع مختلفة. حاليًا ، تقوم أنواع Vue بتسجيل أنواع Vue JSX تلقائيًا على المستوى العام. هذا يعني أن TSX سيعمل من دون أي تكوين عندما تكون أنواع Vue متاحة.

أنواع JSX العامة قد تتسبب في تعارض عند استخدامها مع مكتبات أخرى تحتاج أيضًا إلى استنباط أنواع JSX ، ولا سيما React. ابتداءً من 3.3 ، تدعم Vue تحديد مساحة الأسماء JSX عبر خيار jsxImportSource في TypeScript. نعتزم إزالة تسجيل مساحة الأسماء JSX العامة الافتراضية في 3.4.

لمستخدمي TSX ، من المقترح تعيين jsxImportSource إلى 'vue' في tsconfig.json بعد الترقية إلى 3.3 ، أو الاختيار في كل ملف مع /* jsxImportSource vue@ */. سيتيح لك هذا الاختيار في السلوك الجديد الآن والترقية بسهولة عند إصدار 3.4.

إذا كانت هناك شيفرة تعتمد على وجود مساحة الأسماء العامة JSX ، فيمكنك الاحتفاظ بالسلوك العام الدقيق قبل 3.4 عن طريق الإشارة إلى vue/jsx بشكل صريح ، والذي يسجل مساحة الأسماء العامة JSX.

وصفات لاستخدام دالة التصيير

أدناه سنقدم بعض الوصفات الشائعة لتنفيذ ميزات القالب بما يقابلها من دوال تصيير / JSX.

v-if

القالب:

template
<div>
  <div v-if="ok">نعم</div>
  <span v-else>لا</span>
</div>

المقابل باستخدام دالة التصيير / JSX:

js
h('div', [ok.value ? h('div', 'نعم') : h('span', 'لا')])
jsx
<div>{ok.value ? <div>نعم</div> : <span>لا</span>}</div>
js
h('div', [this.ok ? h('div', 'نعم') : h('span', 'لا')])
jsx
<div>{this.ok ? <div>نعم</div> : <span>لا</span>}</div>

v-for

القالب:

template
<ul>
  <li v-for="{ id, text } in items" :key="id">
    {{ text }}
  </li>
</ul>

المقابل باستخدام دالة التصيير / JSX:

js
h(
  'ul',
  // مع الافتراض أن `items` هو مرجع بقيمة مصفوفة
  items.value.map(({ id, text }) => {
    return h('li', { key: id }, text)
  })
)
jsx
<ul>
  {items.value.map(({ id, text }) => {
    return <li key={id}>{text}</li>
  })}
</ul>
js
h(
  'ul',
  this.items.map(({ id, text }) => {
    return h('li', { key: id }, text)
  })
)
jsx
<ul>
  {this.items.map(({ id, text }) => {
    return <li key={id}>{text}</li>
  })}
</ul>

v-on

الخاصيات التي تبدأ بـ on تليها حرف كبير تعامل على أنها مستمعات للأحداث. على سبيل المثال ، onClick هو المقابل لـ click@ في القوالب.

js
h(
  'button',
  {
    onClick(event) {
      /* ... */
    }
  },
  'انقر على الزر'
)
jsx
<button
  onClick={(event) => {
    /* ... */
  }}
>
  انقر على الزر
</button>

معدلات الأحداث

لمعدلات الأحداث passive. و capture. و once. ، يمكن دمجها بعد اسم الحدث باستخدام صيغة سنام الجمل camelCase.

على سبيل المثال:

js
h('input', {
  onClickCapture() {
    /* مستمع في وضع الالتقاط */
  },
  onKeyupOnce() {
    /* يشغل مرة واحدة فقط */
  },
  onMouseoverOnceCapture() {
    /* مرة واحدة + التقاط */
  }
})
jsx
<input
  onClickCapture={() => {}}
  onKeyupOnce={() => {}}
  onMouseoverOnceCapture={() => {}}
/>

بالنسبة لمعدلات الأحداث والمفاتيح الأخرى ، يمكن استخدام الدالة المساعدة withModifiers:

js
import { withModifiers } from 'vue'

h('div', {
  onClick: withModifiers(() => {}, ['self'])
})
jsx
<div onClick={withModifiers(() => {}, ['self'])} />

المكونات

لإنشاء عقدة افتراضية لمكون ، يجب أن يكون أول وسيط يُمرر إلى ()h هو تعريف المكون. هذا يعني عند استخدام دوال التصيير ، فمن غير الضروري تسجيل المكونات - يمكنك استخدام المكونات المستوردة مباشرة:

js
import Foo from './Foo.vue'
import Bar from './Bar.jsx'

function render() {
  return h('div', [h(Foo), h(Bar)])
}
jsx
function render() {
  return (
    <div>
      <Foo />
      <Bar />
    </div>
  )
}

كما نرى ، يمكن لـ h العمل مع المكونات المستوردة من أي تنسيق ملف طالما أنها مكون Vue صالح.

المكونات الديناميكية بسيطة الاستخدام مع دوال التصيير:

js
import Foo from './Foo.vue'
import Bar from './Bar.jsx'

function render() {
  return ok.value ? h(Foo) : h(Bar)
}
jsx
function render() {
  return ok.value ? <Foo /> : <Bar />
}

إذا سُجل مكون بالاسم ولا يمكن استيراده مباشرة (على سبيل المثال ، سُجل على المستوى العام من قبل مكتبة ما) ، فيمكن حله بشكل برمجي باستخدام الدالة المساعدة ()resolveComponent.

تصيير المنافذ

في دوال التصيير ، يمكن الوصول إلى المنافذ من سياق ()setup. كل منفذ من كائن slots هو دالة تعيد مصفوفة من العقد الافتراضية:

js
export default {
  props: ['message'],
  setup(props, { slots }) {
    return () => [
      // منفذ افتراضي:
      // <div><slot /></div>
      h('div', slots.default()),

      // منفذ مسمى:
      // <div><slot name="footer" :text="message" /></div>
      h(
        'div',
        slots.footer({
          text: props.message
        })
      )
    ]
  }
}

المقابل باستخدام JSX:

jsx
// افتراضي
<div>{slots.default()}</div>

// مسمى
<div>{slots.footer({ text: props.message })}</div>

في دوال التصيير ، يمكن الوصول إلى المنافذ من خلال this.$slots:

js
export default {
  props: ['message'],
  render() {
    return [
      // <div><slot /></div>
      h('div', this.$slots.default()),

      // <div><slot name="footer" :text="message" /></div>
      h(
        'div',
        this.$slots.footer({
          text: this.message
        })
      )
    ]
  }
}

المقابل باستخدام JSX:

jsx
// <div><slot /></div>
<div>{this.$slots.default()}</div>

// <div><slot name="footer" :text="message" /></div>
<div>{this.$slots.footer({ text: this.message })}</div>

تمرير المنافذ

تمرير المكونات الأبناء إلى المكونات الآباء يعمل بشكل مختلف قليلاً عن تمرير المكونات الأبناء إلى العناصر. بدلاً من مصفوفة ، نحتاج إلى تمرير دالة منفذ، أو كائن من دوال المنافذ. يمكن لدوال المنافذ إرجاع أي شيء يمكن لدالة التصيير العادية إرجاعه - والذي سيُطبّع دائمًا إلى مصفوفات من العقد الافتراضية عند الوصول إليه في المكون الإبن.

js
// منفذ افتراضي واحد
h(MyComponent, () => 'السلام عليكم')

// منافذ مسماة
// لاحظ أن `null` مطلوب لتجنب
// معاملة كائن المنافذ على أنه خاصية
h(MyComponent, null, {
  default: () => 'default slot',
  foo: () => h('div', 'foo'),
  bar: () => [h('span', 'one'), h('span', 'two')]
})

المقابل باستخدام JSX:

jsx
// default
<MyComponent>{() => 'السلام عليكم'}</MyComponent>

// named
<MyComponent>{{
  default: () => 'default slot',
  foo: () => <div>foo</div>,
  bar: () => [<span>one</span>, <span>two</span>]
}}</MyComponent>

تمرير المنافذ كدوال يسمح لها بأن تُستدعى بشكل خامل من قبل المكون الإبن. هذا يؤدي إلى تتبع اعتمادات المنفذ من قبل المكون الإبن بدلاً من المكون الأب، مما يؤدي إلى تحديثات أكثر دقة وكفاءة.

المكونات المدمجة

يجب استيراد المكونات المدمجة مثل <KeepAlive>و <Transition>و <TransitionGroup>و <Teleport> و <Suspense> للاستخدام في دوال التصيير:

js
import { h, KeepAlive, Teleport, Transition, TransitionGroup } from 'vue'

export default {
  setup () {
    return () => h(Transition, { mode: 'out-in' }, /* ... */)
  }
}
js
import { h, KeepAlive, Teleport, Transition, TransitionGroup } from 'vue'

export default {
  render () {
    return h(Transition, { mode: 'out-in' }, /* ... */)
  }
}

v-model

وسعت السمة الموجهة v-model إلى خاصيات modelValue و onUpdate:modelValue أثناء تصريف القالب - سنضطر إلى توفير هذه الخصائص بأنفسنا:

js
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    return () =>
      h(SomeComponent, {
        modelValue: props.modelValue,
        'onUpdate:modelValue': (value) => emit('update:modelValue', value)
      })
  }
}
js
export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  render() {
    return h(SomeComponent, {
      modelValue: this.modelValue,
      'onUpdate:modelValue': (value) => this.$emit('update:modelValue', value)
    })
  }
}

السمات الموجهة المخصصة

يمكن تطبيق السمات الموجهة المخصصة على العقد الافتراضية باستخدام الدالة المساعدة withDirectives:

js
import { h, withDirectives } from 'vue'

// سمة موجهة مخصصة  
const pin = {
  mounted() { /* ... */ },
  updated() { /* ... */ }
}

// <div v-pin:top.animate="200"></div>
const vnode = withDirectives(h('div'), [
  [pin, 200, 'top', { animate: true }]
])

إذا سجلت السمة الموجهة بالاسم ولا يمكن استيرادها مباشرة ، فيمكن حلها باستخدام الدالة المساعدة resolveDirective.

مراجع القالب

مع الواجهة التركيبية ، تنشأ مراجع القالب عن طريق تمرير ()ref نفسه كخاصية إلى العقدة الافتراضية:

js
import { h, ref } from 'vue'

export default {
  setup() {
    const divEl = ref()

    // <div ref="divEl">
    return () => h('div', { ref: divEl })
  }
}

مع واجهة الخيارات ، تنشأ مراجع القالب عن طريق تمرير اسم المرجع كسلسلة في خاصيات العقدة الافتراضية:

js
export default {
  render() {
    // <div ref="divEl">
    return h('div', { ref: 'divEl' })
  }
}

الدوال الوظيفية

المكونات الوظيفية هي شكل بديل من المكونات لا تحتوي على أي حالة خاصة بها. إنها تعمل مثل الدوال النقية: استقبال الخاصيات، وارجاع العقد الافتراضية. تُصير دون إنشاء نسخة مكون (أي لا يوجد this)، وبدون خطافات دورة حياة المكون العادية.

لإنشاء مكون وظيفي نستخدم دالة عادية، بدلاً من كائن خيارات. الدالة هي عملياً دالة render للمكون.

بصمة المكون الوظيفي هي نفسها بصمة خطاف ()setup:

js
function MyComponent(props, { slots, emit, attrs }) {
  // ...
}

بما أنه لا يوجد مرجع this للمكون الوظيفي، ستمرر Vue الخاصيات كشكل وسيط أول:

js
function MyComponent(props, context) {
  // ...
}

الوسيط الثاني، context، يحتوي على ثلاث خاصيات: attrs، emit، و slots. هذه مكافئة لخاصيات النسخة attrs$و emit$ و slots$ على التوالي.

معظم خيارات التهيئة العادية للمكونات غير متوفرة للمكونات الوظيفية. ومع ذلك، من الممكن تعريف props و emits عن طريق إضافتهم كخاصيات:

js
MyComponent.props = ['value']
MyComponent.emits = ['click']

إذا لم يُحدَّد خيار props، فإن كائن props الممرر للدالة سيحتوي على جميع السمات، بشكل مشابه لـ attrs. لن تطبع أسماء الخاصيات إلى نمط سنام الجمل camelCase إلا إذا حُدد خيار props.

بالنسبة للمكونات الوظيفية مع props صريحة، السمات المستترة تعمل بنفس الطريقة مع المكونات العادية. ومع ذلك، بالنسبة للمكونات الوظيفية التي لا تحدد props بشكل صريح، فإن السمات class، style، ومستمعات الحدث onXxx فقط ستورث من attrs بشكل افتراضي. في كلا الحالتين، يمكن تعيين inheritAttrs إلى false لتعطيل توريث السمات:

js
MyComponent.inheritAttrs = false

يمكن تسجيل المكونات الوظيفية واستهلاكها تماماً مثل المكونات العادية. إذا قمت بتمرير دالة كوسيط أول لـ ()h، فسيتعامل معها كمكون وظيفي.

إضافة النوع إلى المكونات الوظيفية

المكونات الوظيفية يمكن أن تضاف لها الأنواع بناءً على ما إذا كانت مسماة أو مجهولة الاسم. يدعم Volar أيضًا التحقق من النوع للمكونات الوظيفية المكتوبة بشكل صحيح عند استهلاكها في قوالب SFC.

المكونات الوظيفية المسماة

tsx
import type { SetupContext } from 'vue'
type FComponentProps = {
  message: string
}

type Events = {
  sendMessage(message: string): void
}

function FComponent(
  props: FComponentProps,
  context: SetupContext<Events>
) {
  return (
    <button onClick={() => context.emit('sendMessage', props.message)}>
        {props.message} {' '}
    </button>
  )
}

FComponent.props = {
  message: {
    type: String,
    required: true
  }
}

FComponent.emits = {
  sendMessage: (value: unknown) => typeof value === 'string'
}

المكونات الوظيفية مجهولة الإسم

tsx
import type { FunctionalComponent } from 'vue'

type FComponentProps = {
  message: string
}

type Events = {
  sendMessage(message: string): void
}

const FComponent: FunctionalComponent<FComponentProps, Events> = (
  props,
  context
) => {
  return (
    <button onClick={() => context.emit('sendMessage', props.message)}>
        {props.message} {' '}
    </button>
  )
}

FComponent.props = {
  message: {
    type: String,
    required: true
  }
}

FComponent.emits = {
  sendMessage: (value) => typeof value === 'string'
}
دوال التصيير و JSX has loaded