Olayları Efektlerinden Ayırma

Olay yöneticileri yalnızca aynı etkileşimi tekrar gerçekleştirdiğinizde yeniden çalışır. Olay yöneticileri aksine, Efektler bir prop veya state değişkeni gibi okudukları bir değerin son render sırasında olduğundan farklı olması durumunda yeniden senkronize olur. Bazen, her iki davranışın bir karışımını da istersiniz: bazı değerlere yanıt olarak yeniden çalışan ancak diğerlerine yanıt vermeyen bir Efekt. Bu sayfa size bunu nasıl yapacağınızı öğretecek.

Bunları öğreneceksiniz

  • Bir olay yöneticisi ile bir Efekt arasında nasıl seçim yapılır?
  • Efektler neden reaktiftir ve olay yöneticileri değildir?
  • Efektinizin kodunun bir bölümünün reaktif olmamasını istediğinizde ne yapmalısınız?
  • Efekt olaylarının ne olduğu ve Efektlerinizden nasıl çıkarılacağı
  • Efekt olaylarını kullanarak Efektlerden en son sahne ve durum nasıl okunur?

Olay yöneticileri ve Efektler arasında seçim yapma

İlk olarak, olay yöneticileri ve Efektler arasındaki farkı özetleyelim.

Bir sohbet odası bileşeni oluşturduğunuzu düşünün. Gereksinimleriniz şuna benziyor:

  1. Bileşeniniz seçilen sohbet odasına otomatik olarak bağlanmalıdır.
  2. ”Gönder” düğmesine tıkladığınızda, sohbete bir mesaj göndermelidir.

Diyelim ki bunlar için kodu zaten uyguladınız, ancak nereye koyacağınızdan emin değilsiniz. Olay yöneticileri mi yoksa Efektler mi kullanmalısınız? Bu soruyu her yanıtlamanız gerektiğinde, neden kodun çalışması gerektiğini düşünün.

Olay yöneticileri belirli etkileşimlere yanıt olarak çalışır

Kullanıcının bakış açısına göre, bir mesajın gönderilmesi belirli bir “Gönder” düğmesine tıklandığı için olmalıdır. Mesajlarını başka bir zamanda veya başka bir nedenle gönderirseniz kullanıcı oldukça üzülecektir. İşte bu yüzden mesaj gönderme bir olay yöneticileri olmalıdır. Olay yöneticileri belirli etkileşimleri ele almanızı sağlar:

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// ...
function handleSendClick() {
sendMessage(message);
}
// ...
return (
<>
<input value={message} onChange={e => setMessage(e.target.value)} />
<button onClick={handleSendClick}>Gönder</button>;
</>
);
}

Bir olay yöneticileri ile sendMessage(message)ın sadece kullanıcı düğmeye bastığında çalışacağından emin olabilirsiniz.

Senkronizasyon gerektiğinde Efektler çalışır

Bileşeni sohbet odasına bağlı tutmanız gerektiğini de hatırlayın. Bu kod nereye gidecek?

Bu kodu çalıştırmak için neden belirli bir etkileşim değildir. Kullanıcının sohbet odası ekranına neden veya nasıl gittiği önemli değildir. Artık ona baktıklarına ve onunla etkileşime girebildiklerine göre, bileşenin seçilen sohbet sunucusuna bağlı kalması gerekir. Sohbet odası bileşeni uygulamanızın ilk ekranı olsa ve kullanıcı hiçbir etkileşim gerçekleştirmemiş olsa bile, yine de bağlanmanız gerekir. İşte bu yüzden bir Efekttir:

function ChatRoom({ roomId }) {
// ...
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [roomId]);
// ...
}

Bu kod sayesinde, kullanıcı tarafından gerçekleştirilen belirli etkileşimlerden bağımsız olarak, seçili sohbet sunucusuyla her zaman aktif bir bağlantı olduğundan emin olabilirsiniz. Kullanıcı ister sadece uygulamanızı açmış, ister farklı bir oda seçmiş ya da başka bir ekrana gidip geri dönmüş olsun, Efektiniz bileşenin o anda seçili olan odayla senkronize kalmasını ve gerektiğinde yeniden bağlanmasını sağlar.

