Server state at client state sa React
Bumalik sa blog

Server state at client state: bakit dalawang library ang kailangan ng React apps

Halos bawat React Native app ay may hawak na dalawang uri ng state na magkaiba ang ugali: server state at client state. Kapag naikilala mo sila, halos sasagot na mismo ang pili mong library. Kapag pinaghalo mo sila, mauuwi ka sa paggawa nang manu-mano ng bagay na ibibigay sana ng library nang libre.

Ang dalawang post na ito ay maikling pahinga mula sa Module Federation series, dahil ang federated na trabaho sa unahan ay nakasandal mismo sa distinction na ito. Mas madaling maintindihan muna ito sa isang plain app.

May dalawang pairing na matibay habang lumalaki ang app. Sa Redux Toolkit at RTK Query, iisang store lang ang hawak mo, pero ang server at client state ay hinahawakan ng dalawang magkaibang tool sa loob nito: purpose-built na server-state layer ang RTK Query, kasama na ang buong caching machinery, at ang client state ay nananatili sa plain na createSlice slices. Sa TanStack Query at Zustand naman, hinahati mo ito sa dalawang library: nakatira ang server cache sa sariling client ng TanStack, sa labas ng kahit anong store, at ang client state ay nasa maliit na Zustand store. Alinman doon, magkaiba ang handling ng dalawang uri at hindi kailanman pinipilit sa iisang daan. Ang shortcut na sumisira nito ay isang general-purpose store, isang thunk kada request, at cache logic na pinapalago nang manu-mano. Ayos lang ito sa maliit na app at nagiging pangalawang trabaho habang lumalaki.

Dalawang uri ng state

Ang server state ay isang local copy ng bagay na pag-aari ng server: profile mula sa /users/me, product list, order status. Snapshot lang kailanman ang copy, at puwedeng magbago ang original kaagad matapos mong basahin. Doon galing ang lahat ng trabaho. Ang copy ng remote data ay nangangailangan ng caching, para hindi ka nagre-refetch kada mount; deduplication, para ang dalawang component na humihingi ng parehong resource ay iisang request lang ang pinapaputok; background refetching, para tahimik na nag-fe-freshen ang screen; retry, para sa network na naglalagot; invalidation, para ang isang write ay makapag-mark ng related data bilang stale; at garbage collection, para tanggalin ang walang nagbabantay. Wala sa mga ‘yan ang exclusive sa React. ‘Yan ang utang ng kahit anong client sa datang hindi nito pag-aari.

Ang client state ang kabilang kalahati: data na buong pag-aari ng app. Ang piniling account, sort order, form draft, kung bukas ba ang modal, ang auth tokens na hawak mo pagkatapos mag-login (ang ligtas na tagong lugar nila ay sariling paksa), ang theme at wika ng user. Kailangan itong hawakan, kailangan itong reactive, at paminsan kailangang makaligtas sa restart. Halos ‘yan na ang buong listahan. Hindi ito puwedeng mag-stale, kaya walang caching na kailangan; walang ire-refetch, kaya walang background sync na kailangan. Hindi sila magkaibang antas ng iisang bagay. Magkaiba ang trabaho nila, at ang library na ginawa para sa isa ay maling tool para sa isa pa.

Ano ang hinihingi ng bawat kalahati

Ang server state ay kailangan ng library na may handang machinery. Gawin mo ito nang manu-mano at bawat feature ay pinapalago ang parehong scaffolding: isang request, isang loading flag, isang error field, at isang branch para sa bawat isa sa tatlong state na puwedeng kalagyan ng fetch. Ang server-state library ay ibinibigay lahat ito sa likod ng isang hook. Sa Redux, ang library na ‘yan ay RTK Query:

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: builder => ({
    getProfile: builder.query<Profile, void>({
      query: () => '/users/me',
    }),
  }),
});

export const { useGetProfileQuery } = api;

Isang linya lang ang binabasa ng component, at kasama na roon ang caching, deduplication, retry at refetch-on-focus:

const { data: profile, isLoading, error } = useGetProfileQuery();

Sa labas ng Redux, ginagawa ng TanStack Query ang parehong trabaho sa parehong hugis: i-declare ang query, hayaang patakbuhin ng library ang cache at ang lifecycle.

export const useProfile = () =>
  useQuery({ queryKey: ['profile'], queryFn: getProfile });

Halos wala namang hinihingi ang client state kumpara riyan. Ang preference ay walang server na ikukumpara, kaya kahit anong hawak ng app ay tama by definition, at walang problemang sinusolusyunan ang caching o refetching dito. Sapat na ang maliit na store. Sa Zustand:

export const useSettings = create<Settings>()(set => ({
  theme: 'system',
  setTheme: theme => set({ theme }),
}));

Walang reducer, walang dispatch, walang lifecycle. Sa Redux, ang parehong bagay ay isang plain na createSlice na may ilang setter. Alinman doon, humahawak ito ng value at hinahayaang mag-react dito ang mga component. Parehas ang mali sa magkabilang direksyon: ilagay mo ang client state sa server-state tool at bubuhatin mo ang machinery na hindi nito ginagamit; pagawain mo ang isang hand-rolled na store ng dalawang trabaho at muli mong itatayo nang manu-mano ang trabaho ng library.

Aling hati, at kailan

Walang universal na panalo sa dalawang pairing, at hindi ako magtitiwala sa kahit anong post na magbibigay sa ‘yo ng isa. Kung nakatira na ang team mo sa Redux, mas maikli ang daan ng RTK Query, dahil ibinababa nito ang server-state machinery sa store na pinapatakbo mo na. Kung papunta ka sa fresh na app na walang Redux commitment, ang TanStack Query at Zustand ay dalawang focused na tool na nagko-compose nang hindi nag-aapakan sa isa’t isa. At sa maliit na app, ang isang store na may thunk kada request ay isang lubos na makatwirang simula. Bantayan ang araw na masusumpungan mong nagsusulat ka ng cache logic nang manu-mano: ‘yan ang senyas para umabot sa tool na may dala nito.

Kaya bago ka susunod na umabot sa isang slice para hawakan ang isang API response, huminto muna sa mas maliit na tanong: kailangan mo ba ng server-state machinery, o may pwesto lang para itago ang value na kayang bantayan ng ilang component?

Mas tumatalim ang tanong sa sandaling tumigil ang mga feature sa pagbabahagi ng isang codebase. Kapag bawat isa ay nagsi-ship bilang sariling app at naglo-load sa runtime, ang kaninong cache ang authoritative ay hindi na convenience kundi bagay na kailangan mong i-coordinate. Doon babalik ang Module Federation series, at mas madali itong isipin kapag sanay na sanay ka na sa plain-app na bersyon.

Bago ‘yon, ang susunod na post ay nananatili sa server na kalahati at tumitingin sa iisang pagkakaiba ng mga tool na ito: kung paano hinahawakan ng cache tags ng RTK Query at ng query keys ng TanStack ang parehong invalidation, at kung bakit ang maliit na API difference na ‘yon ang nagpapasya kung gaano katibay ang bawat isa kapag higit sa isang team na ang nagko-commit dito.

Warren de Leon
Warren de Leon

Software Engineering Manager. Pinakahuling pinamunuan ang Mobile Platform team sa Hargreaves Lansdown. Sumusulat tungkol sa engineering leadership, React Native, at pagbuo ng magagandang team.

Tingnan ang profile