reactjs - Her öğesi bir öncekine bağlı olan durumlu bir listenin React tasarımı
Projelerimden birini React ile geliştiriyorum ve her öğesi bir tablo satırını temsil eden ve önceki satıra (ve kısmen bir sonraki satıra) bağlı olan bazı listeler oluşturmam gerekiyor - Excel veya Google Sheet’in çalışmasına benzer şekilde.
Diyelim ki her öğenin üç alanı var: a, b ve c. Her öğe kendi a alanını önceki öğenin a alanına göre, b alanını ise bir sonraki öğenin c alanına göre günceller.
Mevcut tasarımım, her öğeyi ayrı bir bileşen olarak tutmak (tablo satırını render etmek) ve ebeveyn bileşende iki liste tutmak - biri her öğenin durumları için, diğeri ise öğelerin kendisi için.
Şuna benziyor:
interface ItemState {
a: number,
b: number,
c: number,
}
function ItemComponent({prevState, nextState, state, setState}:
{prevState: ItemState; nextState: ItemState; state: ItemState; setState: (state: ItemState) => void;}) {
React.useEffect(() => {
setState({...state, a: prevState.a+1});
}, [prevState.a]);
React.useEffect(() => {
setState({...state, b: nextState.c-1});
}, [nextState.c]);
return (<...>); // table row
}
function ParentComponent() {
const [stateList, setStateList] = React.useState<ItemState[]>([item1, item2, item3]);
const mapState = React.useCallback(
(prev: ItemState | null, curr: ItemState, next: ItemState | null) => {
return (
<ItemComponent
key="someUniqueId"
prevState={prev}
nextState={next}
state={curr}
setState="some function which sets a specific item in the list based on id"
/>
);
},
[]
);
const itemList = React.useMemo(
() =>
stateList.map((state, i) => {
const prev = i === 0 ? null : stateList[i - 1];
const next = i === stateList.length - 1 ? null : stateList[i + 1];
return mapState(prev, stintState, next);
}),
[stateList, mapState]
);
return (<...>); // rendered table containing the itemList
}
(Açıkçası bu basitleştirilmiş bir versiyondur, gerçekte a, b ve c alanlarının ötesinde daha fazla alan üzerinde daha karmaşık hesaplamalar yapıyorum.)
Bu çözüm işe yarıyor. Ancak, performansı berbat, çünkü genellikle listedeki öğe sayısı sadece 3'ten çok daha fazla oluyor ve bu da bir öğe güncellendiğinde tüm alt öğeleri güncelleyebilmek için önemli bir zaman almasına neden oluyor.
Ayrıca, liste yaklaşık 14 öğeden fazla olduğunda "Maksimum güncelleme derinliği aşıldı" hataları almaya başlıyorum; bunun nedeni useEffect içindeki çok sayıda setState çağrısıdır.
Bu soruna çok daha zarif ve etkili bir çözüm olduğuna eminim, sadece kendim çözemedim - ve bu konuda biraz yardım almak isterim.
Düzenle :
Buradaki ana amaç, her satırdaki bazı değerleri değiştirebileceğim bir tabloyu görüntülemektir. i satırındaki değişiklikler aşağıdaki akışları tetiklemelidir:
- Satır
i-1için belirli değerleri yeniden hesaplayın ve tekrar render edin, ancak bunun sonucunda başka herhangi bir hesaplama/render tetiklemeyin. - Adım 1'deki değerlerden farklı olan belirli değerleri
i+1satırı için yeniden hesaplayın ve aynı işlemii+1satırı üzerinde tetikleyin (adımlar 1 ve 2).
Yani, bir kez i satırı değiştirildiğinde, i-1 satırı yeniden çizilmeli ve önceki herhangi bir satır da yeniden çizilmelidir.
Yaptığım hesaplamalar oldukça karmaşık - bu yüzden satır bağımlılığı var (aksi takdirde her güncellemede önceki satırı beklemeden her bir satırın verilerini doğrudan hesaplardım).
Cevap #1
Bence bunu önemli ölçüde gereksiz yere karmaşıklaştırıyorsun. Bence en iyi yaklaşım, kullandığın veriyi bileşenin props'larına uygun biçimde sadece hafızaya almaktır. Şöyle bir şey yap:
const formattedData = useMemo(() => doMyCalculations(stateList), [stateList])
Ve sonra sadece formattedData'yı eşleyin, bu bir nesne dizisi (veya sizin durumunuza daha uygunsa diziler) olmalıdır.
<div>
{formattedData.map((data) => {
return <ItemComponent .../>
})}
</div>
Şu anda hem bileşeni hem de bileşeni oluşturmak için fonksiyonu hafızaya alıyorsunuz, ki bunlar temelde aynı şeyin iki farklı versiyonudur.
Ayrıca, eğer render anında çalışan bir useEffect kancasının içinde bir durumu ayarlarsanız ve bu, nesnenin bağlı olduğu listenin durumunu değiştirirse, sonsuz bir döngü oluşturursunuz. Bu veriyi önceden hesaplamanız veya sadece bir kullanıcı etkileşimi veya koşullu olarak çalışan bir şey üzerinde yapmanız gerekir... hemen render anında değil. Bu, bir yeniden render tetikleyecek, bu da listenin bir başka değişikliğini tetikleyecek ve bu da tekrar bir yeniden render tetikleyecek... ve kendinizi bir kara delikte bulursunuz.
Bunun sonsuz döngüye neden olmasının nedeni
ItemComponent'i oluşturan fonksiyonları içeren itemList, stateList durum parçasına bağlıdır. Her ItemComponent'te, her render işleminden hemen sonra çalışacak bir useEffect kancası vardır. Bu useEffect kancasının içinde, stateList durum parçasını değiştiren bir durum ayarlıyorsunuz. Dizide sadece bir öğeyi değiştirseniz bile fark etmez, bu stateList durum parçasını değiştirecektir ve bu da ItemComponent'in dahil olduğu tüm çocukların yeniden render edilmesine neden olur. Bu da sırayla useEffect kancasının tekrar çalışmasına, bu kancanın tekrar durum ayarlamasına ve sonuç olarak bir başka yeniden render işleminin gerçekleşmesine yol açacaktır.
Eğer tam olarak neyi başarmaya çalıştığınız ve bu useEffect kancasına neden ihtiyaç duyduğunuz hakkında daha fazla detay verebilirseniz, bunu nasıl yapmanız gerektiğine dair daha eksiksiz bir örnek verebilirim.
Düzenle :
i'den küçük satırların güncellenmesi gereken bir şeyi, pahalı bir hesaplamayı tekrar tekrar çalıştırmaya gerek kalmadan başarmak için iki şeyden birini yapabilirsiniz. Ya durumu üst bileşene taşırsınız (şu anda neredeyse sahip olduğunuz gibi) ve alt bileşende veriyi memoize edersiniz (bileşen yeniden render edilse bile veri yeniden hesaplanmaz), ya da alt bileşende veri memoize etmeyi bir kenara bırakıp, tüm veriyi üst bileşende hesaplar, ancak öğe indeksinin güncellenmesi gereken bir indeks olup olmadığını kontrol eder ve değilse mevcut değeri kullanırsınız. Her ikisi de yeniden render gerektirecek, ancak her iki durumda da güncellenmemesi gereken indekslerde pahalı hesaplamayı çalıştırmayacaksınız. İlki ile, özellikle daha fazla durum eklemeden ki bu muhtemelen render sayısını artırır, tıklanan bir indekse dayalı useMemo kancasını çalıştırmaya çalışırken bazı zorluklarla karşılaşabilirsiniz, ancak ikincisi sadece şöyle görünecektir:
<div>
{formattedData.map((data, i) => {
return <ItemComponent ... index={i} updateState={setStateList} stateList={stateList} />
})}
</div>
Ve ItemComponent'de:
const ItemComponent = ({index, updateState, stateList, ...props}) => {
const theExpensiveComputation = (item) => doStuff(item)
const handleUpdate = () => {
updateState(stateList.map((item, i) => i < index ? theExpensiveComputation(item) : item))
}
return (<div>...</div>)
}
