函数组件中的函数应该放在哪里?

我试图转换这个很酷的 <canvas>动画,我发现 给你成为一个反应可重用的组件。看起来这个组件需要一个父组件作为画布,许多子组件作为 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 (
...
);
}

它们各自的优点和缺点是什么,其中一个对于像我这样的特定用例是否更好?

151894 次浏览

首先要注意的是,无状态函数组件不能有方法: 如果呈现的 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和/或如果您需要访问组件闭包的范围,例如,读/写到状态,您应该考虑。就我个人而言,我选择默认在组件内部定义它,只有在需要的时候才让它可重用,以防止从一开始就出现过度设计。最重要的是,重用应用程序逻辑最好使用更具体的钩子,将组件留给表示目的。在使用钩子时定义组件外部的函数实际上取决于您希望应用程序逻辑与 React 的解耦程度。

另一个关于 useCallback普通的讨论问题是是否总是在每个函数中使用它。也就是说,治疗是作为 useCallback0或 useCallback1。我认为应该始终使用 useCallback: 我见过许多因为没有将函数包装在 useCallback中而导致的 bug,而且没有一个场景这样做会以任何方式影响性能或逻辑。在大多数情况下,您希望在依赖项不变的情况下保留引用,因此您可以将函数本身用作其他效果、备忘录或回调的依赖项。在许多情况下,回调将作为一个道具传递给其他元素,如果你用 useCallback制表它,你不会改变道具(因此重新渲染)其他组件,独立于如何便宜或昂贵。我已经看到了在组件中声明的成千上万个函数,没有一个使用 useCallback会有任何缺点。另一方面,大多数没有使用 useCallback制表的函数最终都会发生改变,如果开发人员没有意识到不这样做的后果,就会导致严重的错误或性能问题。使用 useCallback会对性能造成影响,因为这样会创建额外的函数,但是与重新声明函数(无论是否使用 useCallback)以及 React 和 JavaScript 的总体占用相比,这种影响可以忽略不计。因此,如果你真的关心 useCallback对性能的影响,而不是不使用它,你应该问问自己,React 是否是这项工作的正确工具。

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

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>
)
}

如果您想在函数中使用道具或组件的状态,那么应该使用 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 <div {...{onClick}} />
}

编辑: 钩子中的函数

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

我们可以在函数组件中使用反应钩 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”,这是一个数组。

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;