React oraz tablice wielowymiarowe w useState

React oraz tablice wielowymiarowe w useState
goku21
  • Rejestracja:około 7 lat
  • Ostatnio:5 miesięcy
  • Postów:91
0

Hej, staram sie napisac prosta gre typu candy crush. Chcialbym stworzyc sobie tablice ktora mialaby w sobie wiersze i kolumny a wiec pisze cos w stylu:

Kopiuj
import { useState } from 'react';
import GridObject from './GridObject';


const colSize:number = 10;
const rowSize:number = 10;

type  GridObject = {
    color:string,
    rowIndex:number,
    colIndex:number
}

function GameMap() {
   const [gridsArray, setGridsArray] = useState([]);

   const generateGrid = () =>
   {

    let gridArray:Array<Array<GridObject>> = [];

    for(let i = 0; i < rowSize; i++)
    {
        let rowArr = [];

        for(let j = 0; j < colSize; j++)
        {
            rowArr.push(new GridObject());
        }
        console.log("dupa")
        gridArray.push(rowArr);
    }

    setGridsArray(gridArray);

    gridArray.map((el) => {
        console.log(el);
    })
    
   }

    return(
        <div>
         
        </div>
    )
}

export default GameMap;

Wywala blad:

Kopiuj
Argument of type 'GridObject[][]' is not assignable to parameter of type 'SetStateAction<never[]>'.
  Type 'GridObject[][]' is not assignable to type 'never[]'.
    Type 'GridObject[]' is not assignable to type 'never'.ts(2345)

Rozumiem ze nie moge do usestate przypisac tablicy ktora ma w sobie inne tablice, jest na to jakies rozwiazanie?
Poza tym Wyobrazam sobie GridObject jako poszczegolna kratke, ktora ma konkretny wiersz oraz kolumne.

Chcialbym zeby GridObject byl standardowa klasa ktora ma w sobie wszystkie informacje w stylu stanu, grafiki, wymiarow itd. Tylko jak niby mialbym tworzyc instancje owej klasy w react, poniewaz chyba nie moge po prostu napisac rowArr.push(new GridObject());

Moge pisac standardowe klasy czy raczej nie tedy droga i powinienem konkretna kratke napisac jako osobny component cos w stylu:

Kopiuj
const GridObject = () => {

return(
    <div className="grid">
    </div>
)

}
export default GridObject;

i wrzucac tam parametry w formie props?

Generalnie mozna w react mieszac standardowe klasy z construktorem itd. z reactem czy raczej tego unikac i o wszystkim myslec jak o componentach?

edytowany 1x, ostatnio: Riddle
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:17 minut
  • Postów:8398
2

A weź spróbuj dodać jawnie typ:

Kopiuj
const [gridsArray, setGridsArray] = useState<GridObject[][]>([]);

No i funkcja generateGrid. Nigdzie jej nie wywołujesz. Domyślam się, że dlatego, że nie zdążyłeś dopisać kodu, bo ci się pojawił błąd kompilacji?

Ale tak czy siak umieszczanie tej funkcji w komponencie nie ma sensu. Raczej wydzieliłbym ją poza komponent:

Kopiuj

   const generateGrid = () => {

    let gridArray:Array<Array<GridObject>> = [];

    for(let i = 0; i < rowSize; i++)
    {
        let rowArr = [];

        for(let j = 0; j < colSize; j++)
        {
            rowArr.push(new GridObject());
        }
        console.log("dupa")
        gridArray.push(rowArr);
    }

    return gridArray;
    
   }

a w komponencie tylko użył:

Kopiuj

  const [gridsArray, setGridsArray] = useState(generateGrid());

edytowany 4x, ostatnio: LukeJL
goku21
  • Rejestracja:około 7 lat
  • Ostatnio:5 miesięcy
  • Postów:91
0

Ooo działa :)

Mogłem zrobic nawet cos w stylu:

Kopiuj
class GridObject{

    constructor(color:string){//, rowIndex:number, colIndex:number){
        this.color = color;
        //this.rowIndex = rowIndex;
        //this.colIndex = colIndex;
    }

    divToReturn()
    {
        return (
            <div className="grid">
            </div>
        )
    }
}

export default GridObject;

oraz:

