一年多来,React钩子已经风靡一时。让我们来看看我们如何推出我们自己的useFetch 钩子来抽象出我们的组件的获取请求逻辑。
**注意:**这仅仅是为了学术目的。你可以推出你自己的useFetch 钩子并在生产中使用它,但我强烈建议使用像use-http这样的成熟库来为你完成繁重的工作。
我们的 useFetch 函数签名
为了确定我们的useFetch 函数签名,我们应该考虑我们可能需要从终端用户那里获得的信息,以实际执行我们的获取请求。在这种情况下,我们会说我们需要资源url ,我们需要可能与请求一起的options (例如,请求方法)。
function useFetch(initialUrl, initialOptions) {
// Hook here
}
在一个功能更全面的解决方案中,我们可能会给用户一个终止请求的方法,但我们现在对我们的两个参数很满意
在我们的钩子中保持状态
我们的钩子将需要维护一些状态。我们至少需要维护url 和options 的状态(因为我们需要给用户提供一种方法来访问setUrl 和setOptions )。还有一些其他的有状态的变量我们也需要
- data (从我们的请求中返回的数据)
- error (如果我们的请求失败,任何错误)
- loading (一个布尔值,表示我们是否在积极获取)
让我们使用内置的useState 钩子来创建一堆有状态的变量。另外,我们要让我们的用户有机会做以下事情。
- 设置网址
- 设置选项
- 查看获取的数据
- 查看任何错误
- 查看加载状态
因此,我们必须确保从我们的钩子中返回这两个状态设置函数和三个数据!
import { useState } from 'React';
function useFetch(initialUrl, initialOptions) {
const [url, setUrl] = useState(initialUrl);
const [options, setOptions] = useState(initialOptions);
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
// Some magic happens here
return { data, error, loading, setUrl, setOptions };
}
重要的是,我们默认我们的url 和options 为钩子第一次被调用时提供的initialUrl 和initialOptions 。另外,你可能会想,这些是很多不同的变量,你想把它们都维护在同一个对象里,或者几个对象里--这完全没问题!
当我们的URL或选项发生变化时运行一个效果
这是一个相当重要的部分!每当url 或options 变量发生变化时,我们都要执行一个fetch 请求。还有什么比内置的useEffect 钩子更好的方法呢?
import { useState } from 'React';
function useFetch(initialUrl, initialOptions) {
const [url, setUrl] = useState(initialUrl);
const [options, setOptions] = useState(initialOptions);
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
// Fetch here
}, [url, options]);
return { data, error, loading, setUrl, setOptions };
}
用异步等待调用Fetch
比起Promise语法,我更喜欢async/await语法,所以让我们使用前者吧!当然,使用then 、catch 、finally ,而不是使用async/await,也一样可以工作。
import { useState } from 'React';
function useFetch(initialUrl, initialOptions) {
const [url, setUrl] = useState(initialUrl);
const [options, setOptions] = useState(initialOptions);
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
setError(undefined);
async function fetchData() {
try {
const res = await fetch(url, options);
const json = await res.json();
setData(json);
} catch (e) {
setError(e);
}
setLoading(false);
}
fetchData();
}, [url, options]);
return { data, error, loading, setUrl, setOptions };
}
这是一个很大的问题!让我们把它分解一下。当我们运行我们的效果时,我们知道我们开始获取数据了。因此,我们将我们的loading 变量设置为true ,并清除之前可能存在的任何错误。
在我们的异步函数中,我们用一个try/catch 块来包裹我们的fetch 请求代码。我们得到的任何错误都要报告给用户,所以在我们的catch 块中,我们setError ,以报告任何错误。
在我们的try 块中,我们做一个相当标准的fetch 请求。我们假设返回的数据是json ,因为我很懒,但如果我们想让这个钩子成为最通用的钩子,我们可能会给我们的用户一种方法来配置预期的响应类型。最后,假设一切顺利,我们将我们的data 变量设置为我们返回的JSON!
使用钩子
信不信由你,这就是创建我们的自定义钩子的全部内容!现在我们只需要把它引入到我们的网站中。现在我们只需要把它带入一个示例应用程序,并希望它能正常工作。
在下面的例子中,我有一个应用程序,可以加载任何github用户的基本github配置文件数据。这个应用几乎灵活运用了我们为钩子设计的所有功能,除了设置fetch 选项之外。我们可以看到,当获取请求正在加载时,我们可以显示一个 "加载 "指示器。当获取完成后,我们要么显示一个结果错误,要么显示一个字符串化的结果版本。
我们为用户提供了一种方法,可以输入不同的github用户名来执行新的fetch。一旦他们提交,我们使用从我们的useFetch 钩子导出的setUrl 函数,这将导致效果的运行和一个新的请求。我们很快就有了我们的新数据!
const makeUserUrl = (user) => `https://api.github.com/users/${user}`;
function App() {
const { data, error, loading, setUrl } = useFetch(makeUserUrl('nas5w'));
const [user, setUser] = useState('');
return (
<>
<label htmlFor="user">Find user:</label>
<br />
<form
onSubmit={(e) => {
e.preventDefault();
setUrl(makeUserUrl(user));
setUser('');
}}
>
<input
id="user"
value={user}
onChange={(e) => {
setUser(e.target.value);
}}
/>
<button>Find</button>
</form>
<p>{loading ? 'Loading...' : error?.message || JSON.stringify(data)}</p>
</>
);
}
欢迎在这里查看codesandbox上的useFetch 钩子和示例应用程序。
结论性的想法
编写一个自定义的React钩子可以是一个有趣的尝试。起初,它有时会有点棘手,但一旦你掌握了它的窍门,就会非常有趣,而且可以真正缩短和减少组件代码中的冗余部分。