javascript - ESLint ile useEffect'i bir kez çalıştırmak
Bu bir React tarzı sorudur.
Özet React'in useState içindeki set fonksiyonunu alın. Eğer bu fonksiyon her render’da "değişse", useEffect içinde, efekti yalnızca bir kez çalıştıracak şekilde kullanmanın en iyi yolu nedir?
Açıklama Bir kez çalışması gereken bir useEffect’imiz var (Firebase verilerini getiriyor) ve ardından bu veriyi uygulama durumuna ayarlıyor.
İşte basitleştirilmiş bir örnek. Biz little-state-machine kullanıyoruz ve updateProfileData, JSON durumumuzun "profile" bölümünü güncellemek için bir eylemdir.
const MyComponent = () => {
const { actions, state } = useStateMachine({updateProfileData, updateLoginData});
useEffect(() => {
const get_data_async = () => {
const response = await get_firebase_data();
actions.updateProfileData( {user: response.user} );
};
get_data_async();
}, []);
return (
<p>Hello, world!</p>
);
}
Ancak, ESLint bunu sevmez: React Hook useEffect eksik bir bağımlılığa sahip: 'actions'. Ya bunu dahil edin ya da bağımlılık dizisini kaldırın
Mantıklı. Sorun şu: actions her yeniden renderda değişiyor ve durumu güncellemek yeniden rendera neden oluyor. Sonsuz döngü.
updateProfileData değerine başvurma da işe yaramıyor.
Böyle bir şey kullanmak iyi bir uygulama mıdır: tek seferlik useEffect?
Çalışabilir/çalışmayabilir kavram kodu:
const useSingleEffect = (fxn, dependencies) => {
const [ hasRun, setHasRun ] = useState(false);
useEffect(() => {
if(!hasRun) {
fxn();
setHasRun(true);
}
}, [...dependencies, hasRun]);
};
// then, in a component:
const MyComponent = () => {
const { actions, state } = useStateMachine({updateProfileData, updateLoginData});
useSingleEffect(async () => {
const response = await get_firebase_data();
actions.updateProfileData( {user: response.user} );
}, [actions]);
return (
<p>Hello, world!</p>
);
}
Ama o noktada, bağımlılık dizisine neden hatta önem verilsin? Gösterilen ilk kod çalışıyor ve mantıklı (closure'lar doğru değişkenleri/fonksiyonları garanti ediyor), ESLint sadece bunu yapmamayı öneriyor.
React useState öğesinin ikinci dönüş değeri her render'da değişiyormuş gibi:
const [ foo, setFoo ] = useState(null);
// ^ this one
Eğer bu her renderda değişseydi, onunla bir Effect'i bir kez nasıl çalıştırırdık?
Cevap #1
Satır için eslint kuralını görmezden gel
Eğer efekti sadece bileşen yüklendiğinde tam olarak bir kez çalışmasını gerçekten istiyorsanız, boş bir bağımlılık dizisi kullanmanız doğrudur. Bunu göz ardı etmek için o satırdaki eslint kuralını devre dışı bırakabilirsiniz.
useEffect(() => {
const get_data_async = () => {
const response = await get_firebase_data();
actions.updateProfileData( {user: response.user} );
};
get_data_async();
// NOTE: Run effect once on component mount, please
// recheck dependencies if effect is updated.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Not: Eğer daha sonra efekti güncellerseniz ve diğer bağımlılıklardan sonra çalışması gerekirse, bu devre dışı bırakılmış yorum gelecekteki hataları potansiyel olarak gizleyebilir, bu yüzden kurulu linting kuralını geçersiz kılma sebebini belirtmek için oldukça açık bir yorum bırakmanızı öneririm.
Özel kanca mantığı
Alternatif olarak, ilk render'ı belirtmek için bir React ref'i kullanabilirsiniz. Bunu, değeri tutmak için bazı state'leri kullanmaktan daha tercih edilir çünkü state'i güncellemek gereksiz render tetikleyebilir.
const MyComponent = () => {
const { actions, state } = useStateMachine({updateProfileData, updateLoginData});
const initialRenderRef = useRef(true);
useEffect(() => {
const get_data_async = () => {
const response = await get_firebase_data();
actions.updateProfileData( {user: response.user} );
};
if (initialRenderRef.current) {
initialRenderRef.current = false;
get_data_async();
}
}, [actions]); // <-- and any other dependencies the linter complains about
return (
<p>Hello, world!</p>
);
}
Ve evet, kesinlikle bunu eğer kod tabanınızda tekrar tekrar kullanılan bir desen ise, özel bir kanca içine "tek seferlik çalıştırma mantığını" dahil edebilirsiniz.