Kopiuj
function GameMap() {

    const [gridsArray, setGridsArray] = useState(generateGrid());

    //setGridsArray(generateGrid());



    return(
        <div>
            {
                gridsArray.map(row => (
                    row.map(el => (
                        //<GridObject color={el.color}></GridObject>
                        el.divToReturn()
                    ))
                ))
            }

        
        </div>
    )
}

export default GameMap;

I smiga :-) Dzieki za podpowiedz. Pytanie tylko czy to "zalecane" podejscie czy raczej unikac tworzenia klas i tworzyc same komponenty?

Kokoniłaj
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 4 godziny
  • Postów:182
2

Ja jeszcze dodam, że funkcja generateGrid będzie wywoływana przy każdym renderze komponentu.

Żeby tego uniknąć można przekazać funkcję:

Kopiuj
const [gridsArray, setGridsArray] = useState(generateGrid);
const [gridsArray, setGridsArray] = useState(() => generateGrid()); 

https://react.dev/reference/react/useState#examples-initializer

LukeJL
Masz rację. Umknęło mi to.
goku21
  • Rejestracja:około 7 lat
  • Ostatnio:5 miesięcy
  • Postów:91
0

W sensie jak sie wywola funkcje to sie wywola tylko raz a jak sie przekaże funkcje "useState(generateGrid);" to sie generuje za kazdym razem lol to dosc istotne :-)

LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:17 minut
  • Postów:8398
1
goku21 napisał(a):

I smiga :-) Dzieki za podpowiedz. Pytanie tylko czy to "zalecane" podejscie czy raczej unikac tworzenia klas i tworzyc same komponenty?

Stan powinien być raczej niemutowalny. Więc tutaj może być problem. Jak będziesz aktualizować tę siatkę?

Powiedzmy, że chcesz zmienić coś na pozycji x=3, y=4.

I co się wtedy dzieje? Jak to planujesz rozwiązać?

Przekazując to jako stan, raczej wypadałoby, żeby siatka działała wg zasad niemutowalności (czyli żeby się w środku nie zmieniała, tylko żeby zmiana generowała nową siatkę).

wielowymiarowe w useState

Dam ci radę. Jeśli chcesz napisać wielowymiarową tablicę, to łatwiej będzie ci ją napisać albo jako 1-wymiarową tablicę (y * width + x jako wzór na pozycję, gdzie cała tablica ma długość x * y i zawiera kolejne rzędy ciurkiem) albo jako obiekt z kluczami typu x + ',' + y np.

Kopiuj
{
   "3,4": true,
   "3,5": false,
   //...
}

i to opakować w jakąś klasę czy coś, żeby łatwiej z tego korzystać.

Dzięki temu jest to płaskie i możesz choćby łatwo zrobić kopię, zamiast babrać się w jakieś wielowymiarowe inicjalizowanie.

Natomiast jeśli ma być to mutowalny obiekt, to lepiej użyć useRef.

Problem w tym, że przy mutowalnym podejściu (czyli masz sobie grid, który się w środku może zmieniać) to wtedy jak coś zmienisz, to React o tym nie będzie wiedzieć. Więc wtedy po każdym apdejcie tego grida, musisz też powodować update komponentu.


edytowany 2x, ostatnio: LukeJL
goku21
  • Rejestracja:około 7 lat
  • Ostatnio:5 miesięcy
  • Postów:91
0

W takim razie bez sensu w tym przypadku korzystac z klas. Zalozmy jednak ze obiekt gridObject jest bardziej skomplikowany.
Ma w sobie jakas logike, moze nawet chce pobierac za pomoca nodejs jakies dane z serwera.
Oczywiscie moge 1 obiekt dzielic na warstwe GridObjectModel oraz GridObjectView ale tutaj nagle mi sie mnożą pliki, ktore dotycza tego samego obiektu.

Chociaz z drugiej strony jezeli w renderze chcialbym uzyc prostego tagu <GridObject/> to czy on powinien miec jeszcze jakas wieksza logike?
Wlasnie jestem w trakcie przesiadania sie z backendu (nodejs) na frontend z reactem na czele i troche tego nie czuje :/

Poczytam o useRef poniewaz jeszcze sie z tym nie zetknąłem. Dzieki za wskazówkę odnosnie wielowymiarowych tablic.
Na useEfect oraz createContext rzuciłem okiem. Co jeszcze oferuje react co jest czesto uzywane a z jakiegos powodu nie przewija sie to w tutkach? :)

LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:17 minut
  • Postów:8398
1
goku21 napisał(a):

Ma w sobie jakas logike, moze nawet chce pobierac za pomoca nodejs jakies dane z serwera.

Ale czemu obiekt reprezentujący grid ma pobierać dane z serwera? To brzmi jak kiepski design klasy.

Poczytam o useRef poniewaz jeszcze sie z tym nie zetknąłem

To służy do trzymania referencji do mutowalnych obiektów (często używane do elementów DOM, ale to tylko jedno zastosowanie).

W takim razie bez sensu w tym przypadku korzystac z klas.

minimalistycznie podchodząc można coś takiego zrobić:

Kopiuj

function Foo() {
    const [grid, setGrid] = useState({});
    const handleClick = () => {
         setGrid({...grid, '3,4': 'red'});
    };
     
    return <div>
        <button onClick={handleClick}>set (3, 4) to red</button>
    </div>
}

i np. Object.entries do przelatywania po elementach siatki. Albo dwie pętle for:

Kopiuj
const size = 20;
for (let y = 0; y < height; y++) {
   for (let x = 0; x < width; x++) {
      const k = x + ',' + y;
      elements.push(<div key={k} style={{background: grid[k], width: size, height: size, position: 'absolute', left: x * size, top: y * size}}></div>)
   }
}

return <div>{elements}</div>

edytowany 5x, ostatnio: LukeJL
goku21
  • Rejestracja:około 7 lat
  • Ostatnio:5 miesięcy
  • Postów:91
0

"Ale czemu obiekt reprezentujący grid ma pobierać dane z serwera? To brzmi jak kiepski design klasy." Nic nie pobiera, po prostu zastanawiałem sie koncepcyjnie gdyby to byl jakis inny element. Koncepcyjnie przypuśćmy że mamy w jakiejs grze samochod. Samochod moze jechac, skrecac odpalac i gasic silnik i moze np. miec przypisanego kierowce itd.

Fajnie zeby samochod byl 1 obiektem klasy, ktory np. rozszerza jakas klase nadrzedna np. SrodekTransportu.
I teraz taka klasa "Samochód" bedzie miala sporo innej logiki, zmiennych opisujacych ten przedmiot itd.
Dlatego koncepcyjnie sie zastanawiam gdzie sa problemy w pracy z reactem poniewaz raczej nie napisze tego jako komponent w sensie

Kopiuj
function foo2 extends foo1 implement foo {

//jakis kod

}

Nie da sie tego powyzej napisac. Chyba ze powiedzmy jakas klasa abstrakcyjna rozszerzalaby component

Kopiuj
class AbstractFoo extends component (//react component){

constructor(){
super();

}

render(

)

i dalej powiedzmy

Kopiuj
class MovableObject extends AbstractFoo {

let color;
let foo;

constructor{}

move()

turnLeftRight()



}

i na koncu

Kopiuj
class Car extends MovableObject{

super();

override turnLeftRight()

runEngine()

stopEngine()

}


Zastanawiałem sie nad takim przypadkiem :)

edytowany 2x, ostatnio: goku21
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:17 minut
  • Postów:8398
3

Ale czemu klasa reprezentująca samochód pod kątem logiki miałaby być komponentem React?

Niech logika będzie sobie osobno w klasie, a niech React to tylko wyświetla.

No i wyświetlanie zwykle da się zrobić (prawie) takie same dla wszystkich obiektów. Jeśli robisz grę 2D, to pewnie większość obiektów będzie miało np. przypisany jakiś plik graficzny. Czyli każdy obiekt możesz wyświetlać tak samo. Zwykle nie potrzeba robić osobnego komponentu do wyświetlania samochodu i do wyświetlania autobusu. Po prostu inny plik graficzny (ew. inny kształt typu kółko, prostokąt, inny kolor, rozmiar itp. jeśli idziesz w minimalizm)

Owszem, czasem może zajść potrzeba customizacji wyświetlania. Ale znowu - to można łatwo dodać później. Niezależnie od tego, czy stosujesz klasy

Kopiuj
class Car {}

czy robisz to na plain objects np.

Kopiuj
const car = {x: 3, y: 4};

to dobrze jest jak obiekty mają jakieś pole type w sobie np.

