WebHU - Programozási kérdések és válaszok

Optimista megjelenítés és useEffect

TL'DR: Van mód az állapotváltozások pesszimista működtetésére, miközben a useEffect-et használjuk API-hívásokhoz?

Tegyük fel, hogy írt egy komponenst, amely oldalszámozott/rendezett adatrácsot jelenít meg, és ehhez a hamis példához hasonló useEffect-et írt:

useEffect(() => {
  fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`)
    .then(response => response.json())
    .then(data => setState({
      data
    }));
}, [paging, sorting]);

Tehát, ha nem tévedek, ez a lapozási vagy rendezési állapotok / kellékek (whatev) frissítésekor lekéri. Ez azt jelenti, hogy az adatok betöltése előtt optimistán jeleníti meg az új rendezési és lapozási állapotokat. Alapvetően az alkalmazás azt mondja a felhasználónak: "A kért oldalon vagyok a kért rendezéssel, de még mindig betöltöm". Más szavakkal, az alkalmazás állapotának egy része (a lapozás és a rendezés) azt mondja, hogy "kész vagyok", az állapot másik része (a betöltés) pedig azt mondja, hogy "dolgozom rajta".

Ezzel szemben, ha pesszimistán frissítenék az állapotokat, csak a betöltési állapotot állítanám igazra a lekérés előtt, majd a válasz beérkezésekor (és csak ezután) állítanám be a lapozást, a rendezést (& adatok és betöltés). Tehát az alkalmazás azt mondja a felhasználónak, hogy "Hallottam, és most dolgozom rajta", majd amikor sikeres válasz érkezik, az alkalmazás azt mondja, hogy "itt vannak a kért adatok és a frissített kontextus (frissített oldal / rendezési állapot)".

A másik probléma, ha a kérés sikertelen. Az optimisitic/useEffect megközelítésben most vissza kell állítanom azokat a lapozási és rendezési állapotokat, amelyeket frissítettek, mielőtt megpróbálnám betölteni az állapotváltozásokhoz szükséges adatokat. Ez a bonyolultság a függőségek számával nő. Ráadásul itt van a kicker (ismét, ha nem tévedek), ezen állapotok visszaállítása újabb lekérést eredményez (ami ismét meghiúsulhat). A pesszimista megközelítésben csak a loading = false értéket állítja be, ha hiba történik, és nincs más állapotmódosítás, mivel a lapozás és a rendezés csak akkor frissül, ha az adatok sikeresen megérkeztek. Az adatok pesszimista megközelítésben szinkronban maradnak (nem optimisták), ezért gondolom ironikusnak, ha a blogok és videók azt mondják, hogy "gondolkodj a szinkronizálásban" a useEffect használatakor.

Még egy dolog, mondjuk a useEffect-en belül meghívott API (egy másik példában) valójában megváltoztatott valamit a szerver oldalon. Ebben az esetben a useEffect használata és annak optimista megközelítése csak hazudik a felhasználónak. Az alkalmazás azt mondja: "Ok, frissítettem". Talán nem. Ha nem (ha hiba történt), akkor remélhetőleg van állapotfrissítés, amely visszaállítja a felhasználónak hazudott állapotváltozást, és valahogy elkerüli a további API-hívást az állapot visszaállításakor. És remélhetőleg a felhasználó látta, hogy az állapot megváltozott, és remélhetőleg volt egy üzenet... Akkor is jó UX?

Nem azt mondom, hogy az optimista megjelenítés mindig rossz. Azt mondom: "Az idő nagy részében szeretek pesszimista lenni. Hogyan tudom ezt megtenni a useEffect használata közben?" :)

Úgy tűnik, hogy a useEffect-et az alkalmazások legtöbb mellékhatására használják és reklámozzák (SOK). Nem túl gyakran találom hasznosnak. Pesszimista vagyok, és szeretnék egy alternatívát bevezetni.


Válaszok:


1

Ahogyan leírod, úgy hangzik, mintha az összetevőd többi része is hasonló szerkezetű lenne

[state, setState] => useState(initialState)
[paging, setPaging] => useState(initialPaging)
[sorting, setSorting] => useState(initialSort)

useEffect(() => {
  fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`)
    .then(response => response.json())
    .then(data => setState({
      data
    }));
}, [paging, sorting]);

