前言

在日常的开发中,从服务器端异步获取数据并渲染是相当高频的操作。在以往使用React Class组件的时候,这种操作我们已经很熟悉了,即在Class组件的componentDidMount中通过ajax来获取数据并setState,触发组件更新。
随着Hook的到来,我们可以在一些场景中使用Hook的写法来替代Class的写法。但是Hook中没有setState、componentDidMount等函数,又如何做到从服务器端异步获取数据并渲染呢?本文将会介绍如何使用React的新特性Hook来编写组件并获取数据渲染。

数据渲染

先来看一个数据渲染的简单demo

  1. import React, { useState } from 'react';
  2. function App() {
  3. const [data, setData] = useState({ products: [{
  4. productId: '123',
  5. productName: 'macbook'
  6. }] });
  7. return (
  8. <ul>
  9. {data.products.map(i => (
  10. <li key={i.productId}>
  11. {i.productName}
  12. </li>
  13. ))}
  14. </ul>
  15. );
  16. }
  17. export default App;
  18. 复制代码

在demo中,通过useState创建了一个叫data的内部state,该state中有一个产品列表数据保存产品数据。App组件通过data中的products来渲染产品列表数据到页面中。
但现在是写死的一个数据,如果我们期望从服务器端获取数据并渲染,那么就需要在组件渲染完成时fetch服务端数据,然后通过setData去改变state触发渲染。我们接下来准备用axios来获取数据。

  1. import React, { useState, useEffect } from 'react';
  2. import axios from 'axios';
  3. function App() {
  4. const [data, setData] = useState({ products: [{
  5. productId: '123',
  6. productName: 'macbook'
  7. }] });
  8. useEffect(async () => {
  9. const result = await axios(
  10. 'https://c.com/api/products?date=today',
  11. );
  12. setData(result.data);
  13. });
  14. return (
  15. <ul>
  16. {data.products.map(i => (
  17. <li key={i.productId}>
  18. {i.productName}
  19. </li>
  20. ))}
  21. </ul>
  22. );
  23. }
  24. export default App;
  25. 复制代码

代码中使用到的useEffect就是hook的其中一种,叫effect hook。useEffect会在每次组件渲染的时候触发,我们使用它来获取数据并更新state。但是上面的代码是有缺陷的,你发现了吗?
没错,只要你运行一下,你就会发现程序进入了一个死循环。因为useEffect不仅在组件didMounts的时候被触发了,还在didUpdate的时候被触发了。在useEffect中获取数据后,通过setDate改变state,触发组件渲染更新,从而又进入到了useEffect中,无限循环下去。这并不是我们想要的结果。我们最初想要的,只是希望在didMounts的时候获取一次数据而已。所以,这种情况下,我们必须要给useEffect方法的第二个参数传入一个空[],以使得useEffect中的逻辑只在组件didMounts的时候被执行。

  1. import React, { useState, useEffect } from 'react';
  2. import axios from 'axios';
  3. function App() {
  4. const [data, setData] = useState({ products: [{
  5. productId: '123',
  6. productName: 'macbook'
  7. }] });
  8. useEffect(async () => {
  9. const result = await axios(
  10. 'https://c.com/api/products?date=today',
  11. );
  12. setData(result.data);
  13. },[]); //重点
  14. return (
  15. <ul>
  16. {data.products.map(i => (
  17. <li key={i.productId}>
  18. {i.productName}
  19. </li>
  20. ))}
  21. </ul>
  22. );
  23. }
  24. export default App;
  25. 复制代码

虽然看起来这个错误比较低级,但确实比较多人在新上手hook时常常犯的问题。
当然,useEffect第二个参数,也可以传入值。当如果有值的时候,那useEffect会在这些值更新的时候触发。如果只是个空数组,则只会在didMounts的时候触发。
另外,执行这段代码,你会看到控制台警告,Promises and useEffect(async () => ...) are not supported, but you can call an async function inside an effect.。所以如果要使用async,需要修改下写法。

  1. import React, { useState, useEffect } from 'react';
  2. import axios from 'axios';
  3. function App() {
  4. const [data, setData] = useState({ products: [{
  5. productId: '123',
  6. productName: 'macbook'
  7. }] });
  8. useEffect(() => {
  9. const fetchData = async()=>{
  10. const result = await axios(
  11. 'https://c.com/api/products?date=today',
  12. );
  13. setData(result.data);
  14. }
  15. fetchData();
  16. },[]);
  17. return (
  18. <ul>
  19. {data.products.map(i => (
  20. <li key={i.productId}>
  21. {i.productName}
  22. </li>
  23. ))}
  24. </ul>
  25. );
  26. }
  27. export default App;
  28. 复制代码

体验优化

一般的应用在某些请求过程的交互设计上,会加上loading来缓解用户焦虑。那在Hook的写法中,如何实现呢?下面将会介绍。

  1. import React, { useState, useEffect } from 'react';
  2. import axios from 'axios';
  3. function App() {
  4. const [data, setData] = useState({ products: [{
  5. productId: '123',
  6. productName: 'macbook'
  7. }] });
  8. const [isLoading, setIsLoading] = useState(false);
  9. useEffect(() => {
  10. const fetchData = async()=>{
  11. setIsLoading(true);
  12. const result = await axios(
  13. 'https://c.com/api/products?date=today',
  14. );
  15. setData(result.data);
  16. setIsLoading(false);
  17. }
  18. fetchData();
  19. },[]);
  20. return (
  21. {isLoading ? (
  22. <div>Loading ...</div>
  23. ) : (
  24. <ul>
  25. {data.products.map(i => (
  26. <li key={i.productId}>
  27. {i.productName}
  28. </li>
  29. ))}
  30. </ul>
  31. )};
  32. }
  33. export default App;
  34. 复制代码

这里通过加入一个叫isLoading的state来实现。我们在fetch的开始和结束去改变isLoading的值,来控制return返回的组件内容,从而在请求前显示Loading组件,在请求后显示产品列表。

错误处理

请求的过程经常会由于各种原因失败,比如网络、服务器错误等等。所以错误处理必不可少的。

  1. import React, { useState, useEffect } from 'react';
  2. import axios from 'axios';
  3. function App() {
  4. const [data, setData] = useState({ products: [{
  5. productId: '123',
  6. productName: 'macbook'
  7. }] });
  8. const [isLoading, setIsLoading] = useState(false);
  9. const [isError, setIsError] = useState(false);
  10. useEffect(() => {
  11. const fetchData = async()=>{
  12. setIsError(false);
  13. setIsLoading(true);
  14. try{
  15. const result = await axios(
  16. 'https://c.com/api/products?date=today',
  17. );
  18. setData(result.data);
  19. }catch(e){
  20. setIsError(true);
  21. }
  22. setIsLoading(false);
  23. }
  24. fetchData();
  25. },[]);
  26. return (
  27. <div>
  28. {isError && <div>出错了...</div>}
  29. {isLoading ? (
  30. <div>Loading ...</div>
  31. ) : (
  32. <ul>
  33. {data.products.map(i => (
  34. <li key={i.productId}>
  35. {i.productName}
  36. </li>
  37. ))}
  38. </ul>
  39. )};
  40. </div>
  41. }
  42. export default App;
  43. 复制代码

当请求出错时,isError会被设置为true,触发渲染时,错误提示组件就会被渲染出来。这里的处理比较简单,在真实场景中,你可以在错误处理时加入更复杂的逻辑。isError会在每次hook运行的时候被重置。

最后

读到这你已经基本学会了如何使用React Hooks获取数据并渲染组件了。
博客首发于github.com/SugarTurboS…, 欢迎关注。