import { useState, useEffect } from 'react';
import { createConnection, sendMessage } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  function handleSendClick() {
    sendMessage(message);
  }

  return (
    <>
      <h1>{roomId} odasına hoş geldiniz!</h1>
      <input value={message} onChange={e => setMessage(e.target.value)} />
      <button onClick={handleSendClick}>Gönder</button>
    </>
  );
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">genel</option>
          <option value="seyahat">seyahat</option>
          <option value="müzik">müzik</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Sohbeti kapat' : 'Sohbeti aç'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}

Reaktif değerler ve reaktif mantık

Sezgisel olarak, olay yöneticilerinin her zaman “manuel” olarak tetiklendiğini söyleyebilirsiniz, örneğin bir düğmeye tıklayarak. Öte yandan, Efektler “otomatiktir”: senkronize kalmak için gerektiği sıklıkta çalışır ve yeniden çalışırlar.

Bunu düşünmenin daha kesin bir yolu vardır.

Bileşeninizin gövdesi içinde bildirilen prop’lar, durum ve değişkenler reaktif değerler olarak adlandırılır. Bu örnekte, serverUrl reaktif bir değer değildir, ancak roomId ve message reaktif değerlerdir. Oluşturma veri akışına katılırlar:

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');

// ...
}

Bunlar gibi reaktif değerler yeniden oluşturma nedeniyle değişebilir. Örneğin, kullanıcı messageı düzenleyebilir veya bir açılır menüde farklı bir roomId seçebilir. Olay yöneticileri ve Efektler değişikliklere farklı şekilde yanıt verir:

  • Olay yöneticilerinin içindeki mantık * reaktif değildir.* Kullanıcı aynı etkileşimi (örneğin bir tıklama) tekrar gerçekleştirmedikçe tekrar çalışmayacaktır. Olay yöneticileri, değişikliklerine “tepki vermeden” reaktif değerleri okuyabilir.
  • Efektlerin içindeki mantık reaktiftir. Efektiniz reaktif bir değeri okuyorsa, bunu bir bağımlılık olarak belirtmeniz gerekir Ardından, bir yeniden oluşturma bu değerin değişmesine neden olursa, React, Efektinizin mantığını yeni değerle yeniden çalıştıracaktır.

Bu farkı göstermek için bir önceki örneğe geri dönelim.

Olay yöneticileri içindeki mantık reaktif değildir

Şu kod satırına bir göz atın. Bu mantık reaktif olmalı mı olmamalı mı?

// ...
sendMessage(message);
// ...

Kullanıcının bakış açısından, message’da yapılan bir değişiklik, mesaj göndermek istedikleri anlamına gelmez. Bu sadece kullanıcının yazmakta olduğu anlamına gelir. Başka bir deyişle, mesaj gönderen mantık reaktif olmamalıdır. Sadece reactive value değiştiği için tekrar çalışmamalıdır. Bu yüzden olay yöneticisine aittir:

function handleSendClick() {
sendMessage(message);
}

Olay yöneticileri reaktif değildir, bu nedenle sendMessage(message) yalnızca kullanıcı Gönder düğmesine tıkladığında çalışacaktır.

Efektlerin içindeki mantık reaktiftir

Şimdi bu satırlara geri dönelim:

// ...
const connection = createConnection(serverUrl, roomId);
connection.connect();
// ...

Kullanıcının bakış açısından, roomId’deki bir değişiklik farklı bir odaya bağlanmak istedikleri anlamına gelir. Başka bir deyişle, odaya bağlanma mantığı reaktif olmalıdır. Bu kod satırlarının reaktif değere “ayak uydurmasını” ve bu değer farklıysa yeniden çalışmasını istiyorsunuz. Bu yüzden bir Efekte aittir:

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId]);

Efektler reaktiftir, bu nedenle createConnection(serverUrl, roomId) ve connection.connect(), roomIdnin her farklı değeri için çalışacaktır. Efektiniz sohbet bağlantısını o anda seçili olan odayla senkronize tutar.

Reaktif olmayan mantığı Efektlerden çıkarma

Reaktif mantığı reaktif olmayan mantıkla karıştırmak istediğinizde işler daha da zorlaşır.

Örneğin, kullanıcı sohbete bağlandığında bir bildirim göstermek istediğinizi düşünün. Bildirimi doğru renkte gösterebilmek için mevcut temayı (koyu veya açık) prop’lardan okursunuz:

function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
// ...

