لدي مجموعة من القيم الحقيقية / الخاطئة / غير المحددة التي أعرضها كقائمة من مربعات الاختيار.
عند تغيير عنصر مصفوفة إلى أو من 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
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 عملية على الأقل:
<li>
عقد<li>
عقدto-upper-case
4 مراتهذا يبدو ... غير ضروري ومكلف للغاية. نحن نعلم أن العناصر الثلاثة الأولى لم تتغير ، لذا سيكون من الرائع تخطي العمل لتلك الصفوف.
سيكون التنفيذ الأفضل هو محاولة إعادة استخدام الصفوف الحالية وعدم القيام بأي تحديثات غير ضرورية. تتمثل إحدى الأفكار في مطابقة الصفوف مع مواقعها في القوالب. هذا هو أساسًا ما يفعله key="@index"
:
{ first: "Yehuda", last: "Katz" }
بالصف الأول ، <li>Yehuda KATZ</li>
:to-upper-case
، وبالتالي فنحن نعرف ناتج ذلك المساعد ("KATZ" ) _ أيضًا_ لم يتغير ، لذلك لا شيء هنا<li>
to-upper-case
("Timberlake" -> "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>
ولكن كيف_؟
{ first: "Andrew", last: "Timberlake" }
بالصف الأول ، <li>Yehuda KATZ</li>
:to-upper-case
{ first: "Yehuda", last: "Katz" }
بالصف الثاني ، <li>Tom DALE</li>
، عملية 3 أخرى{ first: "Tom", last: "Dale" }
بالصف الثاني ، <li>Godfrey CHAN</li>
، عملية 3 أخرى<li>
to-upper-case
("Chan" -> "CHAN")هذه 14 عملية. أوتش!
بدا هذا غير ضروري ، لأنه من الناحية المفاهيمية ، سواء كنا نضيف أو نلحق ، ما زلنا نغير (ندخل) كائنًا واحدًا في المصفوفة. على النحو الأمثل ، يجب أن نكون قادرين على التعامل مع هذه الحالة تمامًا كما فعلنا في سيناريو الإلحاق.
هنا يأتي دور key="@identity"
. بدلاً من الاعتماد على _order_ للعناصر في المصفوفة ، نستخدم هوية كائن JavaScript الخاصة بهم ( ===
):
===
) الكائن الأول { first: "Andrew", last: "Timberlake" }
. نظرًا لعدم العثور على شيء ، أدخل (قبل) صفًا جديدًا:<li>
to-upper-case
("Timberlake" -> "TIMBERLAKE")===
) العنصر الثاني { first: "Yehuda", last: "Katz" }
. تم العثور على <li>Yehuda KATZ</li>
:to-upper-case
، وبالتالي فإننا نعرف ناتج ذلك المساعد ("KATZ" ) _ أيضًا_ لم يتغير ، لذلك لا شيء هناوبذلك ، نعود إلى العمليات الخمس المثلى.
مرة أخرى ، هذا هو التلويح باليد على المقارنات وتكاليف مسك الدفاتر. في الواقع ، هذه ليست مجانية أيضًا ، وفي هذا المثال البسيط للغاية ، قد لا تستحق ذلك. لكن تخيل أن القائمة كبيرة وأن كل صف يستدعي مكونًا معقدًا (مع الكثير من المساعدين والخصائص المحسوبة والمكونات الفرعية ، إلخ). تخيل موجز أخبار 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: ...
الآن ، لنفترض أننا نقرنا على خانة الاختيار الأولى:
{{action}}
وأعيد إرساله إلى طريقة makeChange
[true, undefined, undefined, undefined, undefined]
.كيف يتم تحديث DOM؟
===
) الكائن الأول true
. نظرًا لعدم العثور على أي شيء ، أدخل (قبل) صفًا جديدًا <input checked=true ...>0: true...
===
) العنصر الثاني undefined
. تم العثور على <input ...>0: ...
(سابقًا الصف الأول):{{idx}}
إلى 1
===
) الكائن الثالث undefined
. نظرًا لأن هذه هي المرة الثانية التي نرى فيها undefined
، فإن المفتاح الداخلي هو undefined-1
، لذلك وجدنا <input ...>1: ...
(سابقًا الصف الثاني):{{idx}}
إلى 2
undefined-2
و undefined-3
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 الأساسي وأن تعكس العمليات في تغيير الخاصية المناسب ، بحيث يمكن إخطار الأطراف المعنية (مثل طبقة العرض).
لست متأكدًا من أين يتركنا ذلك. أتفهم سبب كون هذا مفاجئًا ، لكنني أميل إلى القول إن هذه سلسلة من أخطاء المستخدم المؤسفة. ربما يجب علينا أن نتحكم ضد ربط هذه الخصائص بعناصر الإدخال؟
الكود ذو الصلة:
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L390-L391
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L436-L445
https://github.com/emberjs/ember.js/blob/c24bc23e4139c90c8d8d96c4234d9c0c19e5c594/packages/@ember/ -internals / glimmer / lib / utils / iterable.ts # L451-L466
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. يرجى التحقق من هذا العرض التوضيحي لإعادة إنتاج كل مشكلة.
خطوة:
يمكنك أن ترى العنصر المسحوب يبقى في شجرة دوم.
ولكن ، إذا تم سحب العنصر إلى موضع آخر (ليس العنصر الأخير) ، فيبدو أنه يعمل بشكل جيد.
التعليق الأكثر فائدة
أعتقد أنني أعرف ما يجري هنا. إنها فوضى ولكن سأحاول وصفها. ساهمت الكثير من حالات الحافة (خطأ مستخدم خط الحدود) في هذا ، ولست متأكدًا حقًا من ما هو / ليس خطأ ، وماذا وكيف يتم إصلاح أي منها.
الرائد 🔑
بادئ ذي بدء ، أحتاج إلى وصف ما تفعله المعلمة
key
في{{#each}}
. TL ؛ DR تحاول تحديد متى وما إذا كان من المنطقي إعادة استخدام DOM الحالي ، مقابل مجرد إنشاء DOM من البداية.لغرضنا ، دعونا نقبل أن "لمس DOM" (مثل تحديث محتوى عقدة نصية ، أو سمة ، أو إضافة محتوى أو إزالته ، إلخ) أمر مكلف ويجب تجنبه قدر الإمكان.
دعنا نركز على جزء بسيط من النموذج:
إذا كان
this.names
...ثم ستحصل ...
حتى الان جيدة جدا.
إلحاق عنصر بالقائمة
الآن ماذا لو قمنا بإلحاق
{ first: "Andrew", last: "Timberlake" }
بالقائمة؟ نتوقع أن ينتج النموذج DOM التالي:ولكن كيف_؟
الطريقة الأكثر سذاجة لتنفيذ المساعد
{{#each}}
هي مسح كل محتوى القائمة في كل مرة يتغير فيها محتوى القائمة. للقيام بذلك ، ستحتاج إلى إجراء 23 عملية على الأقل:<li>
عقد<li>
عقدto-upper-case
4 مراتهذا يبدو ... غير ضروري ومكلف للغاية. نحن نعلم أن العناصر الثلاثة الأولى لم تتغير ، لذا سيكون من الرائع تخطي العمل لتلك الصفوف.
🔑index
سيكون التنفيذ الأفضل هو محاولة إعادة استخدام الصفوف الحالية وعدم القيام بأي تحديثات غير ضرورية. تتمثل إحدى الأفكار في مطابقة الصفوف مع مواقعها في القوالب. هذا هو أساسًا ما يفعله
key="@index"
:{ first: "Yehuda", last: "Katz" }
بالصف الأول ،<li>Yehuda KATZ</li>
:1.1. "Yehuda" === "Yehuda" ، لا شيء لعمله
1.2 (لا تحتوي المساحة على بيانات ديناميكية لذلك لا حاجة للمقارنة)
1.3 "Katz" === "Katz" ، نظرًا لأن المساعدين "خالصون" ، فنحن نعلم أننا لن نضطر إلى إعادة استدعاء المساعد
to-upper-case
، وبالتالي فنحن نعرف ناتج ذلك المساعد ("KATZ" ) _ أيضًا_ لم يتغير ، لذلك لا شيء هنا3.1. أدخل عقدة
<li>
3.2 أدخل عقدة نصية ("أندرو")
3.3 أدخل عقدة نصية (المسافة)
3.4. استدعاء المساعد
to-upper-case
("Timberlake" -> "TIMBERLAKE")3.5 أدخل عقدة نصية ("TIMBERLAKE")
لذلك ، مع هذا التنفيذ ، قمنا بتقليل العدد الإجمالي للعمليات من 23 إلى 5 (التلويح باليد على تكلفة المقارنات ، ولكن لغرضنا ، نفترض أنها رخيصة نسبيًا مقارنة بالباقي). ليس سيئا.
إلحاق عنصر مسبقًا بالقائمة
لكن الآن ، ماذا سيحدث إذا ، بدلاً من _إلحاق_
{ first: "Andrew", last: "Timberlake" }
إلى القائمة ، قمنا _إعادة_لإضافة _ بدلاً من ذلك؟ نتوقع أن ينتج النموذج DOM التالي:ولكن كيف_؟
{ 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"
{ first: "Yehuda", last: "Katz" }
بالصف الثاني ،<li>Tom DALE</li>
، عملية 3 أخرى{ first: "Tom", last: "Dale" }
بالصف الثاني ،<li>Godfrey CHAN</li>
، عملية 3 أخرى3.1. أدخل عقدة
<li>
3.2 أدخل عقدة نصية ("Godfrey")
3.3 أدخل عقدة نصية (المسافة)
3.4. استدعاء المساعد
to-upper-case
("Chan" -> "CHAN")3.5 أدخل عقدة نصية ("CHAN")
هذه 14 عملية. أوتش!
🔑 @ هوية
بدا هذا غير ضروري ، لأنه من الناحية المفاهيمية ، سواء كنا نضيف أو نلحق ، ما زلنا نغير (ندخل) كائنًا واحدًا في المصفوفة. على النحو الأمثل ، يجب أن نكون قادرين على التعامل مع هذه الحالة تمامًا كما فعلنا في سيناريو الإلحاق.
هنا يأتي دور
key="@identity"
. بدلاً من الاعتماد على _order_ للعناصر في المصفوفة ، نستخدم هوية كائن JavaScript الخاصة بهم (===
):===
) الكائن الأول{ first: "Andrew", last: "Timberlake" }
. نظرًا لعدم العثور على شيء ، أدخل (قبل) صفًا جديدًا:1.1. أدخل عقدة
<li>
1.2 أدخل عقدة نصية ("أندرو")
1.3 أدخل عقدة نصية (المسافة)
1.4. استدعاء المساعد
to-upper-case
("Timberlake" -> "TIMBERLAKE")1.5 أدخل عقدة نصية ("TIMBERLAKE")
===
) العنصر الثاني{ first: "Yehuda", last: "Katz" }
. تم العثور على<li>Yehuda KATZ</li>
:2.1. "Yehuda" === "Yehuda" ، لا شيء لعمله
2.2. (لا تحتوي المساحة على بيانات ديناميكية لذلك لا حاجة للمقارنة)
2.3 "Katz" === "Katz" ، نظرًا لأن المساعدين "خالصون" ، فنحن نعلم أننا لن نضطر إلى إعادة استدعاء المساعد
to-upper-case
، وبالتالي فإننا نعرف ناتج ذلك المساعد ("KATZ" ) _ أيضًا_ لم يتغير ، لذلك لا شيء هناوبذلك ، نعود إلى العمليات الخمس المثلى.
زيادة
مرة أخرى ، هذا هو التلويح باليد على المقارنات وتكاليف مسك الدفاتر. في الواقع ، هذه ليست مجانية أيضًا ، وفي هذا المثال البسيط للغاية ، قد لا تستحق ذلك. لكن تخيل أن القائمة كبيرة وأن كل صف يستدعي مكونًا معقدًا (مع الكثير من المساعدين والخصائص المحسوبة والمكونات الفرعية ، إلخ). تخيل موجز أخبار LinkedIn ، على سبيل المثال. إذا لم نطابق الصفوف الصحيحة بالبيانات الصحيحة ، فمن المحتمل أن تتسبب وسائط المكونات الخاصة بك في إحداث الكثير من التحديثات مما قد تتوقعه بخلاف ذلك. هناك أيضًا مشكلات في مطابقة عناصر DOM الخاطئة وفقدان حالة DOM ، مثل موضع المؤشر وحالة تحديد النص.
بشكل عام ، فإن تكلفة المقارنة الإضافية وحفظ الكتب تستحق بسهولة معظم الوقت في تطبيق العالم الحقيقي. نظرًا لأن
key="@identity"
هو الخيار الافتراضي في Ember ويعمل جيدًا لجميع الحالات تقريبًا ، فلن تقلق عادةً بشأن تعيين الوسيطةkey
عند استخدام{{#each}}
.الاصطدامات 💥
لكن انتظر ، هناك مشكلة. ماذا عن هذه الحالة؟
المشكلة هنا هي أن نفس الكائن _could_ يظهر عدة مرات في نفس القائمة. هذا يكسر خوارزمية
@identity
الساذجة ، وتحديدًا الجزء الذي قلنا فيه "العثور على صف موجود تتطابق بياناته (===
) ..." - هذا يعمل فقط إذا كانت البيانات بعلاقة DOM هي 1 : 1 ، وهذا ليس صحيحًا في هذه الحالة. قد يبدو هذا غير مرجح في الممارسة العملية ، ولكن كإطار عمل ، علينا التعامل معه.لتجنب ذلك ، نستخدم نوعًا من النهج الهجين للتعامل مع هذه الاصطدامات. داخليًا ، يبدو تعيين key-to-DOM شيئًا كالتالي:
بالنسبة للجزء الأكبر ، هذا _ هو نادر جدًا ، وعندما يظهر ، هذا يعمل بشكل جيد بما فيه الكفاية ™ معظم الوقت. إذا لم ينجح ذلك ، لسبب ما ، فيمكنك دائمًا استخدام مسار مفتاح (أو حتى آلية المفاتيح الأكثر تقدمًا في RFC 321 ).
رجوع إلى "🐛"
بعد كل هذا الحديث ، نحن الآن جاهزون لإلقاء نظرة على السيناريو في Twiddle.
بشكل أساسي ، بدأنا بهذه القائمة:
[undefined, undefined, undefined, undefined, undefined]
.نظرًا لأننا لم نحدد المفتاح ، يستخدم Ember
@identity
افتراضيًا. علاوة على ذلك ، نظرًا لأنها تصادمات ، فقد انتهى بنا الأمر بشيء مثل هذا:الآن ، لنفترض أننا نقرنا على خانة الاختيار الأولى:
{{action}}
وأعيد إرساله إلى طريقةmakeChange
[true, undefined, undefined, undefined, undefined]
.كيف يتم تحديث DOM؟
===
) الكائن الأولtrue
. نظرًا لعدم العثور على أي شيء ، أدخل (قبل) صفًا جديدًا<input checked=true ...>0: true...
===
) العنصر الثانيundefined
. تم العثور على<input ...>0: ...
(سابقًا الصف الأول):2.1. قم بتحديث العقدة النصية
{{idx}}
إلى1
2.2. خلاف ذلك ، بقدر ما يمكن لـ Ember أن يقول ، لم يتغير شيء آخر في هذا الصف ، ولا شيء آخر للقيام به
===
) الكائن الثالثundefined
. نظرًا لأن هذه هي المرة الثانية التي نرى فيهاundefined
، فإن المفتاح الداخلي هوundefined-1
، لذلك وجدنا<input ...>1: ...
(سابقًا الصف الثاني):3.1. قم بتحديث العقدة النصية
{{idx}}
إلى2
3.2 خلاف ذلك ، بقدر ما يمكن لـ Ember أن يقول ، لم يتغير شيء آخر في هذا الصف ، ولا شيء آخر للقيام به
undefined-2
وundefined-3
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 الأساسي وأن تعكس العمليات في تغيير الخاصية المناسب ، بحيث يمكن إخطار الأطراف المعنية (مثل طبقة العرض).لست متأكدًا من أين يتركنا ذلك. أتفهم سبب كون هذا مفاجئًا ، لكنني أميل إلى القول إن هذه سلسلة من أخطاء المستخدم المؤسفة. ربما يجب علينا أن نتحكم ضد ربط هذه الخصائص بعناصر الإدخال؟