React'in useCallback kancasını ve yaygın bağımlılık tuzaklarını anlayarak küresel kitleler için verimli ve ölçeklenebilir uygulamalar oluşturun.
React useCallback Bağımlılıkları: Küresel Geliştiriciler İçin Optimizasyon Tuzaklarında Gezinme
Sürekli gelişen ön yüz (front-end) geliştirme dünyasında performans her şeyden önemlidir. Uygulamalar karmaşıklaştıkça ve çeşitli küresel kitlelere ulaştıkça, kullanıcı deneyiminin her yönünü optimize etmek kritik hale gelir. Kullanıcı arayüzleri oluşturmak için önde gelen bir JavaScript kütüphanesi olan React, bunu başarmak için güçlü araçlar sunar. Bunlar arasında, useCallback
kancası (hook), fonksiyonları hafızaya almak (memoize), gereksiz yeniden render'ları önlemek ve performansı artırmak için hayati bir mekanizma olarak öne çıkar. Ancak, her güçlü araç gibi, useCallback
de kendi zorluklarıyla birlikte gelir; özellikle de bağımlılık dizisi konusunda. Bu bağımlılıkların yanlış yönetilmesi, özellikle farklı ağ koşullarına ve cihaz yeteneklerine sahip uluslararası pazarları hedeflerken daha da büyüyebilen gizli hatalara ve performans düşüşlerine yol açabilir.
Bu kapsamlı rehber, useCallback
bağımlılıklarının inceliklerine dalarak, yaygın tuzakları aydınlatmakta ve küresel geliştiricilerin bunlardan kaçınması için eyleme geçirilebilir stratejiler sunmaktadır. Bağımlılık yönetiminin neden bu kadar önemli olduğunu, geliştiricilerin yaptığı yaygın hataları ve React uygulamalarınızın dünya çapında performanslı ve sağlam kalmasını sağlamak için en iyi uygulamaları keşfedeceğiz.
useCallback ve Memoization'ı Anlamak
Bağımlılık tuzaklarına dalmadan önce, useCallback
'in temel konseptini kavramak esastır. Özünde, useCallback
bir callback fonksiyonunu hafızaya alan (memoize eden) bir React Hook'udur. Memoization, pahalı bir fonksiyon çağrısının sonucunun önbelleğe alındığı ve aynı girdiler tekrar oluştuğunda önbelleğe alınan sonucun döndürüldüğü bir tekniktir. React'te bu, bir fonksiyonun her render'da yeniden oluşturulmasını önlemek anlamına gelir; özellikle de bu fonksiyon, yine memoization kullanan (React.memo
gibi) bir alt bileşene prop olarak geçtiğinde.
Bir üst bileşenin bir alt bileşeni render ettiği bir senaryo düşünün. Eğer üst bileşen yeniden render edilirse, içinde tanımlanan herhangi bir fonksiyon da yeniden oluşturulur. Eğer bu fonksiyon alt bileşene bir prop olarak geçirilirse, alt bileşen bunu yeni bir prop olarak görebilir ve fonksiyonun mantığı ve davranışı değişmemiş olsa bile gereksiz yere yeniden render edilebilir. İşte burada useCallback
devreye girer:
const memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b], );
Bu örnekte, memoizedCallback
yalnızca a
veya b
'nin değerleri değişirse yeniden oluşturulacaktır. Bu, a
ve b
render'lar arasında aynı kalırsa, aynı fonksiyon referansının alt bileşene iletilmesini ve potansiyel olarak yeniden render edilmesini önlemesini sağlar.
Memoization Global Uygulamalar için Neden Önemlidir?
Küresel bir kitleyi hedefleyen uygulamalar için performans considerations daha da önem kazanır. Daha yavaş internet bağlantılarına sahip bölgelerdeki veya daha az güçlü cihazlardaki kullanıcılar, verimsiz render nedeniyle önemli gecikmeler ve kötü bir kullanıcı deneyimi yaşayabilir. useCallback
ile callback'leri hafızaya alarak şunları yapabiliriz:
- Gereksiz Yeniden Render'ları Azaltmak: Bu, tarayıcının yapması gereken iş miktarını doğrudan etkileyerek daha hızlı UI güncellemelerine yol açar.
- Ağ Kullanımını Optimize Etmek: Daha az JavaScript çalıştırılması, potansiyel olarak daha düşük veri tüketimi anlamına gelir ki bu da kotalı bağlantılardaki kullanıcılar için çok önemlidir.
- Yanıt Süresini İyileştirmek: Performanslı bir uygulama daha duyarlı hissettirir, bu da coğrafi konumları veya cihazları ne olursa olsun daha yüksek kullanıcı memnuniyetine yol açar.
- Verimli Prop Aktarımını Sağlamak: Callback'leri hafızaya alınmış alt bileşenlere (
React.memo
) veya karmaşık bileşen ağaçlarına aktarırken, kararlı fonksiyon referansları zincirleme yeniden render'ları önler.
Bağımlılık Dizisinin Kritik Rolü
useCallback
'in ikinci argümanı bağımlılık dizisidir. Bu dizi, React'e callback fonksiyonunun hangi değerlere bağlı olduğunu söyler. React, hafızaya alınmış callback'i yalnızca dizideki bağımlılıklardan biri son render'dan bu yana değişmişse yeniden oluşturur.
Temel kural şudur: Eğer bir değer callback içinde kullanılıyorsa ve render'lar arasında değişebiliyorsa, bağımlılık dizisine dahil edilmelidir.
Bu kurala uymamak iki temel soruna yol açabilir:
- Eski Kapsamlar (Stale Closures): Eğer callback içinde kullanılan bir değer bağımlılık dizisine dahil edilmezse, callback en son oluşturulduğu render'daki değere bir referans tutmaya devam eder. Bu değeri güncelleyen sonraki render'lar, hafızaya alınmış callback içinde yansıtılmayacak ve beklenmedik davranışlara (örneğin, eski bir state değerini kullanma) yol açacaktır.
- Gereksiz Yeniden Oluşturmalar: Eğer callback'in mantığını etkilemeyen bağımlılıklar dahil edilirse, callback gereğinden daha sık yeniden oluşturulabilir ve bu da
useCallback
'in performans avantajlarını ortadan kaldırır.
Yaygın Bağımlılık Tuzakları ve Küresel Etkileri
Geliştiricilerin useCallback
bağımlılıkları ile yaptığı en yaygın hataları ve bunların küresel kullanıcı tabanını nasıl etkileyebileceğini keşfedelim.
Tuzak 1: Bağımlılıkları Unutmak (Eski Kapsamlar - Stale Closures)
Bu, muhtemelen en sık karşılaşılan ve sorunlu tuzaktır. Geliştiriciler genellikle callback fonksiyonu içinde kullanılan değişkenleri (prop'lar, state, context değerleri, diğer hook sonuçları) dahil etmeyi unuturlar.
Örnek:
import React, { useState, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
// Tuzak: 'step' kullanılıyor ama bağımlılıklarda yok
const increment = useCallback(() => {
setCount(prevCount => prevCount + step);
}, []); // Boş bağımlılık dizisi, bu callback'in asla güncellenmeyeceği anlamına gelir
return (
Sayı: {count}
);
}
Analiz: Bu örnekte, increment
fonksiyonu step
state'ini kullanır. Ancak, bağımlılık dizisi boştur. Kullanıcı "Adımı Artır" düğmesine tıkladığında, step
state'i güncellenir. Ancak increment
boş bir bağımlılık dizisiyle hafızaya alındığı için, çağrıldığında her zaman step
'in başlangıç değerini (yani 1'i) kullanır. Kullanıcı, adım değerini artırmış olsa bile "Artır" düğmesine tıklamanın sayıyı her zaman sadece 1 artırdığını gözlemleyecektir.
Küresel Etki: Bu hata, uluslararası kullanıcılar için özellikle sinir bozucu olabilir. Yüksek gecikme süresine sahip bir bölgedeki bir kullanıcıyı düşünün. Bir eylem gerçekleştirebilir (adımı artırmak gibi) ve ardından "Artır" eyleminin bu değişikliği yansıtmasını bekleyebilir. Uygulama eski kapsamlar nedeniyle beklenmedik şekilde davranırsa, bu durum kafa karışıklığına ve uygulamayı terk etmeye yol açabilir, özellikle de ana dilleri İngilizce değilse ve hata mesajları (varsa) mükemmel bir şekilde yerelleştirilmemiş veya net değilse.
Tuzak 2: Aşırı Bağımlılık Ekleme (Gereksiz Yeniden Oluşturmalar)
Diğer uç nokta, aslında callback'in mantığını etkilemeyen veya geçerli bir neden olmaksızın her render'da değişen değerleri bağımlılık dizisine eklemektir. Bu, callback'in çok sık yeniden oluşturulmasına ve useCallback
'in amacını boşa çıkarmasına neden olabilir.
Örnek:
import React, { useState, useCallback } from 'react';
function Greeting({ name }) {
// Bu fonksiyon aslında 'name' kullanmıyor, ama gösterim için öyle varsayalım.
// Daha gerçekçi bir senaryo, prop ile ilgili bazı iç state'leri değiştiren bir callback olabilir.
const generateGreeting = useCallback(() => {
// Bunun isme göre kullanıcı verilerini alıp gösterdiğini hayal edin
console.log(`${name} için selamlama oluşturuluyor`);
return `Merhaba, ${name}!`;
}, [name, Math.random()]); // Tuzak: Math.random() gibi kararsız değerler eklemek
return (
{generateGreeting()}
);
}
Analiz: Bu uydurma örnekte, Math.random()
bağımlılık dizisine dahil edilmiştir. Math.random()
her render'da yeni bir değer döndürdüğü için, generateGreeting
fonksiyonu name
prop'u değişse de değişmese de her render'da yeniden oluşturulacaktır. Bu, bu durumda useCallback
'i hafızaya alma için etkili bir şekilde işe yaramaz hale getirir.
Daha yaygın bir gerçek dünya senaryosu, üst bileşenin render fonksiyonu içinde satır içi olarak oluşturulan nesneleri veya dizileri içerir:
import React, { useState, useCallback } from 'react';
function UserProfile({ user }) {
const [message, setMessage] = useState('');
// Tuzak: Üst bileşende satır içi nesne oluşturma, bu callback'in sık sık yeniden oluşturulacağı anlamına gelir.
// 'user' nesnesinin içeriği aynı olsa bile, referansı değişebilir.
const displayUserDetails = useCallback(() => {
const details = { userId: user.id, userName: user.name };
setMessage(`Kullanıcı ID: ${details.userId}, İsim: ${details.userName}`);
}, [user, { userId: user.id, userName: user.name }]); // Yanlış bağımlılık
return (
{message}
);
}
Analiz: Burada, user
nesnesinin özellikleri (id
, name
) aynı kalsa bile, eğer üst bileşen yeni bir nesne literali geçerse (örneğin, <UserProfile user={{ id: 1, name: 'Alice' }} />
), user
prop referansı değişecektir. Eğer user
tek bağımlılıksa, callback yeniden oluşturulur. Eğer nesnenin özelliklerini veya yeni bir nesne literalini bir bağımlılık olarak eklemeye çalışırsak (yanlış bağımlılık örneğinde gösterildiği gibi), bu daha da sık yeniden oluşturmalara neden olur.
Küresel Etki: Fonksiyonların aşırı oluşturulması, özellikle dünyanın birçok yerinde yaygın olan kaynakları kısıtlı mobil cihazlarda artan bellek kullanımına ve daha sık çöp toplama döngülerine yol açabilir. Performans etkisi eski kapsamlardan daha az dramatik olsa da, genel olarak daha az verimli bir uygulamaya katkıda bulunur ve bu tür ek yükleri karşılayamayan eski donanıma veya daha yavaş ağ koşullarına sahip kullanıcıları potansiyel olarak etkiler.
Tuzak 3: Nesne ve Dizi Bağımlılıklarını Yanlış Anlamak
İlkel değerler (string'ler, sayılar, boolean'lar, null, undefined) değere göre karşılaştırılır. Ancak, nesneler ve diziler referansa göre karşılaştırılır. Bu, bir nesne veya dizinin içeriği tamamen aynı olsa bile, render sırasında oluşturulan yeni bir örnek ise, React'in bunu bir bağımlılık değişikliği olarak kabul edeceği anlamına gelir.
Örnek:
import React, { useState, useCallback } from 'react';
function DataDisplay({ data }) { // data'nın [{ id: 1, value: 'A' }] gibi bir nesne dizisi olduğunu varsayın
const [filteredData, setFilteredData] = useState([]);
// Tuzak: Eğer 'data' her render'da yeni bir dizi referansı ise, bu callback yeniden oluşturulur.
const processData = useCallback(() => {
const processed = data.map(item => ({ ...item, processed: true }));
setFilteredData(processed);
}, [data]); // Eğer 'data' her seferinde yeni bir dizi örneği ise, bu callback yeniden oluşturulur.
return (
{filteredData.map(item => (
- {item.value} - {item.processed ? 'İşlendi' : ''}
))}
);
}
function App() {
const [randomNumber, setRandomNumber] = useState(0);
// 'sampleData', içeriği aynı olsa bile App'in her render'ında yeniden oluşturulur.
const sampleData = [
{ id: 1, value: 'Alpha' },
{ id: 2, value: 'Beta' },
];
return (
{/* App her render olduğunda yeni bir 'sampleData' referansı geçiliyor */}
);
}
Analiz: App
bileşeninde, sampleData
doğrudan bileşen gövdesi içinde bildirilmiştir. App
her yeniden render edildiğinde (örneğin, randomNumber
değiştiğinde), sampleData
için yeni bir dizi örneği oluşturulur. Bu yeni örnek daha sonra DataDisplay
'e geçirilir. Sonuç olarak, DataDisplay
'deki data
prop'u yeni bir referans alır. data
, processData
'nın bir bağımlılığı olduğu için, processData
callback'i, gerçek veri içeriği değişmemiş olsa bile App
'in her render'ında yeniden oluşturulur. Bu, memoization'ı etkisiz hale getirir.
Küresel Etki: Dengesiz internete sahip bölgelerdeki kullanıcılar, hafızaya alınmamış veri yapılarının aşağıya doğru geçirilmesi nedeniyle uygulamanın sürekli olarak bileşenleri yeniden render etmesi durumunda yavaş yükleme süreleri veya yanıt vermeyen arayüzler yaşayabilir. Veri bağımlılıklarını verimli bir şekilde yönetmek, özellikle kullanıcılar uygulamaya çeşitli ağ koşullarından eriştiğinde sorunsuz bir deneyim sağlamanın anahtarıdır.
Etkili Bağımlılık Yönetimi için Stratejiler
Bu tuzaklardan kaçınmak, bağımlılıkları yönetmeye yönelik disiplinli bir yaklaşım gerektirir. İşte etkili stratejiler:
1. React Hooks için ESLint Eklentisini Kullanın
React Hooks için resmi ESLint eklentisi vazgeçilmez bir araçtır. Bağımlılık dizilerinizi otomatik olarak kontrol eden exhaustive-deps
adlı bir kural içerir. Callback'inizin içinde, bağımlılık dizisinde listelenmeyen bir değişken kullanırsanız, ESLint sizi uyaracaktır. Bu, eski kapsamlara karşı ilk savunma hattıdır.
Kurulum:
Projenizin dev bağımlılıklarına eslint-plugin-react-hooks
'u ekleyin:
npm install eslint-plugin-react-hooks --save-dev
# veya
yarn add eslint-plugin-react-hooks --dev
Ardından, .eslintrc.js
(veya benzeri) dosyanızı yapılandırın:
module.exports = {
// ... diğer yapılandırmalar
plugins: [
// ... diğer eklentiler
'react-hooks'
],
rules: {
// ... diğer kurallar
'react-hooks/rules-of-hooks': 'error', // Hook kurallarını kontrol eder
'react-hooks/exhaustive-deps': 'warn' // Efekt bağımlılıklarını kontrol eder
}
};
Bu kurulum, hook kurallarını zorunlu kılacak ve eksik bağımlılıkları vurgulayacaktır.
2. Neleri Dahil Ettiğiniz Konusunda Kasıtlı Olun
Callback'inizin *gerçekten* ne kullandığını dikkatlice analiz edin. Yalnızca değiştiğinde callback fonksiyonunun yeni bir sürümünü gerektiren değerleri dahil edin.
- Prop'lar: Eğer callback bir prop kullanıyorsa, onu dahil edin.
- State: Eğer callback state veya bir state ayarlayıcı fonksiyonu (
setCount
gibi) kullanıyorsa, doğrudan kullanılıyorsa state değişkenini veya kararlı ise ayarlayıcıyı dahil edin. - Context Değerleri: Eğer callback React Context'ten bir değer kullanıyorsa, o context değerini dahil edin.
- Dışarıda Tanımlanan Fonksiyonlar: Eğer callback, bileşen dışında tanımlanmış veya kendisi de hafızaya alınmış başka bir fonksiyonu çağırıyorsa, o fonksiyonu da bağımlılıklara dahil edin.
3. Nesneleri ve Dizileri Hafızaya Alma (Memoizing)
Eğer nesneleri veya dizileri bağımlılık olarak geçmeniz gerekiyorsa ve bunlar satır içi olarak oluşturuluyorsa, bunları useMemo
kullanarak hafızaya almayı düşünün. Bu, referansın yalnızca temel veri gerçekten değiştiğinde değişmesini sağlar.
Örnek (Tuzak 3'ten İyileştirilmiş):
import React, { useState, useCallback, useMemo } from 'react';
function DataDisplay({ data }) {
const [filteredData, setFilteredData] = useState([]);
// Şimdi, 'data' referansının kararlılığı üst bileşenden nasıl geçtiğine bağlı.
const processData = useCallback(() => {
console.log('Veri işleniyor...');
const processed = data.map(item => ({ ...item, processed: true }));
setFilteredData(processed);
}, [data]);
return (
{filteredData.map(item => (
- {item.value} - {item.processed ? 'İşlendi' : ''}
))}
);
}
function App() {
const [dataConfig, setDataConfig] = useState({ items: ['Alpha', 'Beta'], version: 1 });
// DataDisplay'e geçirilen veri yapısını hafızaya al
const memoizedData = useMemo(() => {
return dataConfig.items.map((item, index) => ({ id: index, value: item }));
}, [dataConfig.items]); // Yalnızca dataConfig.items değişirse yeniden oluşturulur
return (
{/* Hafızaya alınmış veriyi geçir */}
);
}
Analiz: Bu geliştirilmiş örnekte, App
memoizedData
'yı oluşturmak için useMemo
kullanır. Bu memoizedData
dizisi yalnızca dataConfig.items
değiştiğinde yeniden oluşturulacaktır. Sonuç olarak, DataDisplay
'e geçirilen data
prop'u, öğeler değişmediği sürece kararlı bir referansa sahip olacaktır. Bu, DataDisplay
'deki useCallback
'in processData
'yı etkili bir şekilde hafızaya almasını sağlayarak gereksiz yeniden oluşturmaları önler.
4. Satır İçi Fonksiyonları Dikkatli Değerlendirin
Sadece aynı bileşen içinde kullanılan ve alt bileşenlerde yeniden render tetiklemeyen basit callback'ler için useCallback
'e ihtiyacınız olmayabilir. Satır içi fonksiyonlar birçok durumda tamamen kabul edilebilirdir. Eğer fonksiyon aşağıya geçirilmiyorsa veya katı referans eşitliği gerektiren bir şekilde kullanılmıyorsa, useCallback
'in kendi ek yükü bazen faydasından ağır basabilir.
Ancak, callback'leri optimize edilmiş alt bileşenlere (React.memo
), karmaşık işlemler için olay işleyicilerine veya sık sık çağrılabilecek ve dolaylı olarak yeniden render tetikleyebilecek fonksiyonlara geçerken useCallback
gerekli hale gelir.
5. Kararlı `setState` Ayarlayıcısı
React, state ayarlayıcı fonksiyonlarının (örneğin, setCount
, setStep
) kararlı olduğunu ve render'lar arasında değişmediğini garanti eder. Bu, linter'ınız ısrar etmedikçe (ki exhaustive-deps
bunu tamlık adına yapabilir) genellikle bunları bağımlılık dizinize eklemeniz gerekmediği anlamına gelir. Eğer callback'iniz yalnızca bir state ayarlayıcısı çağırıyorsa, genellikle onu boş bir bağımlılık dizisiyle hafızaya alabilirsiniz.
Örnek:
const increment = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // setCount kararlı olduğu için burada boş dizi kullanmak güvenlidir
6. Prop'lardan Gelen Fonksiyonları Yönetme
Eğer bileşeniniz bir callback fonksiyonunu prop olarak alıyorsa ve bileşeninizin bu prop fonksiyonunu çağıran başka bir fonksiyonu hafızaya alması gerekiyorsa, prop fonksiyonunu bağımlılık dizisine dahil etmelisiniz.
function ChildComponent({ onClick }) {
const handleClick = useCallback(() => {
console.log('Alt bileşen tıklamayı yönetiyor...');
onClick(); // onClick prop'unu kullanır
}, [onClick]); // onClick prop'unu dahil etmeli
return ;
}
Eğer üst bileşen her render'da onClick
için yeni bir fonksiyon referansı geçerse, o zaman ChildComponent
'in handleClick
'i de sık sık yeniden oluşturulacaktır. Bunu önlemek için, üst bileşenin de aşağıya geçtiği fonksiyonu hafızaya alması gerekir.
Küresel Kitle İçin Gelişmiş Hususlar
Küresel bir kitle için uygulamalar oluştururken, performans ve useCallback
ile ilgili birkaç faktör daha da belirgin hale gelir:
- Uluslararasılaştırma (i18n) ve Yerelleştirme (l10n): Eğer callback'leriniz uluslararasılaştırma mantığı içeriyorsa (örneğin, tarihleri, para birimlerini biçimlendirme veya mesajları çevirme), yerel ayarlar veya çeviri fonksiyonlarıyla ilgili herhangi bir bağımlılığın doğru yönetildiğinden emin olun. Yerel ayardaki değişiklikler, onlara dayanan callback'lerin yeniden oluşturulmasını gerektirebilir.
- Saat Dilimleri ve Bölgesel Veriler: Saat dilimlerini veya bölgeye özgü verileri içeren işlemler, bu değerler kullanıcı ayarlarına veya sunucu verilerine göre değişebiliyorsa, bağımlılıkların dikkatli bir şekilde yönetilmesini gerektirebilir.
- Aşamalı Web Uygulamaları (PWA'lar) ve Çevrimdışı Yetenekler: Kesintili bağlantıya sahip bölgelerdeki kullanıcılar için tasarlanmış PWA'lar için, verimli render ve minimum yeniden render'lar çok önemlidir.
useCallback
, ağ kaynakları sınırlı olduğunda bile sorunsuz bir deneyim sağlamada hayati bir rol oynar. - Bölgeler Arasında Performans Profili Oluşturma: Performans darboğazlarını belirlemek için React DevTools Profiler'ı kullanın. Uygulamanızın performansını sadece yerel geliştirme ortamınızda değil, aynı zamanda küresel kullanıcı tabanınızı temsil eden koşulları (örneğin, daha yavaş ağlar, daha az güçlü cihazlar) simüle ederek de test edin. Bu,
useCallback
bağımlılıklarının yanlış yönetimiyle ilgili gizli sorunları ortaya çıkarmanıza yardımcı olabilir.
Sonuç
useCallback
, fonksiyonları hafızaya alarak ve gereksiz yeniden render'ları önleyerek React uygulamalarını optimize etmek için güçlü bir araçtır. Ancak etkinliği tamamen bağımlılık dizisinin doğru yönetimine bağlıdır. Küresel geliştiriciler için bu bağımlılıklarda ustalaşmak sadece küçük performans kazanımları ile ilgili değildir; konumları, ağ hızları veya cihaz yetenekleri ne olursa olsun herkes için sürekli olarak hızlı, duyarlı ve güvenilir bir kullanıcı deneyimi sağlamakla ilgilidir.
Hook kurallarına özenle uyarak, ESLint gibi araçlardan yararlanarak ve ilkel ve referans türlerinin bağımlılıkları nasıl etkilediğinin farkında olarak, useCallback
'in tüm gücünden yararlanabilirsiniz. Callback'lerinizi analiz etmeyi, yalnızca gerekli bağımlılıkları dahil etmeyi ve uygun olduğunda nesneleri/dizileri hafızaya almayı unutmayın. Bu disiplinli yaklaşım, daha sağlam, ölçeklenebilir ve küresel olarak performanslı React uygulamalarına yol açacaktır.
Bu uygulamaları bugün uygulamaya başlayın ve dünya sahnesinde gerçekten parlayan React uygulamaları oluşturun!