Photo by Zdeněk Macháček on Unsplash

ZUSTAND

Zustand ile Re-Render Senaryoları ve Optimizasyon Yöntemleri — 2 (Kompleks Yapılar)

State Management kütüphaneleri doğru kullanmadığımız taktirde bileşenlerin re-render , yani tekrar tekrar render edildiğini görürüz. Bu durumu nasıl engelleyebiliriz ?

4 min readNov 22, 2023

--

Bu yazı daha önceden yazdığım Zustand ile Re-Render Senaryoları ve Optimizasyon Yöntemleri — 1 blog yazısının devamı niteliğindedir. Bir önceki senaryomuzda ZustandStore tuttuğumuz count sayısını 2ye çıkarıp senaryoları farklılaştırmıştık. Re-render problemini çözmek için Atomic ve Shallow’ dan faydalanmıştık.

Bu örnekte senaryoyu biraz farklılaştırdım.

https://onurdayibasi.dev/state-zustand3

Burada Comp1 içerisindeki Inc1 → Comp2 tetikliyor, Comp2 içerisindeki Inc2 → Comp1 tetikliyor. Ama göreceksiniz ki Shallow da bu işe yaramıyor. Bunun nedeni Inc1 ve Inc2 metodları store içerisinde yer aldığı için Shallow burada işe yaRAmıyor.

//Shallow
export const useSampleSlice = create((set, get) => ({
count1: 0,
count2: 0,
increase1: () =>
set((state) => {
console.log('increase1');
return {count1: state.count1 + 1};
}),
increase2: () =>
set((state) => {
console.log('increase2');
return {count2: state.count2 + 1};
}),
}));

export const useSampleStore = () =>
useSampleSlice(
useShallow((state) => ({
count1: state.count1,
count2: state.count2,
increase1: state.increase1,
increase2: state.increase2,
})),
);

Bunun aksine atomic yöntem de yani Comp3 ve Comp4 de benzer durum tamda istenildiği gibi çalışıyor.

//Atomic
export const useSampleSlice2 = create((set, get) => ({
count1: 0,
count2: 0,
increase1: () =>
set((state) => {
console.log('increase1');
return {count1: state.count1 + 1};
}),
increase2: () =>
set((state) => {
console.log('increase2');
return {count2: state.count2 + 1};
}),
}));

export const useCount1 = () => useSampleSlice2((state) => state.count1);
export const useCount2 = () => useSampleSlice2((state) => state.count2);
export const useIncrease1 = () => useSampleSlice2((state) => state.increase1);
export const useIncrease2 = () => useSampleSlice2((state) => state.increase2);

Atomic yapı buradaki fonksiyondan kaynaklı re-render çözüyor fakat atomic yapmak için tüm kullanımları tek tek atomic hale getirmek ve bileşenlerden bu atomic fonksiyonlarını kullanmasını istemek gerekiyor .

Burada dezavantaj atomic yapıda Client birçok atomic yapıdan seçim yapma ihtiyacının doğması yani API’nin daha zor okunup anlaşılması demek oluyor.

Zustand Atomic Olarak Kapsamlı Store Yapılarında Nasıl Kullanabiliriz ?

Bu örneğimiz de daha kapsamlı bir store yapısı ile karşı karşıya olacağız. Daha kompleks re-render senaryolarını deneyeceğiz

https://onurdayibasi.dev/state-zustand4

Öncelikle store yapımızı inceleyelim.

const useSampleStoreSlice = (set, get) => ({
//primitive
count1: 0,
count2: 0,

//static object
filterInputs: {
name: 'Empty',
no: 0,
},

//dynamic object
handle: {abc: '23123'},

//functions
increase1: () => set((state) => ({count1: state.count1 + 1})),
increase2: () => set((state) => ({count2: state.count2 + 1})),
setFilterInputs: (filterObj) => {
set({filterInputs: {...filterObj}});
},

setHandle: (mode) => {
const p = get().handle;
p[mode + ''] = uuidv4();
set({handle: {...p}});
},
});

Store yapısı içerisinde primitive olarak daha önceki örneklerde de incelediğimiz count1, count2 değerleri bulunuyor

İkinci olarak filterInputs tutan static bir object bulunuyor

Üçüncü olarak gönderilen komutları key, value şeklinde dinamik object olarak tutan handle yer alıyor.

Store yapısının alt kısmında ise yukarıdaki değerleri güncelleyebilecek fonksiyonlarımız bulunuyor.

Şimdi gelin store oluşturalım.

