Funziona in componenti senza stato?


93

Sto cercando di convertire questa fantastica <canvas>animazione che ho trovato qui in un componente riutilizzabile React. Sembra che questo componente richieda un componente padre per l'area di disegno e molti componenti figli per il function Ball().

Per motivi di prestazioni sarebbe probabilmente meglio trasformare i Ballscomponenti senza stato in quanto ce ne saranno molti. Non ho familiarità con la creazione di componenti senza stato e mi chiedevo dove dovrei definire le funzioni this.update()e this.drawche sono definite in function Ball().

Le funzioni per i componenti senza stato vanno all'interno o all'esterno del componente? In altre parole, quale delle seguenti è migliore?

1:

const Ball = (props) => {
    const update = () => {
        ...
    }

    const draw = () => {
        ...
    }

    return (
       ...
    );
}

2:

function update() {
     ...
}

function draw() {
     ...
}

const Ball = (props) => {
    return (
       ...
    );
}

Quali sono i pro / contro di ciascuno e sono migliori per casi d'uso specifici come il mio?


Puoi pubblicare il codice esistente in modo che vediamo come verrà utilizzato?
Scimonster

@Scimonster l'ho postato in un link incorporato, forse te lo sei perso. Ecco il link: codepen.io/awendland/pen/XJExGv
MarksCode

Risposte:


117

La prima cosa da notare è che i componenti funzionali senza stato non possono avere metodi, non dovresti contare sulla chiamata updateo drawsu un rendering Ballse è un componente funzionale senza stato.

Nella maggior parte dei casi dovresti dichiarare le funzioni al di fuori della funzione del componente in modo da dichiararle solo una volta e riutilizzare sempre lo stesso riferimento. Quando si dichiara la funzione all'interno, ogni volta che viene eseguito il rendering del componente la funzione verrà definita di nuovo.

Ci sono casi in cui sarà necessario definire una funzione all'interno del componente, ad esempio, per assegnarlo come gestore di eventi che si comporta in modo diverso in base alle proprietà del componente. Tuttavia è ancora possibile definire la funzione all'esterno Balle associarla alle proprietà, rendendo il codice molto più pulito e le funzioni updateo drawriutilizzabili.

// You can use update somewhere else
const update (propX, a, b) => { ... };

const Ball = props => (
  <Something onClick={update.bind(null, props.x)} />
);

Se stai usando gli hook , puoi usare useCallbackper assicurarti che la funzione venga ridefinita solo quando una delle sue dipendenze ( props.xin questo caso) cambia:

const Ball = props => {
  const onClick = useCallback((a, b) => {
    // do something with a, b and props.x
  }, [props.x]);

  return (
    <Something onClick={onClick} />
  );
}

Questo è il modo sbagliato :

const Ball = props => {
  function update(a, b) {
    // props.x is visible here
  }

  return (
    <Something onClick={update} />
  );
}

Quando si utilizza useCallback, definire la updatefunzione useCallbacknell'hook stesso al di fuori del componente diventa più che altro una decisione di progettazione, da tenere in considerazione se si intende riutilizzare updatee / o se è necessario accedere all'ambito della chiusura del componente a, ad esempio, leggere / scrivere nello stato. Personalmente scelgo di definirlo di default all'interno del componente e di renderlo riutilizzabile solo in caso di necessità, per evitare over-engineering sin dall'inizio. Inoltre, il riutilizzo della logica dell'applicazione è fatto meglio con hook più specifici, lasciando i componenti per scopi di presentazione. La definizione della funzione all'esterno del componente durante l'utilizzo degli hook dipende in realtà dal grado di disaccoppiamento da React che si desidera per la logica dell'applicazione.


Grazie Marco, questo chiarisce un po 'le cose. La cosa di cui sono confuso nel mio caso è relativa alla this.drawfunzione all'interno di Ball. Usa il ctxfrom quello che sarebbe il genitore <canvas>e usa anche la thisparola chiave per quello che sarebbe il Ballcomponente figlio . Quale sarebbe il modo migliore per integrare l'implementazione del componente senza stato in modo che entrambe le proprietà siano accessibili?
MarksCode

non c'è niente thisquando si usano componenti funzionali senza stato, tenetelo a mente. Per il contesto della tela, dovresti passarlo a tutti Ball, non suona affatto bene.
Marco Scabbiolo

