JSX’te && yerine “ternary operator”leri kullanın
Bu yazı Kent C. Dodds tarafından yazılmış olan “Use ternaries rather than && in JSX” yazısının Türkçe çevirisidir. İzin alınarak yayımlanmıştır. Eğer bir hata olduğunu düşünüyorsanız lütfen iletişime geçmekten çekinmeyin.
Aşağıdaki kod bloğundaki sorun nedir?
function ContactList({contacts}) {
return (
<div>
<ul>
{contacts.length &&
contacts.map(contact => (
<li key={contact.id}>
{contact.firstName} {contact.lastName}
</li>
))}
</ul>
</div>
)
}
Emin değil misin? O zaman daha farklı bir şekilde sorayım. Eğer “contacts” değişkeninin değeri “[]” boş bir “array” olsaydı bu kodun çıktısı olarak ekranda ne görürdük? Doğru cevabı bulduğunuzu biliyorum “0” yazardı!
Bu hatayı bir kez PayPal’da “production” ortamına gönderdim. Şaka değil. Bu sayfadaydı:
Bir kullanıcının kişilerimi boş olduğunda ve bu ekrana geldiğinde, gördükleri şey aşağıdaki gibiydi:
Evet pek hoş değildi fakat neden böyle olmuştu?
Çünkü JavaScript herhangi bir şeyi “0 &&” olarak değerlendirdiğinde sonuç her zaman 0 olacaktır çünkü 0 “falsy” (Boolean bağlamında karşılaşıldığında false olarak kabul edilen değerler) bir değerdir, bu nedenle && öğesinin sağ tarafını değerlendirmez.
Peki çözümü nedir? Koşulunuzun sağlanmadığı durumlarda neyi “render” edeceğinizi açıkça belirtmek için “ternary operator”u kullanın. Bizim durmumuzda hiç bir şey “render” etmemekti(bu yüzden “null” kullanmak en iyisiydi):
function ContactList({contacts}) {
return (
<div>
<ul>
{contacts.length
? contacts.map(contact => (
<li key={contact.id}>
{contact.firstName} {contact.lastName}
</li>
))
: null}
</ul>
</div>
)
}
Peki aşağıdaki kod nasıl çalışır? Burada yanlış olan nedir?
function Error({error}) {
return error && <div className="fancy-error">{error.message}</div>
}
Emin değil misin? Peki ya “error” değişkeninin değeri “undefined” olsaydı? Böyle bir durumda aşağıdaki hatayı alırdın.
Uncaught Error: Error(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.
Veya “production” ortamındaysanız aşağıdaki gibi olurdu.
react-dom.production.min.js:13 Uncaught Invariant Violation: Minified React error #152; visit https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=f for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
Buradaki problem “undefined && herhangi bir şey” ifadesinin sonucunun her zaman “undefined” olmasıdır.
Peki ne oluyor?
Aslında her iki durumda da sorun, koşullu “render” yapmak için “&&” kullanıyor olmamızdır. Daha farklı bir şekilde söylemek gerekirse, bir fonksiyona koşullu olarak değişken göndermek için “&&” kullanıyoruz. Unutmayın, “JSX”, “React.createElement”’ fonksiyonunu çağırmaktan daha basit bir “syntactic abstraction”dır. Yani yazması daha kolay olan bir “syntax” olduğunu düşünebiliriz. Dolayısıyla “JSX” içinde “&&” ile bu işlemleri yapmak aşağıdakine benzerdi:
function throwTheCandy(candyNames) {
for (const candyName of candyNames) {
throwCandy(candyName)
}
}
throwTheCandy(candies.length && candies.map(c => c.name))
Bir karmaşa var gibi değil mi? “Type”larımız karıştı. Yani “throwTheCandy” bizden bir “array” bekliyorken bizim ne gönderdiğimiz tam olarak belli değil. “React”’in bunu yapmanıza izin vermesinin nedeni, “0” değerinin onun için geçerli olmasıdır.
Neyse ki TypeScript, ikinci örnekteki sorunu önlemenize yardımcı olabilir. Ancak bu koruma katmanının yanı sıra, neyi “render” etmek istediğimiz konusunda daha açık olmaya ne dersiniz? Koşullu “render” yapmak istiyorsanız, mantıksal “AND” operatörünü (&&) amacına uygun olmayan şekilde kullanmayın.
Aslında mantığa aykırı görünüyor, çünkü “&&”’i genellikle şöyle kullanırız: bir koşuldaki değerlerin hepsi “truthy”(Boolean bağlamında karşılaşıldığında true olarak kabul edilen bir değerler) ise o zaman bu şeyi yapın, aksi takdirde yapmayın. Ne yazık ki bizim kullanım durumumuz için, && operatörü herhangi bir değer “truthy” değilse, “falsy” olanın değerini döndürür:
0 && true // 0
true && 0 // 0
false && true // false
true && '' // ''
“actual branching syntax” kullan
“&&” ve “||” operatörlerini “JSX” içerisinde amacına uygun olmayan şekilde kullanmak yerine “ternary” (koşul ? doğruSonuç : yanlışSonuç) veya hatta “if” ifadeleri (ve gelecekte, belki “do expressions” bile) gibi “actual branching syntax” özelliklerini kullanmanızı şiddetle öneriyorum.
Ben şahsen “ternary” hayranıyım, ancak “if” ifadelerini tercih ederseniz, bu da harika. Yukarıda bahsettiğim kullanıcının kişilerimi kısmındaki yaptığım hatayı “if” ifadeleriyle şu şekilde yapacağım:
function ContactList({contacts}) {
let contactsElements = null
if (contacts.length) {
contactsElements = contacts.map(contact => (
<li key={contact.id}>
{contact.firstName} {contact.lastName}
</li>
))
}
return (
<div>
<ul>{contactsElements}</ul>
</div>
)
}
Şahsen, “ternary”nin daha okunaklı olduğunu düşünüyorum (katılmıyorsanız, “okunabilir”in öznel olduğunu ve bir şeyin okunabilirliğinin her şeyden çok aşinalıkla ilgisi olduğunu unutmayın). Ne yaparsan yap, istediğini yap, sadece “bug” çıkarma.
“actual branching syntax” kullanmanın bir başka yararı da, “test coverage” araçlarının, testlerinizin “branch”lerden birini kapsamadığını algılayıp gösterebilmesidir.
Sonuç
Yaptığım hatayı, “!!contacts.length && …” veya “contacts.length > 0 && …” ya da “Boolean(contacts.length) && …” bu şekilde çözebileceğimin farkındayım, ama yine de “render” için mantıksal “AND” operatörünü amacına uygun olmayan şekilde kullanmamayı tercih ediyorum. “Ternary” kullanarak neyi “render” etmek istediğimi açık olarak belirtmeyi tercih ederim.
Anlattıklarımı pekiştirmek için, bugün bu bileşenlerin her ikisini de yazmak zorunda kalsaydım, onları şu şekilde yazardım:
function ContactList({contacts}) {
return (
<div>
<ul>
{contacts.length
? contacts.map(contact => (
<li key={contact.id}>
{contact.firstName} {contact.lastName}
</li>
))
: null}
</ul>
</div>
)
}
function Error({error}) {
return error ? <div className="fancy-error">{error.message}</div> : null
}
İyi şanslar!