Ancak, theme reaktif bir değerdir (yeniden oluşturma sonucunda değişebilir) ve bir Efekt tarafından okunan her reaktif değerin bağımlılığı olarak bildirilmesi gerekir Şimdi theme Efektinizin bir bağımlılığı olarak belirtmeniz gerekir:

function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Bağlandı!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // ✅ Tüm bağımlılıklar bildirildi
// ...

Bu örnekle oynayın ve bu kullanıcı deneyimindeki sorunu tespit edip edemeyeceğinizi görün:

import { useState, useEffect } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Baglandi!', theme);
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, theme]);

  return <h1>{roomId} odasına hoş geldiniz!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">genel</option>
          <option value="seyahat">seyahat</option>
          <option value="müzik">müzik</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Koyu tema kullanın
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

RoomId değiştiğinde, sohbet beklediğiniz gibi yeniden bağlanır. Ancak theme de bir bağımlılık olduğundan, koyu ve açık tema arasında her geçiş yaptığınızda sohbet ayrıca yeniden bağlanır. Bu hiç de iyi değil!

Başka bir deyişle, bir Efektin (reaktif olan) içinde olmasına rağmen bu satırın reaktif olmasını istemezsiniz:

// ...
showNotification('Bağlandı!', theme);
// ...

Bu reaktif olmayan mantığı, etrafındaki reaktif Efektten ayırmak için bir yola ihtiyacınız var.

Bir Efekt Olayı Bildirme

Yapım Halinde

Bu bölümde, React’in kararlı bir sürümünde henüz yayınlanmamış deneysel bir API açıklanmaktadır.

Bu reaktif olmayan mantığı Efektinizden çıkarmak için useEffectEvent adlı özel bir Hook kullanın:

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Baglandi!', theme);
});
// ...

Burada, onConnected bir Efekt olayı olarak adlandırılır. Efekt mantığınızın bir parçasıdır, ancak daha çok bir olay yöneticisi gibi davranır. İçindeki mantık reaktif değildir ve her zaman sahne ve durumunuzun en son değerlerini “görür”.

Artık onConnected Efekt olayını Efektinizin içinden çağırabilirsiniz:

function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Baglandi!', theme);
});

useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Tüm bagimliliklar bildirildi
// ...

Bu sorunu çözer. Efektinizin bağımlılıkları listesinden onConnected öğesini kaldırmanız gerektiğini unutmayın. Efekt olayları reaktif değildir ve bağımlılıklardan çıkarılmalıdır.

Yeni davranışın beklediğiniz gibi çalıştığını doğrulayın:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification('Baglandi!', theme);
  });

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      onConnected();
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>{roomId} odasına hoş geldiniz!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('genel');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Sohbet odasını seçin:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="genel">genel</option>
          <option value="seyahat">seyahat</option>
          <option value="müzik">müzik</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Koyu tema kullanın
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

Efekt olaylarını olay yöneticilerine çok benzer olarak düşünebilirsiniz. Temel fark, olay işleyicilerinin kullanıcı etkileşimlerine yanıt olarak çalışması, Efekt olaylarının ise sizin tarafınızdan Efektlerden tetiklenmesidir. Efekt olayları, Efektlerin tepkiselliği ile tepkisel olmaması gereken kod arasındaki “zinciri kırmanızı” sağlar.

Efekt olayları ile en son propları ve state okuma

Yapım Halinde

Bu bölümde, React’in kararlı bir sürümünde henüz yayınlanmamış deneysel bir API açıklanmaktadır.

Efekt olayları, bağımlılık bağlayıcısını bastırmak isteyebileceğiniz birçok modeli düzeltmenize olanak tanır.

Örneğin, sayfa ziyaretlerini günlüğe kaydetmek için bir Efektiniz olduğunu varsayalım:

function Page() {
useEffect(() => {
logVisit();
}, []);
// ...
}