const useSampleStore = create(
devtools(useSampleStoreSlice, {
name: 'ZustandSample4Store',
serialize: {options: true},
}),
);

Bu store useSampleStore üzerinden kullandığımız taktirde bileşenlerin re-render durumlarının nasıl problem çıkardığını önceki blog yazılarımda anlatmıştım. Bir önceki örnekte de Atomic yapının Shallow göre daha avantajlı olduğundan bahsetmiştim.

Her bir şeyi atomic API yaptığımızda ise .. useCount1, useCount2 … ile her bileşende kullandığımız terminoloji store kompleksitesini göre artıyor.

// export const useCount1 = () => useSampleStore((state) => state.count1);
// export const useCount2 = () => useSampleStore((state) => state.count2);
// export const useHandle = () => useSampleStore((state) => state.handle);
// export const useHandleSelector = (prop) => useSampleStore((state) => state.handle[prop]);
// ....

Bu durumda bunu daha genel bir hale nasıl getirebiliriz. Kendi selektörümüzü yazarak ..

export const useSampleStoreSelector = (selector) => {
return useSampleStore((state) => {
return selector.split('.').reduce((acc, part) => acc && acc[part], state);
});
};

Bu selector bize Redux ta kullandığımız connect APIsi üzerinden mapStateToProps and mapDispatchToProps ile store getirdiğimiz yapıya dönüştürecektir.

Şimdi gelin bileşenler kısmına bakalım.

import {useSampleStoreSelector} from './store';

function Comp1() {
const increase1 = useSampleStoreSelector('increase1');
const count2 = useSampleStoreSelector('count2');
const setHandle = useSampleStoreSelector('setHandle');
return (
<>
<div>Comp1</div>
<span>{new Date().getMilliseconds()}</span>
<button
onClick={() => {
increase1();
setHandle('list');
}}
>
Inc Count1 & UpdateList
</button>
<div>count 2 :{count2}</div>
</>
);
}

Yada daha kompleks bir obje içerisindeki başka bir prop erişmek isteyelim. filterInputs.name gibi prop path üzerinden ulaşabilirsiniz.

import {useSampleStoreSelector} from './store';

function Comp7() {
const name = useSampleStoreSelector('filterInputs.name');
return (
<>
<div>Comp7</div>
<span>{new Date().getMilliseconds()}</span>
<div>Filter Inputs name :{name}</div>
</>
);
}

Store prop ve fonksiyonlarına Erişim

Yapıyı genel hale getirdik fakat store aşağıdaki şekilde string üzerinden erişmenin getirdiği bir takım problemler var. String kırılgan bir yapı yanlış yazılması durumunda bir çok hataya neden olabilir.

const name = useSampleStoreSelector('filterInputs.name');

Store erişebilecek properties/prop değerleri bir enum üzerinden dağıtılır ve kullanılırsa bu tip hatalar oluşmaz.

Bunun için önce prop type tanımlıyorum.

export const SampleStoreSelectorProps = {
count1: 'count1',
count2: 'count2',
filterInputs: 'filterInputs',
filterInputsName: 'filterInputs.name',
handle: 'handle',
increase1: 'increase1',
increase2: 'increase2',
setFilterInputs: 'setFilterInputs',
setHandle: 'setHandle',
};

sonrasında ise artık bileşenlere bu props üzerinden erişiyorum.

function Comp1() {
const increase1 = useSampleStoreSelector(SampleStoreSelectorProps.increase1);
const count2 = useSampleStoreSelector(SampleStoreSelectorProps.count2);
const setHandle = useSampleStoreSelector(SampleStoreSelectorProps.setHandle);
return (
<>
<div>Comp1</div>
<span>{new Date().getMilliseconds()}</span>
<button
onClick={() => {
increase1();
setHandle('list');
}}
>
Inc Count1 & UpdateList
</button>
<div>count 2 :{count2}</div>
</>
);
}

Daha derindeki props erişimde..

import {SampleStoreSelectorProps, useSampleStoreSelector} from './store';

function Comp7() {
const name = useSampleStoreSelector(SampleStoreSelectorProps.filterInputsName);
return (
<>
<div>Comp7</div>
<span>{new Date().getMilliseconds()}</span>
<div>Filter Inputs name :{name}</div>
</>
);
}

Referanslar

Okumaya Devam Et 😃

Bu yazının devamı veya yazı grubundaki diğer yazılara [React State] erişmek için bu linke tıklayabilirsiniz.

Bu yazının devamı veya yazı grubundaki diğer yazılara [React Performance] erişmek için bu linke tıklayabilirsiniz.

--

--