Ember.js: #each إعادة عرض الخطأ

تم إنشاؤها على ١٤ سبتمبر ٢٠١٨  ·  8تعليقات  ·  مصدر: emberjs/ember.js

لدي مجموعة من القيم الحقيقية / الخاطئة / غير المحددة التي أعرضها كقائمة من مربعات الاختيار.
عند تغيير عنصر مصفوفة إلى أو من true ، يتم إعادة عرض قائمة مربعات الاختيار مع مربع الاختيار التالي (index + 1) الذي يرث التغيير مع مربع الاختيار الذي تم تغييره.
الشفرة:

{{#each range as |value idx|}}
  <label><input type="checkbox" checked={{value}} {{action makeChange idx on="change"}}>{{idx}}: {{value}}</label><br/>
{{/each}}

عندما أستخدم {{#each range key="@index" as |value idx|}} فإنه يعمل بشكل صحيح.

Twiddle: https://ember-twiddle.com/6d63548f35f99da19cee9f58fb64db59

embereach

Bug Has Reproduction Rendering

التعليق الأكثر فائدة

أعتقد أنني أعرف ما يجري هنا. إنها فوضى ولكن سأحاول وصفها. ساهمت الكثير من حالات الحافة (خطأ مستخدم خط الحدود) في هذا ، ولست متأكدًا حقًا من ما هو / ليس خطأ ، وماذا وكيف يتم إصلاح أي منها.

الرائد 🔑

بادئ ذي بدء ، أحتاج إلى وصف ما تفعله المعلمة key في {{#each}} . TL ؛ DR تحاول تحديد متى وما إذا كان من المنطقي إعادة استخدام DOM الحالي ، مقابل مجرد إنشاء DOM من البداية.

لغرضنا ، دعونا نقبل أن "لمس DOM" (مثل تحديث محتوى عقدة نصية ، أو سمة ، أو إضافة محتوى أو إزالته ، إلخ) أمر مكلف ويجب تجنبه قدر الإمكان.

دعنا نركز على جزء بسيط من النموذج:

<ul>
  {{#each this.names as |name|}}
    <li>{{name.first}} {{to-upper-case name.last}}</li>
  {{/each}}
</ul>

إذا كان this.names ...

[
  { first: "Yehuda", last: "Katz" },
  { first: "Tom", last: "Dale" },
  { first: "Godfrey", last: "Chan" }
]

ثم ستحصل ...

<ul>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
</ul>

حتى الان جيدة جدا.

إلحاق عنصر بالقائمة

الآن ماذا لو قمنا بإلحاق { first: "Andrew", last: "Timberlake" } بالقائمة؟ نتوقع أن ينتج النموذج DOM التالي:

<ul>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
  <li>Andrew TIMBERLAKE</li>
</ul>

ولكن كيف_؟

الطريقة الأكثر سذاجة لتنفيذ المساعد {{#each}} هي مسح كل محتوى القائمة في كل مرة يتغير فيها محتوى القائمة. للقيام بذلك ، ستحتاج إلى إجراء 23 عملية على الأقل:

  • إزالة 3 <li> عقد
  • أدخل 4 <li> عقد
  • أدخل 12 عقدة نصية (واحدة للاسم الأول وواحدة للمسافة بينهما وواحدة لاسم العائلة ، مرات 4 صفوف)
  • استدعاء المساعد to-upper-case 4 مرات

هذا يبدو ... غير ضروري ومكلف للغاية. نحن نعلم أن العناصر الثلاثة الأولى لم تتغير ، لذا سيكون من الرائع تخطي العمل لتلك الصفوف.

🔑index

سيكون التنفيذ الأفضل هو محاولة إعادة استخدام الصفوف الحالية وعدم القيام بأي تحديثات غير ضرورية. تتمثل إحدى الأفكار في مطابقة الصفوف مع مواقعها في القوالب. هذا هو أساسًا ما يفعله key="@index" :

  1. قارن الكائن الأول { first: "Yehuda", last: "Katz" } بالصف الأول ، <li>Yehuda KATZ</li> :
    1.1. "Yehuda" === "Yehuda" ، لا شيء لعمله
    1.2 (لا تحتوي المساحة على بيانات ديناميكية لذلك لا حاجة للمقارنة)
    1.3 "Katz" === "Katz" ، نظرًا لأن المساعدين "خالصون" ، فنحن نعلم أننا لن نضطر إلى إعادة استدعاء المساعد to-upper-case ، وبالتالي فنحن نعرف ناتج ذلك المساعد ("KATZ" ) _ أيضًا_ لم يتغير ، لذلك لا شيء هنا
  2. وبالمثل ، لا شيء يجب القيام به للصفين 2 و 3
  3. لا يوجد صف رابع في DOM ، لذا أدخل صفًا جديدًا
    3.1. أدخل عقدة <li>
    3.2 أدخل عقدة نصية ("أندرو")
    3.3 أدخل عقدة نصية (المسافة)
    3.4. استدعاء المساعد to-upper-case ("Timberlake" -> "TIMBERLAKE")
    3.5 أدخل عقدة نصية ("TIMBERLAKE")

لذلك ، مع هذا التنفيذ ، قمنا بتقليل العدد الإجمالي للعمليات من 23 إلى 5 (التلويح باليد على تكلفة المقارنات ، ولكن لغرضنا ، نفترض أنها رخيصة نسبيًا مقارنة بالباقي). ليس سيئا.

إلحاق عنصر مسبقًا بالقائمة

لكن الآن ، ماذا سيحدث إذا ، بدلاً من _إلحاق_ { first: "Andrew", last: "Timberlake" } إلى القائمة ، قمنا _إعادة_لإضافة _ بدلاً من ذلك؟ نتوقع أن ينتج النموذج DOM التالي:

<ul>
  <li>Andrew TIMBERLAKE</li>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
</ul>

ولكن كيف_؟

  1. قارن الكائن الأول { first: "Andrew", last: "Timberlake" } بالصف الأول ، <li>Yehuda KATZ</li> :
    1.1. "Andrew"! == "Yehuda" ، قم بتحديث العقدة النصية
    1.2 (لا تحتوي المساحة على بيانات ديناميكية لذلك لا حاجة للمقارنة)
    1.3 "Timberlake"! == "Katz" ، أعد استدعاء المساعد to-upper-case
    1.4. تحديث العقدة النصية من "KATZ" إلى "TIMBERLAKE"
  2. قارن العنصر الثاني { first: "Yehuda", last: "Katz" } بالصف الثاني ، <li>Tom DALE</li> ، عملية 3 أخرى
  3. قارن العنصر الثاني { first: "Tom", last: "Dale" } بالصف الثاني ، <li>Godfrey CHAN</li> ، عملية 3 أخرى
  4. لا يوجد صف رابع في DOM ، لذا أدخل صفًا جديدًا
    3.1. أدخل عقدة <li>
    3.2 أدخل عقدة نصية ("Godfrey")
    3.3 أدخل عقدة نصية (المسافة)
    3.4. استدعاء المساعد to-upper-case ("Chan" -> "CHAN")
    3.5 أدخل عقدة نصية ("CHAN")

هذه 14 عملية. أوتش!

🔑 @ هوية

بدا هذا غير ضروري ، لأنه من الناحية المفاهيمية ، سواء كنا نضيف أو نلحق ، ما زلنا نغير (ندخل) كائنًا واحدًا في المصفوفة. على النحو الأمثل ، يجب أن نكون قادرين على التعامل مع هذه الحالة تمامًا كما فعلنا في سيناريو الإلحاق.

هنا يأتي دور key="@identity" . بدلاً من الاعتماد على _order_ للعناصر في المصفوفة ، نستخدم هوية كائن JavaScript الخاصة بهم ( === ):

  1. ابحث عن صف موجود تطابق بياناته ( === ) الكائن الأول { first: "Andrew", last: "Timberlake" } . نظرًا لعدم العثور على شيء ، أدخل (قبل) صفًا جديدًا:
    1.1. أدخل عقدة <li>
    1.2 أدخل عقدة نصية ("أندرو")
    1.3 أدخل عقدة نصية (المسافة)
    1.4. استدعاء المساعد to-upper-case ("Timberlake" -> "TIMBERLAKE")
    1.5 أدخل عقدة نصية ("TIMBERLAKE")
  2. ابحث عن صف موجود تطابق بياناته ( === ) العنصر الثاني { first: "Yehuda", last: "Katz" } . تم العثور على <li>Yehuda KATZ</li> :
    2.1. "Yehuda" === "Yehuda" ، لا شيء لعمله
    2.2. (لا تحتوي المساحة على بيانات ديناميكية لذلك لا حاجة للمقارنة)
    2.3 "Katz" === "Katz" ، نظرًا لأن المساعدين "خالصون" ، فنحن نعلم أننا لن نضطر إلى إعادة استدعاء المساعد to-upper-case ، وبالتالي فإننا نعرف ناتج ذلك المساعد ("KATZ" ) _ أيضًا_ لم يتغير ، لذلك لا شيء هنا
  3. وبالمثل ، لا شيء تفعله لصفوف توم وغودفري
  4. قم بإزالة جميع الصفوف التي تحتوي على كائنات غير متطابقة (لا شيء ، لذا لا شيء تفعله في هذه الحالة)

وبذلك ، نعود إلى العمليات الخمس المثلى.

زيادة

مرة أخرى ، هذا هو التلويح باليد على المقارنات وتكاليف مسك الدفاتر. في الواقع ، هذه ليست مجانية أيضًا ، وفي هذا المثال البسيط للغاية ، قد لا تستحق ذلك. لكن تخيل أن القائمة كبيرة وأن كل صف يستدعي مكونًا معقدًا (مع الكثير من المساعدين والخصائص المحسوبة والمكونات الفرعية ، إلخ). تخيل موجز أخبار LinkedIn ، على سبيل المثال. إذا لم نطابق الصفوف الصحيحة بالبيانات الصحيحة ، فمن المحتمل أن تتسبب وسائط المكونات الخاصة بك في إحداث الكثير من التحديثات مما قد تتوقعه بخلاف ذلك. هناك أيضًا مشكلات في مطابقة عناصر DOM الخاطئة وفقدان حالة DOM ، مثل موضع المؤشر وحالة تحديد النص.

بشكل عام ، فإن تكلفة المقارنة الإضافية وحفظ الكتب تستحق بسهولة معظم الوقت في تطبيق العالم الحقيقي. نظرًا لأن key="@identity" هو الخيار الافتراضي في Ember ويعمل جيدًا لجميع الحالات تقريبًا ، فلن تقلق عادةً بشأن تعيين الوسيطة key عند استخدام {{#each}} .

الاصطدامات 💥

لكن انتظر ، هناك مشكلة. ماذا عن هذه الحالة؟

const YEHUDA = { first: "Yehuda", last: "Katz" };
const TOM = { first: "Tom", last: "Dale" };
const GODFREY = { first: "Godfrey", last: "Chan" };

this.list = [
  YEHUDA,
  TOM,
  GODFREY,
  TOM, // duplicate
  YEHUDA, // duplicate
  YEHUDA, // duplicate
  YEHUDA // duplicate
];

المشكلة هنا هي أن نفس الكائن _could_ يظهر عدة مرات في نفس القائمة. هذا يكسر خوارزمية @identity الساذجة ، وتحديدًا الجزء الذي قلنا فيه "العثور على صف موجود تتطابق بياناته ( === ) ..." - هذا يعمل فقط إذا كانت البيانات بعلاقة DOM هي 1 : 1 ، وهذا ليس صحيحًا في هذه الحالة. قد يبدو هذا غير مرجح في الممارسة العملية ، ولكن كإطار عمل ، علينا التعامل معه.

لتجنب ذلك ، نستخدم نوعًا من النهج الهجين للتعامل مع هذه الاصطدامات. داخليًا ، يبدو تعيين key-to-DOM شيئًا كالتالي:

"YEHUDA" => <li>Yehuda...</li>
"TOM" => <li>Tom...</li>
"GODFREY" => <li>Godfrey...</li>
"TOM-1" => <li>Tom...</li>
"YEHUDA-1" => <li>Yehuda...</li>
"YEHUDA-2" => <li>Yehuda...</li>
"YEHUDA-3" => <li>Yehuda...</li>

بالنسبة للجزء الأكبر ، هذا _ هو نادر جدًا ، وعندما يظهر ، هذا يعمل بشكل جيد بما فيه الكفاية ™ معظم الوقت. إذا لم ينجح ذلك ، لسبب ما ، فيمكنك دائمًا استخدام مسار مفتاح (أو حتى آلية المفاتيح الأكثر تقدمًا في RFC 321 ).

رجوع إلى "🐛"

بعد كل هذا الحديث ، نحن الآن جاهزون لإلقاء نظرة على السيناريو في Twiddle.

بشكل أساسي ، بدأنا بهذه القائمة: [undefined, undefined, undefined, undefined, undefined] .

ملاحظة غير ذات صلة: Array(5) _not_ هو نفس الشيء مثل [undefined, undefined, undefined, undefined, undefined] . إنها تنتج "مجموعة مجسمة" وهو شيء يجب تجنبه بشكل عام. ومع ذلك ، لا علاقة له بهذا الخطأ ، لأنه عند الوصول إلى "الثقوب" تحصل بالفعل على undefined مرة أخرى. لذلك بالنسبة لغرضنا _ الضيق جدًا_ فقط ، فإنهما متماثلان.

نظرًا لأننا لم نحدد المفتاح ، يستخدم Ember @identity افتراضيًا. علاوة على ذلك ، نظرًا لأنها تصادمات ، فقد انتهى بنا الأمر بشيء مثل هذا:

"undefined" => <input ...> 0: ...,
"undefined-1" => <input ...> 1: ...,
"undefined-2" => <input ...> 2: ...,
"undefined-3" => <input ...> 3: ...,
"undefined-4" => <input ...> 4: ...

الآن ، لنفترض أننا نقرنا على خانة الاختيار الأولى:

  1. يقوم بتشغيل السلوك الافتراضي لمربع التحديد: تغيير الحالة المحددة إلى true
  2. يقوم بتشغيل حدث النقر ، الذي تم اعتراضه بواسطة المُعدِّل {{action}} وأعيد إرساله إلى طريقة makeChange
  3. يغير القائمة إلى [true, undefined, undefined, undefined, undefined] .
  4. يقوم بتحديث DOM.

كيف يتم تحديث DOM؟

  1. ابحث عن صف موجود تطابق بياناته ( === ) الكائن الأول true . نظرًا لعدم العثور على أي شيء ، أدخل (قبل) صفًا جديدًا <input checked=true ...>0: true...
  2. ابحث عن صف موجود تطابق بياناته ( === ) العنصر الثاني undefined . تم العثور على <input ...>0: ... (سابقًا الصف الأول):
    2.1. قم بتحديث العقدة النصية {{idx}} إلى 1
    2.2. خلاف ذلك ، بقدر ما يمكن لـ Ember أن يقول ، لم يتغير شيء آخر في هذا الصف ، ولا شيء آخر للقيام به
  3. ابحث عن صف موجود تطابق بياناته ( === ) الكائن الثالث undefined . نظرًا لأن هذه هي المرة الثانية التي نرى فيها undefined ، فإن المفتاح الداخلي هو undefined-1 ، لذلك وجدنا <input ...>1: ... (سابقًا الصف الثاني):
    3.1. قم بتحديث العقدة النصية {{idx}} إلى 2
    3.2 خلاف ذلك ، بقدر ما يمكن لـ Ember أن يقول ، لم يتغير شيء آخر في هذا الصف ، ولا شيء آخر للقيام به
  4. وبالمثل ، قم بتحديث undefined-2 و undefined-3
  5. أخيرًا ، قم بإزالة الصف undefined-4 المتطابق (نظرًا لوجود عدد أقل من undefined في المصفوفة بعد التحديث)

هذا يفسر كيف حصلنا على المخرجات التي لديك في الفرش. بشكل أساسي ، تم نقل جميع صفوف DOM إلى أسفل بمقدار واحد ، وتم إدخال صف جديد في الأعلى ، بينما تم تحديث {{idx}} للباقي.

الجزء غير المتوقع حقًا هو 2.2. على الرغم من أن مربع الاختيار الأول (الذي تم النقر عليه) قد تم نقله لأسفل بمقدار صف واحد إلى الموضع الثاني ، فمن المحتمل أن تتوقع Ember إلى تغيير الخاصية checked إلى true نظرًا لأن قيمته المقيدة غير محددة ، فقد تتوقع من Ember تغييرها مرة أخرى إلى false ، وبالتالي إلغاء تحديدها.

لكن هذا ليس كيف يعمل. كما ذكرنا في البداية ، فإن الوصول إلى DOM مكلف. وهذا يشمل _ reading_ من DOM. إذا اضطررنا ، في كل تحديث ، إلى قراءة أحدث قيمة من DOM لمقارناتنا ، فسيؤدي ذلك إلى حد كبير إلى إلغاء الغرض من التحسينات لدينا. لذلك ، من أجل تجنب ذلك ، تذكرنا القيمة الأخيرة التي كتبناها إلى DOM ، وقارننا القيمة الحالية بالقيمة المخزنة مؤقتًا دون الحاجة إلى قراءتها مرة أخرى من DOM. فقط عندما يكون هناك اختلاف ، نكتب القيمة الجديدة إلى DOM (ونخزنها مؤقتًا في المرة القادمة). هذا هو المعنى الذي نشارك فيه نوعًا ما في نفس أسلوب "DOM الظاهري" ، لكننا نفعل ذلك فقط في العقد الطرفية ، وليس افتراضيًا "شجرة" لعنصر DOM بأكمله.

لذلك ، TL ؛ DR ، "ربط" خاصية checked (أو خاصية value لحقل نص ، إلخ) لا تعمل بالطريقة التي تتوقعها حقًا. تخيل أنك قدمت <div>{{this.name}}</div> وقمت يدويًا بتحديث textContent div للعنصر jQuery أو باستخدام عارض الكروم. لم تكن تتوقع أن يلاحظ Ember ذلك ويقوم بتحديث this.name أجلك. هذا هو الشيء نفسه في الأساس: نظرًا لأن التحديث إلى الخاصية checked حدث خارج Ember (من خلال السلوك الافتراضي للمتصفح لمربع الاختيار) ، فلن يعرف Ember عن ذلك.

هذا هو سبب وجود المساعد {{input}} . يجب أن تسجل مستمعي الأحداث ذات الصلة على عنصر HTML الأساسي وأن تعكس العمليات في تغيير الخاصية المناسب ، بحيث يمكن إخطار الأطراف المعنية (مثل طبقة العرض).

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

ال 8 كومينتر

andrewtimberlake يبدو أن استخدام {{#each range key="@index" as |value idx|}} يعمل على حل المشكلة.

ولكن يبدو وكأنه خطأ ، فالدولار key هو لغرض مختلف ، https://www.emberjs.com/api/ember/release/classes/Ember.Templates.helpers/methods/if؟anchor= كل

أعتقد أنني أعرف ما يجري هنا. إنها فوضى ولكن سأحاول وصفها. ساهمت الكثير من حالات الحافة (خطأ مستخدم خط الحدود) في هذا ، ولست متأكدًا حقًا من ما هو / ليس خطأ ، وماذا وكيف يتم إصلاح أي منها.

الرائد 🔑

بادئ ذي بدء ، أحتاج إلى وصف ما تفعله المعلمة key في {{#each}} . TL ؛ DR تحاول تحديد متى وما إذا كان من المنطقي إعادة استخدام DOM الحالي ، مقابل مجرد إنشاء DOM من البداية.

لغرضنا ، دعونا نقبل أن "لمس DOM" (مثل تحديث محتوى عقدة نصية ، أو سمة ، أو إضافة محتوى أو إزالته ، إلخ) أمر مكلف ويجب تجنبه قدر الإمكان.

دعنا نركز على جزء بسيط من النموذج:

<ul>
  {{#each this.names as |name|}}
    <li>{{name.first}} {{to-upper-case name.last}}</li>
  {{/each}}
</ul>

إذا كان this.names ...

[
  { first: "Yehuda", last: "Katz" },
  { first: "Tom", last: "Dale" },
  { first: "Godfrey", last: "Chan" }
]

ثم ستحصل ...

<ul>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
</ul>

حتى الان جيدة جدا.

إلحاق عنصر بالقائمة

الآن ماذا لو قمنا بإلحاق { first: "Andrew", last: "Timberlake" } بالقائمة؟ نتوقع أن ينتج النموذج DOM التالي:

<ul>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
  <li>Andrew TIMBERLAKE</li>
</ul>

ولكن كيف_؟

الطريقة الأكثر سذاجة لتنفيذ المساعد {{#each}} هي مسح كل محتوى القائمة في كل مرة يتغير فيها محتوى القائمة. للقيام بذلك ، ستحتاج إلى إجراء 23 عملية على الأقل:

  • إزالة 3 <li> عقد
  • أدخل 4 <li> عقد
  • أدخل 12 عقدة نصية (واحدة للاسم الأول وواحدة للمسافة بينهما وواحدة لاسم العائلة ، مرات 4 صفوف)
  • استدعاء المساعد to-upper-case 4 مرات

هذا يبدو ... غير ضروري ومكلف للغاية. نحن نعلم أن العناصر الثلاثة الأولى لم تتغير ، لذا سيكون من الرائع تخطي العمل لتلك الصفوف.

🔑index

سيكون التنفيذ الأفضل هو محاولة إعادة استخدام الصفوف الحالية وعدم القيام بأي تحديثات غير ضرورية. تتمثل إحدى الأفكار في مطابقة الصفوف مع مواقعها في القوالب. هذا هو أساسًا ما يفعله key="@index" :

  1. قارن الكائن الأول { first: "Yehuda", last: "Katz" } بالصف الأول ، <li>Yehuda KATZ</li> :
    1.1. "Yehuda" === "Yehuda" ، لا شيء لعمله
    1.2 (لا تحتوي المساحة على بيانات ديناميكية لذلك لا حاجة للمقارنة)
    1.3 "Katz" === "Katz" ، نظرًا لأن المساعدين "خالصون" ، فنحن نعلم أننا لن نضطر إلى إعادة استدعاء المساعد to-upper-case ، وبالتالي فنحن نعرف ناتج ذلك المساعد ("KATZ" ) _ أيضًا_ لم يتغير ، لذلك لا شيء هنا
  2. وبالمثل ، لا شيء يجب القيام به للصفين 2 و 3
  3. لا يوجد صف رابع في DOM ، لذا أدخل صفًا جديدًا
    3.1. أدخل عقدة <li>
    3.2 أدخل عقدة نصية ("أندرو")
    3.3 أدخل عقدة نصية (المسافة)
    3.4. استدعاء المساعد to-upper-case ("Timberlake" -> "TIMBERLAKE")
    3.5 أدخل عقدة نصية ("TIMBERLAKE")

لذلك ، مع هذا التنفيذ ، قمنا بتقليل العدد الإجمالي للعمليات من 23 إلى 5 (التلويح باليد على تكلفة المقارنات ، ولكن لغرضنا ، نفترض أنها رخيصة نسبيًا مقارنة بالباقي). ليس سيئا.

إلحاق عنصر مسبقًا بالقائمة

لكن الآن ، ماذا سيحدث إذا ، بدلاً من _إلحاق_ { first: "Andrew", last: "Timberlake" } إلى القائمة ، قمنا _إعادة_لإضافة _ بدلاً من ذلك؟ نتوقع أن ينتج النموذج DOM التالي:

<ul>
  <li>Andrew TIMBERLAKE</li>
  <li>Yehuda KATZ</li>
  <li>Tom DALE</li>
  <li>Godfrey CHAN</li>
</ul>

ولكن كيف_؟

  1. قارن الكائن الأول { first: "Andrew", last: "Timberlake" } بالصف الأول ، <li>Yehuda KATZ</li> :
    1.1. "Andrew"! == "Yehuda" ، قم بتحديث العقدة النصية
    1.2 (لا تحتوي المساحة على بيانات ديناميكية لذلك لا حاجة للمقارنة)
    1.3 "Timberlake"! == "Katz" ، أعد استدعاء المساعد to-upper-case
    1.4. تحديث العقدة النصية من "KATZ" إلى "TIMBERLAKE"
  2. قارن العنصر الثاني { first: "Yehuda", last: "Katz" } بالصف الثاني ، <li>Tom DALE</li> ، عملية 3 أخرى
  3. قارن العنصر الثاني { first: "Tom", last: "Dale" } بالصف الثاني ، <li>Godfrey CHAN</li> ، عملية 3 أخرى
  4. لا يوجد صف رابع في DOM ، لذا أدخل صفًا جديدًا
    3.1. أدخل عقدة <li>
    3.2 أدخل عقدة نصية ("Godfrey")
    3.3 أدخل عقدة نصية (المسافة)
    3.4. استدعاء المساعد to-upper-case ("Chan" -> "CHAN")
    3.5 أدخل عقدة نصية ("CHAN")

هذه 14 عملية. أوتش!

🔑 @ هوية

بدا هذا غير ضروري ، لأنه من الناحية المفاهيمية ، سواء كنا نضيف أو نلحق ، ما زلنا نغير (ندخل) كائنًا واحدًا في المصفوفة. على النحو الأمثل ، يجب أن نكون قادرين على التعامل مع هذه الحالة تمامًا كما فعلنا في سيناريو الإلحاق.

هنا يأتي دور key="@identity" . بدلاً من الاعتماد على _order_ للعناصر في المصفوفة ، نستخدم هوية كائن JavaScript الخاصة بهم ( === ):

  1. ابحث عن صف موجود تطابق بياناته ( === ) الكائن الأول { first: "Andrew", last: "Timberlake" } . نظرًا لعدم العثور على شيء ، أدخل (قبل) صفًا جديدًا:
    1.1. أدخل عقدة <li>
    1.2 أدخل عقدة نصية ("أندرو")
    1.3 أدخل عقدة نصية (المسافة)
    1.4. استدعاء المساعد to-upper-case ("Timberlake" -> "TIMBERLAKE")
    1.5 أدخل عقدة نصية ("TIMBERLAKE")
  2. ابحث عن صف موجود تطابق بياناته ( === ) العنصر الثاني { first: "Yehuda", last: "Katz" } . تم العثور على <li>Yehuda KATZ</li> :
    2.1. "Yehuda" === "Yehuda" ، لا شيء لعمله
    2.2. (لا تحتوي المساحة على بيانات ديناميكية لذلك لا حاجة للمقارنة)
    2.3 "Katz" === "Katz" ، نظرًا لأن المساعدين "خالصون" ، فنحن نعلم أننا لن نضطر إلى إعادة استدعاء المساعد to-upper-case ، وبالتالي فإننا نعرف ناتج ذلك المساعد ("KATZ" ) _ أيضًا_ لم يتغير ، لذلك لا شيء هنا
  3. وبالمثل ، لا شيء تفعله لصفوف توم وغودفري
  4. قم بإزالة جميع الصفوف التي تحتوي على كائنات غير متطابقة (لا شيء ، لذا لا شيء تفعله في هذه الحالة)

وبذلك ، نعود إلى العمليات الخمس المثلى.

زيادة

مرة أخرى ، هذا هو التلويح باليد على المقارنات وتكاليف مسك الدفاتر. في الواقع ، هذه ليست مجانية أيضًا ، وفي هذا المثال البسيط للغاية ، قد لا تستحق ذلك. لكن تخيل أن القائمة كبيرة وأن كل صف يستدعي مكونًا معقدًا (مع الكثير من المساعدين والخصائص المحسوبة والمكونات الفرعية ، إلخ). تخيل موجز أخبار LinkedIn ، على سبيل المثال. إذا لم نطابق الصفوف الصحيحة بالبيانات الصحيحة ، فمن المحتمل أن تتسبب وسائط المكونات الخاصة بك في إحداث الكثير من التحديثات مما قد تتوقعه بخلاف ذلك. هناك أيضًا مشكلات في مطابقة عناصر DOM الخاطئة وفقدان حالة DOM ، مثل موضع المؤشر وحالة تحديد النص.

بشكل عام ، فإن تكلفة المقارنة الإضافية وحفظ الكتب تستحق بسهولة معظم الوقت في تطبيق العالم الحقيقي. نظرًا لأن key="@identity" هو الخيار الافتراضي في Ember ويعمل جيدًا لجميع الحالات تقريبًا ، فلن تقلق عادةً بشأن تعيين الوسيطة key عند استخدام {{#each}} .

الاصطدامات 💥

لكن انتظر ، هناك مشكلة. ماذا عن هذه الحالة؟

const YEHUDA = { first: "Yehuda", last: "Katz" };
const TOM = { first: "Tom", last: "Dale" };
const GODFREY = { first: "Godfrey", last: "Chan" };

this.list = [
  YEHUDA,
  TOM,
  GODFREY,
  TOM, // duplicate
  YEHUDA, // duplicate
  YEHUDA, // duplicate
  YEHUDA // duplicate
];

المشكلة هنا هي أن نفس الكائن _could_ يظهر عدة مرات في نفس القائمة. هذا يكسر خوارزمية @identity الساذجة ، وتحديدًا الجزء الذي قلنا فيه "العثور على صف موجود تتطابق بياناته ( === ) ..." - هذا يعمل فقط إذا كانت البيانات بعلاقة DOM هي 1 : 1 ، وهذا ليس صحيحًا في هذه الحالة. قد يبدو هذا غير مرجح في الممارسة العملية ، ولكن كإطار عمل ، علينا التعامل معه.

لتجنب ذلك ، نستخدم نوعًا من النهج الهجين للتعامل مع هذه الاصطدامات. داخليًا ، يبدو تعيين key-to-DOM شيئًا كالتالي:

"YEHUDA" => <li>Yehuda...</li>
"TOM" => <li>Tom...</li>
"GODFREY" => <li>Godfrey...</li>
"TOM-1" => <li>Tom...</li>
"YEHUDA-1" => <li>Yehuda...</li>
"YEHUDA-2" => <li>Yehuda...</li>
"YEHUDA-3" => <li>Yehuda...</li>

بالنسبة للجزء الأكبر ، هذا _ هو نادر جدًا ، وعندما يظهر ، هذا يعمل بشكل جيد بما فيه الكفاية ™ معظم الوقت. إذا لم ينجح ذلك ، لسبب ما ، فيمكنك دائمًا استخدام مسار مفتاح (أو حتى آلية المفاتيح الأكثر تقدمًا في RFC 321 ).

رجوع إلى "🐛"

بعد كل هذا الحديث ، نحن الآن جاهزون لإلقاء نظرة على السيناريو في Twiddle.

بشكل أساسي ، بدأنا بهذه القائمة: [undefined, undefined, undefined, undefined, undefined] .

ملاحظة غير ذات صلة: Array(5) _not_ هو نفس الشيء مثل [undefined, undefined, undefined, undefined, undefined] . إنها تنتج "مجموعة مجسمة" وهو شيء يجب تجنبه بشكل عام. ومع ذلك ، لا علاقة له بهذا الخطأ ، لأنه عند الوصول إلى "الثقوب" تحصل بالفعل على undefined مرة أخرى. لذلك بالنسبة لغرضنا _ الضيق جدًا_ فقط ، فإنهما متماثلان.

نظرًا لأننا لم نحدد المفتاح ، يستخدم Ember @identity افتراضيًا. علاوة على ذلك ، نظرًا لأنها تصادمات ، فقد انتهى بنا الأمر بشيء مثل هذا:

"undefined" => <input ...> 0: ...,
"undefined-1" => <input ...> 1: ...,
"undefined-2" => <input ...> 2: ...,
"undefined-3" => <input ...> 3: ...,
"undefined-4" => <input ...> 4: ...

الآن ، لنفترض أننا نقرنا على خانة الاختيار الأولى:

  1. يقوم بتشغيل السلوك الافتراضي لمربع التحديد: تغيير الحالة المحددة إلى true
  2. يقوم بتشغيل حدث النقر ، الذي تم اعتراضه بواسطة المُعدِّل {{action}} وأعيد إرساله إلى طريقة makeChange
  3. يغير القائمة إلى [true, undefined, undefined, undefined, undefined] .
  4. يقوم بتحديث DOM.

كيف يتم تحديث DOM؟

  1. ابحث عن صف موجود تطابق بياناته ( === ) الكائن الأول true . نظرًا لعدم العثور على أي شيء ، أدخل (قبل) صفًا جديدًا <input checked=true ...>0: true...
  2. ابحث عن صف موجود تطابق بياناته ( === ) العنصر الثاني undefined . تم العثور على <input ...>0: ... (سابقًا الصف الأول):
    2.1. قم بتحديث العقدة النصية {{idx}} إلى 1
    2.2. خلاف ذلك ، بقدر ما يمكن لـ Ember أن يقول ، لم يتغير شيء آخر في هذا الصف ، ولا شيء آخر للقيام به
  3. ابحث عن صف موجود تطابق بياناته ( === ) الكائن الثالث undefined . نظرًا لأن هذه هي المرة الثانية التي نرى فيها undefined ، فإن المفتاح الداخلي هو undefined-1 ، لذلك وجدنا <input ...>1: ... (سابقًا الصف الثاني):
    3.1. قم بتحديث العقدة النصية {{idx}} إلى 2
    3.2 خلاف ذلك ، بقدر ما يمكن لـ Ember أن يقول ، لم يتغير شيء آخر في هذا الصف ، ولا شيء آخر للقيام به
  4. وبالمثل ، قم بتحديث undefined-2 و undefined-3
  5. أخيرًا ، قم بإزالة الصف undefined-4 المتطابق (نظرًا لوجود عدد أقل من undefined في المصفوفة بعد التحديث)

هذا يفسر كيف حصلنا على المخرجات التي لديك في الفرش. بشكل أساسي ، تم نقل جميع صفوف DOM إلى أسفل بمقدار واحد ، وتم إدخال صف جديد في الأعلى ، بينما تم تحديث {{idx}} للباقي.

الجزء غير المتوقع حقًا هو 2.2. على الرغم من أن مربع الاختيار الأول (الذي تم النقر عليه) قد تم نقله لأسفل بمقدار صف واحد إلى الموضع الثاني ، فمن المحتمل أن تتوقع Ember إلى تغيير الخاصية checked إلى true نظرًا لأن قيمته المقيدة غير محددة ، فقد تتوقع من Ember تغييرها مرة أخرى إلى false ، وبالتالي إلغاء تحديدها.

لكن هذا ليس كيف يعمل. كما ذكرنا في البداية ، فإن الوصول إلى DOM مكلف. وهذا يشمل _ reading_ من DOM. إذا اضطررنا ، في كل تحديث ، إلى قراءة أحدث قيمة من DOM لمقارناتنا ، فسيؤدي ذلك إلى حد كبير إلى إلغاء الغرض من التحسينات لدينا. لذلك ، من أجل تجنب ذلك ، تذكرنا القيمة الأخيرة التي كتبناها إلى DOM ، وقارننا القيمة الحالية بالقيمة المخزنة مؤقتًا دون الحاجة إلى قراءتها مرة أخرى من DOM. فقط عندما يكون هناك اختلاف ، نكتب القيمة الجديدة إلى DOM (ونخزنها مؤقتًا في المرة القادمة). هذا هو المعنى الذي نشارك فيه نوعًا ما في نفس أسلوب "DOM الظاهري" ، لكننا نفعل ذلك فقط في العقد الطرفية ، وليس افتراضيًا "شجرة" لعنصر DOM بأكمله.

لذلك ، TL ؛ DR ، "ربط" خاصية checked (أو خاصية value لحقل نص ، إلخ) لا تعمل بالطريقة التي تتوقعها حقًا. تخيل أنك قدمت <div>{{this.name}}</div> وقمت يدويًا بتحديث textContent div للعنصر jQuery أو باستخدام عارض الكروم. لم تكن تتوقع أن يلاحظ Ember ذلك ويقوم بتحديث this.name أجلك. هذا هو الشيء نفسه في الأساس: نظرًا لأن التحديث إلى الخاصية checked حدث خارج Ember (من خلال السلوك الافتراضي للمتصفح لمربع الاختيار) ، فلن يعرف Ember عن ذلك.

هذا هو سبب وجود المساعد {{input}} . يجب أن تسجل مستمعي الأحداث ذات الصلة على عنصر HTML الأساسي وأن تعكس العمليات في تغيير الخاصية المناسب ، بحيث يمكن إخطار الأطراف المعنية (مثل طبقة العرض).

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

chancancode - شكرًا على الشرح المذهل. هل هذا يعني أنه لا يجب استخدام <input ... > مطلقًا ولكن استخدام {{input ...}} فقط لمنع كل الأخطاء من هذا القبيل؟

@ boris-petrov قد تكون هناك بعض الحالات المحدودة حيث يكون مقبولاً .. مثل حقل نص للقراءة فقط لنوع من "نسخ عنوان url هذا إلى الحافظة الخاصة بك" ، أو يمكنك _يمكنك استخدام عنصر الإدخال + {{action}} لاعتراض حدث DOM ويعكس تحديثات الخاصية يدويًا (وهو ما حاول Twiddle القيام به ، باستثناء أنه واجه أيضًا تصادم @identity ) ، لكن نعم في مرحلة ما ، أنت فقط تعيد تنفيذ {{input}} والتعامل مع جميع حالات الحافة التي تعاملت معها بالفعل. لذلك أعتقد أنه من العدل أن تقول إنه يجب عليك فقط استخدام {{input}} معظم الأوقات ، إن لم يكن كلها.

ومع ذلك ، لا يزال هذا غير "لإصلاح" هذه الحالة حيث توجد اصطدامات بالمفاتيح. راجع https://ember-twiddle.com/0f2369021128e2ae0c445155df5bb034؟openFiles=templates.application.hbs٪2C

ولهذا قلت ، لست متأكدًا بنسبة 100٪ مما يجب فعله حيال ذلك. من ناحية ، أوافق على أنه مفاجئ وغير متوقع ، ومن ناحية أخرى ، فإن هذا النوع من الاصطدام نادر جدًا في التطبيقات الحقيقية ولهذا السبب يمكن تخصيص وسيطة "المفتاح" (هذه هي الحالة التي يكون فيها مفتاح "identity" الافتراضي الوظيفة ليست جيدة بما فيه الكفاية ™ ، وهذا هو سبب وجود هذه الميزة).

chancancode - هذا يذكرني فتحتها منذ بعض الوقت . هل تعتقد أن هناك شيئًا مشابهًا هناك؟ لا تزال الإجابة التي تلقيتها هناك (حول الحاجة إلى استخدام replace بدلاً من set عند تعيين عناصر المصفوفة) غريبة بالنسبة لي.

@ boris-petrov لا أعتقد أنه مرتبط بذلك

مرحبًا ، نستخدم sortablejs لقائمة قابلة للسحب مع ember. يرجى التحقق من هذا العرض التوضيحي لإعادة إنتاج كل مشكلة.

خطوة:

  • اسحب أي عنصر لآخر
  • التبديل حدد إلى "v2"

يمكنك أن ترى العنصر المسحوب يبقى في شجرة دوم.

ولكن ، إذا تم سحب العنصر إلى موضع آخر (ليس العنصر الأخير) ، فيبدو أنه يعمل بشكل جيد.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات