Funciones en componentes sin estado?

90

Estoy tratando de convertir esta genial <canvas>animación que encontré aquí en un componente reutilizable de React. Parece que este componente requeriría un componente principal para el lienzo y muchos componentes secundarios para function Ball().

Por motivos de rendimiento, probablemente sería mejor convertirlos Ballsen componentes sin estado, ya que habrá muchos de ellos. No estoy tan familiarizado con la creación de componentes sin estado y me preguntaba dónde debería definir las funciones this.update()y this.drawque se definen en function Ball().

¿Las funciones de los componentes sin estado van dentro o fuera del componente? En otras palabras, ¿cuál de las siguientes opciones es mejor?

1:

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

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

    return (
       ...
    );
}

2:

function update() {
     ...
}

function draw() {
     ...
}

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

¿Cuáles son los pros / contras de cada uno? ¿Uno de ellos es mejor para casos de uso específicos como el mío?

MarksCode
fuente
¿Puede publicar el código existente para que veamos cómo se utilizará?
Scimonster
@Scimonster Lo publiqué en un enlace incrustado, tal vez te lo perdiste. Aquí está el enlace: codepen.io/awendland/pen/XJExGv
MarksCode

Respuestas:

113

Lo primero que hay que tener en cuenta es que los componentes funcionales sin estado no pueden tener métodos, no debe contar con llamar updateo drawrenderizar Ballsi es un componente funcional sin estado.

En la mayoría de los casos, debe declarar las funciones fuera de la función del componente para que las declare solo una vez y siempre reutilice la misma referencia. Cuando declaras la función en el interior, cada vez que se renderiza el componente, la función se volverá a definir.

Hay casos en los que necesitará definir una función dentro del componente para, por ejemplo, asignarla como un controlador de eventos que se comporta de manera diferente según las propiedades del componente. Pero aún así, podría definir la función fuera Bally vincularla con las propiedades, haciendo que el código sea mucho más limpio y haciendo que las funciones updateo sean drawreutilizables.

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

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

Si está usando ganchos , puede usar useCallbackpara asegurarse de que la función solo se redefina cuando una de sus dependencias ( props.xen este caso) cambia:

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

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

Esta es la forma incorrecta :

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

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

A la hora de usar useCallback, definir la updatefunción en el useCallbackpropio hook fuera del componente se convierte en una decisión de diseño más que nada, debes tener en cuenta si vas a reutilizar updatey / o si necesitas acceder al alcance del cierre del componente para, por ejemplo, leer / escribir en el estado. Personalmente, elijo definirlo dentro del componente de forma predeterminada y hacerlo reutilizable solo si surge la necesidad, para evitar la ingeniería excesiva desde el principio. Además de eso, es mejor reutilizar la lógica de la aplicación con ganchos más específicos, dejando los componentes para fines de presentación. Definir la función fuera del componente mientras se usan ganchos realmente depende del grado de desacoplamiento de React que desee para la lógica de su aplicación.

Marco Scabbiolo
fuente
Gracias Marco, eso aclara un poco las cosas. Lo que me confunde en mi caso está relacionado con la this.drawfunción dentro de Ball. Utiliza el ctxde lo que sería el componente principal <canvas>y también utiliza la thispalabra clave para lo que sería el Ballcomponente secundario . ¿Cuál sería la mejor manera de integrar la implementación del componente sin estado para que ambas propiedades sean accesibles?
MarksCode
no hay nada thiscuando se utilizan componentes funcionales sin estado, tenlo en cuenta. Para el contexto del lienzo, tendrías que pasarlo a cada uno Ball, eso no suena nada bien.
Marco Scabbiolo
Entonces, en este caso, ¿sería mejor no tener ningún componente secundario que esté diciendo?
MarksCode
1
@MarcoScabbiolo no no, ese no es mi caso, ya utilizo las funciones de flecha de forma nativa durante bastante tiempo, ya que el único navegador que no las admite es IE. En realidad, logré encontrar este comentario en un artículo en el que se afirma que bindespecíficamente en Chrome antes de 59 era incluso más lento que las funciones de flecha. Y en Firefox también ha pasado bastante tiempo ya que ambos funcionan con la misma velocidad. Entonces, diría que en tales casos no hay diferencia cuál es la forma preferida :)
Vadim Kalinin
1
@ MauricioAvendaño de cualquier manera funciona, pero es una mala práctica que el Somethingcomponente sepa que hay un prop X en su componente padre, ya que lo hace consciente de su contexto. Lo mismo ocurre con la pregunta que está haciendo y el código de muestra que he escrito, depende del contexto, que se ignora por simplicidad.
Marco Scabbiolo
12

Podemos tener funciones dentro de componentes funcionales sin estado, a continuación se muestra el ejemplo:

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

Pero, no es una buena práctica ya que la función handlePick()se definirá cada vez que se llame al componente.

Ruchir Saxena
fuente
11
Si no es una buena práctica, ¿cuál será la solución alternativa?
John Samuel
@JohnSamuel La alternativa es definir handlePick()arriba / fuera del componente como si function handlePick() { ... }; const Action = () => { ... }el resultado fuera que handlePick solo se define una vez. Si necesita datos del Actioncomponente, debe pasarlos como parámetros a la handlePickfunción.
James Hay
11
Si esta no es una buena práctica, ¿podría haber agregado la buena práctica con la respuesta? ahora tengo que buscar de nuevo la buena práctica :(
Shamseer Ahammed
2

Podemos usar el gancho React useCallbackcomo se muestra a continuación en un componente funcional:

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;

Obtengo 'img' de su padre y esa es una matriz.

Vishant Rastogi
fuente
0

Si desea usar accesorios o el estado del componente en la función, eso debe definirse en el componente con useCallback.

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

Por otro lado, si no desea utilizar props o state in function, defínalo fuera del componente.

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

function Component(props){
  
}
Hossein Mohammadi
fuente
3
¿Puede dar un ejemplo del uso de un gancho para el método dentro del componente funcional? (método de estado no establecido)
jake-ferguson