React 最佳實踐:處理多個數據源| 8月更文挑戰

當頁面數據來自於多個請求時,咱們該怎麼去處理呢?這也是一個很常見的場景,咱們須要先考慮這幾點:react

  • 請求之間無依賴關係,能夠併發進行
  • 請求之間存在依賴關係,須要依次進行
  • 請求完成以前,頁面顯示 Loading 狀態

說到請求,不如先來簡單講講 Promise 和 async/await 叭!promise

Promise

Promise 對象用於表示一個異步操做的最終完成(或失敗)及其結果值。markdown

一個 Promise 必然處於如下幾種狀態之一:antd

  • 待定(pending): 初始狀態,既沒有被兌現,也沒有被拒絕。
  • 已兌現(fulfilled): 意味着操做成功完成。
  • 已拒絕(rejected): 意味着操做失敗。

鏈式調用:resolve 函數對應 then;reject 函數對應 catch;併發

async/await

async 和 await 關鍵字讓咱們能夠用一種更簡潔的方式寫出基於 Promise 的異步行爲,而無需刻意地鏈式調用 promise。app

async 關鍵字

async function hello() {
  return 'Hello';
}
// let hello = async function() { return "Hello" };
// let hello = async () => {
// return 'Hello';
// };
hello();
複製代碼

調用該函數會返回一個 promise。這是異步函數的特徵之一 —— 它保證函數的返回值爲 promise。異步

在函數聲明爲 async 時,JavaScript 引擎會添加必要的處理,以優化你的程序。async

await 關鍵字

當 await 關鍵字與異步函數一塊兒使用時,它的真正優點就變得明顯了 —— 事實上, await 只在異步函數裏面才起做用。 它能夠放在任何異步的,基於 promise 的函數以前。它會暫停代碼在該行上,直到 promise 完成,而後返回結果值。在暫停的同時,其餘正在等待執行的代碼就有機會執行了。函數

缺陷

Async/await 讓你的代碼看起來是同步的,在某種程度上,也使得它的行爲更加地同步。 await 關鍵字會阻塞其後的代碼,直到 promise 完成,就像執行同步操做同樣。它確實能夠容許其餘任務在此期間繼續運行,但您本身的代碼被阻塞。post

這意味着您的代碼可能會由於大量 await 的 promises 相繼發生而變慢。每一個 await 都會等待前一個完成,而你實際想要的是全部的這些 promises 同時開始處理(就像咱們沒有使用 async/await 時那樣)。

有一種模式能夠緩解這個問題——經過將 Promise 對象存儲在變量中來同時開始它們,而後等待它們所有執行完畢。讓咱們看一些證實這個概念的例子。

async function timeTest() {
  await timeoutPromise(3000);
  await timeoutPromise(3000);
  await timeoutPromise(3000);
}
複製代碼

總運行時間大約爲 9 秒。

async function timeTest() {
  const timeoutPromise1 = timeoutPromise(3000);
  const timeoutPromise2 = timeoutPromise(3000);
  const timeoutPromise3 = timeoutPromise(3000);

  await timeoutPromise1;
  await timeoutPromise2;
  await timeoutPromise3;
}
複製代碼

在這裏,咱們將三個 Promise 對象存儲在變量中,這樣能夠同時啓動它們關聯的進程。總運行時間僅超過 3 秒!

具體場景

request-1.png

有如下三個請求:

  • 請求用戶數據
  • 請求 province 列表
  • 請求 cities 列表

他們之間的邏輯關係是:

  • 首先初始化用戶信息,包括姓名、省份、城市
  • 切換省份時,更新對應的城市列表

實現過程

模擬請求

首先,咱們用 setTimeout 模擬上述三個請求:

獲取用戶信息(fetchUserInfo)

const fetchUserInfo = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        userName: 'Nate',
        province: 'shanghai',
        city: 'pudong',
      });
    }, 2000);
  });
};
複製代碼

獲取省份列表(fetchProvices)

const fetchProvices = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        { name: '北京', key: 'beijing' },
        { name: '上海', key: 'shanghai' },
        { name: '江蘇', key: 'jiangsu' },
        { name: '山東', key: 'shandong' },
      ]);
    }, 2000);
  });
};
複製代碼

獲取城市列表(fetchCities)

const fetchCities = (province) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(
        {
          beijing: [
            { name: '朝陽', key: 'chaoyang' },
            { name: '海淀', key: 'haidian' },
          ],
          shanghai: [
            { name: '浦東', key: 'pudong' },
            { name: '徐匯', key: 'xuhui' },
          ],
          jiangsu: [
            { name: '南京', key: 'nanjing' },
            { name: '蘇州', key: 'suzhou' },
          ],
          shandong: [
            { name: '青島', key: 'qingdao' },
            { name: '德州', key: 'dezhou' },
          ],
        }[province],
      );
    }, 2000);
  });
};
複製代碼

UI 設計

request-2.png

基於 Ant Design,主要用到了 Form, Select, Input 組件。 列表項默認爲 loading 狀態。

import { Form, Select, Input } from 'antd';

const { Option } = Select;

