一般来说,偶尔出现的这个Warning确实不会带来严重的性能问题,但是试想如果是setInterval
的句柄没有被正确在卸载周期中进行清理,那即便你的组件销毁了,它也会持续地生效,不仅会造成memory leak,亦会拖慢你项目的响应速度。所以,作为一个严谨的开发者来说,我们在实现逻辑时,就须要先行考虑到这些问题。另外对于一些强迫症同学来说,肯定不会希望每次打开控制台看到一坨红屏,更别说我们在开发过程中,还会经常遇到另一个常见的对数组结构生成渲染Element缺少key
值的Warning场景。
在一波科学上网后,我大概得到了两种处理方式,“治标”与“治本”。
治标
治标法本质上是在你的class
组件或者hooks
函数组件中声明一个哨兵变量,具体是用什么方式声明,如声明在实例属性、useRef
、useEffect
的局部变量上都无所谓,它们都能达到同样的效果。
我们就以一个获取后台日志的场景为例。
class
组件:
export default class LogList extends PureComponent {
_isMounted = false
componentDidMount() {
this._isMounted = true
}
componentWillUnmount() {
this._isMounted = false
}
fetchLogList = id => {
return axios.get(`/fetchList/${id}`).then(res => {
if (this._isMounted) {
// setState动作...
}
})
}
render() {
// 渲染
}
}
hooks
组件:
// useRef保存哨兵变量
export default function LogList() {
const _isMounted = useRef(false)
const [logList, setLogList] = useState([])
fetchLogList = id => {
return axios.get(`/fetchList/${id}`).then(res => {
if (_isMounted.current) {
// setLogList...
}
})
}
useEffect(() => {
_isMounted.current = true
return () => {
_isMounted.current = false
}
}, [])
return (
// 渲染
)
}
// useEffect内部声明哨兵变量
export default function LogList() {
const [logList, setLogList] = useState([])
fetchLogList = id => {
return axios.get(`/fetchList/${id}`)
}
useEffect(() => {
let _isMounted = true
fetchLogList(1).then(res => {
if (_isMounted) {
// setLogList...
}
})
return () => {
_isMounted = false
}
}, [])
return (
// 渲染
)
}
治本
治本要怎么治呢?其实在仔细观察治标中的操作后,我们发现我们都在当前组件上挂载了一个“脏东西”。作为一个组件本身的定位来说,它不再纯粹了,我们为了处理这种异步渲染的Warning而在组件本身上加东西是不太合适的。调整的核心思路在于**“解耦”**。
参考js本身的timer,我们可以发现它们都会返回一个handler
句柄用于之后的取消任务。那么诸如ajax请求之类的promise
返回也是同理,问题就可以转移成:我们如何提供一个可以取消promise
的方法? 从设计本身而言,这种异步等待的任务都应该具有一个取消的机制,等太久了我是不是应该直接将任务取消再主动发起?另外任务的等待处理逻辑本身也不应该放到组件属性上去做,会使得一个组件设计上职能不集中,看上去就很难受。
大致方法就是实现一个高阶函数,同时返回封装后的新Promise实例以及支持取消该Promise的cancel方法:
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled_ ? reject({ isCanceled: true }) : resolve(val),
error => hasCanceled_ ? reject({ isCanceled: true }) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
之前的问题就可以修改为如下的样子:
import { makeCancelable } from '@/utils'
export default function LogList() {
const [logList, setLogList] = useState([])
fetchLogList = id => {
return axios.get(`/fetchList/${id}`)
}
useEffect(() => {
const { promise, cancel } = makeCancelable(fetchLogList(1))
promise.then(res => {
// setLogList...
})
return () => {
cancel()
}
}, [])
return (
// 渲染
)
}
P.S. 实际上我们也可以再换个思路,通过将状态交由react-redux
的store
掌控,组件拆分为无状态组件进行显示渲染,外层的业务组件进行通过dispatch
派发action
,中间件层进行异步动作,一样可以处理该问题。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!