前两天在做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.ts中getUserInfo的请求,同时也会发送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>
);
}
成功解决问题!