Quindi in questo caso sarebbe meglio non avere un componente figlio che stai dicendo?
MarksCode

1
@MarcoScabbiolo no no, non è il mio caso, già da molto tempo utilizzo nativamente le funzioni freccia, dato che l'unico browser che non le supporta è IE. In realtà sono riuscito a trovare questo commento da un articolo in cui si afferma che in bindparticolare in Chrome prima del 59 era ancora più lento delle funzioni delle frecce. E in Firefox è anche un bel po 'di tempo poiché entrambi funzionano con la stessa velocità. Quindi direi che in questi casi non c'è differenza quale sia il modo preferito :)
Vadim Kalinin

1
@ MauricioAvendaño funziona in entrambi i casi, ma è una cattiva pratica per il Somethingcomponente sapere che c'è un puntello X nel suo componente genitore poiché lo rende consapevole del suo contesto. Lo stesso accade per la domanda che stai ponendo e il codice di esempio che ho scritto, dipende dal contesto, che viene ignorato per semplicità.
Marco Scabbiolo

13

Possiamo avere funzioni all'interno di componenti funzionali senza stato, di seguito è riportato l'esempio:

 const Action = () => {
  function  handlePick(){
     alert("test");
  }
  return (
    <div>
      <input type="button" onClick={handlePick} value="What you want to do ?" />
    </div>
  );
}

Ma non è una buona pratica in quanto la funzione handlePick()verrà definita ogni volta che viene chiamato il componente.


11
Se non è una buona pratica, quale sarà la soluzione alternativa?
John Samuel

@JohnSamuel L'alternativa è definire handlePick()sopra / fuori il componente in modo che function handlePick() { ... }; const Action = () => { ... }il risultato sia che handlePick è definito solo una volta. Se hai bisogno di dati dal Actioncomponente, dovresti passarli come parametri alla handlePickfunzione.
James Hay

14
se questa non è una buona pratica avresti potuto aggiungere la buona pratica con la risposta? ora devo cercare di nuovo la buona pratica :(
Shamseer Ahammed

2

Possiamo usare l'hook React useCallbackcome di seguito in un componente funzionale:

const home = (props) => {
    const { small, img } = props
    const [currentInd, setCurrentInd] = useState(0);
    const imgArrayLength = img.length - 1;
    useEffect(() => {
        let id = setInterval(() => {
            if (currentInd < imgArrayLength) {
                setCurrentInd(currentInd => currentInd + 1)
            }
            else {
                setCurrentInd(0)
            }
        }, 5000);
        return () => clearInterval(id);
    }, [currentInd]);
    const onLeftClickHandler = useCallback(
        () => {
            if (currentInd === 0) {

            }
            else {
                setCurrentInd(currentInd => currentInd - 1)
            }
        },
        [currentInd],
    );

    const onRightClickHandler = useCallback(
        () => {
            if (currentInd < imgArrayLength) {
                setCurrentInd(currentInd => currentInd + 1)
            }
            else {

            }
        },
        [currentInd],
    );
    return (
        <Wrapper img={img[currentInd]}>
            <LeftSliderArrow className={currentInd > 0 ? "red" : 'no-red'} onClick={onLeftClickHandler}>
                <img src={Icon_dir + "chevron_left_light.png"}></img>
            </LeftSliderArrow>
            <RightSliderArrow className={currentInd < imgArrayLength ? "red" : 'no-red'} onClick={onRightClickHandler}>
                <img src={Icon_dir + "chevron_right_light.png"}></img>
            </RightSliderArrow>
        </Wrapper>);
}

export default home;

Ricevo "img" dal suo genitore e questo è un array.


1

Se vuoi usare oggetti di scena o lo stato del componente in funzione, questo dovrebbe essere definito nel componente con useCallback.

function Component(props){
  const onClick=useCallback(()=>{
     // Do some things with props or state
  },[])
}

D'altra parte, se non vuoi usare oggetti di scena o state in function, definiscili al di fuori del componente.

const computeSomethings=()=>{
   // Do some things with params or side effects
}

function Component(props){
  
}

3
puoi fare un esempio di utilizzo di hook for method all'interno di componenti funzionali? (metodo non impostato)
jake-ferguson
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.