React使用hook判断组件是否卸载

8,396 阅读2分钟

前两天在做Taro小程序开发时,发现每次进入都会出现如下的warning:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in %s.%s a useEffect cleanup function. 原因说的很明显,是在组件卸载后,还调用了setState,造成了内存泄漏。

页面代码

这是我的index.js文件, 进入index页面时会请求api去获取列表:

import React, { useEffect, useState } from 'react';
import { View } from '@tarojs/components';
import { getList } from '@/api/list';
import List from './list';
import './index.less';

export default function Index() {
  const [list, setList] = useState([]);  // 列表
  // 获取列表
  const getList = async () => {
    let res: any = await getList();
    if (res.err_no === 0) {
      setList(res.data && res.data.list || []);
    }
  }
  // 请求初始列表
  useEffect(() => {
    getList();
  }, [])
  return (
    <View className='index'>
      <List list={list} />
    </View>
  );
}

Taro的入口文件app.js文件中,会发送请求用户信息的api判断用户是否登录,若未登录,则会重定向至登录页面,代码如下:

import { Component } from 'react';
import { getUserInfo } from './api/parent';
import Taro from '@tarojs/taro';
import './app.less';

class App extends Component {
  componentDidMount() {
    getUserInfo().then(res => {
      if (res.err_no !== 0) {
        Taro.redirectTo({
          url: '/pages/login/index',
        });
      }
    });
  }
  render() {
    return this.props.children;
  }
}

export default App;

错误定位

上面就会引起一个问题,在小程序一开始进入到index页面,会发送app.tsgetUserInfo的请求,同时也会发送index.js中的getList的请求。当getUserInfo请求率先返回结果时,如果用户未登录,则会重定向至login页面,此时index页面组件已经卸载掉,而index.js页面中的getList请求完成后还要调用setList去改变state,便会造成内存泄漏。
知道了问题就好解决了,我们在进行setList操作时,只需要判断一下当前组件是否已卸载,若已卸载便终止setState操作。

通过hook判断组件是否处于unmouted状态

这里我自己写了一个hook,去判断组件是否处于已卸载的状态,然后在setList之前判断一下组件状态:

// hook.js
import React, { useEffect, useRef, useCallback } from 'react';

export const useMounted = () => {
  const mountedRef = useRef(false);
  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
    };
  }, []);
  return () => mountedRef.current;
};

修改后的index.js文件:

import React, { useEffect, useState } from 'react';
import { View } from '@tarojs/components';
import { getList } from '@/api/list';
import { useMounted } from '@/utils/hook';
import List from './list';
import './index.less';

export default function Index() {
  const [list, setList] = useState([]);  // 列表
  const isMounted = useMounted();
  // 获取列表
  const getList = async () => {
    let res: any = await getList();
    // 若isMounted为false,则说明组件已卸载,终止后续操作
    if (!isMounted()) {
      return;
    }
    if (res.err_no === 0) {
      setList(res.data && res.data.list || []);
    }
  }
  // 请求初始列表
  useEffect(() => {
    getList();
  }, [])
  return (
    <View className='index'>
      <List list={list} />
    </View>
  );
}

成功解决问题!