Daha sonra sitenize birden fazla rota eklersiniz. Şimdi Page bileşeniniz geçerli yolu içeren bir url prop alır. urli logVisit çağrınızın bir parçası olarak iletmek istiyorsunuz, ancak bağımlılık linter`ı şikayet ediyor:

function Page({ url }) {
useEffect(() => {
logVisit(url);
}, []); // 🔴 React Hook useEffect'in eksik bir bağımlılığı var: 'url'
// ...
}

Kodun ne yapmasını istediğinizi düşünün. Her URL farklı bir sayfayı temsil ettiğinden, farklı URL’ler için ayrı bir ziyareti günlüğe kaydetmek istiyorsunuz. Başka bir deyişle, bu logVisit çağrısı urlye göre reaktif olmalıdır. Bu nedenle, bu durumda, bağımlılık linter’ını takip etmek ve url öğesini bir bağımlılık olarak eklemek mantıklıdır:

function Page({ url }) {
useEffect(() => {
logVisit(url);
}, [url]); // ✅ Tüm bagimliliklar bildirildi
// ...
}

Şimdi diyelim ki her sayfa ziyaretiyle birlikte alışveriş sepetindeki ürün sayısını da dahil etmek istiyorsunuz:

function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;

useEffect(() => {
logVisit(url, numberOfItems);
}, [url]); // 🔴 React Hook useEffect'in eksik bir bağımlılığı var: 'numberOfItems'
// ...
}

Effect içinde numberOfItems kullandınız, bu nedenle linter sizden bunu bir bağımlılık olarak eklemenizi istiyor. Ancak, logVisit çağrısının numberOfItems ile ilgili olarak reaktif olmasını istemezsiniz. Eğer kullanıcı alışveriş sepetine bir şey koyarsa ve sayıOfItems değişirse, bu kullanıcının sayfayı tekrar ziyaret ettiği anlamına gelmez. Başka bir deyişle, sayfayı ziyaret etmek bir anlamda bir “olaydır”. Zaman içinde kesin bir anda gerçekleşir.

Kodu iki parçaya bölün:

function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;

const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});

useEffect(() => {
onVisit(url);
}, [url]); // ✅ Tüm bagimliliklar bildirildi
// ...
}

Burada, onVisit bir Efekt olayıdır. İçindeki kod reaktif değildir. Bu nedenle numberOfItems (veya başka herhangi bir reaktif değer!) kullanabilir ve bunun çevredeki kodun değişikliklerde yeniden yürütülmesine neden olacağından endişe duymazsınız.

Öte yandan, Efektin kendisi reaktif kalır. Efekt içindeki kod url özelliğini kullanır, bu nedenle Efekt her yeniden oluşturmadan sonra farklı bir url ile yeniden çalışacaktır. Bu da onVisit Efekt olayını çağıracaktır.

Sonuç olarak, url öğesindeki her değişiklik için logVisit öğesini çağıracak ve her zaman en son numberOfItems öğesini okuyacaksınız. Ancak, numberOfItems kendi başına değişirse, bu kodun yeniden çalışmasına neden olmaz.

Not

Hiçbir argüman olmadan onVisit() fonksiyonunu çağırıp içindeki urlyi okuyup okuyamayacağınızı merak ediyor olabilirsiniz:

const onVisit = useEffectEvent(() => {
logVisit(url, numberOfItems);
});

useEffect(() => {
onVisit();
}, [url]);

Bu işe yarayabilir, ancak bu urlyi Efekt olayına açıkça aktarmak daha iyidir. Efekt olayınıza bir argüman olarak url geçerek, farklı bir url ile bir sayfayı ziyaret etmenin kullanıcının bakış açısından ayrı bir “olay” oluşturduğunu söylemiş olursunuz. visitedUrl, gerçekleşen “olayın” bir parçasıdı:

const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});

useEffect(() => {
onVisit(url);
}, [url]);

Efekt olayınızın visitedUrl öğesini açıkça “sorduğu” için, artık url öğesini Efektin bağımlılıklarından yanlışlıkla kaldıramazsınız. Eğer url bağımlılığını kaldırırsanız (farklı sayfa ziyaretlerinin tek bir ziyaret olarak sayılmasına neden olursanız), linter sizi bu konuda uyaracaktır. onVisitin url ile ilgili olarak reaktif olmasını istersiniz, bu nedenle urlyi içeriden okumak yerine (reaktif olmayacağı yerde), Efektinizden *geçirirsiniz.

Bu, özellikle Efekt içinde bazı asenkron mantık varsa önemli hale gelir:

const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});

useEffect(() => {
setTimeout(() => {
onVisit(url);
}, 5000); // Ziyaretleri kaydetmeyi geciktirin
}, [url]);

Burada, onVisit içindeki url en son urlye karşılık gelir (bu zaten değişmiş olabilir), ancak visitedUrl başlangıçta bu Efektin (ve bu onVisit çağrısının) çalışmasına neden olan urlye karşılık gelir.

Derinlemesine İnceleme

Bunun yerine bağımlılık linterini bastırmak doğru olur mu?

Mevcut kod tabanlarında bazen lint kuralının bu şekilde bastırıldığını görebilirsiniz:

function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;

useEffect(() => {
logVisit(url, numberOfItems);
// 🔴 Linteri bu şekilde bastırmaktan kaçının:
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
// ...
}

UseEffectEvent React’in kararlı bir parçası haline geldikten sonra, kuralın asla bastırılmamasını öneriyoruz.

Kuralı bastırmanın ilk dezavantajı, Efektinizin kodunuza eklediğiniz yeni bir reaktif bağımlılığa “tepki vermesi” gerektiğinde React’in artık sizi uyarmayacak olmasıdır. Önceki örnekte, React size bunu yapmanızı hatırlattığı için bağımlılıklara url eklediniz. Linter’ı devre dışı bırakırsanız, bu Efekt üzerinde gelecekte yapacağınız düzenlemeler için artık böyle hatırlatıcılar almayacaksınız. Bu da hatalara yol açar.

Burada, bağlayıcıyı bastırmanın neden olduğu kafa karıştırıcı bir hata örneği verilmiştir. Bu örnekte, handleMove fonksiyonunun, noktanın imleci takip edip etmeyeceğine karar vermek için mevcut canMove durum değişkeni değerini okuması gerekmektedir. Ancak, handleMove içinde canMove her zaman true değerindedir.

Nedenini anlayabiliyor musunuz?

import { useState, useEffect } from 'react';

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [canMove, setCanMove] = useState(true);

  function handleMove(e) {
    if (canMove) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
  }

  useEffect(() => {
    window.addEventListener('pointermove', handleMove);
    return () => window.removeEventListener('pointermove', handleMove);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <label>
        <input type="checkbox"
          checked={canMove}
          onChange={e => setCanMove(e.target.checked)}
        />
        Noktanın hareket etmesine izin verilir
      </label>
      <hr />
      <div style={{
        position: 'absolute',
        backgroundColor: 'pink',
        borderRadius: '50%',
        opacity: 0.6,
        transform: `translate(${position.x}px, ${position.y}px)`,
        pointerEvents: 'none',
        left: -20,
        top: -20,
        width: 40,
        height: 40,
      }} />
    </>
  );
}

Bu kodla ilgili sorun, bağımlılık linterinin bastırılmasıdır. Bastırmayı kaldırırsanız, bu Efektin handleMove fonksiyonuna bağlı olması gerektiğini görürsünüz. Bu mantıklıdır: handleMove bileşen gövdesi içinde bildirilir, bu da onu reaktif bir değer yapar. Her reaktif değer bir bağımlılık olarak belirtilmelidir, aksi takdirde zaman içinde eskimesi olasıdır!

Orijinal kodun yazarı, Effect’in herhangi bir reaktif değere bağlı olmadığını ([]) söyleyerek React’e “yalan söylemiştir”. Bu nedenle React, canMove değiştikten sonra (ve onunla birlikte handleMove) Efekti yeniden senkronize etmedi. React, Efekti yeniden senkronize etmediği için, dinleyici olarak eklenen handleMove, ilk render sırasında oluşturulan handleMove fonksiyonudur. İlk render sırasında canMove true idi, bu yüzden ilk renderdan handleMove sonsuza kadar bu değeri görecektir.

Linter’ı asla bastırmazsanız, eski değerlerle ilgili sorunları asla görmezsiniz.

UseEffectEvent ile linter`a “yalan söylemeye” gerek yoktur ve kod beklediğiniz gibi çalışır:

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [canMove, setCanMove] = useState(true);

  const onMove = useEffectEvent(e => {
    if (canMove) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
  });

  useEffect(() => {
    window.addEventListener('pointermove', onMove);
    return () => window.removeEventListener('pointermove', onMove);
  }, []);

  return (
    <>
      <label>
        <input type="checkbox"
          checked={canMove}
          onChange={e => setCanMove(e.target.checked)}
        />
        Noktanın hareket etmesine izin verilir
      </label>
      <hr />
      <div style={{
        position: 'absolute',
        backgroundColor: 'pink',
        borderRadius: '50%',
        opacity: 0.6,
        transform: `translate(${position.x}px, ${position.y}px)`,
        pointerEvents: 'none',
        left: -20,
        top: -20,
        width: 40,
        height: 40,
      }} />
    </>
  );
}

