vue 封装
重度 Vue 使用者最近一直在写 React 代码,很容易用 Vue 的出发写代码,然后跳到坑里。比如 3 个页面都是用了某个请求,Vue 开发很容易写出下面的代码:
import {ref} from 'vue';
const mockPromise = () => new Promise((resolve, reject) => {
setTimeout(() => {
reject('mock error')
}, 1000)
})
export const useFetch = () => {
const isLoading = ref(false)
const isError = ref(false)
const data = ref('')
const fetchData = async () => {
try {
isLoading.value = true
const res = await mockPromise();
data.value = res
isError.value = false
} catch {
isError.value = true
} finally {
isLoading.value = false
}
};
return { isLoading, fetchData, isError, data };
};
然后使用封装的 hook:
<script setup>
import { ref } from 'vue'
import {useFetch} from './useFetch'
const {isLoading, fetchData, isError, data } = useFetch()
const handleClick = async() => {
await fetchData()
if (isError.value) {
alert('请求出错')
}
}
</script>
<template>
<button @click="handleClick">请求</button>
</template>
react 封装
按照 vue 的逻辑,我很快写出了 react 封装的 hook,还洋洋自得,自己封装的就是好用,🤣 结果出 bug 了,我们先看下错误代码 ❌:
// ❌
import { useState} from 'react';
const mockPromise = () => new Promise((resolve,reject) => {
setTimeout(() => {
reject('mock error')
}, 1000)
})
export const useFetch = () => {
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [data, setData] = useState(null);
const fetchData = async () => {
try {
setIsLoading(true);
const res = await mockPromise();
setData(res);
setIsError(false);
} catch (error) {
setIsError(true);
} finally {
setIsLoading(false);
}
};
return { isLoading, fetchData, isError, data };
};
使用:
const { isLoading, fetchData, isError, data } = useFetch();
const handleFetch = async() => {
await fetchData();
if(isError) {
alert('error')
}
}
return (
<button onClick={handleFetch}>发请求</button>
)
为什么会这样呢?感觉上是对的,结果不是。问题出在了请求完数据,我觉得 isError 应该会自动变成 true ,但是 React 更新数据是异步的,也就是:
// count 是 0
setCount(count+1)
console.log(count) // 输出还是 0
对于 React 来说每一次执行就是一次函数的执行,可以理解成第二次函数执行的结果并不会影响第一次函数的执行。
解决方案
使用 useRef,useRef 每次都会返回相同的引用,拿到最新的值。
import { useState, useRef } from 'react';
const mockPromise = () => new Promise((resolve,reject) => {
setTimeout(() => {
reject('mock error')
}, 1000)
})
export const useFetch = () => {
const [isLoading, setIsLoading] = useState(false);
const isError = useRef(false);
const [data, setData] = useState(null);
const fetchData = async () => {
try {
setIsLoading(true);
const res = await mockPromise();
setData(res);
isError.current = false;
} catch (error) {
isError.current = true;
} finally {
setIsLoading(false);
}
};
return { isLoading, fetchData, isError, data };
};
使用:
const { isLoading, fetchData, isError, data } = useFetch();
const handleFetch = async() => {
await fetchData();
if(isError.current) {
alert('error')
}
}
return (
<button onClick={handleFetch}>发请求</button>
)