REACT zmienna wewnętrzna/zewznętrzna/ jak lepiej?

0

Cześć
Piszę stronę renderującą tabelkę z danymi. Wykorzystuję Reacta.
Wszystko było dobrze do chwili kiedy zorientowałem się że filtrowanie tabeli zmienia jej zawartość, ale numer aktualnej strony tabeli idzie swoją drogą. W konsekwencji są potworki typu : " Znaleziono 46 obiektów na 10 stronach. Aktualna strona ma numer 13."

W tej chwili ogólny schemat jest taki:


...
importy....
...
class App extends React.Component {
    constructor(props) {
    super(props);
this.state={x:X,.....,z:Z}
}
....teraz idą handlery....
render{
dane= new Data(this.state.x)// w tym miejscu tworze instancję zdefiniowanej osobno klasy, z wieloma metodami, a pobiera ona dane ze stanu
... dalej wykonuję różne operacje wykorzystując powyżej utworzony obiekt.
...jeszcze dalej mam return gdzie wszystko powinno się pokazać
}

Rzecz w tym, że numer aktualnej strony jest ustawiany w stanie przez handlery, później ten fragment stanu przechodzi do render żeby render wiedziało od którego do którego miejsca wyświetlać. Nic nadzwyczajnego. Tyle, że użytkownik ma dostęp do filtracji, sortowania i innych narzędzi, które są metodami instancji dane obiektu Data. W tej sytuacji łatwo może się zdarzyć, że stan będzie żądał wyświetlenia strony, która już nie istnieje, bo tabela w instancji dane została zdecydowanie skrócona.
Ustawić stanu z render nie mogę. I tu pojawia się pytanie. Mam trzy rozwiązania, które jest właściwe(to jest niesprzeczne z dobrą praktyką albo czymkolwiek takim?:

  1. Powołuję zmnienną currentPageNumber, całkowicie zewnętrzną - deklaruję ją za importami a przed deklaracją klasy (a wtedy zarówno handlery jak i render mogą na niej działać).

  2. Powołuję zmienną wewnętrzną this.currentPageNumber w konstruktorze i działam na niej zarówno przez handlery jak i w render.

  3. Oby to nie było idealne rozwiązanie: mógłbym zamiast deklarować coś tam coś tam w stanie odpalić w stanie cały obiekt z metodami i wszystkie operacje wykonywać na tym obiekcie - czyli na stanie, a dopiero na koniec wyprowadzać dane na render. Dla mnie ma to dwa minusy - po pierwsze musiałbym to pisać prawie od początku, po drugie - nie jestem pewien, czy zmiany w obiekcie wymuszałyby renderowanie; prawdopodobnie trza by się jakoś posiłkować forceUpdate albo this.setstate{}

Na ten moment 1 i 2 są jako tako sprawdzone i działają (IMHO 2 wydaje mi się nienajgorszym pomysłem) ale chciałbym zapytać o zdanie specjalistów. Może jest jeszcze inne rozwiązanie (tzn na pewno jest niejedno). To nie jest zadanie na zaliczenie, ale zupełnie poważnie zapytam - gdybyście zadali komuś takie zadanie, jakie rozwiązanie by Was usatysfakcjonowało? Na moim etapie odpada REDUX/MOBX - przynajmniej na razie.

I takie zupełnie pomijalne pytanie na boku - macie tabele, w wyniku filtracji wypadają wszystkie dane z widocznej strony, gdzie taka tabela powinna się otworzyć w nowym widoku?

1

Tak na oko, to gwałcisz zalecaną w środowisku Reacta zasadę "single source of truth". Generalnie zaleca się, żeby dane pochodne (derived data) liczyć na podstawie danych głównych.

Rzecz w tym, że numer aktualnej strony jest ustawiany w stanie przez handlery, później ten fragment stanu

Czyli nie trzymać osobno stanu dla aktualnej strony, tylko lepszym podejściem pewnie by było wyliczać aktualną stronę na podstawie tego, w którym miejscu tablicy jesteśmy. Bo dodatkowa zmienna = dodatkowy problem przy synchronizacji tego wszystkiego.

poza tym.

 dane= new Data(this.state.x)/

czy obiekt dane jest niemutowalny (niezmienny, raz ustalisz dane i one tam są), czy jednak zmieniasz go potem pod spodem? Bo jeśli to drugie, to React nie będzie widział tych zmian. Tj. załóżmy, że zrobiłbyś tak:

dane = new Data(this.state.x)
//... po jakimś czasie
dane.x = 1000;

i już React nie będzie wiedział, że coś się zmieniło. Więc tutaj łatwo o rozsynchronizowanie rzeczywistego stanu aplikacji z tym, co widzi React (chyba, że użyjesz biblioteki typu Mobx, która pozwa na obserwowanie takich obiektów przez React. Ale i tak nie wiem, czy to dobry pomysł. Za dużo złożoności, która niekoniecznie jest nawet potrzebna).

Powołuję zmienną wewnętrzną this.currentPageNumber w konstruktorze i działam na niej zarówno przez handlery jak i w render.

Tu ta sama uwaga. Jeśli działasz na jakiejś zmiennej ręcznie, to React nie będzie widział, że są czynione zmiany. W szczególności nie powinieneś zmieniać tej zmiennej luzem w metodzie render, bo to efekt uboczny (chodzi mi o to, żebyś nie pisał tak:

render() {
    this.currentPageNumber++;
    return <div></div>
}

(swoją drogą nie wiem, czemu byś tak miał pisać, ale napisałeś, że "działasz na niej w render".

Z drugiej strony możesz użyć właściwości state oraz this.setState do uaktualniania stanu.

0

Czyli nie trzymać osobno stanu dla aktualnej strony, tylko lepszym podejściem pewnie by było wyliczać aktualną stronę na podstawie tego, w którym miejscu tablicy jesteśmy. Bo dodatkowa zmienna = dodatkowy problem przy synchronizacji tego wszystkiego.>

Widzisz, tu idzie to taką linią: mam wyświetloną tabelkę oraz strzałki. Kliknięcie strzałki (oraz okienko wyboru numeru). Kliknięcie strzałki wywołuje handler, który zmienia .this.state.currentPage o +-1 (albo na pożądany numer). To co zostanie ustalone w taki sposób w stanie idzie potem do render. Oczywiście mam kontrolę nad tym, żeby nie wyklikać poza zakres aktualnie dostepnych stron, gdyż w chwili kiedy klikam na handlera w jego otoczeniu ( w render) wiadomo ile jest stron. Problem pojawia się kiedy przez filtrowanie tych stron ubywa. Zasadniczo filtrowanie też jest wynikiem kliknięcia i teoretycznie mogę sobie wyobrazić (ale dopiero teraz, wcześniej nie myślałem o takim rozwiązaniu) że handler filtrowania wyliczy ilość stron, ale logika do tego jest w tej chwili w render (czyli w praktyce prowadziłoby to do rozwiązania numer 3.

poza tym.
dane= new Data(this.state.x)/
czy obiekt dane jest niemutowalny (niezmienny, raz ustalisz dane i one tam są), czy jednak zmieniasz go potem pod spodem? Bo jeśli to drugie, to React nie będzie widział tych zmian. Tj. załóżmy, że zrobiłbyś tak:
dane = new Data(this.state.x)
//... po jakimś czasie
dane.x = 1000;
i już React nie będzie wiedział, że coś się zmieniło. >

Ten kod wygląda inaczej, tu dałem tylko ogólne info, żeby było mniej więcej wiadomo co się dzieje. W praktyce klasa Disciplines ma metodę statyczną CreateFrom którą tworzy instancję na podstawie tablicy z danymi, czyli w kodzie o którym pisze new się faktycznie nie pojawia. Napisałem jak napisałem, bo myślałem, że tak będzie prościej


Dane = Disciplines.CreateFrom(this.state.table);

W this state.table natomiast są dane ściągnięte z zewnętrznego źródła. I to faktycznie jest taki twardy niemutowalny wzorzec. do dalszego wykorzystania ale niemodyfikowany w stanie, natomiast na jego podstawie w render tworzę obiekt z większością danych z oryginału i metodami. I tu dane są sortowane i filtrowane i jeszcze drobniejsze operacje. Następnie z tych danych pobierany jest wycinek o wielkości jednego ekranu( na podstawie this.state.currentPage) do wyświetlenia.

Powołuję zmienną wewnętrzną this.currentPageNumber w konstruktorze i działam na niej zarówno przez handlery jak i w render. Tu ta sama uwaga. Jeśli działasz na jakiejś zmiennej ręcznie, to React nie będzie widział, że są czynione zmiany. W szczególności nie powinieneś zmieniać tej zmiennej luzem w metodzie render, bo to efekt uboczny.>

No i tutaj mam bardzo konkretne pytanie. Właściwie większość źródeł na tema React pisze o tym co robić ze state i propsami. Pytanie, czy niezależnie od tego po komponentach nie mogą pojawiać się takie 'luźne' zmienne nie będące ani jednym ani drugim? Widziałem i takie przykłady gdzie to było podane jako coś oczywistego ale nie widziałem ani kawałka komentarza/teorii na ten temat.

W szczególności nie powinieneś zmieniać tej zmiennej luzem w metodzie render, bo to efekt uboczny.>

Czy także w wypadku kiedy ta zmienna w żadnym miejscu nie czyta z/pisze do stanu? W jednym miejscu faktycznie ustawiają ją handlery, w drugim render porównuje ją z wyliczoną liczbą stron i ewentualnie zmniejsza. Niemniej to jest jak piszesz efekt uboczny funkcji render. Z drugiej strony, zostaje to w ramach klasy komponentu, więc dlaczego właściwie funkcja render nie miałaby działać na zmiennej tego komponentu? Acz tak jak piszesz stan przestaje być SSoT w tym momencie.
W sumie konkluzja byłaby chyba taka, że najmniej wad ma podejście 3. I tutaj pytanie: załóżmy, że obiekt o którym piszę na początku

Dane = Disciplines.CreateFrom(this.state.table);>

jest w state. O ile wiem, jeżeli nie napiszę wprost this set state to nie będzie re-renderu, czyli operacje zmieniający tylko wewnętrzne jego właściwości nie będą wywoływac re-render. Czy zapis this.set.state({}) jest skuteczny i akceptowalny dla wywołania re-render?

0

Przepraszam, to ja napisałem powyższą odpowiedź nie zalogowawszy się

0

Kliknięcie strzałki wywołuje handler, który zmienia .this.state.currentPage o +-1 (albo na pożądany numer).

@Kiszuriwalilibori w React nie możesz (nie powinieneś) ręcznie zmieniać stanu. Czyli zamiast

this.state.currentPage += 1

robisz

this.setState({currentPage: state.currentPage + 1});
// albo, bezpieczniej
this.setState(state => ({currentPage: state.currentPage + 1}));

Dlaczego? Bo tak React został napisany, to jest napisane w dokumentacji
https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly
i może gdybyś poczytał najpierw dokumentację, i jak obsługiwać state, to łatwiej by ci było. Bo próbujesz zrobić z Reacta coś, czym React nie jest (przynajmniej nie bez zewnętrznych bibliotek).

Pytanie, czy niezależnie od tego po komponentach nie mogą pojawiać się
takie 'luźne' zmienne nie będące ani jednym ani drugim?

mogą się pojawiać, czasem nawet powinny, tylko trzeba wiedzieć, jak ich używać i samemu nad nimi panować. Ja bym użył zmiennej w instancji np. do tego, żeby trzymać jakieś dane, które nie mają sensu dla logiki aplikacji i nie wpływają na rendering, a są po prostu jakimiś wewnętrznymi zmiennymi. Np. jeśli bym ustawiał setInterval w componentDidMount, to bym mógł zapisać uchwyt do intervala właśnie w zmiennej (this.interval = setInterval(......)), dawanie tego do state nie miałoby wiele sensu.

Niemniej to jest jak piszesz efekt uboczny funkcji render. Z drugiej strony, zostaje to w ramach klasy komponentu,
więc dlaczego właściwie funkcja render nie miałaby działać na zmiennej tego komponentu?

bo to dalej będzie zmiana stanu (ale nie stanu state tylko stanu w sensie stanu, w którym się znajduje komponent. React niestety trochę namieszał i podpier*olił informatyce pojęcie state).

Funkcja render jest wywoływana przez framework React niezależnie od ciebie i nie wiesz, kiedy zostanie wywołana (przynajmniej teoretycznie nie wiesz), a co jeśli potencjalnie zostanie wywołana kilka razy i o kilka razy za dużo powiększysz zmienną? Dlatego lepiej, żeby nic nie robiła samodzielnie (co najwyżej np. przypisywała handlery do eventów (np. <div onClick={this.handleClick}>).

W przyszłych wersjach React ma być coś takiego jak withEffects czy parę innych helperów, które pozwalają umieszczać efekty uboczne w funkcjach renderujących. Ale to jako ciekawostkę wspominam, myślę, że to, co chcesz osiągnąć, da się bez tego osiągnąć. Tylko trzeba się wczytać w dokumentację i pisać kod z duchem Reacta. Albo zmienić framework.

w ramach klasy komponentu,

na jedno wychodzi, bo klasa komponentu (w zasadzie instancja klasy) jest i tak czymś zewnętrznym w stosunku do samej funkcji render. To może się wydać, że jedna klasa czyli zostaje w rodzinie, ale tak nie jest. (co innego jakbyś robił zmienne na poziomie funkcji:

render() {
   let a = 2;
   a += 20;
   return <div>{a}</div>
}

to by było ok, bo zmienna a będzie istnieć tylko przy danym wywoływaniu funkcji (i przy drugim wywołaniu zostanie stworzona na nowo). Więc tutaj faktycznie nie byłoby efektów ubocznych. Ale jak zmieniasz coś w this to już są.

0

Dzięki

React nie możesz (nie powinieneś) ręcznie zmieniać stanu. Czyli >

Tak robię, nawet jeżeli to nie wynika z tego co napisałem. Przy czym zazwyczaj jest to wersja z this.setState, tej drugiej użyłem ze swa razy w szczególnym wypadku kiedy pobierałem dane asynchro i wtedy rzeczywiście musiałem wiedzieć co robię. Ogólnie to co piszesz jest bardzo ciekawe i dotyka kwestii których nie znajdzie się wprost napisanych. Generalnie mało jest o ogólnej architekturze- tzn powtarza się o rozdziale logiki i prezencji ale nie wiele poza tym.
Właściwie biorąc to pod uwagę zrobiłem dwa prototypy likwidujące komplikacje wynikające z niezależnego zmieniania liczby dostępnych stron.
Pierwszy - cały obiekt jest uruchamiany w stanie jako jedna wartość stanu o dość złożonej, ale nie przesadnie, strukturze wewnetrznej. Handlery nie zmieniają już poszczególnych wartości podstawowego stanu ale wywołują metody tego obiektu. Jedno co działa z pewnymi oporami to wymuszenie renderu.
Drugi - handlery są przepisane jako funkcje wewnętrzne funkcji render i nie mają nic wspolnego ze stanem. W stanie jest niemutowalne żródło danych z którego czerpie obiekt odpalany w render.
Właściwie to skłaniam się ku pierwszej wersji ale na jakimś forum pisali aby stanu używać raczej oszczędnie. Toteż chciałbym zapytać, które podejście jest trafniejsze?

1 użytkowników online, w tym zalogowanych: 0, gości: 1