Bu, useEffectEventin her zaman doğru çözüm olduğu anlamına gelmez. Bunu yalnızca reaktif olmasını istemediğiniz kod satırlarına uygulamalısınız. Yukarıdaki sanal alanda, Efekt kodunun canMove ile ilgili olarak reaktif olmasını istemediniz. Bu yüzden bir Efekt olayı çıkarmak mantıklı oldu.

Linteri bastırmanın diğer doğru alternatifleri için Efekt Bağımlılıklarını Kaldırma bölümünü okuyun.

Efekt Olaylarının Sınırlamaları

Yapım Halinde

Bu bölümde, React’in kararlı bir sürümünde henüz yayınlanmamış deneysel bir API açıklanmaktadır.

Efekt Olayları, kullanma şekliniz açısından oldukça sınırlıdır:

  • Sadece Efektlerin içinden çağırın.
  • Asla diğer bileşenlere veya Hook’lara aktarmayın.

Örneğin, bir Efekt olayını şu şekilde bildirmeyin ve geçirmeyin:

function Timer() {
const [count, setCount] = useState(0);

const onTick = useEffectEvent(() => {
setCount(count + 1);
});

useTimer(onTick, 1000); // 🔴 Kaçının: Efekt olaylarini geçmek

return <h1>{count}</h1>
}

