【问题标题】:Where should functions in function components go?函数组件中的函数应该去哪里?
【发布时间】:2018-02-18 15:49:07
【问题描述】:

我正在尝试将我发现的 here 这个很酷的 <canvas> 动画转换为 React 可重用组件。看起来这个组件需要一个父组件用于画布,而许多子组件用于function Ball()

出于性能原因,将Balls 制作成无状态组件可能会更好,因为它们中会有很多。我对制作无状态组件不太熟悉,想知道应该在哪里定义function Ball() 中定义的this.update()this.draw 函数。

无状态组件的函数是在组件内部还是外部?换句话说,以下哪个更好?

1:

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

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

    return (
       ...
    );
}

2:

function update() {
     ...
}

function draw() {
     ...
}

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

每种方法的优缺点是什么,其中一种更适合我的特定用例?

【问题讨论】:

  • 您能否发布现有代码,以便我们了解如何使用它?
  • @Scimonster 我将它发布在嵌入式链接中,也许你错过了它。这是链接:codepen.io/awendland/pen/XJExGv

标签: javascript reactjs function


【解决方案1】:

首先要注意的是,无状态功能组件不能有方法,如果它是无状态功能组件,则不应指望在渲染的Ball 上调用updatedraw

在大多数情况下,您应该在组件函数之外声明这些函数,以便只声明一次并始终重复使用相同的引用。当你在里面声明函数时,每次渲染组件时都会重新定义函数。

在某些情况下,您需要在组件内部定义一个函数,例如,将其分配为一个事件处理程序,该处理程序根据组件的属性表现不同。但是您仍然可以在 Ball 之外定义函数并将其与属性绑定,从而使代码更加简洁,并使 updatedraw 函数可重用。

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

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

如果您使用钩子,您可以使用useCallback 确保仅在其依赖项之一(本例中为props.x)发生更改时重新定义函数:

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

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

这是错误的方式

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

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

当使用useCallback 时,在useCallback 钩子本身中定义update 函数在我们的组件外部成为最重要的设计决策,您应该考虑是否要重用update 和/或者如果你需要访问组件闭包的范围,例如,读/写状态。我个人选择默认在组件内部定义它,并使其仅在需要时可重用,以防止从一开始就过度设计。除此之外,重用应用程序逻辑最好使用更具体的钩子来完成,而留下组件用于演示目的。在使用 hooks 时定义组件外部的函数实际上取决于您希望应用程序逻辑与 React 的解耦等级。

【讨论】:

  • 谢谢 Marco,这让事情变得更清楚了。在我的情况下,我感到困惑的事情与Ball 中的this.draw 函数有关。它使用父级的&lt;canvas&gt; 中的ctx,并且还使用this 关键字作为子级Ball 组件。集成实现无状态组件以使这两个属性都可以访问的最佳方法是什么?
  • 在使用无状态功能组件时没有this,请记住这一点。对于画布上下文,您必须将其传递给每一个 Ball,这听起来一点都不好。
  • @MarcoScabbiolo 不不,这不是我的情况,已经在本地使用箭头函数很长一段时间了,因为唯一不支持它们的浏览器是 IE。实际上,我设法从 one article 找到了这条评论,实际上它声称 bind 特别是在 Chrome 早于 59 中甚至比箭头函数。在 Firefox 中也需要很长一段时间,因为它们都以相同的速度运行。所以我会说在这种情况下,首选方式是什么没有区别:)
  • @MauricioAvendaño 任何一种方式都有效,但 Something 组件知道其父组件中有一个 prop X 是一种不好的做法,因为它会使其知道其上下文。您提出的问题和我编写的示例代码也是如此,这取决于上下文,为了简单起见,将其忽略。
  • @Atif 取决于组件及其子组件。了解原因:reactjs.org/docs/reconciliation.html 衡量它:reactjs.org/docs/optimizing-performance.html
【解决方案2】:

您可以将函数放置在无状态功能组件中:

function Action() {
    function handlePick(){
        alert("test");
    }

    return (
        <div>
            <input type="button" onClick={handlePick} value="What you want to do ?" />
        </div>
    )
}

但这不是一个好习惯,因为每次渲染组件时都会定义函数handlePick()

最好在组件外定义函数:

function handlePick(){
    alert("test");
}

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

【讨论】:

    【解决方案3】:

    如果你想在函数中使用组件的 props 或 state,应该在组件中使用 useCallback 定义。

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

    另一方面,如果您不想在函数中使用道具或状态,请在组件之外定义。

    const computeSomethings=()=>{
       // Do some things with params or side effects
    }
    
    function Component(props){
      return <Something onClick={computeSomethings} />
    }
    

    对于 HTML 标签,你不需要 useCallback,因为它会在反应端处理并且不会分配给 HTML

    function Component(props){
      const onClick=()=>{
         // Do some things with props or state
      }
    
      return <Something {...{onClick}} />
    }
    

    编辑:钩子中的函数

    对于 hooks 中的 use 函数,例如 useEffect,我的建议是在 useEffect 中定义函数,如果您担心 DRY,请让您的函数在 hook 中纯调用它并给它您的参数。 钩子部门呢?您应该/可以将所有参数添加到 hooks deps,但 useEffect 只需要 deps 会影响它们的更改。

    【讨论】:

    • 你能举一个在功能组件内部使用钩子方法的例子吗? (不设置状态方法)
    【解决方案4】:

    我们可以在功能组件中使用 React hook useCallback,如下所示:

    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;
    

    我从它的父母那里得到'img',那是一个数组。

    【讨论】:

    • 谢谢 - 我感觉有一个钩子,以便我们可以列出函数组件中 const 函数的依赖关系!
    • UseCallback 只对记忆函数有用,因此在每次渲染时,函数都可以自己记忆,这需要一个 props 或 state 的依赖关系。
    【解决方案5】:
    import React, { useState } from 'react';
    function Example() {
      // Declare a new state variable, which we'll call "count"
      const [count, setCount] = useState(0);
      const a = () => {
        setCount(count + 1);
      };
      return (
        <div>
          <p>You clicked {count} times</p>
          <button onClick={a}>Click me</button>
        </div>
      );
    }
    export default Example;
    

    【讨论】:

    • 我建议不要回答已经存在 3 年的问题,并且回答的问题有很多赞成票。你可能不会得到你的投票。
    猜你喜欢
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-28
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 1970-01-01
    相关资源
    最近更新 更多