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 最佳实践

相关文章
相关标签/搜索