function useTimer(callback, delay) {
useEffect(() => {
const id = setInterval(() => {
callback();
}, delay);
return () => {
clearInterval(id);
};
}, [delay, callback]); // Bağımlılıklarda "callback" fonksiyonunu belirtmeniz gerekiyor
}

Bunun yerine, her zaman Efekt olaylarını doğrudan onları kullanan Efektlerin yanında bildirin:

function Timer() {
const [count, setCount] = useState(0);
useTimer(() => {
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>
}

function useTimer(callback, delay) {
const onTick = useEffectEvent(() => {
callback();
});

useEffect(() => {
const id = setInterval(() => {
onTick(); // ✅ İyi: Yalnızca bir Efektin içinde yerel olarak çağrılır
}, delay);
return () => {
clearInterval(id);
};
}, [delay]); // Bağımlılık olarak "onTick" (bir Efekt olayı) belirtmeye gerek yok
}

Efekt olayları, Efekt kodunuzun reaktif olmayan “parçalarıdır”. Kendilerini kullanan Efektin yanında olmalıdırlar.

Özet

  • Olay yöneticileri belirli etkileşimlere yanıt olarak çalışır.
  • Efektler, senkronizasyon gerektiğinde çalışır.
  • Olay yöneticilerinin içindeki mantık reaktif değildir.
  • Efektlerin içindeki mantık reaktiftir.
  • Reaktif olmayan mantığı Efektlerden Efekt olaylarına taşıyabilirsiniz.
  • Efekt olaylarını yalnızca Efektlerin içinden çağırın.
  • Efekt olaylarını diğer bileşenlere veya Hook’lara aktarmayın.

Problem 1 / 4:
Güncellenmeyen bir değişkeni düzeltme

Bu Timer bileşeni her saniye artan bir count durum değişkenini tutar. Artan değer increment durum değişkeninde saklanır. Artı ve eksi düğmeleriyle increment değişkenini kontrol edebilirsiniz.

Ancak, artı düğmesine kaç kez tıklarsanız tıklayın, sayaç yine de her saniye bir artar. Bu kodda yanlış olan nedir? Efektin kodu içinde increment neden her zaman 1e eşittir? Hatayı bulun ve düzeltin.

import { useState, useEffect } from 'react';

export default function Timer() {
  const [count, setCount] = useState(0);
  const [increment, setIncrement] = useState(1);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + increment);
    }, 1000);
    return () => {
      clearInterval(id);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <>
      <h1>
        Sayaç: {count}
        <button onClick={() => setCount(0)}>Reset</button>
      </h1>
      <hr />
      <p>
        Saniyedeki artış miktari:
        <button disabled={increment === 0} onClick={() => {
          setIncrement(i => i - 1);
        }}></button>
        <b>{increment}</b>
        <button onClick={() => {
          setIncrement(i => i + 1);
        }}>+</button>
      </p>
    </>
  );
}