Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Node

  • VSCode插件

  • Webpack

  • Redux

  • React源码

  • 组件库

    • Dialog组件封装
    • 组件导入思考
    • Keepalive 伪代码
    • React 组件:Loading 弹窗
      • 0. 前言
      • 1. 代数分离逻辑
      • 2. 函数方案
      • 3. 进一步优化:
      • 4. 细节
  • React高阶系列

  • UMI插件

  • 前端工程化

  • 单元测试vitest

  • 重点技术
  • 组件库
wangjiasheng
2023-02-21
目录

React 组件:Loading 弹窗

# 0. 前言

实现效果:当发起网络请求后,在数据获取前,展示全屏 Loading 弹窗;数据获取后,展示实际的内容页面。

# 1. 代数分离逻辑

const SamplePreview = () => {
  // 将 Loading 的状态,通过 useState 进行副作用分离操作
  const [condition, setCondition] = useState({});
  async function init() {
    setCondition({ xxx: xxxx });
  }
  if (condition.xxx) {
    init();
    return <Loading fullScreen />;
  }
  return <div>真实的组件显示</div>;
};

ReactDOM.render(<SamplePreview />, document.getElementById("root"));
1
2
3
4
5
6
7
8
9
10
11
12
13
14

参考:https://github.com/alibaba/lowcode-demo/blob/main/demo-general/src/preview.tsx

# 2. 函数方案

上面的方案中,需借助 React 中抽离 state 副作用的特性,对 Loading 组件进行渲染拦截,当 condition 满足条件后,才允许打开弹窗。

此方案的缺点是复用性比较差,需要改变原有组件逻辑,可通过 HOC 实现。如果抽离成函数,则能大大提供复用性。

const open = (target: string | HTMLElement = "body", props?: any) => {
  /* 1. 获取绑定父元素 */
  let divEle: HTMLElement;
  if (typeof target === "string") {
    divEle = document.querySelector(target) as HTMLElement;
  } else {
    divEle = target;
  }
  /* 2. 创建 loading 子元素 */
  const loadingEle = document.createElement("div");
  loadingEle.className = "loading-box";
  /* 3. 两者产生联系 */
  divEle.append(loadingEle);
  /* 使用 ReactDOM.render 函数对组件进行渲染 */
  ReactDOM.render(<Loading {...props} />, loadingEle);

  /* 提供销毁方法 */
  return {
    destroy: () => {
      /* 1.删除 dom 元素 */
      loadingEle.remove();
      if (loadingEle) {
        ReactDOM.unmountComponentAtNode(loadingEle);
      }
    },
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

open 函数内部的逻辑

  1. 找到 loading 挂载的父元素,构建 loading 自身的子元素,通过 append产生联系。
  2. 提供 target 入参,手动选择 loading 挂载的父元素,默认body。并提供 destroy 销毁函数。

# 3. 进一步优化:

以上函数还是有缺点,当同一时间发生多次网络请求,会出现 loading 弹窗提前关闭的问题。因此需要对 loading 进行计数操作。

计数操作:

interface LoadingCountObj {
  count: number;
  instance: {
    destroy: () => void;
  };
}
/* key 值为绑定的父元素, value 为弹窗打开的计数 */
const loadingCounts = new Map<string | HTMLElement, LoadingCountObj>();
1
2
3
4
5
6
7
8

每次执行 openLoading 操作时,第一次打开弹窗时,open 调用后 count = 1,当已打开弹窗时,不会调用 open ,只是 count += 1。

/* 用户可传入 target */
interface LoadingConfig {
  target?: string | HTMLElement;
}

const openLoading = (options: LoadingConfig) => {
  /* 当打开 loading 后,初始化 Map */
  const target = options.target || "body";
  if (!loadingCounts.has(target)) {
    loadingCounts.set(target, {
      count: 0,
      instance: undefined,
    });
  }

  /* 如果 Map 中存在 target,则计数*/
  const countObj = loadingCounts.get(target);

  if (countObj.count <= 0) {
    countObj.count = 1;
    countObj.instance = open(target);
  } else {
    countObj.count += 1;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

关闭弹窗逻辑类似,关闭弹窗时,弹窗计数重置 count = 0

const closeLoading = (options: LoadingConfig) => {
  const target = options.target || "body";

  const countObj = loadingCounts.get(target);
  if (!countObj) {
    return;
  }

  countObj.count = countObj.count - 1;
  if (countObj.count <= 0) {
    countObj.count = 0;
    if (countObj.instance) {
      countObj.instance.destroy();
      countObj.instance = undefined;
    }
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 4. 细节

Loading 弹窗组件有可能存在 zIndex 问题,即,子元素的 zIndex 受到父元素的 zIndex 的影响,因此需通过 React.createProtal 渲染到 .loading-box 节点下。

const Loading = () => {
  return (
    React.createProtal(
      <div>
        <Spin spining={true}></Spin>
      </div>,
    ),
    document.querySelector(".loading-box")
  );
};
1
2
3
4
5
6
7
8
9
10
编辑 (opens new window)
上次更新: 2023/02/21, 10:02:00
Keepalive 伪代码
高阶组件基础

← Keepalive 伪代码 高阶组件基础→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式