Funções em componentes sem estado?

93

Estou tentando converter esta <canvas>animação legal que encontrei aqui em um componente reutilizável React. Parece que esse componente exigiria um componente pai para a tela e muitos componentes filhos para o function Ball().

Por motivos de desempenho, provavelmente seria melhor transformá- Ballslos em componentes sem estado, pois haverá muitos deles. Não estou tão familiarizado com a criação de componentes sem estado e queria saber onde devo definir as funções this.update()e this.drawque são definidas em function Ball().

As funções para componentes sem estado ficam dentro ou fora do componente? Em outras palavras, qual das opções a seguir é melhor?

1:

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

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

    return (
       ...
    );
}

2:

function update() {
     ...
}

function draw() {
     ...
}

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

Quais são os prós / contras de cada um e um deles é melhor para casos de uso específicos como o meu?

MarksCode
fonte
Você pode postar o código existente para que possamos ver como ele será usado?
Scimonster
@Scimonster Eu postei em um link embutido, talvez você tenha perdido. Aqui está o link: codepen.io/awendland/pen/XJExGv
MarksCode

Respostas:

117

A primeira coisa a notar é que os componentes funcionais sem estado não podem ter métodos, você não deve contar com a chamada updateou drawcom um renderizado Ballse for um componente funcional sem estado.

Na maioria dos casos, você deve declarar as funções fora da função do componente para declará-las apenas uma vez e sempre reutilizar a mesma referência. Quando você declara a função interna, toda vez que o componente for renderizado, a função será definida novamente.

Há casos em que você precisará definir uma função dentro do componente para, por exemplo, atribuí-lo como um manipulador de eventos que se comporta de maneira diferente com base nas propriedades do componente. Mesmo assim, você pode definir a função externamente Balle vinculá-la às propriedades, tornando o código muito mais limpo e tornando as funções updateou drawreutilizáveis.

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

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

Se estiver usando ganchos , você pode usar useCallbackpara garantir que a função só seja redefinida quando uma de suas dependências ( props.xneste caso) mudar:

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

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

Este é o caminho errado :

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

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

Ao usar useCallback, definir a updatefunção no useCallbackpróprio gancho ou fora do componente torna-se uma decisão de design mais do que qualquer coisa, você deve levar em consideração se for reutilizar updatee / ou se precisar acessar o escopo do fechamento do componente para, por exemplo, ler / gravar no estado. Pessoalmente, eu escolho defini-lo dentro do componente por padrão e torná-lo reutilizável apenas se for necessário, para evitar o excesso de engenharia desde o início. Além disso, a reutilização da lógica do aplicativo é melhor realizada com ganchos mais específicos, deixando os componentes para fins de apresentação. Definir a função fora do componente durante o uso de ganchos realmente depende do grau de desacoplamento do React que você deseja para a lógica do aplicativo.

Marco Scabbiolo
fonte
Obrigado Marco, isso esclarece um pouco as coisas. O que estou confuso no meu caso está relacionado à this.drawfunção dentro do Ball. Ele usa a ctxpartir do que seria do pai <canvas>e também usa a thispalavra - chave para o que seria o Ballcomponente filho . Qual seria a melhor maneira de integrar a implementação do componente sem estado para que ambas as propriedades sejam acessíveis?
MarksCode
não há thisquando usar componentes funcionais sem estado, tenha isso em mente. Para o contexto do canvas, você teria que passá-lo para cada um Ball, isso não soa bem.
Marco Scabbiolo
Portanto, neste caso, seria melhor simplesmente não ter nenhum componente filho, o que você está dizendo?
MarksCode
1
@MarcoScabbiolo não não, não é o meu caso, já uso as funções de seta nativamente há bastante tempo, pois o único navegador que não as suporta é o IE. Na verdade, consegui encontrar esse comentário em um artigo onde afirmava que bindespecificamente no Chrome anterior ao 59 era ainda mais lento do que as funções de seta. E no Firefox já demorou bastante, já que ambos funcionam com a mesma velocidade. Então eu diria que nesses casos não há diferença de qual é a forma preferida :)
Vadim Kalinin
1
@MuricioAvendaño de qualquer maneira funciona, mas é uma má prática para o Somethingcomponente saber que existe um prop X em seu componente pai, pois o torna ciente de seu contexto. O mesmo acontece com a pergunta que você está fazendo e com o código de amostra que escrevi, depende do contexto, que é ignorado por uma questão de simplicidade.
Marco Scabbiolo
13

Podemos ter funções dentro de componentes funcionais sem estado, abaixo está o exemplo:

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

Porém, não é uma boa prática, pois a função handlePick()será definida sempre que o componente for chamado.

Ruchir Saxena
fonte
11
Se não for uma boa prática, qual será a solução alternativa?
John Samuel
@JohnSamuel A alternativa é definir handlePick()acima / fora do componente como function handlePick() { ... }; const Action = () => { ... }o resultado sendo que handlePick é definido apenas uma vez. Se você precisar de dados do Actioncomponente, deve passá-los como parâmetros para a handlePickfunção.
James Hay
14
se esta não for uma boa prática, você poderia ter adicionado a boa prática à resposta? agora tenho que pesquisar novamente as boas práticas :(
Shamseer Ahammed
2

Podemos usar o gancho React useCallbackcomo abaixo em um 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;

Estou pegando 'img' de seu pai e isso é uma matriz.

Vishant Rastogi
fonte
1

Se você quiser usar adereços ou estado de componente em função, isso deve ser definido em componente com useCallback.

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

Por outro lado, se você não quiser usar adereços ou estado em função, defina isso fora do componente.

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

function Component(props){
  
}
Hossein Mohammadi
fonte
3
você pode dar um exemplo de uso de gancho para método dentro do componente funcional? (método de estado não definido)
jake-ferguson