好吧,useEffect会用就行
前言
之前也看过关于useEffect的文章,但是真到自己使用,或者叫我讲出为啥的时候,我是真讲不出来。但是有些项目却总是开启了useEffect依赖检测,如果依赖没有使用正确的话控制台会warning或者直接报错。没办法,虽然我记不住啥啥啥原理,我知道怎么用就好了。虽然说是这么说,但是基础的东西是要知道的
前提
请开启useEffect依赖规则,如果不开启配置的话,我们有时候甚至不会去关注useEffect依赖问题。
yarn add eslint-plugin-react-hooks --dev
然后在.eslintrc.json文件中,引入规则。
-
在plugin: ["react-hooks"]
-
在rules: { "react-hooks/rules-of-hooks": "warn", // 检查 Hook 的规则 "react-hooks/exhaustive-deps": "warn", // 检查 effect 的依赖 } 如何使用webpack配置react项目可以看我之前写的文章。上面的配置也是写在之前写文章时用的项目中。
useEffect是用来干什么的?
useEffect本身就是一个函数,接受一个函数作为参数,然后这个函数会在组件渲染到屏幕之后执行
。由于是每轮渲染完后执行,所以它可以做一下改变DOM、添加订阅、设置定时器,请求接口数据等有副作用的操作。因为useEffect是放到组件里面,所以组件的每次更新理论上来讲都会生成一个新的useEffect并且执行,但是我们可以有选择性的让他执行,这个就是我们需要讲的依赖
。反正我用来请求接口居多...
没有依赖
不需要依赖就是useEffect中传入一个函数作为第一个参数,第二个参数不传。这样每次组件状态更新useEffect都会更新(更新前会记录当前的状态)
并且执行,意思就是不会被当前状态影响。如下
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count', count);
})
如果没有依赖,每次count改变,都会打印最新的count。
前面一直说,每次更新都会一个新的useEffect,并且更新前会记录当前的状态。例子如下
const Dashboard = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1)
}
useEffect(() => {
setTimeout(() => {
console.log('count', count);
}, 2000)
})
return (
<>
---{count}---
<button onClick={handleClick}>点击+1</button>
</>
);
}
如果我连续点击3次button是count加1,大家可以思考下,setTimeout里面输出的是什么样子的?是0,1,2,3?还是全都是3. 答案是前者,因为组件中的函数(包括useEffect)会捕获每次状态更新时的状态。
有依赖
依赖以数组的方式作为useEffect的第二个参数。当依赖发生改变useEffect会再次执行。下面有几个例子是当使用依赖报错时的解决办法
场景一: []
作为依赖
[]作为依赖,原意是为了让页面一挂载就执行,并且只执行一次。但有时候这样往往不尽人意。
useEffect(() => {
console.log('页面挂载我就渲染,组件状态改变我是不会渲染的');
}, []);
场景二:页面一挂载就开启倒计时
强行举个场景例子。例如一进入这个页面,我就开启一个倒计时(我只想运行一次useEffect),倒计时过后退出这个页面。你可能会这样写
import React, {
useEffect,
useState
} from 'react';
const Dashboard = () => {
const [count, setCount] = useState(60);
useEffect(() => {
const timer = setInterval(() => {
setCount(count -1)
}, 1000)
return () => clearInterval(timer)
}, [])
return (
<>
---{count}---
</>
);
}
export default Dashboard
这样写,如果开起来依赖报错,那么编辑器肯定就会提醒你依赖错误,如
抛开这个报错,你认为count会如何变化。运行下代码就知道了,count变为59就不会变了。因为由于useEffect依赖设置为[],所以只会执行一次,然后加上useEffect会记录之前的状态,所以记录的是count为60的状态。于是每隔一秒钟setInterval执行的时候count都是60,所以页面会一直显示59
如果你根据提示先
添加count作为依赖。首先效果可以达到,但不是很理想。分析一下,如果count作为依赖,第一次count为60,然后useEffect开始执行,每隔一秒钟count减1,即count一直再变。那么根据依赖count在变,useEffect会重新执行,每次新建一个定时器,然后再次渲染之后又会清除前面一个定时器再新建一个。虽然能实现效果,但是不完美。
比较正确的做法
就是尽量不依靠依赖,尽量不要在useEffect中使用组件里面的状态。比如说这个count我们其实可以不依赖这个count,我们可以这样做
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev - 1)
}, 1000)
return () => clearInterval(timer)
}, [])
场景三:函数作为依赖
函数作为依赖第一印象是不行的,因为组件每次更新同一个函数都会重新生成,并且与之前都不一样,所以如果单纯的把函数作为依赖,那么useEffect会无限调用。
有这样的场景,页面一挂载完成就要去请求数据.
单独写一个方法,然后放到useEffect中,这个方面不适用任何一个组件中的状态
如下处理,也可以把fetchData方法提出来,放到组件外面,这样也可以减少每次组件渲染重复加载这个方法。
import React, {
useEffect,
useState
} from 'react';
import Axios from 'axios';
const Dashboard = () => {
const [userId, setUserId] = useState(101);
const [data, setData] = useState<any[]>([]);
const fetchData = async () => {
const result = await Axios({
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
method: 'post',
url: 'https://jsonplaceholder.typicode.com/posts',
data: JSON.stringify({
title: 'foo',
body: 'bar',
userId: 1
})
}).then((res: any) => {
console.log('res', res);
return []
});
setData(result)
}
useEffect(() => {
fetchData()
}, []);
return (
<>
---{count}---
</>
);
}
export default Dashboard
单独写一个方法,但是需要用到组件中的状态
如果你直接把userId,如下直接放入fetchData方法中,那么编辑器会报错,会提醒你需要fetchData这个方法作为依赖。好,然后我们就继续把fetchData 这个方法作为useEffect的依赖
第一步
import React, {
useEffect,
useState
} from 'react';
import Axios from 'axios';
const Dashboard = () => {
const [userId, setUserId] = useState(101);
const [data, setData] = useState<any[]>([]);
const fetchData = async () => {
const result = await Axios({
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
method: 'post',
url: 'https://jsonplaceholder.typicode.com/posts',
data: JSON.stringify({
title: 'foo',
body: 'bar',
userId: userId
})
}).then((res: any) => {
console.log('res', res);
return []
});
setData(result)
}
useEffect(() => {
fetchData()
}, []);
return (
<>
---{userId}---
</>
);
}
第二步
把fetchData作为依赖,这就比较坑了。如果你没有开启依赖报错,那么页面已刷新会一直执行featData这个方法。分析下,因为fetchData在每次组件更新时,都会生成新的fetchData与之前fetchData不一样,所以useEffect就会不断执行。怎么解决这个问题嘞,我们可以使用useCallback(因为useCallback第二个参数也可以传递依赖,当这个依赖发生变化时,fetchData才会重新执行)
useEffect(() => {
fetchData()
}, [fetchData]);
第三步
用useCallback去包裹fetchaData。如下,这样就可以了,只有userId发生改变的时候useEffect才会重新执行
const fetchData = useCallback(async () => {
const result = await Axios({
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
method: 'post',
url: 'https://jsonplaceholder.typicode.com/posts',
data: JSON.stringify({
title: 'foo',
body: 'bar',
userId: userId
})
// data: {a: count}
}).then((res: any) => {
console.log('res', res);
return []
});
setData(result)
}, [userId])
函数做依赖总结
- 如果这个方法不需要状态(state),那么可以把这个方法单独提出这个组件。
- 如果需要state且这个方法不需要被复用,可以把这个方法放到useEffect中,然后把这个state作为useEffect的依赖.
useEffect(() => {
const fetchData = async () => {
const result = await Axios({
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
method: 'post',
url: 'https://jsonplaceholder.typicode.com/posts',
data: JSON.stringify({
title: 'foo',
body: 'bar',
userId: userId
})
// data: {a: count}
}).then((res: any) => {
console.log('res', res);
return []
});
setData(result)
}
fetchData()
}, [userId]);
- 如果需要state且这个方法不需要被复用.把这个方法提出去,然后把state作为参数传递给这个方法。然后把这个方法放到useEffect中,最后把这个state作为依赖
// 这个方法放到组件外部
const fetchData = async (id: number) => {
const result = await Axios({
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
method: 'post',
url: 'https://jsonplaceholder.typicode.com/posts',
data: JSON.stringify({
title: 'foo',
body: 'bar',
userId: id
})
// data: {a: count}
}).then((res: any) => {
console.log('res', res);
return []
});
// setData(result)
return result
}
useEffect(() => {
fetchData(userId)
}, [userId]);
- 需要state,且这个方法在组件中还有别的地方调用。可以在组件中生命这个方法,且使用useCallback包裹着,并让使用的state作为useCallback的依赖项,最后在useEffect中调用这个方法,并使这个方法称为useEffect的依赖。
import React, {
useCallback,
useEffect,
useState
} from 'react';
import Axios from 'axios';
const Dashboard = () => {
const [userId, setUserId] = useState(60);
const [data, setData] = useState<any[]>([]);
const fetchData = useCallback(async () => {
const result = await Axios({
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
method: 'post',
url: 'https://jsonplaceholder.typicode.com/posts',
data: JSON.stringify({
title: 'foo',
body: 'bar',
userId: userId
})
// data: {a: count}
}).then((res: any) => {
console.log('res', res);
return []
});
setData(result)
}, [userId])
useEffect(() => {
fetchData()
}, [fetchData]);
return (
<>
---{userId}---
</>
);
}
export default Dashboard
其实函数作为依赖,主要是得注意函数每次状态更新都会生成不一样的函数,每次不一样的话相当于这个依赖无效,所以要保证函数的不变性。
场景四:props作为依赖
prop作为依赖其实和state一样的,按照上述的解决办法就行。
总结
个人感觉useEffect还不够智能,不能实现不需要使用依赖就能自己帮我们做处理,必须我们自己去设置。不过具体为什么这么去实现,我还没有深究。以上也是我在使用useEffect过程中遇到的问题,以及解决办法,我也不需要去理解useEffect,会用就行。ps:理解了还是会忘记,烦!!!
转载自:https://juejin.cn/post/7167676124275671047