在过去的一周里,React核心团队为一个新的React hook发布了一个Request for Comment(RFC):useEvent 。这篇文章试图记录下这个钩子是什么,它不是什么,以及我的初步反应是什么。
请注意,这是一个RFC,还没有发布,所以它还不能用,它的行为可能会改变。
试图解决一个真正的问题
有一个真正的问题useEvent ,试图解决。在我们讨论useEvent 是什么之前,让我们先把这个问题想清楚。
React的执行模型主要是通过比较事物的当前值和先前值来实现的。这发生在组件和钩子中,如useEffect,useMemo, 和useCallback 。
考虑一下下面的组件。
function MyApp() {
const [count, setCount] = useState(0);
return <Counter count={count} />;
}
如果count 变量发生变化,Counter 组件将重新渲染。假设我们也想在count 发生变化时运行某种效果。我们可以使用useEffect 钩子。
function MyApp() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
return <Counter count={count} />;
}
由于我们在useEffect 钩子的依赖数组中包含了count ,所以每次count 变化时,效果就会重新运行。
那么问题出在哪里呢?
在这种模式下,许多React开发者发现自己遇到了同样的问题:太多的组件重现或太多的钩子重运行(有时是无限的!)。
RFC有一些很好的例子,所以我将使用它们首先,让我们考虑一个聊天应用程序,我们在一个组件中有一些text 状态,然后有一个单独的组件用于SendMessage 按钮。
function Chat() {
const [text, setText] = useState('');
const onClick = () => {
sendMessage(text);
};
return <SendButton onClick={onClick} />;
}
问题是,每当我们的text 变化时,onClick 函数就会重新创建。它永远不会与传递给SendButton 的最后一个onClick 函数相对等,因此我们很容易在每个按键上重新渲染SendButton 。
现在让我们考虑一个例子,当useEffect 运行得太频繁时。这是Dan Abravov(React核心团队)在Twitter上的一个例子。在这个例子中,我们有一个效果,每次route.url 的变化都会记录一次页面访问。
function Page({ route, currentUser }) {
useEffect(() => {
logAnalytics('visit_page', route.url, currentUser.name);
}, [route.url, currentUser.name]);
}
然而,我们也会在用户的名字被更新时记录一次页面访问。我们不希望这样。我们可以将currentUser.name 从依赖关系数组中移除,但这在React中是一个相当糟糕的做法--如果你的依赖关系数组没有反映出你的效果函数体中的所有依赖关系,你就会出现陈旧的闭合和难以追踪的错误。这一点非常重要,在react-hooks ESLint插件中,有一条 "详尽的deps "规则,React核心团队强烈鼓励。
建议的解决方案:useEvent
这是很重要的设置;很高兴你坚持了下来。现在我们可以讨论一下useEvent 。创建这个新的钩子是为了确保我们对一个函数有一个稳定的引用,而不需要根据它的依赖关系来创建一个新的函数。
这是一个口号--也许直接展示更容易。 让我们重新审视一下我们的聊天应用程序,其中的SendButton ,它重读的次数太多了。当useEvent 存在时,我们就可以包裹我们的点击处理程序,并且有一个不改变引用的函数,即使它所围绕的text 正在变化。
function Chat() {
const [text, setText] = useState('');
const onClick = useEvent(() => {
sendMessage(text);
});
return <SendButton onClick={onClick} />;
}
现在,onClick 将始终引用同一个函数,而不是在每次渲染时重新创建,因此SendButton 将不会不断地被重新渲染。
接下来,我们来研究一下页面访问记录器。
function Page({ route, currentUser }) {
const logVisit = useEvent((pageUrl) => {
logAnalytics('visit_page', pageUrl, currentUser.name);
});
useEffect(() => {
logVisit(route.url);
}, [route.url]);
}
现在我们已经创建了一个稳定的logVisit 函数,我们可以从useEffect 函数体中删除currentUser.name ,并且只在route.url 发生变化时运行该效果。
初步印象
我对useEvent 钩子的最初反应是 "哼,useEvent 甚至是什么意思?"像许多人一样,我对这个钩子的名字不以为然。撇开这一点不谈,在过去的几年里,这个钩子会让我在与效果依赖性的搏斗中省去很多麻烦。总的来说,我认为这将是对React生态系统的一个伟大补充。
这个钩子不是什么
useEvent 钩子并不是银弹,这是肯定的。它为React增加了另一个概念。它也不能改变React不是真正的反应式的事实。我将为SolidJS这个真正的反应式框架做一个简短的介绍,以及记录器将变得多么简单。
function Page(props) {
createEffect(() => {
logAnalytics(
'visit_page',
props.route.url,
untrack(() => props.currentUser.name)
);
});
}
免责声明:我是SolidJS文档团队的一员,所以我对它有偏见。但是,我仍然认为这要简单得多!"。效果对props.route.url 和props.currentUser.name 的依赖性被自动跟踪。要 "取消追踪 "这些依赖关系之一,Solid 提供了一个untrack 函数。上面的代码将只在props.route.url 变化时触发,但会在用户的名字周围有当前的封闭。