Yui*_*Yui 5 javascript event-handling node.js reactjs react-functional-component
考虑 React 中这个简单的 TextField 组件:
import React, { useState } from "react";
import { TextField, Grid } from "@mui/material";
const handleEnter = (event) => {
console.log("In handleEnter");
if (event.key == "Enter" && event.shiftKey) {
console.log("Detected Shift+Enter key");
} else if (event.key == "Enter") {
console.log("Detected Enter key");
}
};
export default function Example() {
const [value, setValue] = React.useState("");
const handleChatBoxChange = (event) => {
setValue(event.target.value);
};
return (
<Grid container>
<TextField
id='chatBox'
maxRows={90}
onKeyDown={handleEnter}
value={value}
onChange={handleChatBoxChange}
variant='filled'
></TextField>
</Grid>
);
}
Run Code Online (Sandbox Code Playgroud)
这是一个非常简单的组件,handleEnter会检测用户何时按下 Enter 或 Shift+Enter 组合键。我意识到handleEnter的实现可以写在函数Example()之外,也可以写在Example()内部。但是,handleChatBoxChange() 不能放置在外部,因为它依赖于通过 useState() 创建的 setValue(并且 useState() 必须位于功能组件内部。对此的最佳实践是什么?
继续阅读原因...
在这个答案中,我忽略了代码风格和源代码模块管理(这是意见)。
当您阅读此答案时,需要理解的一些重要概念是纯函数、闭包和“不纯”函数(不属于其他两类的所有函数)之间的区别。Stack Overflow 上已经对此进行了广泛介绍,因此我不会在这里重复。
除了您对闭包本质的观察之外,对引用相等(对象标识)的影响也非常不同。考虑以下:
在下面的代码中(您可以想象它是一个名为 的模块文件component.jsx),回调函数是在模块的顶层、组件外部创建的。它在程序运行时初始化一次,并且其值在 JS 内存的生命周期内不会改变:
const handleClick = ev => {/*...*/};
const Component = () => {
return <div onClick={handleClick}></div>;
};
Run Code Online (Sandbox Code Playgroud)
相反,下面的代码显示了在组件内部创建的回调函数。因为组件本身只是函数,这意味着每次 React 调用该组件时,都会重新创建回调函数(一个全新的函数,不等于先前渲染中的“自身版本”):
const Component = () => {
const handleClick = ev => {/*...*/};
return <div onClick={handleClick}></div>;
};
Run Code Online (Sandbox Code Playgroud)
在上述场景中,函数直接用作 child 的事件处理程序回调ReactElement,不会产生明显的差异。然而,当将函数传递给子组件或在钩子的回调函数中使用它时useEffect,事情会变得更加复杂:
import {useEffect} from 'react';
const handleClick = ev => {/*...*/};
const Component = () => {
useEffect(() => {
const message = `The name of the function is ${
handleClick.name
}, and this message will only appear in the console when this component first mounts`;
console.log(message);
}, []);
return <div onClick={handleClick}></div>;
};
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,该函数不需要包含在效果的依赖项列表中,因为它的值不能也永远不会改变。它不是在组件内部创建的,也不是从 props 接收的,因此引用将是稳定的。
相反,考虑这个例子:
import {useEffect} from 'react';
const Component = () => {
const handleClick = ev => {/*...*/};
useEffect(() => {
const message = `The name of the function is ${
handleClick.name
}, and this message will appear in the console EVERY time this component renders`;
console.log(message);
}, [handleClick]);
return <div onClick={handleClick}></div>;
};
Run Code Online (Sandbox Code Playgroud)
在上面的代码中,每次在渲染期间调用组件时都会重新创建该函数,从而导致效果检测到依赖项数组中的不同值并再次运行该效果。正是由于这个原因,需要将该函数包含在依赖项数组中。
当将函数作为 props 传递给子组件时,同样的概念也适用。当组件接收函数作为 prop 值时(prop 值只是函数组件的对象参数上的属性值),它无法了解有关该函数的引用稳定性的任何信息,因此该函数必须始终包含在使用它的依赖项列表中。
这引出了记忆化的概念和相关的内置钩子(例如useMemo,useCallback)。(与函数类型类似,记忆化是一个更大的主题,并且已经在 SO 和许多其他地方进行了介绍。)
在结束这个答案之前,我将给出一个实际示例,说明如何为闭包函数创建稳定的对象标识:
import {useCallback, useState} from 'react';
const Component = () => {
const [count, setCount] = useState(0);
const adjustCount = useCallback(
(amount) => setCount(count => count + amount),
[setCount],
// ^^^^^^^^
); // It's optional to include the "setState" function provided by `useState`,
// in the dependency array here (it's a special case exception because React
// already guarantees that the "setState" function it gives you will remain
// stable). However, it doesn't hurt to include it (although you might
// choose to omit it if you're working on a refactor toward
// performance micro-optimizations).
// Now, when you pass that function to a child component as a prop value,
// the object identity won't change on subsequent renders:
// vvvvvvvvvvv
return <SomeChildComponent adjustCount={adjustCount} />;
};
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2217 次 |
| 最近记录: |