在 React 中使用钩子创建事件处理程序的正确方法?
Correct way to create event handlers using hooks in React?
在典型的基于 class 的 React 组件中,这就是我创建事件处理程序的方式:
class MyComponent extends Component {
handleClick = () => {
...
}
render() {
return <button onClick={this.handleClick}>Click Me</button>;
}
}
但是,当我使用基于钩子的函数范式时,我发现自己有两个选择:
const MyComponent = () => {
const [handleClick] = useState(() => () => {
...
});
return <button onClick={handleClick}>Click Me</button>;
};
或者:
const MyComponent = () => {
const handleClick = useRef(() => {
...
});
return <button onClick={handleClick.current}>Click Me</button>;
};
哪一个客观上更好,原因是什么?还有其他(更好的)方法我还没有听说过也没有发现吗?
感谢您的帮助。
编辑:我举了一个例子 here on CodeSandbox 来展示这两种方法。两者似乎都没有在每次渲染时不必要地重新创建事件处理程序,正如您从那里的代码中看到的那样,所以我认为可能的性能问题是不可能的。
我不推荐 useState
或 useRef
。
你实际上根本不需要任何钩子。在许多情况下,我建议简单地这样做:
const MyComponent = () => {
const handleClick = (e) => {
//...
}
return <button onClick={handleClick}>Click Me</button>;
};
但是,有时建议避免在渲染函数内声明函数(例如 jsx-no-lambda
tslint 规则)。这有两个原因:
- 作为性能优化,避免声明不必要的函数。
- 避免不必要的纯组件重新渲染。
我不太担心第一点:钩子将在函数内部声明函数,而且该成本不太可能成为影响应用程序性能的主要因素。
但第二点有时是有效的:如果一个组件被优化(例如使用 React.memo
或被定义为 PureComponent
)以便它只在提供新道具时重新渲染,传递一个新的函数实例可能会导致组件不必要地重新渲染。
为了处理这个问题,React 提供了 useCallback
钩子,用于记忆回调:
const MyComponent = () => {
const handleClick = useCallback((e) => {
//...
}, [/* deps */])
return <OptimizedButtonComponent onClick={handleClick}>Click Me</button>;
};
useCallback
只会在必要时 return 一个新函数(每当 deps 数组中的值发生变化时),因此 OptimizedButtonComponent
不会重新渲染超过必要的次数。所以这解决了问题#2。 (请注意,它没有解决问题 #1,每次我们渲染时,仍然会创建一个新函数并传递给 useCallback
)
但我只会在必要时这样做。您可以将每个回调包装在 useCallback
中,它会起作用......但在大多数情况下,它无济于事:您使用 <button>
的原始示例不会从记忆回调中受益,因为<button>
不是优化组件。
在典型的基于 class 的 React 组件中,这就是我创建事件处理程序的方式:
class MyComponent extends Component {
handleClick = () => {
...
}
render() {
return <button onClick={this.handleClick}>Click Me</button>;
}
}
但是,当我使用基于钩子的函数范式时,我发现自己有两个选择:
const MyComponent = () => {
const [handleClick] = useState(() => () => {
...
});
return <button onClick={handleClick}>Click Me</button>;
};
或者:
const MyComponent = () => {
const handleClick = useRef(() => {
...
});
return <button onClick={handleClick.current}>Click Me</button>;
};
哪一个客观上更好,原因是什么?还有其他(更好的)方法我还没有听说过也没有发现吗?
感谢您的帮助。
编辑:我举了一个例子 here on CodeSandbox 来展示这两种方法。两者似乎都没有在每次渲染时不必要地重新创建事件处理程序,正如您从那里的代码中看到的那样,所以我认为可能的性能问题是不可能的。
我不推荐 useState
或 useRef
。
你实际上根本不需要任何钩子。在许多情况下,我建议简单地这样做:
const MyComponent = () => {
const handleClick = (e) => {
//...
}
return <button onClick={handleClick}>Click Me</button>;
};
但是,有时建议避免在渲染函数内声明函数(例如 jsx-no-lambda
tslint 规则)。这有两个原因:
- 作为性能优化,避免声明不必要的函数。
- 避免不必要的纯组件重新渲染。
我不太担心第一点:钩子将在函数内部声明函数,而且该成本不太可能成为影响应用程序性能的主要因素。
但第二点有时是有效的:如果一个组件被优化(例如使用 React.memo
或被定义为 PureComponent
)以便它只在提供新道具时重新渲染,传递一个新的函数实例可能会导致组件不必要地重新渲染。
为了处理这个问题,React 提供了 useCallback
钩子,用于记忆回调:
const MyComponent = () => {
const handleClick = useCallback((e) => {
//...
}, [/* deps */])
return <OptimizedButtonComponent onClick={handleClick}>Click Me</button>;
};
useCallback
只会在必要时 return 一个新函数(每当 deps 数组中的值发生变化时),因此 OptimizedButtonComponent
不会重新渲染超过必要的次数。所以这解决了问题#2。 (请注意,它没有解决问题 #1,每次我们渲染时,仍然会创建一个新函数并传递给 useCallback
)
但我只会在必要时这样做。您可以将每个回调包装在 useCallback
中,它会起作用......但在大多数情况下,它无济于事:您使用 <button>
的原始示例不会从记忆回调中受益,因为<button>
不是优化组件。