Bileşenler Arasında Durum Paylaşımı
Bazen, iki bileşenin durumunun her zaman birlikte değişmesini istersiniz. Bunu yapmak için, her ikisinden de durumu çıkarın, en yakın ortak üst bileşenlerine taşıyın ve ardından bunu props üzerinden onlara iletin. Bu işleme durum yükseltme denir ve bu, React kodu yazarken yapacağınız en yaygın şeylerden biridir.
- Durumu bileşenler arasında yükselterek paylaşmayı
- Kontrollü ve kontrolsüz bileşenlerin ne olduğunu
Örnekle Durum Yükseltme {/lifting-state-up-by-example/}
Bu örnekte, bir üst Accordion
bileşeni iki ayrı Panel
bileşeni render ediyor:
Accordion
Panel
Panel
Her Panel
bileşeni, içeriğinin görünür olup olmadığını belirleyen bir boolean isActive
durumuna sahiptir.
Her iki panel için "Göster" butonuna basın:
import { useState } from 'react';
function Panel({ title, children }) {
const [isActive, setIsActive] = useState(false);
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={() => setIsActive(true)}>
Göster
</button>
)}
</section>
);
}
export default function Accordion() {
return (
<>
<h2>Almatı, Kazakistan</h2>
<Panel title="Hakkında">
Yaklaşık 2 milyonluk bir nüfusa sahip olan Almatı, Kazakistan'ın en büyük şehridir. 1929'dan 1997'ye kadar ülkenin başkenti olmuştur.
</Panel>
<Panel title="Etymoloji">
İsim, Kazakça "elma" anlamına gelen <span lang="kk-KZ">алма</span> kelimesinden gelmektedir ve genellikle "elmalarla dolu" olarak çevrilir. Aslında, Almatı'yı çevreleyen bölgenin elmanın atası olduğu düşünülmektedir ve vahşi <i lang="la">Malus sieversii</i> modern evcil elmanın muhtemel atası olarak kabul edilmektedir.
</Panel>
</>
);
}
h3, p { margin: 5px 0px; }
.panel {
padding: 10px;
border: 1px solid #aaa;
}
Bir panel butonuna basmanın diğer paneli etkilemediğine dikkat edin; bunlar bağımsızdır.
Başlangıçta, her Panel
'in isActive
durumu false
olduğundan, her ikisi de çökük görünür
Herhangi bir Panel
'in butonuna tıkladığınızda sadece o Panel
'in isActive
durumu güncellenir
Ama şimdi, yalnızca bir panelin herhangi bir zamanda genişletilmesini isteyip istemediğinizi düşünelim. Bu tasarımla, ikinci panelin genişletilmesi birinci panelin kapanmasını sağlamalıdır. Bunu nasıl yaparsınız?
Bu iki paneli koordine etmek için, durumu "yükseltmeniz" gerekir. Bu işlem üç aşamada gerçekleştirilir:
- Durumu çocuk bileşenlerden kaldırın.
- Ortak üstten sabit veriler geçin.
- Ortak üst bileşene durum ekleyin ve olay işleyicileri ile birlikte aşağıya geçin.
Bu, Accordion
bileşeninin her iki Panel
'i koordine etmesini ve yalnızca birinin genişlemesini sağlar.
Adım 1: Durumu çocuk bileşenlerden kaldırın {/step-1-remove-state-from-the-child-components/}
Panel
'in isActive
kontrolünü üst bileşenine vereceksiniz. Bu, üst bileşenin isActive
değerini Panel
'e bir prop olarak ileteceği anlamına gelir. Başlangıçta Panel
bileşeninden bu satırı kaldırın:
const [isActive, setIsActive] = useState(false);
Ve onun yerine, Panel
'in prop listesine isActive
ekleyin:
function Panel({ title, children, isActive }) {
Artık Panel
'in üst bileşeni isActive
değerini prop olarak ileterek kontrol edebilir.
Tersine, Panel
bileşeni artık isActive
değerinin üzerinde hiçbir kontrol sahibi değildir; artık bu üst bileşene bağlıdır!
Adım 2: Ortak üstten sabit verileri geçin {/step-2-pass-hardcoded-data-from-the-common-parent/}
Durumu yükseltmek için, koordine etmek istediğiniz her iki çocuk bileşenin en yakın ortak üst bileşenini bulmanız gerekir:
Accordion
(en yakın ortak üst)Panel
Panel
Bu örnekte, Accordion
bileşenidir. Her iki panelin üzerinde yer aldığı ve bunların props'larını kontrol edebildiği için, hangi panelin şu anda aktif olduğunu belirleyen "gerçeklik kaynağı" olacaktır. Accordion
bileşeninin her iki panele de sabit bir isActive
değeri (örneğin true
) geçmesini sağlayın:
import { useState } from 'react';
export default function Accordion() {
return (
<>
<h2>Almatı, Kazakistan</h2>
<Panel title="Hakkında" isActive={true}>
Yaklaşık 2 milyonluk bir nüfusa sahip olan Almatı, Kazakistan'ın en büyük şehridir. 1929'dan 1997'ye kadar ülkenin başkenti olmuştur.
</Panel>
<Panel title="Etymoloji" isActive={true}>
İsim, Kazakça "elma" anlamına gelen <span lang="kk-KZ">алма</span> kelimesinden gelmektedir ve genellikle "elmalarla dolu" olarak çevrilir. Aslında, Almatı'yı çevreleyen bölgenin elmanın atası olduğu düşünülmektedir ve vahşi <i lang="la">Malus sieversii</i> modern evcil elmanın muhtemel atası olarak kabul edilmektedir.
</Panel>
</>
);
}
function Panel({ title, children, isActive }) {
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={() => setIsActive(true)}>
Göster
</button>
)}
</section>
);
}
h3, p { margin: 5px 0px; }
.panel {
padding: 10px;
border: 1px solid #aaa;
}
Accordion
bileşenindeki sabit isActive
değerlerini düzenlemeyi deneyin ve sonuçları ekranda görün.
Adım 3: Ortak üst bileşene durum ekleyin {/step-3-add-state-to-the-common-parent/}
Durumu yükseltmek, genellikle neyi durum olarak saklayacağınızı değiştirir.
Bu durumda, yalnızca bir panel her seferinde aktif olmalıdır. Bu, Accordion
ortak üst bileşeninin hangi panelin aktif olduğunu takip etmesi gerektiği anlamına gelir. boolean
değeri yerine, durumu indeks olarak aktif Panel
için bir sayı kullanabilir:
const [activeIndex, setActiveIndex] = useState(0);
activeIndex
0
olduğunda, birinci panel aktiftir; 1
olduğunda, ikinci panel aktiftir.
Herhangi bir Panel
'deki "Göster" butonuna tıklamak, Accordion
içindeki aktif indeksi değiştirmelidir. Panel
doğrudan activeIndex
durumunu ayarlayamaz, çünkü bu değer Accordion
içinde tanımlıdır. Accordion
bileşeni, Panel
bileşeninin durumunu değiştirmesine açıkça izin vermelidir bir olay işleyicisini prop olarak geçirerek
:
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
Panel
içindeki `, artık tıklama olay işleyicisi olarak
onShow` propunu kullanacaktır:
import { useState } from 'react';
export default function Accordion() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<>
<h2>Almatı, Kazakistan</h2>
<Panel
title="Hakkında"
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
Yaklaşık 2 milyonluk bir nüfusa sahip olan Almatı, Kazakistan'ın en büyük şehridir. 1929'dan 1997'ye kadar ülkenin başkenti olmuştur.
</Panel>
<Panel
title="Etymoloji"
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
İsim, Kazakça "elma" anlamına gelen <span lang="kk-KZ">алма</span> kelimesinden gelmektedir ve genellikle "elmalarla dolu" olarak çevrilir. Aslında, Almatı'yı çevreleyen bölgenin elmanın atası olduğu düşünülmektedir ve vahşi <i lang="la">Malus sieversii</i> modern evcil elmanın muhtemel atası olarak kabul edilmektedir.
</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>
);
}
h3, p { margin: 5px 0px; }
.panel {
padding: 10px;
border: 1px solid #aaa;
}
Bu, durumu yükseltme işlemini tamamlar! Durumu ortak üst bileşene taşımak, iki paneli koordine etmenizi sağladı. İki "görüntüleniyor" bayrağı yerine aktif indeksi kullanmak, her seferinde yalnızca bir panelin aktif olmasını sağladı. Olay işleyicisini alt bileşene geçmek, alt bileşenin üst bileşenin durumunu değiştirmesine olanak tanıdı.
Başlangıçta, Accordion
'ın activeIndex
değeri 0
olduğundan, ilk Panel
isActive = true
alır
Accordion
'ın activeIndex
durumu 1
'e değiştiğinde, ikinci Panel
isActive = true
alır
Kontrollü ve Kontrolsüz Bileşenler {/controlled-and-uncontrolled-components/}
Bir bileşenin bir miktar yerel durumu olması "kontrolsüz" olarak adlandırılır. Örneğin, orijinal Panel
bileşeni bir isActive
durum değişkenine sahipse, kontrolsüzdür çünkü üst bileşeni, panelin aktif olup olmadığını etkileyemez.
Tersine, bir bileşenin önemli bilgileri, kendi yerel durumundan ziyade props tarafından yönlendiriliyorsa, "kontrollü" olduğu söylenebilir. Bu, üst bileşenin tam olarak davranışını belirlemesine olanak tanır. Son Panel
bileşeni, isActive
prop ile Accordion
bileşeni tarafından kontrol edilmektedir.
Kontrolsüz bileşenler, üstler içinde daha az yapılandırma gerektirdiğinden daha kolay kullanılır. Ancak, onları bir arada koordine etmek istediğinizde daha az esneklik sunar. Kontrollü bileşenler en yüksek esneklik sağlar, ancak üst bileşenlerin onları tamamen konfigüre etmelerini gerektirir.
Pratikte, "kontrollü" ve "kontrolsüz" kesin teknik terimler değildir; her bileşen genellikle hem yerel durum hem de props'un bir karışımına sahiptir. Ancak, bileşenlerin nasıl tasarlandığı ve hangi yetenekleri sunduğuna dair konuşmak için yararlı bir yoldur.
Bir bileşen yazarken, içinde hangi bilginin kontrollü (props aracılığıyla) ve hangisinin kontrolsüz (durum aracılığıyla) olması gerektiğini düşünün. Ancak her zaman fikrinizi değiştirebilir ve yeniden yapılandırabilirsiniz.
Her Durum İçin Tek Bir Doğru Kaynak {/a-single-source-of-truth-for-each-state/}
Bir React uygulamasında, birçok bileşen kendi durumuna sahip olacaktır. Bazı durumlar, ağaçtaki yaprak bileşenlerin (en alttaki bileşenler) yakınında "yaşayabilir", diğer durumlar ise uygulamanın üst tarafında "yaşayabilir". Örneğin, istemci tarafı yönlendirme kütüphaneleri genellikle mevcut rotayı React durumunda saklayarak ve bunu props aracılığıyla geçirerek uygulanır!
Her bir benzersiz durum parçası için "sahip" olan bileşeni seçeceksiniz. Bu ilkeye, "tek bir doğru kaynak" oluşturma da denir. Bu, tüm durumların tek bir yerde yaşadığı anlamına gelmez; ancak her durum parçası için belirli bir bileşenin bu bilgiyi tutması gerektiği anlamına gelir. Bileşenler arasındaki paylaşılan durumu çoğaltmak yerine, onu ortak paylaşılan üst bileşene yükseltin ve onu ihtiyaç duyan çocuklara iletiin.
Uygulamanız üzerinde çalışırken değişiklikler olacaktır. Her bir durum parçasının "nerede yaşadığı" konusunda hala fikir edinmeye çalışırken, durumları aşağı veya yukarı taşımanız yaygındır. Bu, sürecin bir parçasıdır!
Bunun pratikte nasıl hissettirdiğini görmek için birkaç bileşenle React'ta Düşünmek
yazısına bakın.
- İki bileşeni koordine etmek istediğinizde, durumlarını ortak üst bileşenlerine taşıyın.
- Sonra, ortak üst bileşenden bilgi geçirin props aracılığıyla.
- Son olarak, çocukların üst bileşenin durumunu değiştirebilmesi için olay işleyicilerini aşağı gönderen props randomklarllä geçirikoa sağlarsınız.
- Bileşenleri "kontrollü" (props tarafından yönlendirilen) veya "kontrolsüz" (durum tarafından yönlendirilen) olarak düşünmek faydalıdır.
Senkronize Girdiler {/synced-inputs/}
Bu iki girdi bağımsızdır. Onları senkronize tutun: bir girişi düzenlemek diğer girişi aynı metinle güncellemelidir ve tam tersi.
Durumlarını ebeveyn bileşenine yükseltmeniz gerekecek.
import { useState } from 'react';
export default function SyncedInputs() {
return (
<>
<Input label="Birinci girdi" />
<Input label="İkinci girdi" />
</>
);
}
function Input({ label }) {
const [text, setText] = useState('');
function handleChange(e) {
setText(e.target.value);
}
return (
<label>
{label}
{' '}
<input
value={text}
onChange={handleChange}
/>
</label>
);
}
input { margin: 5px; }
label { display: block; }
text
durum değişkenini yanı sıra handleChange
işleyicisini ebeveyn bileşenine taşıyın. Ardından bunları her iki Input
bileşenine prop olarak geçirin. Bu, onları senkronize tutacaktır.
import { useState } from 'react';
export default function SyncedInputs() {
const [text, setText] = useState('');
function handleChange(e) {
setText(e.target.value);
}
return (
<>
<Input
label="Birinci girdi"
value={text}
onChange={handleChange}
/>
<Input
label="İkinci girdi"
value={text}
onChange={handleChange}
/>
</>
);
}
function Input({ label, value, onChange }) {
return (
<label>
{label}
{' '}
<input
value={value}
onChange={onChange}
/>
</label>
);
}
input { margin: 5px; }
label { display: block; }
Bir Listeyi Filtreleme {/filtering-a-list/}
Bu örnekte, SearchBar
'ın kendi query
durumu, metin girişini kontrol eder. Üst FilterableList
bileşeni bir List
öğeleri gösterirken, bu arama sorgusunu dikkate almaz.
Listeyi arama sorgusuna göre filtrelemek için filterItems(foods, query)
fonksiyonunu kullanın. Değişikliklerinizi test etmek için, girişi "s" yazarak listeyi "Sushi", "Shish kebab" ve "Dim sum" ile filtrelediğinizi kontrol edin.
filterItems
zaten uygulanmış ve içe aktarılmıştır, bu yüzden kendiniz yazmanıza gerek yoktur!
query
durumunu ve handleChange
işleyicisini SearchBar
'dan kaldırmanız ve bunları FilterableList
'e taşımanız gerekecek. Sonra bunları SearchBar
'a query
ve onChange
props'u olarak geçirin.
import { useState } from 'react';
import { foods, filterItems } from './data.js';
export default function FilterableList() {
return (
<>
<SearchBar />
<hr />
<List items={foods} />
</>
);
}
function SearchBar() {
const [query, setQuery] = useState('');
function handleChange(e) {
setQuery(e.target.value);
}
return (
<label>
Arama:{' '}
<input
value={query}
onChange={handleChange}
/>
</label>
);
}
function List({ items }) {
return (
<table>
<tbody>
{items.map(food => (
<tr key={food.id}>
<td>{food.name}</td>
<td>{food.description}</td>
</tr>
))}
</tbody>
</table>
);
}
export function filterItems(items, query) {
query = query.toLowerCase();
return items.filter(item =>
item.name.split(' ').some(word =>
word.toLowerCase().startsWith(query)
)
);
}
export const foods = [{
id: 0,
name: 'Sushi',
description: 'Sushi, hazırlanan sirke ile harmanlanmış pirinçten oluşan geleneksel bir Japon yemeğidir.'
}, {
id: 1,
name: 'Dal',
description: 'Dal'ı hazırlamanın en yaygın yolu, soğan, domates ve çeşitli baharatlar eklenerek bir çorba şeklinde yapılmasıdır.'
}, {
id: 2,
name: 'Pierogi',
description: 'Pierogi, dolgusunun etrafına mayasız hamur sarmak ve kaynar suda pişirmek suretiyle yapılan dolgulardır.'
}, {
id: 3,
name: 'Shish kebab',
description: 'Şiş kebabı, şişe dizilmiş ve ızgara yapılmış et küplerinden oluşan popüler bir yemektir.'
}, {
id: 4,
name: 'Dim sum',
description: 'Dim sum, geleneksel olarak kahvaltı ve öğle yemeği yemekleri için restoranlarda tadına bakılan küçük yemeklerin geniş bir yelpazesidir.'
}];
query
durumunu FilterableList
bileşenine yükseltin. filterItems(foods, query)
fonksiyonunu çağırarak filtrelenmiş listeyi alın ve bunu List
'e geçirin. Artık arama girişi değiştiğinde listede de yansır:
import { useState } from 'react';
import { foods, filterItems } from './data.js';
export default function FilterableList() {
const [query, setQuery] = useState('');
const results = filterItems(foods, query);
function handleChange(e) {
setQuery(e.target.value);
}
return (
<>
<SearchBar
query={query}
onChange={handleChange}
/>
<hr />
<List items={results} />
</>
);
}
function SearchBar({ query, onChange }) {
return (
<label>
Arama:{' '}
<input
value={query}
onChange={onChange}
/>
</label>
);
}
function List({ items }) {
return (
<table>
<tbody>
{items.map(food => (
<tr key={food.id}>
<td>{food.name}</td>
<td>{food.description}</td>
</tr>
))}
</tbody>
</table>
);
}
export function filterItems(items, query) {
query = query.toLowerCase();
return items.filter(item =>
item.name.split(' ').some(word =>
word.toLowerCase().startsWith(query)
)
);
}
export const foods = [{
id: 0,
name: 'Sushi',
description: 'Sushi, hazırlanan sirke ile harmanlanmış pirinçten oluşan geleneksel bir Japon yemeğidir.'
}, {
id: 1,
name: 'Dal',
description: 'Dal'ı hazırlamanın en yaygın yolu, soğan, domates ve çeşitli baharatlar eklenerek bir çorba şeklinde yapılmasıdır.'
}, {
id: 2,
name: 'Pierogi',
description: 'Pierogi, dolgusunun etrafına mayasız hamur sarmak ve kaynar suda pişirmek suretiyle yapılan dolgulardır.'
}, {
id: 3,
name: 'Shish kebab',
description: 'Şiş kebabı, şişe dizilmiş ve ızgara yapılmış et küplerinden oluşan popüler bir yemektir.'
}, {
id: 4,
name: 'Dim sum',
description: 'Dim sum, geleneksel olarak kahvaltı ve öğle yemeği yemekleri için restoranlarda tadına bakılan küçük yemeklerin geniş bir yelpazesidir.'
}];