Kopiuj
const car = {type: 'car', x: 3, y: 4};
class Car {
   type = 'car';
}

to ułatwia traktowanie każdego obiektu tak samo, jak również wydobywanie w łatwy sposób informacji o jego typie (i np. wyświetlanie inaczej w zależności od typu).

Bez takiego pola type trzeba byłoby się bawić w instanceof, co nie musi być wygodne.


edytowany 6x, ostatnio: LukeJL
Kokoniłaj
  • Rejestracja:ponad 7 lat
  • Ostatnio:około 4 godziny
  • Postów:182
2
goku21 napisał(a):

W sensie jak sie wywola funkcje to sie wywola tylko raz a jak sie przekaże funkcje "useState(generateGrid);" to sie generuje za kazdym razem lol to dosc istotne :-)

Jak przekażesz funkcję useState(generateGrid); to wykona się tylko przy initial renderze.
Jak wywołasz funkcję useState(generateGrid()); to ona będzie wywoływana przy każdym renderze, ale jej wartość będzie odrzucana.

edytowany 1x, ostatnio: Kokoniłaj
goku21
const createGrids = () => { ///cos sie robi console.log(arr.length, arr); return arr; } const [gridsArray, setGridsArray] = useState<GridProps[]>(createGrids); Nigdzie w kodzie nie wywoluje "createGrids" a loguje mi sie 2 razy console.log(arr.length, arr); jak tylko odswieze strone to jak to jest z tym useState
goku21
Miałem to domyslnie: createRoot(document.getElementById('root')!).render( <StrictMode> <App /> </StrictMode>, )
Kokoniłaj
@goku21: No i dlatego taki efekt. Z punktu widzenia poprawności działania aplikacji nie ma znaczenia czy ta funkcja się wywoła raz czy dwa razy. Jeżeli nie ma side effectów to nic się nie dzieje. W tym linku, który wrzuciłem w komentarzu możesz poczytać jak StrictMode może Ci pomóc w wykrywaniu błędów w działaniu aplikacji. Podsumowując, funkcja createGrids wywołała się dwa razy, dlatego że aplikacja opakowana jest w StrictMode. Na buildzie produkcyjnym wywoła się tylko raz.
goku21
  • Rejestracja:około 7 lat
  • Ostatnio:5 miesięcy
  • Postów:91
0

"Niech logika będzie sobie osobno w klasie, a niech React to tylko wyświetla." no ok ale useState w klasie nie uzyje a jak bede chcial cos przerenderowac w zaleznosci od logiki ktora jest w klasie? Wysylac po prostu z klasy jakis eventDispatcher czy przesylac jakas metode z komponentu zeby ja wywolac czy moze jeszcze jakos inaczej?

LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:17 minut
  • Postów:8398
1

Jak masz mało obiektów, to mógłbyś aktualizować wszystkie naraz. Chociaż jak będzie dużo obiektów, to warto się podpinać pod konkretne obiekty.

Wysylac po prostu z klasy jakis eventDispatcher czy przesylac jakas metode z komponentu zeby ja wywolac czy moze jeszcze jakos inaczej?

no na przykład. Możesz użyć EventTarget https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
albo czegokolwiek innego, co ci da metodę subskrypcji.

Możesz zrobić tak, że każda zmiana w klasie musiałaby iść przez np. metodę update (albo jakkolwiek to nazwiesz). Ta metoda np. emitowałaby event update

Kopiuj
class GameObject {
   update() {
      // ...
      this.eventTarget.emit('update');
   }
   subscribe(f) {
      this.eventTarget.addEventListener('update', f);
   }
}

Z kolei od strony Reacta mógłbyś używając useEffect zasubskrybować się pod ten event np.

Kopiuj
function Sprite({ gameObject }) {
   const [counter, setCounter] = useState(0);

   useEffect(() => {
    gameObject.subscribe(() => {
        setCounter(x => x + 1);
    );
     return () => {//czyszczenie subskrypcji }
   }, [gameObject]);

   return <div>..........</div>
}


edytowany 6x, ostatnio: LukeJL
goku21
  • Rejestracja:około 7 lat
  • Ostatnio:5 miesięcy
  • Postów:91
1

Kumam, wlasnie nad ta strukturą sie zastanawiałem. Wiszę piwo :-)

Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)