return <div>
    <span>Current page is {paging}</span>
    <Button onClick={() => setPaging(page => page + 1)}>Nav Next</Button>
    <Button onClick={() => setPaging(page => page - 1)}>Nav Prev</Button>
    // ... and so on and so forth
</div>

és ha rákattint a Nav Következő vagy Előző lehetőségre, nem akarja frissíteni a lapozást, amíg az általa kiváltott hatás nincs megoldva. Ha a lekérés sikertelen, akkor nem szeretné, hogy a felhasználó lássa, hogy a lapozás felfelé, majd lefelé csúszik a teendő tisztítás során.

Ennek elérése érdekében nem lehetne késleltetni az "előre néző" értékek beállítását közbenső "reménykedő" vagy "függő" állapotértékekkel? Például ez működne?

[state, setState] => useState(initialState)

// Component state values
[paging, setPaging] => useState(initialPaging)
[sorting, setSorting] => useState(initialSort)

// "Optimist's" state values
[pendingPaging, setPendingPaging] => useState(paging)
[pendingSorting, setPendingSorting] => useState(sorting)

useEffect(() => {
  // checks if current state values differ from pending,
  // meaning app wants to do something
  // If pending and "actual" values match, do nothing
  if( paging !== pendingPaging
    || sorting !== pendingSorting
  ){
      fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`)
        .then(response => response.json())
        .then(data => {
          // things worked, updated component state to match pending
          setPaging(pendingPaging);
          setSorting(pendingSorting);
          setState({data});
        })
        .catch(err => {
          // ugh, I knew things would fail. Let me reset my hopes
          setPendingPaging(paging);
          setpendingSorting(sorting);
          setState({data});
        });
  }
}, [pendingPaging, pendingSorting, paging, sorting, setState]);

return <div>

    // show user "actual" page number
    <span>Current page is {paging}</span>

    // but update only pending values in response to events
    <Button onClick={() => setPendingPaging(page + 1)}>Nav Next</Button>
    <Button onClick={() => setPendingPaging(page - 1)}>Nav Prev</Button>

    // ... and so on and so forth
</div>

Így felállítod a remélt állapotodat, teszel valamit, majd egymáshoz igazítod az állapotodat és az elvárásaidat. Siker esetén a megjelenített állapot az optimista értékekhez igazodik, sikertelenség esetén pedig a függőben lévő értékeket állítja vissza a valóságba.

A legnagyobb probléma, amit látok, az a tény, hogy több setState hívás történik egyszerre, amikor az aszinkronhívás megoldódik. A Reactnek kötegelnie kell ezeket a hívásokat, de érdemes lehet más módszereket keresni a probléma kezelésére. egyetlen hívásban.

Akárhogy is, a függőben lévő és a tényleges értékek megegyeznek, tehát míg a useEffect aktiválódik, nem fut le újabb lekérés.

1. SZERKESZTÉS Köszönjük a pozitív visszajelzést, örülök, hogy tetszik az ötlet. Egyetértek, hogy a bonyolultság nehézkessé válhat, de szerintem létrehozhat egy gyári osztályt, hogy tisztábban kezelje a dolgokat.

Például használhat egy makePessimistic osztályt, amely végül kiadja a „tényleges” állapotot, a „reménykedő” állapotot és legalább egy usePessimisticEffect visszahívást. Az osztály így nézhet ki:

class pessimistFactory {
  constructor(){
    this.state = {};
    this.effects = [];
  }
  // util from https://dzone.com/articles/how-to-capitalize-the-first-letter-of-a-string-in
  ucfirst = (string) => string.charAt(0).toUpperCase() + string.slice(1);

  registerState(param, initialVal){
    this.state[param] = initialVal;
  }

  makePessimisticEffect = (callback, dependencies) => useEffect(() => {
    async function handlePessimistically(){
      try {
        const result = await callback(...dependencies);
        // update theState with result
        setState({...hopefulState, result})
      }catch(err){
        // reset hopefulState and handle error
        setHopefulState({...theState})
      }
    }

    // Do something if some action pending
    if(/* pending and current state not the same */) handlePessimistically();

  }, [callback, dependencies]);

  registerEffect(callback, dependencies = []){
    this.effects.push(
      this.makePessimisticEffect(callback, dependencies)
    );
  }

  makeHooks = () => {
    const initialState = useState(this.state);
    return {
      theState: initialState,
      hopefulState: initialState,
      effects: this.effects
    }
  }
}

És a gyakorlatban:

// Make factory instance
const factory = new pessimistFactory();

// Register state and effects
factory.registerState('paging', initialPaging)
factory.registerState('sorting', initialSorting)
factory.registerEffect('fetchData', () => fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`))

// Make the hooks
const {theState, hopefulState, effects} = factory.makeHooks();

// Implement hooks in component
const SomeComponent = () => {
  const [state, setState] = theState();
  const [pendingState, setPendingState] = hopefulState();

  effects.forEach(useEffect => useEffect());

  return <div>displayed stuff</div>
}

A legbonyolultabb a makePessimisticEffect effektus beállítása a generált setState-ok által létrehozott értékek és visszahívások olvasásához és kezeléséhez, amelyekről tudomásom szerint, ha teljesen tönkrement, ahogy írtam.

Jelenleg nincs időm kitalálni a részleteket, de úgy gondolom, hogy az elérhető új horgokkal, például useCallback.

Ez az ötlet azonban nagyon tetszik, így később, amikor lesz időm, megpróbálom kitalálni. Ha előtte csinálsz egy működő gyári osztályt, vagy ha valakinek van jobb megoldása, ami teljesen más, akkor nagyon érdekelne.

14.08.2019
  • Okos! Lehet, hogy ezt fogom, majd összerakom az állapotot egy useState-be, és a visszahívási setState API-t használom a frissítésekhez. Vagy használd a Reducert. Az egyetlen probléma az, hogy nem szeretném megismételni ezt a komplexitást (más API-hívásoknál ugyanabban az alkalmazásban), vagy fenntartani, ha a függőségi lista növekedne. Kíváncsi vagyok, van-e mód kivonatolni/absztrahálni/bekapszulázni egy újrafelhasználható horog. Egy usePessimisticEffect horog, ha úgy tetszik... valószínűleg van rá mód. Játszanom kell vele egy kicsit. Kösz!!! Megjelölöm válaszként, ha másnak nem lesz hamarosan más. 15.08.2019
  • Egyetértek visszajelzésével a bonyolultságról és a beágyazódásról – válaszomban frissítettem a válaszomat. 15.08.2019
  • Új anyagok

    A rádiógomb ellenőrzött eseményének használata a jQueryben
    Ebben a cikkben látni fogjuk, hogyan kell dolgozni a jquery választógombbal ellenőrzött eseményeivel. A választógombok HTML gombok, amelyek segítenek kiválasztani egyetlen értéket egy csoportból...

    Körkörös függőségek megoldása terraformban adatforrásokkal – lépésről lépésre
    Mi az a körkörös függőségek Dolgozzunk egy egyszerű eseten, amikor az SQS-sor és az S3-vödör közötti körkörös függőség problémája van egy egymástól függő címkeérték miatt. provider..

    Miért érdemes elkezdeni a kódolást 2023-ban?
    01100011 01101111 01100100 01100101 — beep boop beep boop Világunk folyamatosan fejlődik a technológia körül, és naponta fejlesztenek új technológiákat a valós problémák megoldására. Amint..

    🎙 Random Noise #2  – Örökbefogadás és hit
    az analitika íratlan világának gondozása Szeretné, hogy ezek a frissítések a postaládájába kerüljenek? Iratkozzon fel itt . "Ha önvezető autókat gyártanak, akkor mi miért ne..

    A legrosszabb politika és prediktív modellek májátültetésre jelöltek számára az Egyesült Államokban
    A máj (vagy óangolul lifer) az emberi test legnehezebb belső szervére utal, amely csendesen működik a nap 24 órájában. Mit csinál a máj? 500 feladatot hajt végre a szervezet egészségének..

    5 webhely, amely 2022-ben fejleszti front-end fejlesztői készségeit
    Frontendmentor.io A tényleges projektek létrehozásával a Frontendmentor.io segítséget nyújt a front-end kódolási képességeinek fejlesztésében. A kódolást azután kezdheti meg, hogy..

    Mikor kell használni a Type-t az interfészhez képest a TypeScriptben?
    A TypeScript a JavaScript gépelt szuperkészlete, amely statikus gépelést ad a nyelvhez. Ez megkönnyíti a robusztus és karbantartható kód írását azáltal, hogy a hibákat a fordítási időben..