Uygulamanız büyüdükçe, state’inizin nasıl düzenlendiği ve bileşenleriniz arasında veri akışının nasıl olduğu konusunda daha bilinçli olmanız size yardımcı olur. Gereksiz ve yenilenen state, yaygın bir hata kaynağıdır. Bu bölümde, state’inizi nasıl iyi yapılandıracağınızı, state güncelleme mantığınızı nasıl sürdürülebilir tutacağınızı ve uzak bileşenler arasında state’i nasıl paylaşacığınızı öğreneceksiniz.
Bu bölümde
- UI değişiklikleri nasıl state değişikliği olarak düşünülür
- State nasıl iyi yapılandırılabilir
- Bileşenler arasında paylaşmak için state nasıl “yukarı kaldırılır”
- State’in korunup korunmayacağı ya da sıfırlanıp sıfırlanmayacağı nasıl kontrol edilir
- Karmaşık state mantığı bir fonksiyonda nasıl birleştirilir
- ”Prop drilling” yapmadan bilgi nasıl iletilir
- Uygulamanız büyüdükçe state yönetimi nasıl ölçeklenidirilir
State ile girdiye reaksiyon verme
React ile kullanıcı arayüzünü direkt olarak koddan modifiye etmeyeceksiniz. Örneğin, “butonu devre dışı bırak”, “butonu etkinleştir”, “başarılı mesajını göster” gibi komutlar yazmayacaksınız. Onun yerine, bileşeninizin farklı görsel state’leri (“başlangıç state’i”, “yazma state’i”, “başarı state’i”) için görmek istediğiniz kullanıcı arayüzünü tanımlayacak ve ardından kullanıcı girdisine yanıt olarak state değişikliklerini tetikleyeceksiniz. Bu, tasarımcıları kullanıcı arayüzünü nasıl düşündüğüyle benzerdir.
Aşağıda React ile yapılmış bir kısa sınav formu vardır. Gönder butonunun etkinleştirilip etkinleştirilmeyeceğini ve bunun yerine başarı mesajının gösterilip gösterilmeyeceğini belirlemek için status
durum değişkeninin nasıl kullanıldağına dikkat edin.
import { useState } from 'react'; export default function Form() { const [answer, setAnswer] = useState(''); const [error, setError] = useState(null); const [status, setStatus] = useState('typing'); if (status === 'success') { return <h1>Doğru!</h1> } async function handleSubmit(e) { e.preventDefault(); setStatus('submitting'); try { await submitForm(answer); setStatus('success'); } catch (err) { setStatus('typing'); setError(err); } } function handleTextareaChange(e) { setAnswer(e.target.value); } return ( <> <h2>Şehir sorusu</h2> <p> İki kıta üzerinde konumlanmış şehir hangisidir? </p> <form onSubmit={handleSubmit}> <textarea value={answer} onChange={handleTextareaChange} disabled={status === 'submitting'} /> <br /> <button disabled={ answer.length === 0 || status === 'submitting' }> Gönder </button> {error !== null && <p className="Error"> {error.message} </p> } </form> </> ); } function submitForm(answer) { // Ağa istek atıyormuş gibi yapalım. return new Promise((resolve, reject) => { setTimeout(() => { let shouldError = answer.toLowerCase() !== 'istanbul' if (shouldError) { reject(new Error('İyi tahmin ama yanlış cevap. Tekrar dene!')); } else { resolve(); } }, 1500); }); }
Bu konuyu öğrenmeye hazır mısınız?
Girdiye State ile Reaksiyon Verme sayfasını okuyarak etkileşimlere state odaklı zihniyetle nasıl yaklaşılacağını öğrenebilirsiniz.
Devamını OkuState yapısını seçme
State’i iyi yapılandırmak, değiştirmesi ve hata ayıklaması keyfli bir bileşen ile sürekli hata kaynağı olan bir bileşen arasında fark yaratabilir. En önemli ilke, state’in gereksiz veya yinelenen bilgiler içermemesidir. Gereksiz state varsa, güncellemeyi unutmak ve hatalara neden olmak kolaydır!
Örneğin, bu form gereksiz bir fullName
state değişkenine sahip:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const [fullName, setFullName] = useState(''); function handleFirstNameChange(e) { setFirstName(e.target.value); setFullName(e.target.value + ' ' + lastName); } function handleLastNameChange(e) { setLastName(e.target.value); setFullName(firstName + ' ' + e.target.value); } return ( <> <h2>Hadi bilgilerinizi girelim</h2> <label> Adın:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Soyadın:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Biletiniz şu kişiye düzenlenecek: <b>{fullName}</b> </p> </> ); }
Bileşen render edilirken fullName
’i hesaplayarak state’i kaldırabilir ve kodu basitleştirebilirsiniz:
import { useState } from 'react'; export default function Form() { const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const fullName = firstName + ' ' + lastName; function handleFirstNameChange(e) { setFirstName(e.target.value); } function handleLastNameChange(e) { setLastName(e.target.value); } return ( <> <h2>Hadi bilgilerinizi girelim</h2> <label> Adın:{' '} <input value={firstName} onChange={handleFirstNameChange} /> </label> <label> Soyadın:{' '} <input value={lastName} onChange={handleLastNameChange} /> </label> <p> Biletiniz şu kişiye düzenlenecek: <b>{fullName}</b> </p> </> ); }
Bu küçük bir değişiklik gibi görünebilir ama React uygulamalarındaki bir çok hata bu şekilde düzeltilir.
Bu konuyu öğrenmeye hazır mısınız?
State Yapısını Seçme sayfasını okuyarak hatalardan kaçınmak için state’i nasıl yapılandıracağınızı öğrenebilirsiniz.
Devamını OkuBileşenler arasında state’i paylaşma
Bazen, iki bileşenin state’inin birlikte değişmesini istersiniz. Bunu yapmak için, her ikisinin de state’ini kaldırın, state’i en yakın ortak üst bileşene taşıyın ve sonra iki bileşene prop’lar ile iletin. Bu “state’i yukarı kaldırmak” olarak bilinir ve React kodu yazarken en çok yapacağınız şeylerden biridir.
Bu örnekte, aynı anda sadece bir panel aktif olmalıdır. Bunu başarmak için, aktif state’i her bir panelin içinde tutmak yerine, üst bileşen state’i tutar ve alt bileşenler için prop’ları belirler.
import { useState } from 'react'; export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0); return ( <> <h2>Ankara, Türkiye</h2> <Panel title="About" isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > Ankara, Türkiye'nin başkenti ve İstanbul'dan sonra en kalabalık ikinci ilidir. </Panel> <Panel title="Etymology" isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > Belgelere dayanmayan ve günümüze kadar gelen söylentilere göre tarihte bahsedilen ilk adı Galatlar tarafından verilen ve Yunanca "çapa" anlamına gelen <i lang="el">Ankyra</i>'dır. Bu isim zamanla değişerek Ancyre, Engüriye, Engürü, Angara, Angora ve nihayet Ankara olmuştur. </Panel> </> ); } function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Göster </button> )} </section> ); }
Bu konuyu öğrenmeye hazır mısınız?
Bileşenler Arasında State Paylaşımı sayfasını okuyarak state’i nasıl yukarı kaldıracağınızı ve bileşenleri senkronize tutacağınızı öğrenebilirsiniz.
Devamını OkuState’i korumak ve resetlemek
Bir bileşeni yeniden render ettiğinizde, React, ağacın hangi kısımlarını tutacağın (ve güncelleyeciğine) ve hangi kısımları atacağına ya da sıfırdan yeniden oluşturacağına karar vermelidir. Pek çok durumda, React’in otomatik davranışı yeterince iyi çalışmaktadır. Varsayılan olarak React, ağacın daha önce render edilmiş bileşen ağacıyla “eşleşen” kısımlarını korur.
Ancak, bazen bunu istemezsiniz. Bu sohbet uygulamasında, bir mesaj yazmak ve ardından alıcıyı değiştirmek girdiyi sıfırlamamaktadır. Bu kullanıcının kazara yanlış kişiye mesaj göndermesine neden olabilir:
import { useState } from 'react'; import Chat from './Chat.js'; import ContactList from './ContactList.js'; export default function Messenger() { const [to, setTo] = useState(contacts[0]); return ( <div> <ContactList contacts={contacts} selectedContact={to} onSelect={contact => setTo(contact)} /> <Chat contact={to} /> </div> ) } const contacts = [ { name: 'Ayşe', email: 'ayse@mail.com' }, { name: 'Zeynep', email: 'zeynep@mail.com' }, { name: 'Ahmet', email: 'ahmet@mail.com' } ];
React, varsayılan davranışı geçersiz kılmanıza ve bir bileşene <Chat key={email} />
gibi farklı bir key
ileterek state’i sıfırlamaya zorlamanıza izin verir. Bu React’e, eğer alıcı farklı ise, yeni verilerle (ve girdiler gibi kullanıcı arayüzüyle) sıfırdan yeniden render edilmesi gereken farklı bir Chat
bileşeni olarak kabul edilmesi gerektiğini söyler. Şimdi alıcılar arasında geçiş yapmak, aynı bileşeni render etseniz bile girdi alanını sıfırlar.
import { useState } from 'react'; import Chat from './Chat.js'; import ContactList from './ContactList.js'; export default function Messenger() { const [to, setTo] = useState(contacts[0]); return ( <div> <ContactList contacts={contacts} selectedContact={to} onSelect={contact => setTo(contact)} /> <Chat key={to.email} contact={to} /> </div> ) } const contacts = [ { name: 'Ayşe', email: 'ayşe@mail.com' }, { name: 'Zeynep', email: 'zeynep@mail.com' }, { name: 'Ahmet', email: 'ahmet@mail.com' } ];
Bu konuyu öğrenmeye hazır mısınız?
State’i Korumak ve Sıfırlamak sayfasını okuyarak state’in ömrünü ve onu nasıl kontrol edebileceğinizi öğrenebilirsiniz.
Devamını OkuState mantığını bir reducer’a aktarma
Birçok olay yöneticisine yayılmış çok fazla sayıda state güncellemesine sahip bileşenler can sıkıcı olabilir. Bu gibi durumlarda tüm state güncelleme mantıklarını “reducer (redüktör)” adı verilen tek bir fonksiyonda birleştirebilirsiniz. Olay yönetecileriniz, yalnızca kullanıcı “eylemlerini” belirttikleri için kısa ve öz hale gelir. Dosyanın en altında, reducer fonksiyonu her bir eyleme yanıt olarak state’in nasıl güncellenmesi gerektiğini belirtir!
import { useReducer } from 'react'; import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; export default function TaskApp() { const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); } function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); } function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); } return ( <> <h1>Prag Gezisi Planı</h1> <AddTask onAddTask={handleAddTask} /> <TaskList tasks={tasks} onChangeTask={handleChangeTask} onDeleteTask={handleDeleteTask} /> </> ); } function tasksReducer(tasks, action) { switch (action.type) { case 'added': { return [...tasks, { id: action.id, text: action.text, done: false }]; } case 'changed': { return tasks.map(t => { if (t.id === action.task.id) { return action.task; } else { return t; } }); } case 'deleted': { return tasks.filter(t => t.id !== action.id); } default: { throw Error('Bilinmeyen eylem: ' + action.type); } } } let nextId = 3; const initialTasks = [ { id: 0, text: 'Kafka Müzesini ziyaret et', done: true }, { id: 1, text: 'Kukla gösterisi izle', done: false }, { id: 2, text: "Lennon Duvarı'nda fotoğraf çek", done: false } ];
Bu konuyu öğrenmeye hazır mısınız?
State Mantığını Bir Reducer’a Aktarma sayfasını okuyarak reducer fonksiyonunda mantığın nasıl birleştirileceğini öğrenebilirsiniz.
Devamını OkuContext ile veriyi derinlemesine aktarma
Bilgiyi genelde prop’lar vasıtasıyla üst elemandan alt elemana doğru aktarırsınız. Ancak, aktarmanız gereken bileşen ulaşana kadar birçok ara bileşene iletmeniz veya birden çok bileşene aktarmanız gerekiyorsa prop kullanmak zahmetli ve karmaşık hale gelir. Context, bilgiyi üst bileşenden ihtiyaç duyan alt bileşenlere (derinliğine bakılmaksızın) prop olarak açıkça belirtmeden iletmenizi sağlar.
Burada, Heading
bileşeni başlık seviyesini en yakın Section
’a seviyesini “sorarak” belirler. Her Section
, üst Section
’a sorarak ve ona bir tane ekleyerek kendi seviyesini takip eder. Her Section
, tüm alt bileşenlerine prop aktarmadan bilgi sağlar ve bunu context aracılığıyla yapar.
import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() { return ( <Section> <Heading>Üst Başlık</Heading> <Section> <Heading>Başlık</Heading> <Heading>Başlık</Heading> <Heading>Başlık</Heading> <Section> <Heading>Alt-başlık</Heading> <Heading>Alt-başlık</Heading> <Heading>Alt-başlık</Heading> <Section> <Heading>Alt-alt-başlık</Heading> <Heading>Alt-alt-başlık</Heading> <Heading>Alt-alt-başlık</Heading> </Section> </Section> </Section> </Section> ); }
Bu konuyu öğrenmeye hazır mısınız?
Context ile Veriyi Derinlemesine Aktarma sayfasını okuyarak prop iletmesine alternatif olarak context’i nasıl kullanacağınızı öğrenebilirsiniz.
Devamını OkuReducer ve context ile ölçeklendirme
Reducer’lar bir bileşenin state güncelleme mantığını bir araya getirmenizi sağlar. Context, bilgileri diğer bileşenlere derinlemesine iletmeye olanak tanır. Reducer’ları ve context’i bir araya getirerek karmaşık bir ekranın state’ini yönetebilirsiniz.
Bu yaklaşımla birlikte, karmaşık state’e sahip bir üst bileşen bunu bir reducer ile yönetir. Ağacın herhangi bir yerindeki diğer bileşenler context aracılığıyla state’i okuyabilir. Ayrıca bu state’i güncellemek için eylemler de dispatch edebilirler.
import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; import { TasksProvider } from './TasksContext.js'; export default function TaskApp() { return ( <TasksProvider> <h1>İstanbul'da bir gün</h1> <AddTask /> <TaskList /> </TasksProvider> ); }
Bu konuyu öğrenmeye hazır mısınız?
Reducer ve Context ile Ölçeklendirme sayfasını okuyarak büyüyen bir uygulamada state yönetiminin nasıl ölçeklendirildiğini öğrenin.
Devamını OkuSırada ne var?
Girdiye State ile Reaksiyon Verme sayfasına giderek okumaya başlayın!
Ya da, bu konulara zaten aşina iseniz, neden Kaçış Yolları sayfasını okumuyorsunuz?