来自 Web前端 2020-04-29 17:43 的文章
当前位置: 网上澳门金莎娱乐 > Web前端 > 正文

结合React的Effect Hook分析组件副作用的清除

时间: 2019-09-07阅读: 136标签: Hook一个订阅好友在线的组件

时间: 2019-09-06阅读: 256标签: hooks

我们在DidMount的时候通过ID订阅了好友的在线状态,并且为了防止内存泄漏,我们需要在WillUnmount清除订阅

想象一下:你有一个非常好用的函数组件,然后有一天,咱们需要向它添加一个生命周期方法。刚开始咱们可能会想怎么能解决这个问题,然后最后变成,通常的做法是将它转换成一个类。但有时候咱们就是要用函数方式,怎么破?useEffecthook 出现就是为了解决这种情况。

但是当组件已经显示在屏幕上时,friend prop 发生变化时会发生什么? 我们的组件将继续展示原来的好友状态。这是一个 bug。而且我们还会因为取消订阅时使用错误的好友 ID 导致内存泄露或崩溃的问题。

使用useEffect,可以直接在函数组件内处理生命周期事件。 如果你熟悉 React class 的生命周期函数,你可以把useEffectHook 看做componentDidMount,componentDidUpdate和componentWillUnmount这三个函数的组合。来看看例子:

class FriendStatus extends React.Component { constructor(props) { super(props); this.state = { isOnline: null }; this.handleStatusChange = this.handleStatusChange.bind(this); } componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); } handleStatusChange(status) { this.setState({ isOnline: status.isOnline }); } render() { if (this.state.isOnline === null) { return 'Loading...'; } return this.state.isOnline ? 'Online' : 'Offline'; }}
import React, { useEffect, useState } from 'react';import ReactDOM from 'react-dom';function LifecycleDemo() { useEffect(() = { // 默认情况下,每次渲染后都会调用该函数 console.log('render!'); // 如果要实现 componentWillUnmount, // 在末尾处返回一个函数 // React 在该函数组件卸载前调用该方法 // 其命名为 cleanup 是为了表明此函数的目的, // 但其实也可以返回一个箭头函数或者给起一个别的名字。 return function cleanup () { console.log('unmounting...'); } }) return "I'm a lifecycle demo";}function App() { // 建立一个状态,为了方便 // 触发重新渲染的方法。 const [random, setRandom] = useState(Math.random()); // 建立一个状态来切换 LifecycleDemo 的显示和隐藏 const [mounted, setMounted] = useState(true); // 这个函数改变 random,并触发重新渲染 // 在控制台会看到 render 被打印 const reRender = () = setRandom(Math.random()); // 该函数将卸载并重新挂载 LifecycleDemo // 在控制台可以看到 unmounting 被打印 const toggle = () = setMounted(!mounted); return (  button onClick={reRender}Re-render/button button onClick={toggle}Show/Hide LifecycleDemo/button {mounted  LifecycleDemo/} / );}ReactDOM.render(App/, document.querySelector('#root'));

优化订阅好友在线的组件

在CodeSandbox中尝试一下。

为了解决props更新导致的BUG我们需要在DidUpdate中进行修改订阅

单击“Show/Hide”按钮,看看控制台,它在消失之前打印“unmounting...”,并在它再次出现时打印 “render!”。现在,点击Re-render按钮。每次点击,它都会打render!,还会打印umounting,这似乎是奇怪的。

 componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate(prevProps) { // 取消订阅之前的 friend.id ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // 订阅新的 friend.id ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); }

为啥每次渲染都会打印 'unmounting'。咱们可以有选择性地从useEffect返回的cleanup函数只在组件卸载时调用。React 会在组件卸载的时候执行清除操作。正如之前学到的,effect在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个effect进行清除。这实际上比componentWillUnmount生命周期更强大,因为如果需要的话,它允许咱们在每次渲染之前和之后执行副作用。

Effect Hook

不完全的生命周期

useEffect() 可以让你在函数组件中执行副作用操作默认情况下,它在第一次渲染之后和每次更新之后都会执行。

useEffect在每次渲染后运行(默认情况下),并且可以选择在再次运行之前自行清理。

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

与其将useEffect看作一个函数来完成3个独立生命周期的工作,不如将它简单地看作是在渲染之后执行副作用的一种方式,包括在每次渲染之前和卸载之前咱们希望执行的需要清理的东西。

这是React官网最基础的Hooks应用

阻止每次重新渲染都会执行 useEffect

import React, { useState, useEffect } from 'react';function Example() { const [count, setCount] = useState(0); // Similar to componentDidMount and componentDidUpdate: useEffect(() = { // Update the document title using the browser API document.title = `You clicked ${count} times`; }); return ( div pYou clicked {count} times/p button onClick={() = setCount(count + 1)} Click me /button /div );}

如果希望effect较少运行,可以提供第二个参数 - 值数组。 将它们视为该effect的依赖关系。 如果其中一个依赖项自上次更改后,effect将再次运行。

需要清除的 effect

const [value, setValue] = useState('initial');useEffect(() = { // 仅在 value 更改时更新 console.log(value);}, [value]) 

为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

上面这个示例中,咱们传入[value]作为第二个参数。这个参数是什么作用呢?如果value的值是5,而且咱们的组件重渲染的时候value还是等于 5,React 将对前一次渲染的[5]和后一次渲染的[5]进行比较。因为数组中的所有元素都是相等的(5 === 5),React 会跳过这个effect,这就实现了性能的优化。

React 何时清除 effect? React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。

仅在挂载和卸载的时候执行

import React, { useState, useEffect } from 'react';function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() = { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // Specify how to clean up after this effect: return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline';

如果想执行只运行一次的effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的effect不依赖于props或state中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

现在假设props.friend.id在更新100-200-300我们并不需要特定的代码来处理更新逻辑,因为 useEffect 默认就会处理。它会在调用一个新的 effect 之前对前一个 effect 进行清理。这样基于不会产生之前因为Props改变而产生的BUG

useEffect(() = { console.log('mounted'); return () = console.log('unmounting...');}, []) 
// Mount with { friend: { id: 100 } } propsChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 运行第一个 effect// Update with { friend: { id: 200 } } propsChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一个 effectChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 运行下一个 effect// Update with { friend: { id: 300 } } propsChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一个 effectChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 运行下一个 effect// UnmountChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一个 effect

这样只会在组件初次渲染的时候打印mounted,在组件卸载后打印:unmounting。

怎么跳过一些不必要的副作用函数

不过,这隐藏了一个问题:传递空数组容易出现bug。如果咱们添加了依赖项,那么很容易忘记向其中添加项,如果错过了一个依赖项,那么该值将在下一次运行useEffect时失效,并且可能会导致一些奇怪的问题。

按照上一节的思路,每次重新渲染都要执行一遍这些副作用函数,显然是不经济的。怎么跳过一些不必要的计算呢?我们只需要给useEffect传第二个参数即可。用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(第一个参数)。

只在挂载的时候执行

useEffect(() = { document.title = `You clicked ${count} times`;}, [count]); // 只有当count的值发生变化时,才会重新执行`document.title`这一句

在这个例子中,一起来看下如何使用useEffect和useRefhook 将input控件聚焦在第一次渲染上。

扩展:如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。这并不属于特殊情况 —— 它依然遵循依赖数组的工作方式。

import React, { useEffect, useState, useRef } from "react";import ReactDOM from "react-dom";function App() { // 存储对 input 的DOM节点的引用 const inputRef = useRef(); // 将输入值存储在状态中 const [value, setValue] = useState(""); useEffect( () = { // 这在第一次渲染之后运行 console.log("render"); // inputRef.current.focus(); }, // effect 依赖 inputRef [inputRef] ); return ( input ref={inputRef} value={value} onChange={e = setValue(e.target.value)} / );}ReactDOM.render(App /, document.querySelector("#root"));

来自:

在顶部,我们使用useRef创建一个空的ref。 将它传递给input的ref prop,在渲染DOM 时设置它。 而且,重要的是,useRef返回的值在渲染之间是稳定的 - 它不会改变。

因此,即使咱们将[inputRef]作为useEffect的第二个参数传递,它实际上只在初始挂载时运行一次。这基本上是componentDidMount效果了。

使用 useEffect 获取数据

再来看看另一个常见的用例:获取数据并显示它。在类组件中,无们通过可以将此代码放在componentDidMount方法中。在 hook 中可以使用 useEffect hook 来实现,当然还需要用useState来存储数据。

下面是一个组件,它从Reddit获取帖子并显示它们

本文由网上澳门金莎娱乐发布于Web前端,转载请注明出处:结合React的Effect Hook分析组件副作用的清除

关键词: