Projelerinizde Kullanabileceğiniz Faydalı React Hook'ları
Yayınlanan: 2022-03-10Kancalar , React özelliklerine bağlanmanıza veya bunlardan yararlanmanıza izin veren basit işlevlerdir. Sınıf bileşenlerinin üç ana sorununu ele almak için React Conf 2018'de tanıtıldılar: sarmalayıcı cehennem, devasa bileşenler ve kafa karıştırıcı sınıflar. Kancalar, React fonksiyonel bileşenlerine güç vererek, onunla bütün bir uygulamanın geliştirilmesini mümkün kılar.
Sınıf bileşenlerinin yukarıda belirtilen problemleri birbirine bağlıdır ve biri olmadan diğeri çözülürse daha fazla problem ortaya çıkabilir. Neyse ki, kancalar, React'te daha ilginç özelliklere yer açarken tüm sorunları basit ve verimli bir şekilde çözdü. Kancalar, halihazırda var olan React kavramlarının ve sınıflarının yerini almaz, yalnızca onlara doğrudan erişmek için bir API sağlarlar.
React ekibi, React 16.8'de birkaç kanca tanıttı. Ancak, uygulamanızda üçüncü taraf sağlayıcıların kancalarını da kullanabilir veya hatta özel bir kanca oluşturabilirsiniz. Bu eğitimde, React'teki bazı kullanışlı kancalara ve bunların nasıl kullanılacağına göz atacağız. Her kancanın birkaç kod örneğini inceleyeceğiz ve ayrıca nasıl özel bir kanca oluşturacağınızı keşfedeceğiz.
Not: Bu eğitim, Javascript (ES6+) ve React hakkında temel bir anlayış gerektirir.
Kancaların Arkasındaki Motivasyon
Daha önce belirtildiği gibi, kancalar üç sorunu çözmek için yaratıldı: sarmalayıcı cehennemi, devasa bileşenler ve kafa karıştırıcı sınıflar. Bunların her birine daha ayrıntılı olarak bakalım.
sarıcı cehennem
Sınıf bileşenleriyle oluşturulmuş karmaşık uygulamalar, kolayca sarmalayıcı cehennemine girer. Uygulamayı React Dev Tools'da incelerseniz, derinlemesine iç içe geçmiş bileşenleri fark edeceksiniz. Bu, bileşenlerle çalışmayı veya bunların hatalarını ayıklamayı çok zorlaştırır. Bu problemler daha yüksek dereceli bileşenler ve render props ile çözülebilse de, kodunuzu biraz değiştirmenizi gerektirir. Bu, karmaşık bir uygulamada karışıklığa yol açabilir.
Kancaların paylaşılması kolaydır, mantığı yeniden kullanmadan önce bileşenlerinizi değiştirmeniz gerekmez.
Buna güzel bir örnek, Redux mağazasına abone olmak için Redux connect
Higher Order Component'in (HOC) kullanılmasıdır. Tüm HOC'lerde olduğu gibi, connect HOC'yi kullanmak için, bileşeni tanımlanmış üst düzey işlevlerle birlikte dışa aktarmanız gerekir. connect
durumunda, bu formda bir şeye sahip olacağız.
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
mapStateToProps
ve mapDispatchToProps
tanımlanacak işlevlerdir.
Hooks döneminde ise Redux useSelector
ve useDispatch
kancalarını kullanarak aynı sonucu düzgün ve özlü bir şekilde kolayca elde edebilirsiniz.
Büyük Bileşenler
Sınıf bileşenleri genellikle yan etkiler ve durum bilgisi içeren mantık içerir. Uygulamanın karmaşıklığı arttıkça, bileşenin dağınık ve kafa karıştırıcı hale gelmesi yaygındır. Bunun nedeni, yan etkilerin işlevsellikten çok yaşam döngüsü yöntemleriyle organize edilmesinin beklenmesidir. Bileşenleri bölmek ve daha basit hale getirmek mümkün olsa da, bu genellikle daha yüksek düzeyde bir soyutlama sağlar.
Kancalar, yan etkileri işlevselliğe göre düzenler ve bir bileşeni işlevselliğe göre parçalara ayırmak mümkündür.
Kafa Karıştırıcı Sınıflar
Sınıflar genellikle işlevlerden daha zor bir kavramdır. React sınıfı tabanlı bileşenler ayrıntılıdır ve yeni başlayanlar için biraz zordur. Javascript'te yeniyseniz, sınıflara kıyasla hafif sözdizimleri nedeniyle başlangıç için daha kolay işlevler bulabilirsiniz. Sözdizimi kafa karıştırıcı olabilir; bazen, kodu kırabilecek bir olay işleyicisinin bağlanmasını unutmak mümkündür.
React, bu sorunu işlevsel bileşenler ve kancalarla çözerek geliştiricilerin kod sözdizimi yerine projeye odaklanmasını sağlar.
Örneğin, aşağıdaki iki React bileşeni tamamen aynı sonucu verecektir.
import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }
İlk örnek sınıf tabanlı bir bileşen, ikincisi ise işlevsel bir bileşendir. Bu basit bir örnek olmasına rağmen, ilk örneğin ikinci ile karşılaştırıldığında ne kadar sahte olduğuna dikkat edin.
Hooks Sözleşmesi ve Kuralları
Çeşitli kancaları incelemeden önce, bunlar için geçerli olan sözleşmeye ve kurallara bir göz atmak faydalı olabilir. İşte kancalar için geçerli olan kurallardan bazıları.
- Kancaların adlandırma kuralı,
use
önekiyle başlamalıdır. BöyleceuseState
,useEffect
, vs.'ye sahip olabiliriz. Atom ve VSCode gibi modern kod düzenleyicileri kullanıyorsanız, ESLint eklentisi React kancaları için çok kullanışlı bir özellik olabilir. Eklenti, en iyi uygulamalar hakkında faydalı uyarılar ve ipuçları sağlar. - Hook'lar, return ifadesinden önce bir bileşenin en üst seviyesinde çağrılmalıdır. Koşullu bir ifade, döngü veya iç içe işlevler içinde çağrılamazlar.
- Kancalar bir React işlevinden çağrılmalıdır (bir React bileşeninin veya başka bir kancanın içinde). Vanilla JS işlevinden çağrılmamalıdır.
useState
Kancası
useState
kancası, en temel ve kullanışlı React kancasıdır. Diğer yerleşik kancalar gibi, bu kancanın da uygulamamızda kullanılabilmesi için react
içe aktarılması gerekir.
import {useState} from 'react'
Durumu başlatmak için hem durumu hem de güncelleyici işlevini bildirmeli ve bir başlangıç değeri iletmeliyiz.
const [state, updaterFn] = useState('')
Durumumuzu ve güncelleyici işlevimizi istediğimiz gibi çağırmakta özgürüz, ancak geleneksel olarak dizinin ilk öğesi durumumuz, ikinci öğe ise güncelleyici işlevi olacaktır. Güncelleyici fonksiyonumuzun önüne deve durumu formunda devletimizin adının geldiği önek seti ile eklemek yaygın bir uygulamadır.
Örneğin, sayım değerlerini tutacak bir durum belirleyelim.
const [count, setCount] = useState(0)
count
durumumuzun ilk değerinin boş bir dize değil 0
olarak ayarlandığına dikkat edin. Başka bir deyişle, durumumuzu sayı, dize, boolean, dizi, nesne ve hatta BigInt gibi herhangi bir JavaScript değişkenine başlatabiliriz. useState
kancasıyla ayar durumları ve sınıf tabanlı bileşen durumları arasında açık bir fark vardır. useState
kancasının, durum değişkenleri olarak da bilinen bir dizi döndürmesi dikkat çekicidir ve yukarıdaki örnekte diziyi state
ve updater
işlevine dönüştürdük.
Yeniden Oluşturma Bileşenleri
useState
kancasıyla durumları ayarlamak, karşılık gelen bileşenin yeniden oluşturulmasına neden olur. Ancak bu, yalnızca React önceki veya eski durum ile yeni durum arasında bir fark tespit ederse gerçekleşir. React, Javascript Object.is
algoritmasını kullanarak durum karşılaştırmasını yapar.
useState
ile Durumları Ayarlama
count
durumumuz, yeni değeri aşağıdaki gibi setCount
updater işlevine geçirerek yeni durum değerlerine ayarlanabilir setCount(newValue)
.
Bu yöntem, önceki durum değerine başvurmak istemediğimizde çalışır. Bunu yapmak istiyorsak, setCount
işlevine bir işlev iletmemiz gerekir.
Bir butona her tıklandığında count
değişkenimize 5 eklemek istediğimizi varsayarsak, aşağıdakileri yapabiliriz.
import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample
Yukarıdaki kodda, ilk olarak useState
kancasını içe react
ve ardından count
durumunu varsayılan 0 değeriyle başlattık. Düğmeye her tıklandığında count
değerini 5 artırmak için bir onClick
işleyicisi oluşturduk. Sonra sonucu bir h1
etiketinde gösterdik.
Dizileri ve Nesne Durumlarını Ayarlama
Diziler ve nesneler için durumlar, diğer veri türleriyle hemen hemen aynı şekilde ayarlanabilir. Ancak, mevcut değerleri korumak istiyorsak, durumları ayarlarken ES6 yayılma operatörünü kullanmamız gerekir.
Javascript'teki yayılma operatörü, zaten var olan bir nesneden yeni bir nesne oluşturmak için kullanılır. Bu, burada yararlıdır çünkü React
, durumları Object.is
işlemiyle karşılaştırır ve ardından buna göre yeniden oluşturur.
Düğme tıklandığında durumları ayarlamak için aşağıdaki kodu ele alalım.
import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample
Yukarıdaki kodda, arr
ve obj
olmak üzere iki durum oluşturduk ve bunları sırasıyla bazı dizi ve nesne değerlerine başlattık. Ardından, sırasıyla dizinin ve nesnenin durumlarını ayarlamak için handleArrClick
ve handleObjClick
adlı onClick
işleyicileri oluşturduk. handleArrClick
setArr
ve zaten var olan dizi değerlerini yaymak ve ona newArr
eklemek için ES6 spread operatörünü kullanırız.
Aynı şeyi handleObjClick
işleyicisi için de yaptık. Burada setObj
adını verdik, mevcut nesne değerlerini ES6 yayılma operatörünü kullanarak yaydık ve name
ve age
değerlerini güncelledik.
useState
Async Doğası
Daha önce gördüğümüz gibi, updater fonksiyonuna yeni bir değer ileterek useState
ile durumları belirledik. Güncelleyici birden çok kez çağrılırsa, yeni değerler bir kuyruğa eklenir ve JavaScript Object.is
karşılaştırması kullanılarak buna göre yeniden oluşturma yapılır.
Durumlar eşzamansız olarak güncellenir. Bu, yeni durumun önce bir bekleme durumuna eklendiği ve ardından durumun güncellendiği anlamına gelir. Böylece, ayarlandığı hemen duruma erişirseniz, eski durum değerini almaya devam edebilirsiniz.
Bu davranışı gözlemlemek için aşağıdaki örneği ele alalım.
Yukarıdaki kodda useState
kancasını kullanarak bir count
durumu oluşturduk. Ardından, düğmeye her tıklandığında count
durumunu artırmak için bir onClick
işleyicisi oluşturduk. h2
etiketinde gösterildiği gibi, count
durumunun artmasına rağmen, önceki durumun konsolda günlüğe kaydedildiğini gözlemleyin. Bu, kancanın zaman uyumsuz doğasından kaynaklanmaktadır.
Eğer yeni durumu elde etmek istiyorsak, bunu asenkron fonksiyonlarda yaptığımız gibi halledebiliriz. İşte bunu yapmanın bir yolu.
Burada, güncellenen sayım değerini saklamak için oluşturulan newCountValue
depoladık ve ardından count
durumunu güncellenen değerle ayarladık. Ardından güncellenen sayım değerini konsola kaydettik.
Kullanım useEffect
Kancası
useEffect
, çoğu projede kullanılan bir diğer önemli React kancasıdır. Sınıf tabanlı bileşenin componentDidMount
, componentWillUnmount
ve componentDidUpdate
yaşam döngüsü yöntemlerine benzer bir şey yapar. useEffect
bize uygulama üzerinde yan etkileri olabilecek zorunlu kodlar yazmamıza olanak sağlıyor. Bu tür etkilerin örnekleri arasında günlüğe kaydetme, abonelikler, mutasyonlar vb.
Kullanıcı, useEffect
ne zaman çalışacağına karar verebilir, ancak ayarlanmazsa, yan etkiler her oluşturma veya yeniden oluşturma işleminde çalışır.
Aşağıdaki örneği düşünün.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }
Yukarıdaki kodda, useEffect
sadece count
kaydettik. Bu, bileşenin her oluşturulmasından sonra çalışacaktır.
Bazen, bileşenimizde kancayı bir kez (montajda) çalıştırmak isteyebiliriz. Bunu, useEffect
kancasına ikinci bir parametre sağlayarak başarabiliriz.
import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }
useEffect
kancasının iki parametresi vardır, ilk parametre çalıştırmak istediğimiz fonksiyondur, ikinci parametre ise bir dizi bağımlılıktır. İkinci parametre sağlanmazsa, kanca sürekli çalışacaktır.
Kancanın ikinci parametresine boş bir köşeli ayraç ileterek, useEffect
kancasını yuva üzerinde yalnızca bir kez çalıştırmasını söyleriz. Bileşen bağlandığında sayı 0'dan 1'e bir kez güncelleneceğinden, bu h1
etiketinde 1
değerini gösterecektir.
Bazı bağımlı değerler değiştiğinde yan etkimizi de çalıştırabiliriz. Bu, bu değerleri bağımlılıklar listesinde geçirerek yapılabilir.
Örneğin, aşağıdaki gibi count
değiştiğinde useEffect
çalıştırabiliriz.
import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;
Yukarıdaki useEffect
, bu iki koşuldan biri karşılandığında çalışacaktır.
- Montajda — bileşen oluşturulduktan sonra.
-
count
değeri değiştiğinde.
Bağlamada, console.log
ifadesi çalışacak ve count
0'a günlüğe kaydedecektir. count
güncellendiğinde, ikinci koşul karşılanır, bu nedenle useEffect
yeniden çalışır, düğme her tıklandığında bu devam eder.
useEffect
için ikinci argümanı sağladığımızda, tüm bağımlılıkları ona aktarmamız beklenir. ESLINT
kuruluysa, parametre listesine herhangi bir bağımlılık geçirilmediyse tiftik hatası gösterecektir. Bu ayrıca, özellikle aktarılmayan parametrelere bağlıysa, yan etkinin beklenmedik şekilde davranmasına neden olabilir.
Efekti Temizlemek
useEffect
ayrıca, bileşenin bağlantısını kesmeden önce kaynakları temizlememize de olanak tanır. Bu, bellek sızıntılarını önlemek ve uygulamayı daha verimli hale getirmek için gerekli olabilir. Bunu yapmak için, kancanın sonundaki temizleme işlevini döndürürüz.
useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })
Yukarıdaki useEffect
kancası, bileşen mounted
edildiğinde takılı günlüğe kaydedilir. Unmounting… buradaki temizleme , bileşen bağlantısı kesildiğinde günlüğe kaydedilir. Bu, bileşen kullanıcı arayüzünden kaldırıldığında gerçekleşebilir.
Temizleme işlemi tipik olarak aşağıdaki formu takip eder.
useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })
useEffect
abonelikleri için çok fazla kullanım örneği bulamasanız da, abonelikler ve zamanlayıcılarla uğraşırken kullanışlıdır. Özellikle, web soketleriyle uğraşırken, kaynaktan tasarruf etmek ve bileşen ayrıldığında performansı artırmak için ağ aboneliğinden çıkmanız gerekebilir.
useEffect
ile Verileri Alma ve Yeniden Getirme
useEffect
kancasının en yaygın kullanım durumlarından biri, bir API'den veri almak ve önceden getirmektir.
Bunu göstermek için, useEffect
kancasıyla verileri almak için JSONPlaceholder
oluşturduğum sahte kullanıcı verilerini kullanacağız.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }
Yukarıdaki kodda useState
kancasını kullanarak bir users
durumu oluşturduk. Daha sonra Axios kullanarak bir API'den veri aldık. Bu eşzamansız bir işlemdir ve bu nedenle eşzamansız/bekleme işlevini kullandık, noktayı sonra sözdizimini de kullanabilirdik. Bir kullanıcı listesi getirdiğimizden, verileri görüntülemek için basitçe haritayı çıkardık.
Kancaya boş bir parametre ilettiğimize dikkat edin. Bu, bileşen monte edildiğinde yalnızca bir kez çağrılmasını sağlar.
Ayrıca bazı koşullar değiştiğinde verileri yeniden getirebiliriz. Bunu aşağıdaki kodda göstereceğiz.
import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }
Burada iki useEffect
kancası oluşturduk. İlkinde, tüm kullanıcıları API'mızdan almak için nokta sonra sözdizimini kullandık. Bu, kullanıcı sayısını belirlemek için gereklidir.
Daha sonra, id
dayalı bir kullanıcı elde etmek için başka bir useEffect
kancası oluşturduk. Bu useEffect
, kimlik değiştiğinde verileri yeniden getirecektir. Bunu sağlamak için bağımlılık listesinde id
geçtik.
Daha sonra, butonlara her tıklandığında id
değerini güncellemek için fonksiyonlar oluşturduk. useEffect
id
çalışacak ve verileri yeniden getirecektir.
İstersek, Axios'ta söze dayalı belirteci temizleyebilir veya iptal edebiliriz, bunu yukarıda tartışılan temizleme yöntemiyle yapabiliriz.
useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);
Burada, Axios'un belirtecini axios.get
ikinci bir parametre olarak ilettik. Bileşen bağlantısı kaldırıldığında, kaynak nesnenin iptal yöntemini çağırarak aboneliği iptal ettik.
Kullanım useReducer
Kancası
useReducer
kancası, useState
kancasına benzer bir şey yapan çok kullanışlı bir React kancasıdır. React belgelerine göre, bu kanca, useState
kancasından daha karmaşık mantığı işlemek için kullanılmalıdır. useState
kancasıyla uygulandığını belirtmekte fayda var.
Kanca argüman olarak bir indirgeyici alır ve isteğe bağlı olarak ilk durumu ve bir init fonksiyonunu argüman olarak alabilir.
const [state, dispatch] = useReducer(reducer, initialState, init)
Burada init
bir fonksiyondur ve ilk durumu tembelce oluşturmak istediğimizde kullanılır.
Aşağıdaki sanal alanda gösterildiği gibi basit bir yapılacaklar uygulaması oluşturarak useReducer
kancasının nasıl uygulanacağına bakalım.
Öncelikle durumları tutacak redüktörümüzü oluşturmalıyız.
export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;
Eylem türlerimize karşılık gelen üç sabit oluşturduk. Dizeleri doğrudan kullanabilirdik, ancak yazım hatalarını önlemek için bu yöntem tercih edilir.
Daha sonra redüktör fonksiyonumuzu oluşturduk. Redux
olduğu gibi, redüktörün durumu ve eylem nesnesini alması gerekir. Ancak Redux'un aksine burada redüktörümüzü başlatmamız gerekmiyor.
Ayrıca, birçok durum yönetimi kullanım senaryosu için, bağlam aracılığıyla sunulan dispatch
birlikte bir useReducer
, daha büyük bir uygulamanın eylemleri başlatmasını, state
güncellemesini ve dinlemesini sağlayabilir.
Ardından, kullanıcı tarafından iletilen eylem türünü kontrol etmek için switch
deyimlerini kullandık. Eylem türü ADD_TODO
ise yeni bir yapılacak iş geçmek ve REMOVE_TODO
ise yapılacakları filtrelemek ve kullanıcı tarafından iletilen id
karşılık gelen öğeyi kaldırmak istiyoruz. COMPLETE_TODO
ise, yapılacaklar arasında eşlemek ve kullanıcı tarafından geçen id
sahip olanı değiştirmek istiyoruz.
İşte reducer
uyguladığımız App.js
dosyası.
import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }
Burada, kullanıcının girdisini toplamak için bir girdi öğesi ve eylemi tetiklemek için bir düğme içeren bir form oluşturduk. Form gönderildiğinde, yeni bir kimlik ve yapılacaklar metni ileterek ADD_TODO
türünde bir eylem gönderdik. Önceki id değerini 1 artırarak yeni bir id oluşturduk. Ardından giriş metin kutusunu temizledik. Yapılacakları silmek ve tamamlamak için uygun eylemleri göndermemiz yeterlidir. Bunlar, yukarıda gösterildiği gibi redüktörde zaten uygulanmıştır.
Ancak, sihir, useReducer
kancasını kullandığımız için gerçekleşir. Bu kanca, indirgeyiciyi ve başlangıç durumunu kabul eder ve durumu ve gönderme işlevini döndürür. Burada, sevk işlevi, useState
kancası için ayarlayıcı işleviyle aynı amaca hizmet eder ve onu dispatch
yerine istediğimiz herhangi bir şey olarak adlandırabiliriz.
Yapılacakları görüntülemek için, yukarıdaki kodda gösterildiği gibi durum nesnemizde döndürülen yapılacaklar listesinin haritasını çıkardık.
Bu, useReducer
kancasının gücünü gösterir. Bu işlevi useState
kancasıyla da elde edebiliriz, ancak yukarıdaki örnekten de görebileceğiniz gibi, useReducer
kancası işleri daha düzenli tutmamıza yardımcı oldu. useReducer
, durum nesnesi karmaşık bir yapı olduğunda ve basit bir değer değiştirmeye göre farklı şekillerde güncellendiğinde genellikle faydalıdır. Ayrıca, bu güncelleme işlevleri daha karmaşık hale geldiğinde, useReducer
, tüm bu karmaşıklığı bir redüktör işlevinde (saf bir JS işlevidir) tutmayı kolaylaştırır, bu da yalnızca redüktör işlevi için testler yazmayı çok kolaylaştırır.
İlk durumu tembelce oluşturmak için üçüncü argümanı useReducer
kancasına da iletebilirdik. Bu, bir init
işlevinde ilk durumu hesaplayabileceğimiz anlamına gelir.
Örneğin, aşağıdaki gibi bir init
işlevi oluşturabiliriz:
const initFunc = () => [ { id: id, text: "First Item", completed: false } ]
ve sonra bunu useReducer
.
const [state, dispatch] = useReducer(reducer, initialState, initFunc)
Bunu yaparsak, initFunc
sağladığımız initialState
geçersiz kılacak ve başlangıç durumu tembelce hesaplanacaktır.
useContext
Hook
React Context API, React bileşen ağacı boyunca durumları veya verileri paylaşmanın bir yolunu sağlar. API, bir süredir deneysel bir özellik olarak React'te mevcuttu ancak React 16.3.0'da kullanımı güvenli hale geldi. API, pervane delme işlemini ortadan kaldırırken bileşenler arasında veri paylaşımını kolaylaştırır.
React Context'i uygulamanızın tamamına uygulayabileceğiniz gibi, uygulamanın bir kısmına da uygulamanız mümkündür.
Kancayı kullanmak için önce React.createContext
kullanarak bir bağlam oluşturmanız gerekir ve bu bağlam daha sonra kancaya geçirilebilir.
useContext
kancasının kullanımını göstermek için, uygulamamız boyunca yazı tipi boyutunu artıracak basit bir uygulama oluşturalım.
context.js
dosyasında oluşturalım.
import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;
Burada bir bağlam oluşturduk ve ona 16
bir başlangıç değeri ilettik ve ardından bağlamı dışa aktardık. Ardından, bağlamımızı uygulamamıza bağlayalım.
import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;
Yukarıdaki kodda, tüm bileşen ağacımızı FontSizeContext.Provider ile FontSizeContext.Provider
ve size
değerini prop değerine ilettik. Burada size
, useState
kancasıyla oluşturulan bir durumdur. Bu, size
durumu değiştiğinde prop değerini değiştirmemize izin verir. Tüm bileşeni Provider
ile sararak, içeriğe uygulamamızın herhangi bir yerinden erişebiliriz.
Örneğin, <PageOne />
ve <PageTwo />
içindeki içeriğe eriştik. Bunun bir sonucu olarak, App.js
dosyasından artırdığımızda yazı tipi boyutu bu iki bileşen arasında artacaktır. Yazı boyutunu yukarıda gösterildiği gibi butonlardan büyütüp küçültebiliyoruz ve yaptığımızda yazı boyutu uygulama genelinde değişiyor.
import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;
Burada, PageOne
bileşenimizdeki useContext
kancasını kullanarak içeriğe eriştik. Daha sonra yazı tipi boyutu özelliğimizi ayarlamak için bu bağlamı kullandık. Benzer bir prosedür PageTwo.js
dosyası için de geçerlidir.
Temalar veya diğer üst düzey uygulama düzeyinde yapılandırmalar, bağlamlar için iyi adaylardır.
useContext
ve useReducer
kullanma
useReducer
kancasıyla birlikte kullanıldığında useContext
, kendi durum yönetim sistemimizi oluşturmamıza olanak tanır. Uygulamamızda küresel devletler oluşturabilir ve bunları kolayca yönetebiliriz.
Bağlam API'sini kullanarak yapılacaklar uygulamamızı geliştirelim.
Her zamanki gibi, todoContext.js
dosyasında bir todoContext
oluşturmamız gerekiyor.
import { createContext } from "react"; const initialState = []; export default createContext(initialState);
Burada, boş bir dizinin başlangıç değerini ileterek bağlamı oluşturduk. Sonra bağlamı dışa aktardık.
Yapılacaklar listesini ve öğeleri ayırarak App.js
dosyamızı yeniden düzenleyelim.
import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }
Burada, App.js
dosyamızı TodoContext.Provider ile TodoContext.Provider
ve ardından todoReducer'ımızın dönüş değerlerini ona todoReducer
. Bu, redüktörün durumu ve dispatch
fonksiyonunun uygulamamız boyunca erişilebilir olmasını sağlar.
Daha sonra yapılacaklar ekranını TodoList
bileşenine ayırdık. Bunu, Context API sayesinde sondaj yapmadan gerçekleştirdik. TodoList.js
dosyasına bir göz atalım.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;
Dizi yok etmeyi kullanarak, useContext
kancasını kullanarak bağlamdan duruma (gönderme işlevinden çıkarak) erişebiliriz. Daha sonra durumu haritalayabilir ve yapılacaklar öğelerini görüntüleyebiliriz. Bunu yine de bir Todo
bileşeninde çıkardık. ES6+ harita işlevi, benzersiz bir anahtar iletmemizi gerektirir ve belirli bir yapılacaklığa ihtiyacımız olduğundan, onu da yanında iletiyoruz.
Şimdi Todo
bileşenine bir göz atalım.
import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;
Yine dizi yok etmeyi kullanarak, bağlamdan sevk işlevine eriştik. Bu, useReducer
bölümünde daha önce tartışıldığı gibi completeTodo
ve removeTodo
işlevini tanımlamamızı sağlar. todoList.js
geçirilen todo
prop ile bir yapılacaklar öğesini görüntüleyebiliriz. Ayrıca tamamlandı olarak işaretleyebilir ve uygun gördüğümüz yapılacakları kaldırabiliriz.
Uygulamamızın köküne birden fazla bağlam sağlayıcı yerleştirmek de mümkündür. Bu, bir uygulamada farklı işlevleri gerçekleştirmek için birden fazla bağlam kullanabileceğimiz anlamına gelir.
Bunu göstermek için, yapılacaklar örneğine tema ekleyelim.
İşte inşa edeceğimiz şey.
Yine themeContext
oluşturmamız gerekiyor. Bunu yapmak için bir themeContext.js
dosyası oluşturun ve aşağıdaki kodları ekleyin.
import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);
Burada bir bağlam oluşturduk ve ilk değer olarak colors.light
geçtik. Renkleri bu özellik ile colors.js
dosyasında tanımlayalım.
const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;
Yukarıdaki kodda, açık ve koyu özelliklerini içeren bir colors
nesnesi oluşturduk. Her özelliğin backgroundColor
ve color
nesnesi vardır.
Ardından, tema durumlarını işlemek için themeReducer
oluşturuyoruz.
import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;
Tüm redüktörler gibi, themeReducer
durumu ve eylemi alır. Ardından, geçerli eylemi belirlemek için switch
ifadesini kullanır. LIGHT
türündeyse, Colors.light
atarız ve DARK
Colors.dark
görüntüleriz. Bunu useState
kancasıyla kolayca yapabilirdik, ancak noktayı eve götürmek için useReducer
.
themeReducer
sonra onu App.js
dosyamıza entegre edebiliriz.
import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }
Yukarıdaki kodda, zaten var olan yapılacaklar uygulamamıza birkaç şey ekledik. ThemeContext
, themeReducer
, ThemeToggler
ve Colors
öğelerini içe aktararak başladık. We created a reducer using the useReducer
hook, passing the themeReducer
and an initial value of Colors.light
to it. This returned the themeState
and themeDispatch
to us.
We then nested our component with the provider function from the ThemeContext
, passing the themeState
and the dispatch
functions to it. We also added theme styles to it by spreading out the themeStates
. This works because the colors
object already defined properties similar to what the JSX styles will accept.
However, the actual theme toggling happens in the ThemeToggler
component. Bir göz atalım.
import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;
In this component, we used the useContext
hook to retrieve the values we passed to the ThemeContext.Provider
from our App.js
file. As shown above, these values include the ThemeState
, dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight
to determine the current theme. This allows us to easily change the button text depending on the current theme.
The useMemo
Hook
The useMemo
hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo
will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.
The hook can be used as follows:
const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])
Let's consider three cases of the useMemo
hook.
- When the dependency values, a and b remain the same.
TheuseMemo
hook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - When no dependency value is passed.
The hook will recompute the value.
Let's take a look at an example to demonstrate this concept.
In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.
The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.
This is shown in the sandbox below.
First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).
const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);
İlk useEffect
axios
ve async/await
yöntemini, ikincide nokta ve ardından sözdizimini kullandık. Bu iki yaklaşım aynı şekilde çalışır.
Ardından, yukarıdan aldığımız çalışan verilerini kullanarak kabartma değişkenlerini hesaplayalım:
const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);
Bu oldukça karmaşık bir hesaplamadır ve bu yüzden onu not almak veya optimize etmek için bir useMemo
kancasına sarmamız gerekiyordu. Bu şekilde hafızaya almak, aynı çalışana tekrar erişmeye çalışırsak hesaplamanın yeniden hesaplanmamasını sağlayacaktır.
Ayrıca, yukarıda elde edilen vergi indirimi değerlerini kullanarak, PAYE'yi ve PAYE'den sonraki geliri hesaplamak istiyoruz.
const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);
Yukarıda hesaplanan vergi değişkenlerini kullanarak vergi hesaplaması (oldukça karmaşık bir hesaplama) gerçekleştirdik ve ardından bunu useMemo
kancasıyla not aldık.
Kodun tamamı burada mevcuttur.
Bu, burada verilen vergi hesaplama prosedürünü takip eder. Vergi indirimini önce gelir, çocuk sayısı ve bağımlı akraba sayısı üzerinden hesapladık. Daha sonra vergilendirilebilir geliri PIT oranları ile basamaklar halinde çarpıyoruz. Söz konusu hesaplama bu eğitim için tamamen gerekli olmasa da, useMemo
neden gerekli olabileceğini bize göstermek için sağlanmıştır. Bu aynı zamanda oldukça karmaşık bir hesaplamadır ve bu nedenle yukarıda gösterildiği gibi useMemo
ile ezberlememiz gerekebilir.
Değerleri hesapladıktan sonra sonucu gösterdik.
useMemo
kancası hakkında aşağıdakilere dikkat edin.
-
useMemo
yalnızca hesaplamayı optimize etmek gerektiğinde kullanılmalıdır. Başka bir deyişle, yeniden hesaplama pahalı olduğunda. - İlk önce hesaplamayı ezberlemeden yazmanız ve yalnızca performans sorunlarına neden oluyorsa ezberlemeniz önerilir.
-
useMemo
kancasının gereksiz ve alakasız kullanımı performans sorunlarını daha da karmaşık hale getirebilir. - Bazen çok fazla not alma da performans sorunlarına neden olabilir.
Kullanım Geri useCallback
Kancası
useCallback
, useMemo
ile aynı amaca hizmet eder, ancak not edilmiş bir değer yerine not edilmiş bir geri arama döndürür. Başka bir deyişle, useCallback
, bir işlev çağrısı olmadan useMemo
aynıdır.
Örneğin, aşağıdaki kodları inceleyin.
import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample
Yukarıdaki örnekte, hem memoResult
hem de callbackResult
, aynı 12
değerini verecektir. Burada useCallback
, not edilmiş bir değer döndürür. Bununla birlikte, onu bir fonksiyon olarak ileterek, not edilmiş bir geri arama döndürmesini de sağlayabiliriz.
Aşağıdaki useCallback
, not alınmış bir geri arama döndürür.
... const callbackResult = useCallback(() => a + b, [a, b]) ...
Ardından, bir eylem gerçekleştirildiğinde veya bir useEffect
kancasında geri aramayı tetikleyebiliriz.
import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample
Yukarıdaki kodda useCallback
kancasını kullanarak bir geri çağırma işlevi tanımladık. Daha sonra bileşen monte edildiğinde ve ayrıca bir düğmeye tıklandığında useEffect
kancasında geri aramayı çağırdık.
Hem useEffect
hem de düğme tıklaması aynı sonucu verir.
useMemo kancası için geçerli olan kavramların, yapılması ve yapılmaması gerekenlerin useMemo
kancası için de geçerli olduğunu useCallback
. useMemo
örneğini useCallback
ile yeniden oluşturabiliriz.
useRef
useRef
, bir uygulamada kalıcı olabilen bir nesne döndürür. Kancanın yalnızca bir özelliği vardır: current
ve ona kolayca bir argüman iletebiliriz.
Sınıf tabanlı bileşenlerde kullanılan createRef
ile aynı amaca hizmet eder. Bu kanca ile aşağıdaki gibi bir referans oluşturabiliriz:
const newRef = useRef('')
Burada newRef
adında yeni bir ref oluşturduk ve ona boş bir dize ilettik.
Bu kanca esas olarak iki amaç için kullanılır:
- DOM'a erişme veya DOM'u değiştirme ve
- Değişken durumları saklamak — bu, bir değer değiştiğinde bileşenin yeniden oluşturulmasını istemediğimizde kullanışlıdır.
DOM'yi manipüle etme
Bir DOM öğesine iletildiğinde, ref nesnesi o öğeye işaret eder ve DOM özniteliklerine ve özelliklerine erişmek için kullanılabilir.
İşte bu kavramı göstermek için çok basit bir örnek.
import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample
Yukarıdaki örnekte, boş bir dize geçen useRef
kancasını kullanarak headingRef
tanımladık. Daha sonra ref = {headingRef}
ileterek h1
etiketindeki ref'i ayarladık. Bu referansı ayarlayarak, headingRef
h1
elemanımızı işaret etmesini istedik. Bu, h1
elemanımızın özelliklerine referanstan erişebileceğimiz anlamına gelir.
Bunu görmek için, console.log(headingRef)
değerini kontrol edersek, {current: HTMLHeadingElement}
veya {current: h1}
alırız ve öğenin tüm özelliklerini veya niteliklerini değerlendirebiliriz. Benzer bir şey, diğer herhangi bir HTML öğesi için de geçerlidir.
Örneğin, bileşen bağlandığında metni italik yapabiliriz.
useEffect(() => { headingRef.current.style.font; }, []);
Metni başka bir şeye bile değiştirebiliriz.
... headingRef.current.innerHTML = "A Changed H1 Element"; ...
Hatta ana konteynerin arka plan rengini de değiştirebiliriz.
... headingRef.current.parentNode.style.backgroundColor = "red"; ...
Her türlü DOM manipülasyonu burada yapılabilir. headingRef.current
document.querySelector('.topheading')
ile aynı şekilde okunabileceğini gözlemleyin.
DOM öğesinin işlenmesinde useRef
kancasının ilginç bir kullanım durumu, imleci giriş öğesine odaklamaktır. Hızlıca üzerinden geçelim.
import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample
Yukarıdaki kodda, useRef
kancasını kullanarak inputRef
yarattık ve ardından girdi öğesini işaret etmesini istedik. Ardından, bileşen yüklendiğinde ve inputRef.current.focus()
kullanılarak düğme tıklandığında imleci giriş ref'ine odakladık. Bu mümkündür çünkü focus()
girdi öğelerinin bir özniteliğidir ve bu nedenle başvuru, yöntemleri değerlendirebilecektir.
Bir üst bileşende oluşturulan referanslar, React.forwardRef()
kullanılarak iletilerek alt bileşende değerlendirilebilir. Bir göz atalım.
İlk önce başka bir NewInput.js
bileşeni oluşturalım ve buna aşağıdaki kodları ekleyelim.
import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;
Bu bileşen props
ve ref
kabul eder. Ref'i ref prop'una ve props.val
yer tutucu prop'una geçirdik. Normal React bileşenleri bir ref
özniteliği almaz. Bu öznitelik yalnızca yukarıda gösterildiği gibi React.forwardRef
ile sardığımızda kullanılabilir.
Daha sonra bunu ana bileşende kolayca çağırabiliriz.
... <NewInput val="Just an example" ref={inputRef} /> ...
Değişken Halleri Saklamak
Referanslar yalnızca DOM öğelerini işlemek için kullanılmaz, tüm bileşeni yeniden oluşturmadan değiştirilebilir değerleri depolamak için de kullanılabilirler.
Aşağıdaki örnek, bileşeni yeniden oluşturmadan bir düğmenin kaç kez tıklandığını tespit edecektir.
import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }
Yukarıdaki kodda buton tıklandığında countRef
değerini arttırdık ve ardından konsola kaydettik. Değer konsolda gösterildiği gibi artırılsa da, doğrudan bileşenimizde değerlendirmeye çalışırsak herhangi bir değişiklik göremeyiz. Yalnızca yeniden oluşturulduğunda bileşende güncellenir.
useState
eşzamansızken, useRef
eşzamanlı olduğunu unutmayın. Başka bir deyişle, değer güncellendikten hemen sonra kullanılabilir.
useLayoutEffect
Kancası
useEffect
kancası gibi, useLayoutEffect
, bileşen monte edildikten ve oluşturulduktan sonra çağrılır. Bu kanca, DOM mutasyonundan sonra tetiklenir ve bunu eşzamanlı olarak yapar. UseLayoutEffect, DOM mutasyonundan sonra eşzamanlı olarak çağrılmaktan ayrı olarak, useLayoutEffect
ile aynı şeyi useEffect
.
useLayoutEffect
yalnızca DOM mutasyonu veya DOM ile ilgili ölçüm gerçekleştirmek için kullanılmalıdır, aksi takdirde useEffect
kancasını kullanmalısınız. DOM mutasyon işlevleri için useEffect
kancasının kullanılması, titreme gibi bazı performans sorunlarına neden olabilir, ancak useLayoutEffect
, mutasyonlar gerçekleştikten sonra çalıştığı için bunları mükemmel şekilde işler.
Bu kavramı göstermek için bazı örneklere bakalım.
- Yeniden boyutlandırmada pencerenin genişliğini ve yüksekliğini alacağız.
import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample
Yukarıdaki kodda, width ve height özelliklerine sahip bir windowSize
durumu oluşturduk. Ardından, pencere yeniden boyutlandırıldığında durumu sırasıyla mevcut pencerenin genişliğine ve yüksekliğine ayarlarız. Ayrıca, ayrıldığında kodu da temizledik. Temizleme işlemi, DOM manipülasyonunu temizlemek ve verimliliği artırmak için useLayoutEffect
çok önemlidir.
-
useLayoutEffect
ile bir metni bulanıklaştıralım.
import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }
Yukarıdaki kodda useRef
ve useLayoutEffect
birlikte kullandık. İlk önce paragrafımıza işaret etmek için bir ref, paragraphRef
oluşturduk. Ardından, paragrafın ne zaman tıklandığını izlemek için bir tıklama olay dinleyicisi oluşturduk ve ardından tanımladığımız stil özelliklerini kullanarak paragrafı bulanıklaştırdık. Son olarak, removeEventListener
kullanarak olay dinleyicisini temizledik.
useDispatch
And useSelector
Hooks
useDispatch
, bir uygulamadaki eylemleri göndermek (tetiklemek) için bir Redux kancasıdır. Argüman olarak bir eylem nesnesi alır ve eylemi çağırır. useDispatch
, kancanın mapDispatchToProps
ile eşdeğeridir.
Öte yandan useSelector
, Redux durumlarını değerlendirmek için bir Redux kancasıdır. Mağazadan tam Redux redüktörünü seçmek için bir fonksiyon alır ve ardından ilgili durumları döndürür.
Redux mağazamız Redux sağlayıcısı aracılığıyla bir React uygulamasına bağlandığında, useDispatch ile eylemleri çağırabilir ve useDispatch
ile durumlara useSelector
. Her Redux eylemi ve durumu bu iki kanca ile değerlendirilebilir.
Bu durumların React Redux (bir React uygulamasında Redux mağazasını değerlendirmeyi kolaylaştıran bir paket) ile birlikte geldiğini unutmayın. Çekirdek Redux kitaplığında mevcut değiller.
Bu kancaların kullanımı çok basittir. İlk olarak, sevk fonksiyonunu bildirmeli ve sonra onu tetiklemeliyiz.
import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample
Yukarıdaki kodda, useDispatch
ve useSelector
react-redux
öğesinden içe aktardık. Ardından, bir useEffect
kancasında eylemi gönderdik. Eylemi başka bir dosyada tanımlayabilir ve ardından burada çağırabilir veya doğrudan useEffect
çağrısında gösterildiği gibi tanımlayabiliriz.
Eylemleri gönderdikten sonra, durumlarımız mevcut olacaktır. Daha sonra, gösterildiği gibi useSelector
kancasını kullanarak durumu alabiliriz. Durumlar, useState
kancasındaki durumları kullandığımız gibi kullanılabilir.
Bu iki kancayı göstermek için bir örneğe bakalım.
Bu konsepti göstermek için bir Redux mağazası, redüktör ve eylemler oluşturmamız gerekiyor. Burada işleri basitleştirmek için, JSONPlaceholder'ın sahte veritabanımızla Redux Toolkit kitaplığını kullanacağız.
Başlamak için aşağıdaki paketleri kurmamız gerekiyor. Aşağıdaki bash komutlarını çalıştırın.
npm i redux @reduxjs/toolkit react-redux axios
İlk olarak, çalışanlarımızın API'si için azaltıcıyı ve eylemi işlemek için employeesSlice.js
oluşturalım.
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;
Bu, Redux araç takımı için standart kurulumdur. Zaman uyumsuz eylemler gerçekleştirmek üzere Thunk
ara yazılımına erişmek için createAsyncThunk
kullandık. Bu, API'den çalışanların listesini getirmemize izin verdi. Daha sonra employeesSlice
oluşturduk ve eylem türlerine bağlı olarak “yükleniyor”, “hata” ve çalışanların verilerini döndürdük.
Redux araç seti ayrıca mağazanın kurulumunu da kolaylaştırır. İşte mağaza.
import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;
Burada, redüktörleri bir araya getirmek için combineReducers
ve mağazayı kurmak için Redux araç takımı tarafından sağlanan configureStore
işlevini kullandık.
Bunu uygulamamızda kullanmaya devam edelim.
Öncelikle Redux'u React uygulamamıza bağlamamız gerekiyor. İdeal olarak, bu bizim uygulamamızın kökünde yapılmalıdır. Bunu index.js
dosyasında yapmayı seviyorum.
import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );
Burada, yukarıda oluşturduğum mağazayı ve ayrıca Provider
react-redux
içe aktardım.
Ardından, tüm uygulamayı Provider
işleviyle sardım, mağazayı ona aktardım. Bu, mağazayı uygulamamız boyunca erişilebilir kılar.
Daha sonra verileri almak için useDispatch
ve useSelector
kancalarını kullanmaya devam edebiliriz.
Bunu App.js
dosyamızda yapalım.
import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }
Yukarıdaki kodda, employeesSlice.js
dosyasında oluşturulan fetchEmployees
eylemini çağırmak için useDispatch
kancasını kullandık. Bu, çalışanların durumunu uygulamamızda kullanılabilir hale getirir. Sonra durumları almak için useSelector
kancasını kullandık. Daha sonra sonuçları employees
üzerinden haritalandırarak görüntüledik.
Kullanım useHistory
Kancası
React uygulamasında navigasyon çok önemlidir. Bunu birkaç yolla başarabilirken, React Router, bir React uygulamasında dinamik yönlendirme elde etmek için basit, verimli ve popüler bir yol sağlar. Ayrıca, React Router, yönlendiricinin durumunu değerlendirmek ve tarayıcıda gezinmeyi gerçekleştirmek için birkaç kanca sağlar, ancak bunları kullanmak için önce uygulamanızı düzgün bir şekilde kurmanız gerekir.
Herhangi bir React Router kancasını kullanmak için öncelikle uygulamamızı BrowserRouter ile BrowserRouter
. Daha sonra rotaları Switch
ve Route
ile iç içe geçirebiliriz.
Ama önce aşağıdaki komutları çalıştırarak paketi kurmamız gerekiyor.
npm install react-router-dom
Ardından aşağıdaki gibi uygulamamızı kurmamız gerekiyor. Bunu App.js
yapmayı seviyorum.
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }
Oluşturmak istediğimiz bileşenlerin sayısına bağlı olarak mümkün olduğunca çok Rotamız olabilir. Burada sadece Employees
bileşenini oluşturduk. path
özelliği, React Router DOM'a bileşenin yolunu söyler ve sorgu dizesi veya diğer çeşitli yöntemlerle değerlendirilebilir.
Burada sıra önemli. Kök rota, alt rotanın altına yerleştirilmelidir. Bu sırayı geçersiz kılmak için, kök yoluna exact
anahtar kelimeyi eklemeniz gerekir.
<Route path='/' exact > <Employees /> </Route>
Yönlendiriciyi kurduğumuza göre, artık uygulamamızda useHistory
kancasını ve diğer React Yönlendirici kancalarını kullanabiliriz.
useHistory
kancasını kullanmak için önce onu aşağıdaki gibi bildirmemiz gerekiyor.
import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }
Konsola geçmişi kaydedersek, onunla ilişkili birkaç özellik görürüz. Bunlar arasında block
, createHref
, go
, goBack
, goForward
, length
, listen
, location
, push
, replace
yer alır. Tüm bu özellikler faydalı olsa da, büyük olasılıkla history.push
ve history.replace
diğer özelliklerden daha sık kullanacaksınız.
Bir sayfadan diğerine geçmek için bu özelliği kullanalım.
Adlarına tıkladığımızda belirli bir çalışan hakkında veri almak istediğimizi varsayalım. Çalışan bilgilerinin görüntüleneceği yeni sayfaya gitmek için useHistory
kancasını kullanabiliriz.
function moveToPage = (id) =>{ history.push(`/employees/${id}`) }
Bunu aşağıdakileri ekleyerek Employee.js
dosyamıza uygulayabiliriz.
import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }
pushToPage
işlevinde, çalışanın sayfasına gitmek ve yanına çalışan kimliğini iletmek için useHistory
kancasındaki history
kullandık.
useLocation
Kancası
Bu kanca ayrıca React Router DOM ile birlikte gelir. Sorgu dizesi parametresiyle çalışmak için kullanılan çok popüler bir kancadır. Bu kanca, tarayıcıdaki window.location
benzer.
import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample
useLocation
kancası, pathname
, search
parametresini, hash
ve state
. En sık kullanılan parametreler pathname
ve search
içerir, ancak aynı şekilde hash
kullanabilir ve uygulamanızda çok şey state
.
Location pathname
özelliği, Route
kurulumumuzda belirlediğimiz yolu döndürecektir. search
, varsa sorgu arama parametresini döndürür. Örneğin, sorgumuza 'http://mywebsite.com/employee/?id=1'
iletirsek, pathname
/employee
olur ve search
?id=1
olur.
Daha sonra çeşitli arama parametrelerini sorgu dizesi gibi paketleri kullanarak veya bunları kodlayarak alabiliriz.
useParams
Kancası
Rotamızı yol özelliğinde bir URL parametresi ile kurarsak, bu parametreleri useParams
kancasıyla anahtar/değer çiftleri olarak değerlendirebiliriz.
Örneğin, aşağıdaki Rotaya sahip olduğumuzu varsayalım.
<Route path='/employees/:id' > <Employees /> </Route>
Rota, :id
yerine dinamik bir kimlik bekleyecektir.
useParams
kancası ile varsa kullanıcının geçtiği id'yi değerlendirebiliriz.
Örneğin, kullanıcının history.push
ile fonksiyonda aşağıdakileri geçtiğini varsayarsak,
function goToPage = () => { history.push(`/employee/3`) }
Bu URL parametresine erişmek için useParams
kancasını aşağıdaki gibi kullanabiliriz.
import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample
params
konsola kaydedersek, şu nesneyi {id: "3"}
alırız.
useRouteMatch
Kancası
Bu kanca, eşleşme nesnesine erişim sağlar. Herhangi bir argüman sağlanmazsa, bir bileşene en yakın eşleşmeyi döndürür.
Match nesnesi, path
(Rota'da belirtilen yolla aynı), URL
, params
nesnesi ve isExact
dahil olmak üzere birkaç parametre döndürür.
Örneğin, rotaya dayalı bileşenleri döndürmek için useRouteMatch
kullanabiliriz.
import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;
Yukarıdaki kodda, useRouteMatch
ile bir rotanın yolunu belirledik ve ardından kullanıcı tarafından seçilen rotaya bağlı olarak <Employee />
veya <Admin />
bileşenini oluşturduk.
Bunun çalışması için rotayı App.js
dosyamıza eklememiz gerekiyor.
... <Route> <CustomRoute /> </Route> ...
Özel Bir Kanca Oluşturma
React belgelerine göre, özel bir kanca oluşturmak, bir mantığı yeniden kullanılabilir bir işleve çıkarmamızı sağlar. Ancak, React kancaları için geçerli olan tüm kuralların özel kancanız için de geçerli olduğundan emin olmanız gerekir. Bu öğreticinin üst kısmındaki React kancası kurallarını kontrol edin ve özel kancanızın her birine uygun olduğundan emin olun.
Özel kancalar, işlevleri bir kez yazmamıza ve gerektiğinde bunları yeniden kullanmamıza ve dolayısıyla DRY ilkesine uymamıza izin verir.
Örneğin, sayfamızda kaydırma pozisyonunu almak için aşağıdaki gibi özel bir kanca oluşturabiliriz.
import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };
Burada, bir sayfadaki kaydırma konumunu belirlemek için özel bir kanca tanımladık. Bunu başarmak için önce kaydırma konumunu depolamak için scrollPos
adlı bir durum oluşturduk. Bu DOM'yi değiştireceğinden, useLayoutEffect
yerine useEffect
kullanmamız gerekiyor. x ve y kaydırma konumlarını yakalamak için bir kaydırma olay dinleyicisi ekledik ve ardından olay dinleyicisini temizledik. Sonunda kaydırma pozisyonuna döndük.
Bu özel kancayı uygulamamızın herhangi bir yerinde çağırarak ve diğer durumları kullanacağımız gibi kullanarak kullanabiliriz.
import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App
Burada, yukarıda oluşturduğumuz özel kanca useScrollPos
içe aktardık. Sonra onu başlattık ve ardından değeri konsolumuza kaydettik. Sayfayı kaydırırsak, kanca bize kaydırmanın her adımında kaydırma konumunu gösterecektir.
Uygulamamızda hayal edebileceğimiz hemen hemen her şeyi yapmak için özel kancalar oluşturabiliriz. Gördüğünüz gibi, bazı işlevleri gerçekleştirmek için yalnızca yerleşik React kancasını kullanmamız gerekiyor. Özel kancalar oluşturmak için üçüncü taraf kitaplıkları da kullanabiliriz, ancak bunu yaparsak, kancayı kullanabilmek için bu kitaplığı kurmamız gerekecek.
Çözüm
Bu eğitimde, uygulamalarınızın çoğunda kullanacağınız bazı kullanışlı React kancalarına bir göz attık. Ne sunduklarını ve uygulamanızda nasıl kullanacağınızı inceledik. Bu kancaları anlamanıza ve uygulamanıza uygulamanıza yardımcı olmak için birkaç kod örneğine de baktık.
Onları daha iyi anlamak için bu kancaları kendi uygulamanızda denemenizi tavsiye ederim.
React Dokümanlarından Kaynaklar
- Kancalar SSS
- Redux Araç Seti
- Durum Kancasını Kullanma
- Efekt Kancasını Kullanma
- Kancalar API Referansı
- Tepki Redux Kancaları
- React Yönlendirici Kancaları