export default function MultipleRequestAsync(props) {
  return (
    <Form labelCol={{ span: 8 }} wrapperCol={{ span: 8 }} initialValues={{ province: 'loading...', city: 'loading...' }} > <Form.Item label="姓名:" name="userName"> <Input placeholder="user name" /> </Form.Item> <Form.Item label="Province:" name="province"> <Select style={{ width: 120 }}> <Option value="loading">loading...</Option> </Select> </Form.Item> <Form.Item label="City:" name="city"> <Select style={{ width: 120 }}> <Option value="loading">loading...</Option> </Select> </Form.Item> </Form>
  );
}
複製代碼

數據處理

初始化數據時,三個請求之間存在依賴關係:

  • 獲取數據

    const [userInfo, setUserInfo] = useState({});
    const [provinces, setProvinces] = useState([]);
    const [cities, setCities] = useState([]);
    
    useEffect(() => {
      async function getData() {
        const info = await fetchUserInfo();
        setUserInfo(info);
        const provinces = await fetchProvices();
        setProvinces(provinces);
        const cities = await fetchCities(info.province);
        setCities(cities);
        form.setFieldsValue(info);
      }
      getData();
    }, []);
    複製代碼
  • 渲染數據

    • 省份列表

      <Select style={{ width: 120 }} onChange={handleProvinceChange}>
        {provinces.length ? (
          provinces.map((provinces) => (
            <Option key={provinces.key} value={provinces.key}> {provinces.name} </Option>
          ))
        ) : (
          <Option value="loading">loading...</Option>
        )}
      </Select>
      複製代碼
    • 城市列表

      <Select style={{ width: 120 }}>
        {cities.length ? (
          cities.map((city) => (
            <Option key={city.key} value={city.key}> {city.name} </Option>
          ))
        ) : (
          <Option value="loading">loading...</Option>
        )}
      </Select>
      複製代碼
  • 切換省份時,更新對應的城市列表

    const [form] = Form.useForm();
    
    const handleProvinceChange = async (newProvince) => {
      // 顯示中間loading狀態
      form.setFieldsValue({ city: 'loading' });
      // 拉取城市列表
      const cities = await fetchCities(newProvince);
      setCities(cities);
      // 設置默認選擇值
      form.setFieldsValue({ city: cities[0].key });
    };
    複製代碼

完整代碼

import { useState, useEffect } from 'react';
import { Form, Select, Input, Button } from 'antd';

const { Option } = Select;

const fetchUserInfo = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        userName: 'Nate',
        province: 'shanghai',
        city: 'pudong',
      });
    }, 2000);
  });
};

const fetchProvices = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([
        { name: '北京', key: 'beijing' },
        { name: '上海', key: 'shanghai' },
        { name: '江蘇', key: 'jiangsu' },
        { name: '山東', key: 'shandong' },
      ]);
    }, 2000);
  });
};

const fetchCities = (province) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(
        {
          beijing: [
            { name: '朝陽', key: 'chaoyang' },
            { name: '海淀', key: 'haidian' },
          ],
          shanghai: [
            { name: '浦東', key: 'pudong' },
            { name: '徐匯', key: 'xuhui' },
          ],
          jiangsu: [
            { name: '南京', key: 'nanjing' },
            { name: '蘇州', key: 'suzhou' },
          ],
          shandong: [
            { name: '青島', key: 'qingdao' },
            { name: '德州', key: 'dezhou' },
          ],
        }[province],
      );
    }, 2000);
  });
};

export default function MultipleRequestAsync(props) {
  const [userInfo, setUserInfo] = useState({});
  const [provinces, setProvinces] = useState([]);
  const [cities, setCities] = useState([]);
  const [form] = Form.useForm();

  useEffect(() => {
    async function getData() {
      const info = await fetchUserInfo();
      setUserInfo(info);
      const provinces = await fetchProvices();
      setProvinces(provinces);
      const cities = await fetchCities(info.province);
      setCities(cities);
      form.setFieldsValue(info);
    }
    getData();
  }, []);

  const handleProvinceChange = async (newProvince) => {
    form.setFieldsValue({ city: 'loading' });
    const cities = await fetchCities(newProvince);
    setCities(cities);
    form.setFieldsValue({ city: cities[0].key });
  };

  return (
    <Form form={form} labelCol={{ span: 8 }} wrapperCol={{ span: 8 }} initialValues={{ province: 'loading...', city: 'loading...' }} > <Form.Item label="姓名:" name="userName"> <Input placeholder="user name" /> </Form.Item> <Form.Item label="Province:" name="province"> <Select style={{ width: 120 }} onChange={handleProvinceChange}> {provinces.length ? ( provinces.map((provinces) => ( <Option key={provinces.key} value={provinces.key}> {provinces.name} </Option> )) ) : ( <Option value="loading">loading...</Option> )} </Select> </Form.Item> <Form.Item label="City:" name="city"> <Select style={{ width: 120 }}> {cities.length ? ( cities.map((city) => ( <Option key={city.key} value={city.key}> {city.name} </Option> )) ) : ( <Option value="loading">loading...</Option> )} </Select> </Form.Item> </Form>
  );
}
複製代碼

React 最佳實踐

相關文章
相關標籤/搜索