无状态组件中的功能?


90

我正在尝试将<canvas>这里找到的这个很棒的动画转换为React可重用的组件。看来此组件将需要一个父组件作为画布,而许多子组件则为function Ball()

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

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

1:

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

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

    return (
       ...
    );
}

2:

function update() {
     ...
}

function draw() {
     ...
}

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

每种都有哪些优点/缺点,其中哪一种对我的特定用例而言更好?


您能否发布现有代码,以便我们了解如何使用它?
Scimonster

@Scimonster我将其发布在嵌入式链接中,也许您错过了它。这是链接:codepen.io/awendland/pen/XJExGv
MarksCode

Answers:


113

首先要注意的是,无状态功能组件不能具有方法,您不应该依赖于调用updatedraw呈现Ball如果它是无状态功能组件。

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

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

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

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

如果您使用的是hooks,则可以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} />
  );
}

使用时useCallbackupdateuseCallback挂钩本身中定义函数本身,我们在组件外部的决定比其他任何事情都要重要,这是设计决策,您应该考虑是否要重用update和/或是否需要访问组件闭包的范围,例如,读取/写入状态。我个人选择默认在组件内部定义它,并使其仅在需要时才可重用,以防止从一开始就过度设计。最重要的是,重用应用程序逻辑可以通过使用更具体的钩子来更好地完成,而将组件留作演示用。在使用钩子时在组件外部定义功能实际上取决于您希望为应用程序逻辑从React解耦的程度。


谢谢Marco,这使事情变得井然有序。在我的情况下,我感到困惑的事情与中的this.draw功能有关Ball。它使用ctx来自父级的from <canvas>,还使用this关键字作为子级的Ball组件。集成实现无状态组件以便可以访问这两个属性的最佳方法是什么?
MarksCode

没有this使用无状态的功能组件时,必须考虑到这一点。对于canvas上下文,您必须将其传递给每一个Ball,这听起来一点也不好。
Marco Scabbiolo

因此,在这种情况下,最好不要包含您要说的子组件?
MarksCode

1
@MarcoScabbiolo不,不,这不是我的情况,很长一段时间以来一直在本地使用箭头功能,因为唯一不支持箭头功能的浏览器是IE。实际上,我设法从一篇文章中找到了此评论,该文章实际上声称bind专门针对Chrome的版本早于59,甚至比箭头功能还。在Firefox中,由于它们的执行速度相同,因此还有相当长的一段时间。因此,我想说在这种情况下,首选方法没有区别:)
Vadim Kalinin

1
@MauricioAvendaño两种方法都可以,但是对于Something组件来说,知道其父组件中有一个prop X,因为它知道其上下文,这是一种不好的做法。对于您要问的问题和我编写的示例代码,也会发生同样的情况,这取决于上下文,为简单起见,忽略了上下文。
Marco Scabbiolo

12

我们可以在无状态功能组件中包含函数,下面是示例:

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

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


11
如果这不是一个好的做法,那么替代解决方案是什么?
约翰·塞缪尔

@JohnSamuel替代方法是handlePick()在组件上方/外部定义,就像function handlePick() { ... }; const Action = () => { ... }结果是handlePick仅定义一次。如果需要Action组件中的数据,则应将其作为参数传递给handlePick函数。
James Hay,

11
如果这不是一个好的做法,那么您可以在答案中添加一个好的做法吗?现在,我必须再次搜索良好做法:(
Shamseer Ahammed

2

我们可以useCallback在功能组件中如下使用React钩子:

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”,那是一个数组。


0

如果要在函数中使用道具或组件的状态,则应在useCallback组件中进行定义。

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

另一方面,如果您不想在功能中使用道具或状态,请在组件外部定义该属性。

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

function Component(props){
  
}

3
您能否举一个在功能组件内部使用钩子作为方法的示例?(未设置状态方法)
jake-ferguson,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.