diff --git a/404.html b/404.html index 1773759..f991b3e 100644 --- a/404.html +++ b/404.html @@ -12,8 +12,8 @@
Skip to content

404

PAGE NOT FOUND

But if you don't change your direction, and if you keep looking, you may end up where you are heading.
- + \ No newline at end of file diff --git "a/Algo/01.\346\261\207\346\200\273.html" "b/Algo/01.\346\261\207\346\200\273.html" index 397cbbd..ea6ddad 100644 --- "a/Algo/01.\346\261\207\346\200\273.html" +++ "b/Algo/01.\346\261\207\346\200\273.html" @@ -3,18 +3,18 @@ - 题型汇总 🔥 | @luminous/docs + 常见算法题型 🔥 | @luminous/docs - + -
Skip to content
On this page

题型汇总 🔥

  1. 算法的复杂度分析。
  2. 排序算法,以及他们的区别和优化。
  3. 数组中的双指针、滑动窗口思想。
  4. 利用 Map 和 Set 处理查找表问题。
  5. 链表的各种问题。
  6. 利用递归和迭代法解决二叉树问题。
  7. 栈、队列、DFS、BFS。
  8. 回溯法、贪心算法、动态规划。

滑动窗口问题

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

利用set()自动去重的特点

js
/**
+    
Skip to content
On this page

常见算法题型 🔥

  1. 算法的复杂度分析。
  2. 排序算法,以及他们的区别和优化。
  3. 数组中的双指针、滑动窗口思想。
  4. 利用 Map 和 Set 处理查找表问题。
  5. 链表的各种问题。
  6. 利用递归和迭代法解决二叉树问题。
  7. 栈、队列、DFS、BFS。
  8. 回溯法、贪心算法、动态规划。

滑动窗口问题

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

利用set()自动去重的特点

js
/**
  * @param {string} s
  * @return {number}
  */
@@ -555,9 +555,9 @@
 }
 module.exports = {
   getSolution: getSolution
-};

上次更新于:

- +};

上次更新于:

+ \ No newline at end of file diff --git "a/Algo/02.\345\217\214\346\214\207\351\222\210.html" "b/Algo/02.\345\217\214\346\214\207\351\222\210.html" index 5168c1d..e192e5e 100644 --- "a/Algo/02.\345\217\214\346\214\207\351\222\210.html" +++ "b/Algo/02.\345\217\214\346\214\207\351\222\210.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

双指针问题

16. 最接近的三数之和

先按照升序排序,然后分别从左往右依次选择一个基础点 i0 <= i <= nums.length - 3),在基础点的右侧用双指针去不断的找最小的差值。

假设基础点是 i,初始化的时候,双指针分别是:

  • lefti + 1,基础点右边一位。
  • right: nums.length - 1 数组最后一位。

然后求此时的和,如果和大于 target,那么可以把右指针左移一位,去试试更小一点的值,反之则把左指针右移。

在这个过程中,不断更新全局的最小差值 min,和此时记录下来的和 res

最后返回 res 即可。

js
/**
+    
Skip to content
On this page

双指针问题

16. 最接近的三数之和

先按照升序排序,然后分别从左往右依次选择一个基础点 i0 <= i <= nums.length - 3),在基础点的右侧用双指针去不断的找最小的差值。

假设基础点是 i,初始化的时候,双指针分别是:

  • lefti + 1,基础点右边一位。
  • right: nums.length - 1 数组最后一位。

然后求此时的和,如果和大于 target,那么可以把右指针左移一位,去试试更小一点的值,反之则把左指针右移。

在这个过程中,不断更新全局的最小差值 min,和此时记录下来的和 res

最后返回 res 即可。

js
/**
  * @param {number[]} nums
  * @param {number} target
  * @return {number}
@@ -64,9 +64,9 @@
         }
     }
     return area
-};

上次更新于:

- +};

上次更新于:

+ \ No newline at end of file diff --git "a/Algo/\345\210\267\351\242\230\347\254\224\350\256\2601.html" "b/Algo/\345\210\267\351\242\230\347\254\224\350\256\2601.html" index cf1782f..7c4e2cd 100644 --- "a/Algo/\345\210\267\351\242\230\347\254\224\350\256\2601.html" +++ "b/Algo/\345\210\267\351\242\230\347\254\224\350\256\2601.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

刷题笔记1

1 两数之和

js
/**
+    
Skip to content
On this page

刷题笔记1

1 两数之和

js
/**
  * @param {number[]} nums
  * @param {number} target
  * @return {number[]}
@@ -547,9 +547,9 @@
         }
     }
     return dummy.next
-};

上次更新于:

- +};

上次更新于:

+ \ No newline at end of file diff --git "a/Algo/\345\210\267\351\242\230\347\254\224\350\256\2602.html" "b/Algo/\345\210\267\351\242\230\347\254\224\350\256\2602.html" index 39cf2a4..c2ade4b 100644 --- "a/Algo/\345\210\267\351\242\230\347\254\224\350\256\2602.html" +++ "b/Algo/\345\210\267\351\242\230\347\254\224\350\256\2602.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

刷题笔记2

90 子集 II

本题目的关键在于对子集中的元素进行去重, 关键点是要在for循环内部去重

js
/**
+    
Skip to content
On this page

刷题笔记2

90 子集 II

本题目的关键在于对子集中的元素进行去重, 关键点是要在for循环内部去重

js
/**
  * @param {number[]} nums
  * @return {number[][]}
  */
@@ -346,9 +346,9 @@
          product *= nums[i]
      }
      return result
-};

上次更新于:

- +};

上次更新于:

+ \ No newline at end of file diff --git "a/Algo/\345\210\267\351\242\230\347\254\224\350\256\2603.html" "b/Algo/\345\210\267\351\242\230\347\254\224\350\256\2603.html" index 90dc315..dd8bd3a 100644 --- "a/Algo/\345\210\267\351\242\230\347\254\224\350\256\2603.html" +++ "b/Algo/\345\210\267\351\242\230\347\254\224\350\256\2603.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

刷题笔记3

242 有效的字母异位词

使用Map统计每个character出现的次数即可

js
/**
+    
Skip to content
On this page

刷题笔记3

242 有效的字母异位词

使用Map统计每个character出现的次数即可

js
/**
  * @param {string} s
  * @param {string} t
  * @return {boolean}
@@ -224,9 +224,9 @@
         }
     }
     return nums
-};

上次更新于:

- +};

上次更新于:

+ \ No newline at end of file diff --git "a/Algo/\345\210\267\351\242\230\347\254\224\350\256\2604.html" "b/Algo/\345\210\267\351\242\230\347\254\224\350\256\2604.html" index a692440..5712d79 100644 --- "a/Algo/\345\210\267\351\242\230\347\254\224\350\256\2604.html" +++ "b/Algo/\345\210\267\351\242\230\347\254\224\350\256\2604.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

刷题笔记4

多多的数字组合

多多君最近在研究某种数字组合: 定义为:每个数字的十进制表示中(0~9),每个数位各不相同且各个数位之和等于N。 满足条件的数字可能很多,找到其中的最小值即可。

多多君还有很多研究课题,于是多多君找到了你--未来的计算机科学家寻求帮助。

共一行,一个正整数N,如题意所示,表示组合中数字不同数位之和。
+    
Skip to content
On this page

刷题笔记4

多多的数字组合

多多君最近在研究某种数字组合: 定义为:每个数字的十进制表示中(0~9),每个数位各不相同且各个数位之和等于N。 满足条件的数字可能很多,找到其中的最小值即可。

多多君还有很多研究课题,于是多多君找到了你--未来的计算机科学家寻求帮助。

共一行,一个正整数N,如题意所示,表示组合中数字不同数位之和。
 (1 <= N <= 1,000)
共一行,一个整数,表示该组合中的最小值。
 如果组合中没有任何符合条件的数字,那么输出-1即可。

输入例子:

5

输出例子:

5

例子说明:

符合条件的数字有:5,14,23,32,41
 其中最小值为5
js
const rl = require("readline").createInterface({ input: process.stdin });
@@ -59,9 +59,9 @@
         let a = parseInt(tokens[0]);
         console.log(minOfCombination(a));
     }
-})();

上次更新于:

- +})();

上次更新于:

+ \ No newline at end of file diff --git "a/CSS/CSS\345\205\245\351\227\250.html" "b/CSS/CSS\345\205\245\351\227\250.html" index 0448634..b0a2cb6 100644 --- "a/CSS/CSS\345\205\245\351\227\250.html" +++ "b/CSS/CSS\345\205\245\351\227\250.html" @@ -45,8 +45,8 @@ </body> </html> - + \ No newline at end of file diff --git "a/DesignPattern/\345\217\221\345\270\203\350\256\242\351\230\205\346\250\241\345\274\217.html" "b/DesignPattern/\345\217\221\345\270\203\350\256\242\351\230\205\346\250\241\345\274\217.html" index 3afd954..3183bd8 100644 --- "a/DesignPattern/\345\217\221\345\270\203\350\256\242\351\230\205\346\250\241\345\274\217.html" +++ "b/DesignPattern/\345\217\221\345\270\203\350\256\242\351\230\205\346\250\241\345\274\217.html" @@ -54,8 +54,8 @@ // 发布test主题,并传入数据hello world pubsub.publish("test", "hello world"); - + \ No newline at end of file diff --git a/JS/JS01.html b/JS/JS01.html index 367debe..18e7022 100644 --- a/JS/JS01.html +++ b/JS/JS01.html @@ -151,8 +151,8 @@ //null是给了,但是空——不触发默认值 console.log(b); //null - + \ No newline at end of file diff --git a/JS/JS02.html b/JS/JS02.html index cfa65a8..962dec3 100644 --- a/JS/JS02.html +++ b/JS/JS02.html @@ -231,8 +231,8 @@ } }); } - + \ No newline at end of file diff --git a/JS/JS03.html b/JS/JS03.html index 580ea96..d26711b 100644 --- a/JS/JS03.html +++ b/JS/JS03.html @@ -23,8 +23,8 @@ };

如果我们想用person对象作为this来调用greet函数,我们可以用call或apply方法:

javascript
greet.call(person, "Hello"); // Hello Alice
 greet.apply(person, ["Hi"]); // Hi Alice

注意call和apply的第一个参数都是person,表示this的值,而后面的参数是传给greet函数的。区别在于call接受多个参数,而apply接受一个数组。

如果我们想创建一个新的函数,让它总是用person对象作为this来调用greet函数,我们可以用bind方法:

javascript
var boundGreet = greet.bind(person); // 返回一个新的函数
 boundGreet("Hey"); // Hey Alice

注意bind不会立即执行greet函数,而是返回一个新的函数,这个函数绑定了person作为this。这样我们就可以在以后任何时候调用这个新的函数。

- + \ No newline at end of file diff --git a/JS/JS04.html b/JS/JS04.html index 8061702..b5f3e36 100644 --- a/JS/JS04.html +++ b/JS/JS04.html @@ -37,8 +37,8 @@ }); console.log(proxy.name); // Hello Alice console.log(proxy.age); // Hello 20

元编程在js里的应用场景也有很多,主要可以利用Proxy和Reflect对象来拦截和定义基本语言操作的自定义行为,例如属性查找、赋值、枚举、函数调用等¹。例如,你可以使用Proxy对象来实现一个虚拟DOM,它可以在访问真实DOM时进行一些优化和处理²。你也可以使用Reflect对象来实现一个动态代理,它可以在调用目标对象的方法时进行一些拦截和增强³。

虚拟DOM

虚拟DOM的例子有很多,比如React、Vue等前端框架都使用了虚拟DOM的技术。虚拟DOM是一个JS对象,它可以描述一个DOM节点的标签、属性和子节点。虚拟DOM可以通过render函数转化为真实的DOM,并插入到页面中。虚拟DOM还可以给任何其他实体建立映射关系,比如iOS应用、安卓应用、小程序等

虚拟DOM和真实DOM的区别主要有以下几点:

- + \ No newline at end of file diff --git "a/JS/JS\346\211\213\345\206\231.html" "b/JS/JS\346\211\213\345\206\231.html" index 36fe232..f3f1c45 100644 --- "a/JS/JS\346\211\213\345\206\231.html" +++ "b/JS/JS\346\211\213\345\206\231.html" @@ -136,8 +136,8 @@ console.log(window.scrollY); } window.addEventListener("scroll", throttle(handleScroll, 2000)); - + \ No newline at end of file diff --git "a/Network/01.\350\256\241\347\275\221\350\265\267\346\255\245.html" "b/Network/01.\350\256\241\347\275\221\350\265\267\346\255\245.html" index 8e2bb90..ec1c517 100644 --- "a/Network/01.\350\256\241\347\275\221\350\265\267\346\255\245.html" +++ "b/Network/01.\350\256\241\347\275\221\350\265\267\346\255\245.html" @@ -15,8 +15,8 @@
Skip to content
On this page

入门篇

基于TCP/IP四层体系结构, 通过自顶向下的思路, 先后为大家介绍应用层->传输层->网络层->网络接口层的具体内容

素材来源

模型划分

image-20230315121954214

针对OSI七层模型,

  • 就对数据名称来说,不同层分别有报文,段,包,帧,比特
  • 就地址来说,有端口号,IP逻辑地址和MAC物理地址
  • 就传输功能,有服务进程到服务进程,端到端,跳到跳

image-20230315133643185

整体流程:

image-20230315134003561

学习路线:

  • 了解计算机网络的基本概念和体系结构,如OSI七层模型、TCP/IP四层模型、网络设备和协议等。
  • 学习计算机网络的核心协议和技术,如TCP/UDP、IP、ARP、ICMP、DNS、DHCP、HTTP等,并掌握其工作原理和特点。
  • 学习计算机网络的常用工具和方法,如ping、traceroute、telnet、ssh、ftp等,并熟悉如何使用它们进行网络测试和调试。
  • 学习计算机网络的高级主题和应用,如路由算法、拥塞控制、安全加密、无线网络等,并了解它们在实际场景中的作用和挑战。
  • 多做一些计算机网络相关的练习题和模拟面试题,并总结自己的知识点和疑问。

应用层(HTTP/DNS/FTP/SMTP)

负责处理应用程序的沟通问题,是最接近用户的一层

但应用层只不过是逻辑上把两个应用连通,实际物理上的联通是需要物理层的

传输层(TCP/UDP)

服务于应用层, 将其作为应用之间数据传输的媒介,帮助实现应用到应用的通信

名为传输层, 实际的传输功能交给下一层 - 网络层

传输层(运输层) 在网络层的端到端基础上,实现了服务进程到服务进程的传输

传输层管理两个节点之间数据的传输,负责可靠传输和不可靠传输,主要协议为TCP,UDP以及QUIC(HTTP/3.0)中配置UDP使用

TCP协议允许应用把字节流变成多分段,而不是发送整个字节流

流量控制保证传输速度错误控制进行数据的完整接收

image-20230315123216703

网络层(IP/ARP)

添加IP头部

IP地址

网络层负责将数据从一个设备传输到另一个设备,世界上那么多设备,又该如何找到对方呢?

因此,网络层需要有区分设备的编号, 我们一般用 IP 地址给设备进行编号

IP地址这样的逻辑地址是实现端到端的基础

路由器是网络层的核心,包就是网络层里数据的名字,在封装成帧之前就是包

路由器根据包里的IP地址进行路由转发,地址管理和路由选择就是这一层的核心

IP协议

IP协议有两大作用:

  • 寻址:告诉我们去往下一个目的地该朝哪个方向走「像是导航」
  • 路由:根据“下一个目的的”选择路径「像是操作方向盘」

网络接口层

实现网卡接口的网络驱动程序,以处理数据在物理媒介上的传输

生成了 IP 头部之后,接下来要交给网络接口层Link Layer)在 **IP 头部的前面加上 MAC 头部,并封装成数据帧(Data frame)**发送到网络上。

IP 头部中的接收方 IP 地址表示网络包的目的地,通过这个地址我们就可以判断要将包发到哪里,但在以太网的世界中,这个思路是行不通的。

什么是以太网呢?电脑上的以太网接口,Wi-Fi接口,以太网交换机、路由器上的千兆,万兆以太网口,还有网线,它们都是以太网的组成部分。以太网就是一种在「局域网」内,把附近的设备连接起来,使它们之间可以进行通讯的技术。

以太网在判断网络包目的地时和 IP 的方式不同,因此必须采用相匹配的方式才能在以太网中将包发往目的地,而 MAC 头部就是干这个用的,所以,在以太网进行通讯要用到 MAC 地址。

MAC 头部是以太网使用的头部,它包含了接收方和发送方的 MAC 地址等信息,我们可以通过 ARP 协议获取对方的 MAC 地址。

所以说,网络接口层主要为网络层提供「链路级别」传输的服务,负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标识网络上的设备。

总结

综上所述,TCP/IP 网络通常是由上到下分成 4 层,分别是应用层,传输层,网络层和网络接口层

image-20230306130449248

每一层的封装格式如下啊

image-20230306130547711

深入学习

图书推荐:

  • 图解 HTTP
  • 图解 TCP/IP
    • 这本书也是以大量的图文来介绍了 TCP/IP 网络模式的每一层,但是这个书籍的顺序不是从「应用层 —> 物理层」,而是从「物理层 -> 应用层」顺序开始讲的,这一点我觉得不太好,这样一上来就把最枯燥的部分讲了,很容易就被劝退了,所以我建议先跳过前面几个章节,先看网络层和传输层的章节,然后再回头看前面的这几个章节。
  • 网络是怎样连接的
  • 计算机网络 - 自顶向下方法
  • TCP/IP 详解 卷一:协议

实战演练:

在学习书籍资料的时候,不管是 TCP、UDP、ICMP、DNS、HTTP、HTTPS 等协议,最好都可以亲手尝试抓数据报,接着可以用 Wireshark 工具 (opens new window)看每一个数据报文的信息,这样你会觉得计算机网络没有想象中那么抽象了,因为它们被你「抓」出来了,并毫无保留地显现在你面前了,于是你就可以肆无忌惮地「扒开」它们,看清它们每一个头信息。

键入网址到网页显示发生了什么

键入网址到网页显示,期间发生了很多复杂的过程,大致可以分为以下几个步骤:

  • 域名解析:浏览器需要将输入的网址(域名)转换为对应的IP地址,才能找到目标服务器。这个过程涉及到本地hosts文件、本地DNS缓存、本地DNS服务器和远程DNS服务器等多个环节。
  • TCP连接:浏览器通过域名解析得到IP地址后,需要和服务器建立一个TCP连接,以便进行数据传输。这个过程涉及到三次握手、端口号、套接字等概念。
  • HTTP请求:浏览器通过TCP连接向服务器发送一个HTTP请求报文,包含了请求方法、请求头、请求体等信息。这个过程涉及到HTTP协议、URL格式、Cookie等概念。
  • HTTP响应:服务器收到浏览器的HTTP请求后,根据请求内容进行处理,并返回一个HTTP响应报文,包含了状态码、响应头、响应体等信息。这个过程涉及到HTTP协议、状态码含义、缓存控制等概念。
  • 渲染页面:浏览器收到服务器的HTTP响应后,根据响应体中的HTML代码开始渲染页面,同时可能会发送额外的HTTP请求获取CSS、JS、图片等资源。这个过程涉及到HTML语法、CSS规则、JS脚本、DOM树构建等概念。
  • 断开连接:浏览器渲染完页面后,会根据一定的规则和服务器断开TCP连接,以释放资源。这个过程涉及到四次挥手、长连接和短连接等概念。

在小林Coding的图解网络中, 有更加详细的介绍(2.2 键入网址到网页显示,期间发生了什么? | 小林coding (xiaolincoding.com))

以www.server.com为例

  • 孤单小弟: 解析URL, 产生HTTP请求报文
  • 真实地址查询: 通过本地DNS服务器进行真实地址的查询, 如果解析后的URL存在于本地DNS服务器中则直接获取了真实地址, 若不存在于本地DNS服务器中, 则通过域名的层级关系依次向根DNS服务器->顶级域DNS服务器(.com)->权威DNS服务器(server.com)
  • 指南好帮手: 告诉HTTP数据包传输时需要哪些大佬的帮助
  • 可靠传输TCP: 通过TCP三次握手建立连接, 保证通信双方都有发送和接收的能力, 添加TCP头部
  • 远程定位IP: 添加IP头部
  • 两点传输MAC:添加MAC头部
  • 出口-网卡:网络包只是存放在内存中的一串二进制数字信息,没有办法直接发送给对方。因此,我们需要将数字信息转换为电信号,才能在网线上传输,也就是说,这才是真正的数据发送过程。
  • 通过交换机(基于以太网设计, 二层网络设备)与路由器(基于IP设计, 三层网络设备), 数据包最终抵达目的地
  • 服务器层层解析, 向客户端返回数据包, 经过相同的过程抵达客户端
  • 客户端离开, 服务器TCP四次挥手

上次更新于:

- + \ No newline at end of file diff --git "a/Network/02.HTTP\344\274\230\345\214\226.html" "b/Network/02.HTTP\344\274\230\345\214\226.html" index 2a41aea..151a9e1 100644 --- "a/Network/02.HTTP\344\274\230\345\214\226.html" +++ "b/Network/02.HTTP\344\274\230\345\214\226.html" @@ -15,8 +15,8 @@
Skip to content
On this page

HTTP/1.1如何优化

我们可以从下面这三种优化思路来优化 HTTP/1.1 协议:

  • 尽量避免发送 HTTP 请求
    • 缓存
  • 在需要发送 HTTP 请求时,考虑如何减少请求次数
    • 减少重定向请求次数
    • 合并请求
    • 延迟发送请求
  • 减少服务器的 HTTP 响应的数据大小
    • 无损压缩
    • 有损压缩

避免发送HTTP请求

强制缓存与协商缓存

  • 强制缓存
  • 协商缓存
    • 时间标识符「Last-Modified」
    • Etag 「响应唯一标识符」

image-20230306213651610

减少HTTP请求次数

减少重定向请求次数

将重定向的工作交由代理服务器完成,进而减少 HTTP 请求次数

302是临时重定向

301308 响应码是告诉客户端可以将重定向响应缓存到本地磁盘,之后客户端就自动用 url2 替代 url1 访问服务器的资源。

合并请求

如果把多个访问小文件的请求合并成一个大的请求,虽然传输的总资源还是一样,但是减少请求,也就意味着减少了重复发送的 HTTP 头部

另外由于 HTTP/1.1 是请求响应模型,如果第一个发送的请求,未收到对应的响应,那么后续的请求就不会发送(PS:HTTP/1.1 管道模式是默认不使用的,所以讨论 HTTP/1.1 的队头阻塞问题,是不考虑管道模式的),于是为了防止单个请求的阻塞,所以一般浏览器会同时发起 5-6 个请求,每一个请求都是不同的 TCP 连接,那么如果合并了请求,也就会减少 TCP 连接的数量,因而省去了 TCP 握手和慢启动过程耗费的时间

有的网页会含有很多小图片、小图标,有多少个小图片,客户端就要发起多少次请求。那么对于这些小图片,我们可以考虑使用 CSS Image Sprites 技术把它们合成一个大图片,这样浏览器就可以用一次请求获得一个大图片,然后再根据 CSS 数据把大图片切割成多张小图片。

image-20230306215109542

这种方式就是通过将多个小图片合并成一个大图片来减少 HTTP 请求的次数,以减少 HTTP 请求的次数,从而减少网络的开销

除了将小图片合并成大图片的方式,还有服务端使用 webpack 等打包工具将 js、css 等资源合并打包成大文件,也是能达到类似的效果。

另外,还可以将图片的二进制数据用 base64 编码后,以 URL 的形式嵌入到 HTML 文件,跟随 HTML 文件一并发送.

text
<image src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPoAAAFKCAIAAAC7M9WrAAAACXBIWXMAA ... />

这样客户端收到 HTML 后,就可以直接解码出数据,然后直接显示图片,就不用再发起图片相关的请求,这样便减少了请求的次数。

可以看到,合并请求的方式就是合并资源,以一个大资源的请求替换多个小资源的请求

但是这样的合并请求会带来新的问题,当大资源中的某一个小资源发生变化后,客户端必须重新下载整个完整的大资源文件,这显然带来了额外的网络消耗。

延迟发送请求

不要一口气吃成大胖子,一般 HTML 里会含有很多 HTTP 的 URL,当前不需要的资源,我们没必要也获取过来,于是可以通过「按需获取」的方式,来减少第一时间的 HTTP 请求次数。

请求网页的时候,没必要把全部资源都获取到,而是只获取当前用户所看到的页面资源,当用户向下滑动页面的时候,再向服务器获取接下来的资源,这样就达到了延迟发送请求的效果。

如何减少 HTTP 响应的数据大小?

通常 HTTP 的响应的数据大小会比较大,也就是服务器返回的资源会比较大。

于是,我们可以考虑对响应的资源进行压缩,这样就可以减少响应的数据大小,从而提高网络传输的效率。

压缩的方式一般分为 2 种,分别是:

  • 无损压缩
  • 有损压缩

无损压缩

无损压缩是指资源经过压缩后,信息不被破坏,还能完全恢复到压缩前的原样,适合用在文本文件、程序可执行文件、程序源代码。

首先,我们针对代码的语法规则进行压缩,因为通常代码文件都有很多换行符或者空格,这些是为了帮助程序员更好的阅读,但是机器执行时并不要这些符,把这些多余的符号给去除掉。

接下来,就是无损压缩了,需要对原始资源建立统计模型,利用这个统计模型,将常出现的数据用较短的二进制比特序列表示,将不常出现的数据用较长的二进制比特序列表示,生成二进制比特序列一般是「霍夫曼编码」算法。

gzip 就是比较常见的无损压缩。客户端支持的压缩算法,会在 HTTP 请求中通过头部中的 Accept-Encoding 字段告诉服务器:

text
Accept-Encoding: gzip, deflate, br

服务器收到后,会从中选择一个服务器支持的或者合适的压缩算法,然后使用此压缩算法对响应资源进行压缩,最后通过响应头部中的 Content-Encoding 字段告诉客户端该资源使用的压缩算法。

text
Content-Encoding: gzip

gzip 的压缩效率相比 Google 推出的 Brotli 算法还是差点意思,也就是上文中的 br,所以如果可以,服务器应该选择压缩效率更高的 br 压缩算法。

有损压缩

与无损压缩相对的就是有损压缩,经过此方法压缩,解压的数据会与原始数据不同但是非常接近。

有损压缩主要将次要的数据舍弃,牺牲一些质量来减少数据量、提高压缩比,这种方法经常用于压缩多媒体数据,比如音频、视频、图片。

可以通过 HTTP 请求头部中的 Accept 字段里的「 q 质量因子」,告诉服务器期望的资源质量。

text
Accept: audio/*; q=0.2, audio/basic

关于图片的压缩,目前压缩比较高的是 Google 推出的 WebP 格式

总结

这次主要从 3 个方面介绍了优化 HTTP/1.1 协议的思路。

第一个思路是,通过缓存技术来避免发送 HTTP 请求。客户端收到第一个请求的响应后,可以将其缓存在本地磁盘,下次请求的时候,如果缓存没过期,就直接读取本地缓存的响应数据。如果缓存过期,客户端发送请求的时候带上响应数据的摘要,服务器比对后发现资源没有变化,就发出不带包体的 304 响应,告诉客户端缓存的响应仍然有效。

第二个思路是,减少 HTTP 请求的次数,有以下的方法:

  1. 将原本由客户端处理的重定向请求,交给代理服务器处理,这样可以减少重定向请求的次数;
  2. 将多个小资源合并成一个大资源再传输,能够减少 HTTP 请求次数以及 头部的重复传输,再来减少 TCP 连接数量,进而省去 TCP 握手和慢启动的网络消耗;
  3. 按需访问资源,只访问当前用户看得到/用得到的资源,当客户往下滑动,再访问接下来的资源,以此达到延迟请求,也就减少了同一时间的 HTTP 请求次数。

第三思路是,通过压缩响应资源,降低传输资源的大小,从而提高传输效率,所以应当选择更优秀的压缩算法。

HTTPS 如何优化?

  • 性能损耗

  • 硬件优化

  • 软件优化

  • 协议优化

  • 证书优化

  • 会话复用

    • Session ID

    • Session Ticket - 类似HTTP的Cookie

      • 为了解决 Session ID 的问题,就出现了 Session Ticket,服务器不再缓存每个客户端的会话密钥,而是把缓存的工作交给了客户端,类似于 HTTP 的 Cookie。

        客户端与服务器首次建立连接时,服务器会加密「会话密钥」作为 Ticket 发给客户端,交给客户端缓存该 Ticket。

        客户端再次连接服务器时,客户端会发送 Ticket,服务器解密后就可以获取上一次的会话密钥,然后验证有效期,如果没问题,就可以恢复会话了,开始加密通信。

      • 重放攻击

        • image-20230307105648956

        • 假设 Alice 想向 Bob 证明自己的身份。 Bob 要求 Alice 的密码作为身份证明,爱丽丝应尽全力提供(可能是在经过如哈希函数的转换之后)。与此同时,Eve 窃听了对话并保留了密码(或哈希)。

          交换结束后,Eve(冒充 Alice )连接到 Bob。当被要求提供身份证明时,Eve 发送从 Bob 接受的最后一个会话中读取的 Alice 的密码(或哈希),从而授予 Eve 访问权限。

          重放攻击的危险之处在于,如果中间人截获了某个客户端的 Session ID 或 Session Ticket 以及 POST 报文,而一般 POST 请求会改变数据库的数据,中间人就可以利用此截获的报文,不断向服务器发送该报文,这样就会导致数据库的数据被中间人改变了,而客户是不知情的。

          避免重放攻击的方式就是需要对会话密钥设定一个合理的过期时间

总结

对于硬件优化的方向,因为 HTTPS 是属于计算密集型,应该选择计算力更强的 CPU,而且最好选择支持 AES-NI 特性的 CPU,这个特性可以在硬件级别优化 AES 对称加密算法,加快应用数据的加解密。

对于软件优化的方向,如果可以,把软件升级成较新的版本,比如将 Linux 内核 2.X 升级成 4.X,将 openssl 1.0.1 升级到 1.1.1,因为新版本的软件不仅会提供新的特性,而且还会修复老版本的问题。

对于协议优化的方向:

  • 密钥交换算法应该选择 ECDHE 算法,而不用 RSA 算法,因为 ECDHE 算法具备前向安全性,而且客户端可以在第三次握手之后,就发送加密应用数据,节省了 1 RTT。
  • 将 TLS1.2 升级 TLS1.3,因为 TLS1.3 的握手过程只需要 1 RTT,而且安全性更强。

对于证书优化的方向:

  • 服务器应该选用 ECDSA 证书,而非 RSA 证书,因为在相同安全级别下,ECC 的密钥长度比 RSA 短很多,这样可以提高证书传输的效率;
  • 服务器应该开启 OCSP Stapling 功能,由服务器预先获得 OCSP 的响应,并把响应结果缓存起来,这样 TLS 握手的时候就不用再访问 CA 服务器,减少了网络通信的开销,提高了证书验证的效率;

对于重连 HTTPS 时,我们可以使用一些技术让客户端和服务端使用上一次 HTTPS 连接使用的会话密钥,直接恢复会话,而不用再重新走完整的 TLS 握手过程。

常见的会话重用技术有 Session ID 和 Session Ticket,用了会话重用技术,当再次重连 HTTPS 时,只需要 1 RTT 就可以恢复会话。对于 TLS1.3 使用 Pre-shared Key 会话重用技术,只需要 0 RTT 就可以恢复会话。

这些会话重用技术虽然好用,但是存在一定的安全风险,它们不仅不具备前向安全,而且有重放攻击的风险,所以应当对会话密钥设定一个合理的过期时间。

上次更新于:

- + \ No newline at end of file diff --git "a/Network/02.HTTP\345\237\272\347\241\200.html" "b/Network/02.HTTP\345\237\272\347\241\200.html" index af00dc3..be23133 100644 --- "a/Network/02.HTTP\345\237\272\347\241\200.html" +++ "b/Network/02.HTTP\345\237\272\347\241\200.html" @@ -15,8 +15,8 @@
Skip to content
On this page

HTTP基础

HTTP基本概念

HTTP是什么?

HTTP是基于TCP协议应用层传输协议, 由请求和响应构成, 是一个标准的客户端服务器模型

注意:客户端与服务器的角色不是固定的,一端充当客户端,也可能在某次请求中充当服务器。这取决与请求的发起端。HTTP协议属于应用层,建立在传输层协议TCP之上。客户端通过与服务器建立TCP连接,之后发送HTTP请求与接收HTTP响应都是通过访问Socket接口来调用TCP协议实现。

HTTP 是一种无状态 (stateless) 协议, HTTP协议本身不会对发送过的请求和相应的通信状态进行持久化处理。这样做的目的是为了保持HTTP协议的简单性,从而能够快速处理大量的事务, 提高效率。

然而,在许多应用场景中,我们需要保持用户登录的状态或记录用户购物车中的商品。由于HTTP是无状态协议,所以必须引入一些技术来记录管理状态,例如Cookie

HTTP是一个在「两点」之间传输超文本数据的「约定和规范」

  • 基于TCP传输层协议
  • 是应用层传输协议
  • 是无状态协议, 通过类似Cookie的技术记录和管理状态
  • 用来在两点之间传输超文本(文字、图片、音频、视频等)数据

常见状态码

  • 1xx: 目前是协议处理的中间状态, 需要后续操作

    • 「协议切换」HTTP协议切换为WebSocket协议
  • 2xx: 成功, 报文已收到并被正确处理

    • 「200 OK」响应头有body数据(对于非HEAD请求)
    • 「202 Accepted」接受请求
    • 「204 NoCotent」响应头没有body数据
    • 「206 Partial Content」是应用于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。
  • 3xx: 重定向

    • 301: 永久重定向
    • 302: 临时重定向
    • 304: 缓存重定向, 告诉客户端可以继续使用缓存资源,用于缓存控制。
  • 4xx: 客户端错误

    • 400 Bad Request」表示客户端请求的报文有错误,但只是个笼统的错误。
    • 403 Forbidden」表示服务器禁止访问资源,并不是客户端的请求出错。
    • 404 Not Found」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。
  • 5xx: 服务端错误

    • 500 Internal Server Error」与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。
    • 501 Not Implemented」表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。
    • 502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
    • 503 Service Unavailable」表示服务器当前很忙,暂时无法响应客户端,类似“网络服务正忙,请稍后重试”的意思。

HTTP常见字段

  • Host: 指定服务器域名
  • Content- Length: 表示本次回应的数据长度
    • 大家应该都知道 HTTP 是基于 TCP 传输协议进行通信的,而使用了 TCP 传输协议,就会存在一个“粘包”的问题,HTTP 协议通过设置回车符、换行符作为 HTTP header 的边界,通过 Content-Length 字段作为 HTTP body 的边界,这两个方式都是为了解决“粘包”的问题。具体什么是 TCP 粘包,可以看这篇文章:如何理解是 TCP 面向字节流协议?
  • Connection字段:
    • 「Connection: Keep-Alive」客户端要求服务器使用「HTTP长链接」机制, 以便于其他请求复用
  • Content-Type字段
    • 用于服务器回应时,告诉客户端本次数据的格式
    • 和请求头的「Accept」相对应
  • Content- Encoding字段
    • 说明数据的压缩方法
    • 与客户端在请求时的 「Accept-Encoding」 字段对应

HTTP方法

方法描述
GET请求
HEAD请求文档的首部
POST向服务器发送数据
PUT在指明的URL下存储一个文档
DELETE删除URL标志的文档
CONNECT用于代理服务器
OPTIONS请求一些选项信息
TRACE用来进行环回测试
PATCH对PUT方法的补充,用来对已知资源进行局部更新

GET和POST是否安全和幂等

先说明下安全和幂等的概念:

  • 在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。
  • 所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。

如果从 RFC 规范定义的语义来看:

  • GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。所以,可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),而且在浏览器中 GET 请求可以保存为书签
  • POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。所以,浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签

小结:

GET 的语义是请求获取指定的资源。GET 方法是安全、幂等、可被缓存的。

POST 的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。POST 不安全,不幂等,(大部分实现)不可缓存。

但是实际过程中,开发者不一定会按照 RFC 规范定义的语义来实现 GET 和 POST 方法。比如:

  • 可以用 GET 方法实现新增或删除数据的请求,这样实现的 GET 方法自然就不是安全和幂等。

  • 可以用 POST 方法实现查询数据的请求,这样实现的 POST 方法自然就是安全和幂等。

再引申一下「安全」的概念:

如果「安全」放入概念是指信息是否会被泄漏的话,虽然 POST 用 body 传输数据,而 GET 用 URL 传输,这样数据会在浏览器地址拦容易看到,但是并不能说 GET 不如 POST 安全的。

因为 HTTP 传输的内容都是明文的,虽然在浏览器地址拦看不到 POST 提交的 body 数据,但是只要抓个包就都能看到了。

所以,要避免传输过程中数据被窃取,就要使用 HTTPS 协议,这样所有 HTTP 的数据都会被加密传输。

HTTP缓存技术

实现方式

对于一些重复性的HTTP请求, 可以把这对的数据都缓存在本地, 下次直接读取本地数据即可, 从而避免了发送HTTP请求, 提升了性能

有两种实现方式:

  • 强制缓存
  • 协商缓存

强制缓存

强缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边

强缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:

  • Cache-Control, 是一个相对时间;(优先级高于Expires)
  • Expires,是一个绝对时间;

Cache-control 选项更多一些,设置更加精细,所以建议使用 Cache-Control 来实现强缓存。具体的实现流程如下:

  • 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
  • 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
  • 服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。

协商缓存

当我们在浏览器使用开发者工具的时候,你可能会看到过某些请求的响应码是 304,这个是告诉浏览器可以使用本地缓存的资源,通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存

协商缓存需要在强制缓存的基础上使用

image-20230306182216376

上图就是一个协商缓存的过程,所以协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存

协商缓存可以基于两种头部来实现。

第一种:请求头部中的 If-Modified-Since 字段与响应头部中的 Last-Modified 字段实现,这两个字段的意思是:

  • 响应头部中的 Last-Modified:标示这个响应资源的最后修改时间;
  • 请求头部中的 If-Modified-Since:当资源过期了,发现响应头中具有 Last-Modified 声明,则再次发起请求的时候带上 Last-Modified 的时间,服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存。

第二种:请求头部中的 If-None-Match 字段与响应头部中的 ETag 字段,这两个字段的意思是:

  • 响应头部中 Etag:唯一标识响应资源;
  • 请求头部中的 If-None-Match:当资源过期时,浏览器发现响应头里有 Etag,则再次向服务器发起请求时,会将请求头 If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。

第一种实现方式是基于时间实现的,第二种实现方式是基于一个唯一标识实现的

相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。

**为什么 ETag 的优先级更高?**这是因为 ETag 主要能解决 Last-Modified 几个比较难以解决的问题:

  1. 在没有修改文件内容情况下文件的最后修改时间可能也会改变,这会导致客户端认为这文件被改动了,从而重新请求;
  2. 可能有些文件是在秒级以内修改的,If-Modified-Since 能检查到的粒度是秒级的,使用 Etag就能够保证这种需求下客户端在 1 秒内能刷新多次;
  3. 有些服务器不能精确获取文件的最后修改时间。

注意,协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求

当使用 ETag 字段实现的协商缓存的过程:

  • 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 ETag 唯一标识,这个唯一标识的值是根据当前请求的资源生成的;

  • 当浏览器再次请求访问服务器中的该资源时,首先会先检查强制缓存是否过期:

    • 如果没有过期,则直接使用本地缓存;
    • 如果缓存过期了,会在 Request 头部加上 If-None-Match 字段,该字段的值就是 ETag 唯一标识;
  • 服务器再次收到请求后,

    会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较

    • 如果值相等,则返回 304 Not Modified,不会返回资源
    • 如果不相等,则返回 200 状态码和返回资源,并在 Response 头部加上新的 ETag 唯一标识;
  • 如果浏览器收到 304 的请求响应状态码,则会从本地缓存中加载资源,否则更新资源。

HTTP特性

HTTP/1.1 优点

  • 简单
  • 易于拓展
    • HTTP是工作在应用层(OSI第七层)的协议, 它的下层可以随意变化, 例如
      • HTTPS就是在HTTP与TCP层之间添加了SSL/TLS安全传输层
      • HTTP/1.1 和 HTTP/2.0 传输协议使用的是 TCP 协议,而到了 HTTP/3.0 传输协议改用了 UDP 协议
  • 应用广泛, 天然的跨平台优越性

HTTP/1.1 缺点

HTTP协议有两把「双刃剑」:「无状态」「明文传输」「不安全」

  • 「无状态」
    • 不需要额外资源来记录状态, 减轻服务器负担
    • 很难完成具有关联性的操作
    • 解决方案: Cookie技术, 即客户端第一次请求后, 服务器额外返回一个带有客户端信息的「小贴纸」,客户端第二次请求时就会带上这个「小贴纸」,服务器就认得了
  • 明文传输
    • 抓包后的数据便于阅读, 没有隐私
  • 不安全
    • 使用明文通信, 内容会被窃听
    • 不验证通信方的身份, 有可能被伪装
    • 无法证明报文的完整性,所以有可能已遭篡改

HTTP 的安全问题,可以用 HTTPS 的方式解决,也就是通过引入 SSL/TLS 安全传输层,使得在安全上达到了极致。

HTTP性能

HTTP 协议是基于 TCP/IP,并且使用了「请求 - 应答」的通信模式,所以性能的关键就在这两点里。

  • 长连接
    • 不需要每发起一个请求,都要新建一次 TCP 连接(三次握手),减轻了服务器端的负载
    • 只要任意一端没有明确提出断开连接,则保持 TCP 连接状态
  • 队头阻塞
    • HTTP基于「请求-应答」的模式, 当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一同被阻塞了,会招致客户端一直请求不到数据,这也就是「队头阻塞」,好比上班的路上塞车。
  • 管道网络传输(非默认开启)
    • 解决了请求的队头阻塞
      • 在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
    • 无法解决响应的队头阻塞:
      • 服务器必须按照接收请求的顺序发送对这些管道化请求的响应。如果服务端在处理 A 请求时耗时比较长,那么后续的请求的处理都会被阻塞住,这称为「队头堵塞」
    • 实际上 HTTP/1.1 管道化技术不是默认开启,而且浏览器基本都没有支持

总之 HTTP/1.1 的性能一般般,后续的 HTTP/2 和 HTTP/3 就是在优化 HTTP 的性能

HTTP 与 HTTPS

区别

  • HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
  • HTTP 连接建立相对简单, TCP 三次握手之后便可进行 HTTP 的报文传输。而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
  • 两者的默认端口不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。
  • HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的。

解决了HTTP的哪些问题

HTTP明文传输,安全上存在如下风险:

  • 窃听风险
  • 篡改风险
  • 冒充风险

HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议,可以很好的解决了上述的风险:

  • 信息加密:交互信息无法被窃取,但你的号会因为「自身忘记」账号而没。
  • 校验机制:无法篡改通信内容,篡改了就不能正常显示,但百度「竞价排名」依然可以搜索垃圾广告。
  • 身份证书:证明淘宝是真的淘宝网,但你的钱还是会因为「剁手」而没。

HTTPS是如何解决风险的呢:

  • 混合加密的方式实现信息的机密性,解决了窃听的风险。
  • 摘要算法的方式来实现完整性,它能够为数据生成独一无二的「指纹」,指纹用于校验数据的完整性,解决了篡改的风险; 数字签名保证消息的来源可靠性(确认消息是由持有私钥的一方发送的)
  • 将服务器公钥放入到数字证书中,解决了冒充的风险。

混合加密

  • HTTPS 采用的是对称加密非对称加密结合的「混合加密」方式
    • 在通信建立前采用非对称加密的方式交换「会话秘钥」,后续就不再使用非对称加密
    • 在通信过程中全部使用对称加密的「会话秘钥」的方式加密明文数据
  • 采用混合加密的原因
    • 对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换。
    • 非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢。

摘要算法 + 数字签名

在计算机里会用摘要算法(哈希函数)来计算出内容的哈希值,也就是内容的「指纹」,这个哈希值是唯一的,且无法通过哈希值推导出内容

通过哈希算法可以确保内容不会被篡改,但是并不能保证「内容 + 哈希值」不会被中间人替换,因为这里缺少对客户端收到的消息是否来源于服务端的证明

image-20230306191130659

此时需要引入数字签名的概念, 在此之前要先理解一下非对称加密算法

计算机里会用非对称加密算法来解决,共有两个密钥:

  • 一个是公钥,这个是可以公开给所有人的;
  • 一个是私钥,这个必须由本人管理,不可泄露。

这两个密钥可以双向加解密的,比如可以用公钥加密内容,然后用私钥解密,也可以用私钥加密内容,公钥解密内容。

流程的不同,意味着目的也不相同:

  • 公钥加密,私钥解密。这个目的是为了保证内容传输的安全,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容;
  • 私钥加密,公钥解密。这个目的是为了保证消息不会被冒充,因为私钥是不可泄露的,如果公钥能正常解密出私钥加密的内容,就能证明这个消息是来源于持有私钥身份的人发送的。

一般我们不会用非对称加密来加密实际的传输内容,因为非对称加密的计算比较耗费性能的。

所以非对称加密的用途主要在于通过「私钥加密,公钥解密」的方式,来确认消息的身份,我们常说的数字签名算法,就是用的是这种方式,不过私钥加密内容不是内容本身,而是对内容的哈希值加密

image-20230306191322311

一个例子可以很好的帮助理解:

你想向老师请假,一般来说是要求由家长写一份请假理由并签名,老师才能允许你请假。

但是你有模仿你爸爸字迹的能力,你用你爸爸的字迹写了一份请假理由然后签上你爸爸的名字,老师一看到这个请假条,查看字迹和签名,就误以为是你爸爸写的,就会允许你请假。

那作为老师,要如何避免这种情况发生呢?现实生活中的,可以通过电话或视频来确认是否是由父母发出的请假,但是计算机里可没有这种操作。

私钥是由服务端保管,然后服务端会向客户端颁发对应的公钥。如果客户端收到的信息,能被公钥解密,就说明该消息是由服务器发送的。

引入了数字签名算法后,你就无法模仿你爸爸的字迹来请假了,你爸爸手上持有着私钥(相当于服务端),你老师持有着公钥(相当于客户端)。

这样只有用你爸爸手上的私钥才对请假条进行「签名」,老师通过公钥看能不能解出这个「签名」,如果能解出并且确认内容的完整性,就能证明是由你爸爸发起的请假条,这样老师才允许你请假,否则老师就不认。

数字证书(CA)

image-20230306191829322

HTTPS一定安全可靠嘛?

这个问题的场景是这样的:客户端通过浏览器向服务端发起 HTTPS 请求时,被「假基站」转发到了一个「中间人服务器」,于是客户端是和「中间人服务器」完成了 TLS 握手,然后这个「中间人服务器」再与真正的服务端完成 TLS 握手

但是要发生这种场景是有前提的,前提是用户点击接受了中间人服务器的证书。

中间人服务器与客户端在 TLS 握手过程中,实际上发送了自己伪造的证书给浏览器,而这个伪造的证书是能被浏览器(客户端)识别出是非法的,于是就会提醒用户该证书存在问题。

image-20230306211035355

如果用户执意点击「继续浏览此网站」,相当于用户接受了中间人伪造的证书,那么后续整个 HTTPS 通信都能被中间人监听了。

另外,如果你的电脑中毒了,被恶意导入了中间人的根证书,那么在验证中间人的证书的时候,由于你操作系统信任了中间人的根证书,那么等同于中间人的证书是合法的,这种情况下,浏览器是不会弹出证书存在问题的风险提醒的。

这其实也不关 HTTPS 的事情,是你电脑中毒了才导致 HTTPS 数据被中间人劫持的。

所以,HTTPS 协议本身到目前为止还是没有任何漏洞的,即使你成功进行中间人攻击,本质上是利用了客户端的漏洞(用户点击继续访问或者被恶意导入伪造的根证书),并不是 HTTPS 不够安全

如何避免中间人攻击? - 通过HTTPS双向认证

一般我们的 HTTPS 是单向认证,客户端只会验证了服务端的身份,但是服务端并不会验证客户端的身份。

如果用了双向认证方式,不仅客户端会验证服务端的身份,而且服务端也会验证客户端的身份。服务端一旦验证到请求自己的客户端为不可信任的,服务端就拒绝继续通信,客户端如果发现服务端为不可信任的,那么也中止通信。

HTTP演变

HTTP/1.1相比 HTTP/1.0 提高了什么性能?

HTTP/1.1 相比 HTTP/1.0 性能上的改进:

  • 使用长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
  • 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。

HTTP/1.1 还是有性能瓶颈:

  • 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩 Body 的部分;
  • 每次互相发送相同的首部造成的浪费较多;
  • 服务器端有可能产生「队头阻塞」
  • 没有请求优先级控制
  • 请求只能从客户端开始, 服务器只能被动响应

HTTP/2的优化

HTTP/2 协议是基于 HTTPS 的,这保证了 HTTP/2 的安全性

image-20230306211524458

那 HTTP/2 相比 HTTP/1.1 性能上的改进:

  • 头部压缩
  • 二进制格式
  • 并发传输
  • 服务器主动推送资源

1. 头部压缩

HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分

这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。

2. 二进制格式

HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)

image-20230306211643270

收到报文后, 无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率

3. 并发传输

我们都知道 HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。

而 HTTP/2 就很牛逼了,引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接。

比如下图,服务端并行交错地发送了两个响应: Stream 1 和 Stream 3,这两个 Stream 都是跑在一个 TCP 连接上,客户端收到后,会根据相同的 Stream ID 有序组装成 HTTP 消息。

image-20230306212023782

4、服务器推送

HTTP/2 还在一定程度上改善了传统的「请求 - 应答」工作模式,服务端不再是被动地响应,可以主动向客户端发送消息。

客户端和服务器双方都可以建立 Stream, Stream ID 也是有区别的,客户端建立的 Stream 必须是奇数号,而服务器建立的 Stream 必须是偶数号。

再比如,客户端通过 HTTP/1.1 请求从服务器那获取到了 HTML 文件,而 HTML 可能还需要依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返,如下图左边部分:

image-20230306212138783

​ 如上图右边部分,在 HTTP/2 中,客户端在访问 HTML 时,服务器可以直接主动推送 CSS 文件,减少了消息传递的次数。

HTTP/2 有什么缺陷?

​ HTTP/2 通过 Stream 的并发能力,解决了 HTTP/1 队头阻塞的问题,看似很完美了,但是 HTTP/2 还是存在“队头阻塞”的问题,只不过问题不是在 HTTP 这一层面,而是在 TCP 这一层。

HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。

​ 例如发送方发送了很多个 packet,每个 packet 都有自己的序号,你可以认为是 TCP 的序列号,其中 packet 3 在网络中丢失了,即使 packet 4-6 被接收方收到后,由于内核中的 TCP 数据不是连续的,于是接收方的应用层就无法从内核中读取到,只有等到 packet 3 重传后,接收方的应用层才可以从内核中读取到数据,这就是 HTTP/2 的队头阻塞问题,是在 TCP 层面发生的。

​ 所以,一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来

HTTP/3的优化

前面我们知道了 HTTP/1.1 和 HTTP/2 都有队头阻塞的问题:

  • HTTP/1.1 中的管道( pipeline)虽然解决了请求的队头阻塞,但是没有解决响应的队头阻塞,因为服务端需要按顺序响应收到的请求,如果服务端处理某个请求消耗的时间比较长,那么只能等响应完这个请求后, 才能处理下一个请求,这属于 HTTP 层队头阻塞。
  • HTTP/2 虽然通过多个请求复用一个 TCP 连接解决了 HTTP 的队头阻塞 ,但是一旦发生丢包,就会阻塞住所有的 HTTP 请求,这属于 TCP 层队头阻塞。

HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!

image-20230306212450385

UDP 发送是不管顺序,也不管丢包的,所以不会出现像 HTTP/2 队头阻塞的问题。大家都知道 UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。

QUIC 有以下 3 个特点。

  • 无队头阻塞

    • QUIC也有类似 HTTP/2 Stream 与多路复用的概念,也是可以在同一条连接上并发传输多个 Stream,Stream 可以认为就是一条 HTTP 请求。
    • QUIC 有自己的一套机制可以保证传输的可靠性的。当某个流发生丢包时,只会阻塞这个流,其他流不会受到影响,因此不存在队头阻塞问题。这与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。
  • 更快的连接建立

    • HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是 QUIC 内部包含了 TLS
    • 它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS/1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商
  • 连接迁移

    • 基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。

    • 那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接。而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。

      而 QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。

上次更新于:

- + \ No newline at end of file diff --git "a/Network/02.\345\272\224\347\224\250\345\261\202\345\215\217\350\256\256\347\247\215\347\261\273.html" "b/Network/02.\345\272\224\347\224\250\345\261\202\345\215\217\350\256\256\347\247\215\347\261\273.html" index 96d61a9..7ade62c 100644 --- "a/Network/02.\345\272\224\347\224\250\345\261\202\345\215\217\350\256\256\347\247\215\347\261\273.html" +++ "b/Network/02.\345\272\224\347\224\250\345\261\202\345\215\217\350\256\256\347\247\215\347\261\273.html" @@ -15,8 +15,8 @@
Skip to content
On this page

应用层-协议种类

应用层有许多协议,包括

  1. 域名系统DNS协议
  2. FTP文件传输协议
  3. Telnet远程终端协议
  4. Telnet远程登录协议
  5. SMTP电子邮件协议
  6. POP3邮件读取协议
  7. SNMP简单网络管理协议等
  8. HTTP超文本传输协议

应用层协议 (application layer protocol)定义了运行在不同端系统上的应用程序进程

相互传递报文的方式

DNS

FTP

SMTP与POP3

邮件的发送与读取流程

电子邮件系统采用客户/服务器(C/S)方式。 电子邮件系统的三个主要组成构件:用户代理,邮件服务器,以及电子邮件所需的协议。

  • 用户代理是用户与电子邮件系统的接口,又称为电子邮件客户端软件

  • 邮件服务器是电子邮件系统的基础设施。因特网上所有的ISP都有邮件服务器,其功能是发送和接收邮件,同时还要负责维护用户的邮箱。

  • 协议包括邮件发送协议(例如SMTP)和邮件读取协议(例如POP3, IMAP)

image-20230314123453949

邮件的发送要经历两个流程

  • 用户代理到发送方邮件服务器
  • 发送方邮件服务器到接受方邮件服务器

邮件的接收要经历一个流程

  • 接收方邮件服务器到接受方用户代理

SMTP原理

image-20230314122454058

注意:

  1. 为了简单起见,省略了认证过程;

  2. 应答代码后面一般都跟有简单的描述信息

  3. 不同的SMTP服务器给出的相同应答代码 的描述信息可能不同

邮件格式

电子邮件的信息格式并不是由SMTP定义的,而是在RFC 822中单独定义的。这个RFC文档己在2008年更新为RFC 5322。一个电子邮件有信封和内容两部分。而内容又由首部和主体两部分构成。

image-20230314122845393

SMTP的缺陷与MIME的产生

  • SMTP协议只能传送ASCI码文本数据,不能传送可执行文件或其他的二进制对象。

  • SMTP协议只能传送7位(7比特)的ASCII码

  • SMTP不能满足传送多媒体邮件(例如带有图片、音频或视频数据)的需要。并且许多其他非英语国家的文字 (例如中文、俄文、甚至带有重音符号的法文或德文)也无法用SMTP传送。

  • 为解决SMTP传送非ASCII码文本的问题,提出了多用途因特网邮件扩展MIME (Multipurpose Internet Mail Extensions)

    • 增加了5个新的邮件首部字段,这些字段提供了有关邮件主体的信息。

    • 定义了许多邮件内容的格式,对多媒体电子邮件的表示方法进行了标淮化。 口

    • 定义了传送编码,可对任何内容格式进行转换,而不会被邮件系统改变。

    • 实际上,MIME不仅仅用于SMTP,也用于后来的同样面向ASCl字符的HTTP。

邮件的读取

常用的邮件读取协议有以下两个:

  • 邮局协议POP (Post Office Protocol),POP3是其第三个版本,是因特网正式标准。 非常简单、功能有限的邮件读取协议。用户只能以下载并删除方式或下载并保留方式从邮件服务器下载邮件到用户方计算机。不允许用户在邮件服务器上管理自己的邮件。(例如创建文件夹,对邮件进行分类管理等)。
  • 因特网邮件访问协议IMAP (Internet Message Access Protocol),IMAP4是其第四个版本,目前还只是因特网建议标准。功能比POP3强大的邮件读取协议。用户在自己的计算机上就可以操控邮件服务器中的邮箱,就像在本地操控一样,因此IMAP是一个联机协议。
  • POP3和IMAP4都采用基于TCP连接的客户/服务器方式。POP3使用熟知端口110,IMAP4使用熟 知端口143。

基于HTTP的邮件发送

  • 通过浏览器登录(提供用户名和口令)邮件服务器万维网网站就可以撰写、收发、阅读和管理电子邮件。

  • 这种工作模式与IMAP很类似,不同的是用户计算机无需安装专门的用户代理程序,只需要 使用通用的万维网浏览器。

  • 邮件服务器网站通常都提供非常强大和方便的邮件管理功能,用户可以在邮件服务器网站上管理和处理自己的邮件,而不需要将邮件下载到本地进行管理。

    image-20230314124435366

习题

image-20230314124759796

WebSocket的产生

HTTP 不断轮询

问题的痛点在于,怎么样才能在用户不做任何操作的情况下,网页能收到消息并发生变更。

最常见的解决方案是,网页的前端代码里不断定时发 HTTP 请求到服务器,服务器收到请求后给客户端响应消息。

这其实时一种「」服务器推的形式。

它其实并不是服务器主动发消息到客户端,而是客户端自己不断偷偷请求服务器,只是用户无感知而已。

以「扫码登录」的场景为例:

某信公众号平台,登录页面二维码出现之后,前端网页根本不知道用户扫没扫,于是不断去向后端服务器询问,看有没有人扫过这个码。而且是以大概 1 到 2 秒的间隔去不断发出请求,这样可以保证用户在扫码后能在 1 到 2 秒内得到及时的反馈,不至于等太久

使用HTTP定时轮询

但这样,会有两个比较明显的问题:

  • 当你打开 F12 页面时,你会发现满屏的 HTTP 请求。虽然很小,但这其实也消耗带宽,同时也会增加下游服务器的负担。
  • 最坏情况下,用户在扫码后,需要等个 1~2 秒,正好才触发下一次 HTTP 请求,然后才跳转页面,用户会感到明显的卡顿

使用起来的体验就是,二维码出现后,手机扫一扫,然后在手机上点个确认,这时候卡顿等个 1~2 秒,页面才跳转。

使用长轮询可以解决这个问题

HTTP 长轮询

我们知道,HTTP 请求发出后,一般会给服务器留一定的时间做响应,比如 3 秒,规定时间内没返回,就认为是超时。

如果我们的 HTTP 请求将超时设置的很大,比如 30 秒,在这 30 秒内只要服务器收到了扫码请求,就立马返回给客户端网页。如果超时,那就立马发起下一次请求。

这样就减少了 HTTP 请求的个数,并且由于大部分情况下,用户都会在某个 30 秒的区间内做扫码操作,所以响应也是及时的。

image-20230307122650848

比如,某度云网盘就是这么干的。所以你会发现一扫码,手机上点个确认,电脑端网页就秒跳转,体验很好。

像这种发起一个请求,在较长时间内等待服务器响应的机制,就是所谓的长训轮机制。我们常用的消息队列 RocketMQ 中,消费者去取数据时,也用到了这种方式。

复杂场景下:

上面提到的两种解决方案(不断轮询和长轮询),本质上,其实还是客户端主动去取数据。

对于像扫码登录这样的简单场景还能用用。但如果是网页游戏呢,游戏一般会有大量的数据需要从服务器主动推送到客户端。

这就得说下 WebSocket 了。

WebSocket是什么

WebSocket是应用层协议, 是为了解决HTTP/1.1半双工的缺陷

我们知道 TCP 连接的两端,同一时间里双方都可以主动向对方发送数据。这就是所谓的全双工

而现在使用最广泛的HTTP/1.1,也是基于TCP协议的,同一时间里,客户端和服务器只能有一方主动发数据,这就是所谓的半双工

也就是说,好好的全双工 TCP,被 HTTP/1.1 用成了半双工。

这是由于 HTTP 协议设计之初,考虑的是看看网页文本的场景,能做到客户端发起请求再由服务器响应,就够了,根本就没考虑网页游戏这种,客户端和服务器之间都要互相主动发大量数据的场景。

所以,为了更好的支持这样的场景,我们需要另外一个基于TCP的新协议

于是新的应用层协议WebSocket就被设计出来了。注意:socket和Web Socket毫无关系

HTTP和WebSocket的关系

WebSocket和HTTP一样都是基于TCP的协议。经历了三次TCP握手之后,利用 HTTP 协议升级为 WebSocket 协议

你在网上可能会看到一种说法:"WebSocket 是基于HTTP的新协议",其实这并不对,因为WebSocket只有在建立连接时才用到了HTTP,升级完成之后就跟HTTP没有任何关系了

WebSocket消息格式

WebSocket的数据格式也是数据头(内含payload长度) + payload data 的形式。

这是因为 TCP 协议本身就是全双工,但直接使用纯裸TCP去传输数据,会有粘包的"问题"。为了解决这个问题,上层协议一般会用消息头+消息体的格式去重新包装要发的数据。

消息头里一般含有消息体的长度,通过这个长度可以去截取真正的消息体。

HTTP 协议和大部分 RPC 协议,以及我们今天介绍的WebSocket协议,都是这样设计的。

WebSocket的使用场景

WebSocket完美继承了 TCP 协议的全双工能力,并且还贴心的提供了解决粘包的方案。

它适用于需要服务器和客户端(浏览器)频繁交互的大部分场景,比如网页/小程序游戏,网页聊天室,以及一些类似飞书这样的网页协同办公软件。

回到文章开头的问题,在使用 WebSocket 协议的网页游戏里,怪物移动以及攻击玩家的行为是服务器逻辑产生的,对玩家产生的伤害等数据,都需要由服务器主动发送给客户端,客户端获得数据后展示对应的效果。

图片

总结

  • TCP 协议本身是全双工的,但我们最常用的 HTTP/1.1,虽然是基于 TCP 的协议,但它是半双工的,对于大部分需要服务器主动推送数据到客户端的场景,都不太友好,因此我们需要使用支持全双工的 WebSocket 协议。
  • 在 HTTP/1.1 里,只要客户端不问,服务端就不答。基于这样的特点,对于登录页面这样的简单场景,可以使用定时轮询或者长轮询的方式实现服务器推送(comet)的效果。
  • 对于客户端和服务端之间需要频繁交互的复杂场景,比如网页游戏,都可以考虑使用 WebSocket 协议。
  • WebSocket 和 socket 几乎没有任何关系,只是叫法相似。
  • 正因为各个浏览器都支持 HTTP协 议,所以 WebSocket 会先利用HTTP协议加上一些特殊的 header 头进行握手升级操作,升级成功后就跟 HTTP 没有任何关系了,之后就用 WebSocket 的数据格式进行收发数据。

HTTP

详见后文...

上次更新于:

- + \ No newline at end of file diff --git "a/Network/03.\344\274\240\350\276\223\345\261\202.html" "b/Network/03.\344\274\240\350\276\223\345\261\202.html" index 714caf8..226a12a 100644 --- "a/Network/03.\344\274\240\350\276\223\345\261\202.html" +++ "b/Network/03.\344\274\240\350\276\223\345\261\202.html" @@ -15,8 +15,8 @@
Skip to content
On this page

TCP与UDP

TCP和UDP的区别

TCP(传输控制协议)和UDP(用户数据报协议)是两种不同的传输层协议, 属于TCP/IP体系分层下的第二层, 是直接服务于应用层的协议

面向连接, 可靠传输, 面向字节流是TCP的三大特点

无连接和面向连接

image-20230307224921078

不可靠传输与可靠传输

image-20230307230515771

面向应用报文和面向字节流

image-20230307230049271

单播,多播与广播

image-20230307225538844

协议首部

image-20230307230806237

总结🔥

  1. TCP是面向连接的,即在发送数据之前需要先建立连接;UDP是无连接的,即发送数据之前不需要建立连接。
  2. TCP提供可靠传输,即通过TCP传输的数据不会丢失、乱序或重复;UDP无法保证可靠传输,可能出现丢包、乱序或重复的情况。
  3. TCP面向字节流,即将数据看作一个连续的字节序列;UDP面向应用报文,即将数据看作一个个独立的报文。
  4. TCP有较长的首部(20字节),增加了传输开销;UDP有较短的首部(8字节),减少了传输开销。
  5. TCP有流量控制和拥塞控制机制,可以根据网络状况和接收方能力调整发送速率;UDP没有这些机制,发送方可以以任意速率发送数据

记忆表格如下:

特征TCPUDP
连接性面向连接,需要先建立连接再发送数据¹²³无连接,不需要建立连接就可以发送数据¹²³
可靠性可靠,不会出现数据丢失、乱序或重复¹²³不可靠,可能出现数据丢包、乱序或重复¹²³
数据单位面向字节流,将数据看作一个连续的字节序列¹²³面向报文,将数据看作一个个独立的报文¹²³
控制机制有流量控制和拥塞控制机制,可以根据网络状况和接收方能力调整发送速率¹²³没有流量控制和拥塞控制机制,发送方可以以任意速率发送数据¹²³
首部长度较长(20字节),增加了传输开销¹²³较短(8字节),减少了传输开销¹²³

应用场景

TCP和UDP的应用场景取决于数据传输的需求。

一般来说,TCP适合那些需要可靠、有序、完整的数据传输的应用,例如网页浏览、文件传输、电子邮件、远程登录等¹²³。UDP适合那些需要快速、实时、低延迟的数据传输的应用,例如视频会议、语音通话、在线游戏等¹²³。当然,这些应用场景并不是绝对的,有些应用可能会同时使用TCP和UDP,或者使用其他的协议。

UDP用户数据报协议

  • UDP是一种无连接, 不可靠的传输层协议
  • 支持快速, 无延迟的通信
  • 不保证数据的安全性和完整性
    • 发件人和收件人之间缺乏相互身份验证, 这确保了UDP的出色传输速度, 但UDP协议不能保证数据包的完整性和安全性
  • 没有TCP的拥塞控制和三次握手的机制来保证可靠传输

实时应用程序主要使用基于UDP的实时传输协议(RTP),与基本协议不同,它可以检测数据包丢失

UDP是传输层协议,是面向用户数据包协议,不保证可靠交付,没用TCP的拥塞控制和三次握手的机制保证可靠传传输,UDP尽最大努力交付。最少只有8个字节。UDP支持单播、组播、广播,UDP相对TCP更简单,传输的速率更快,所以通常可以用在实时性比较强的需求上,比如视频会议等。我们可以通过一些操作区增强UDP的可靠传输,腾讯的UDP+TCP机制,最简单的方式是通过 setsockopt来减小缓冲区的大小

UDP有以下特点:

  • 简单,轻量化
  • 面向报文,保留报文边界
  • 尽最大努力交付,不保证可靠性
  • 没有拥塞控制,适合实时应用
  • 支持一对一、一对多、多对一和多对多的交互通信

TCP流量控制

我们希望数据传输的快一些

但如果数据发送过快, 接收方来不及接收, 就会造成数据丢失

流量控制: 让发送方的发送速率不要太快, 要让接收方来得及接受

利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制

  • TCP接收方利用自己的接收窗口大小来限制发送方发送窗口的大小
  • TCP发送方收到接受方的零窗口通知后, 启动持续计时器; 持续计时器超时后, 向接受方发送零窗口探测报文

image-20230307231915692

image-20230307231949167

这种情况要用到零窗口探测报文

image-20230307232119984

零窗口探测报文也有重传计时器, 即使零窗口探测报文发送失效, 当重传计时器超时后, 重传计时器会被重传

例题:

image-20230307232541378

TCP拥塞控制

视频链接🔥: 5.5 TCP的拥塞控制_哔哩哔哩_bilibili

image-20230308182617679

TCP有四种拥塞控制算法, 分别是:

  • 慢开始
  • 拥塞避免
  • 快重传
  • 快恢复

下面介绍它们的基本原理, 假定如下条件:

  • 数据总是单向传送,另一个方向只传送确认
  • 接受方总是有足够大的存储空间, 因而发送方发送窗口的大小由网络的拥塞程度来决定
  • 以TCP最大报文段MSS的个数为讨论问题的单位, 而不是以字节为单位

image-20230308183042963

慢开始和拥塞避免

image-20230308183701180

Note:

  • "慢开始"是指一开始想网络注入的报文段少, 并不是指拥塞窗口cwnd增长速度慢
  • "拥塞避免"并非指能够完全避免拥塞, 而是指在拥塞避免阶段将拥塞窗口控制为按照现行规律增长, 使得网络比较不容易拥塞

快重传和快恢复

  • 慢开始和拥塞避免算法是1988年提出的TCP拥塞控制算法 (TCP Tahoe版本)
  • 1990年又增加了两个新的拥塞控制算法改进TCP性能, 这就是快重传和快恢复 (TCP Reno版本)
    • 有时, 个别报文段会在网络中丢失,但实际上网络并未发生拥塞
      • 这将导致发送方超时重传, 并误认为网络发生了拥塞
      • 于是, 发送方把拥塞窗口cwnd又设置为1, 并错误的启动慢开始算法, 降低了传输效率
      • image-20230308184512750
  • 采用快重传算法可以让发送方尽早知道发生了个别报文段的丢失
  • 所谓快重传, 就是使得发送方尽快进行重传, 而不是等超时重传计时器超时再重传
    • 要求接受方不要等待自己发送数据时才进行稍待确认, 而是要立即发送确认
    • 即使收到了失序的报文段也要立即发出对已收到的报文段的重复确认
    • 发送方一旦收到三个连续的重复确认, 就将相应报文段立即重传, 而不是等超时重传计时器超时再重传
    • 对于个别丢失的报文段, 发送方不会出现超时重传, 也就不会误认为出现了拥塞 (进而降低拥塞窗口cwnd为1), 使用快重传算法可以使得整个网络的吞吐量提高约20%
    • image-20230308185200071
  • 发送方一旦收到3个重复确认, 就知道现在只是丢失了个别的报文段, 于是不启动慢开始算法, 而是执行快恢复算法
    • 发送方将慢开始门限ssthresh值调整为初始慢开始门限的一半, 更新拥塞窗口cwnd的值为当前慢开始门限的值, 开始执行拥塞避免算法
    • 也有的是把快恢复开始时的cwnd值增大为ssthresh+3
      • 3个报文段已经停留在接收方的数据缓存中
      • 可见现在网络中减少了3个报文段, 因此可以适当把拥塞窗口扩大一些

总结

image-20230308190109244

例题

image-20230308190441755

TCP超时重传时间的选择

image-20230308192319143

image-20230308192426647

image-20230308192642853

若出现超时重传, 则往返时间RTT会很难测量

image-20230308192838134

示例

image-20230308193050629

TCP可靠传输的实现

5.7 TCP可靠传输的实现_哔哩哔哩_bilibili

image-20230308193904946

image-20230308194224992

TCP可靠传输的实现

image-20230308194859290

例题

image-20230308194551701

image-20230308194839132

TCP运输连接管理

介绍

  • TCP是面向连接的协议, 它基于运输连接来传送TCP报文段
  • TCP运输连接的建立和释放是每一次面向连接的通信中必不可少的过程
  • TCP运输连接有三个阶段
    • 建立TCP连接「三报文握手」
    • 数据传输
    • 释放TCP连接「四报文挥手」
  • TCP的运输连接管理就是使得运输连接的建立和释放正常进行

TCP连接建立「三次握手」

TCP连接建立要解决三个问题

  • 使得TCP双方确认对方的存在
  • 使得TCP双方协商一些参数 (如最大窗口值, 是否使用窗口扩大选项和时间戳选项等)
  • 使得TCP双方能够运输实体资源 (如缓存大小, 链接表中的项目等) 进行分配

使用「三报文握手」建立连接

TCP标志位, 当标志位取1时, 对应操作才有效

  • SYN「同步位」
  • ACK「确认位」
  • ack「确认号字段」
  • seq「初始序号」
  • FIN「终止标志位」:用来释放TCP连接, 表面是TCP连接释放报文段
  • RST「复位标志位」:用来复位TCP连接
  • 「紧急标志位」

SYN=1,ACK=1说明这是一个TCP连接请求确认报文段, 确认号字段ack的值是对客户端seq初时序号的确认, 确认报文段中的seq是主机乙的初始序号, 可以随意指定

  • TCP标准规定, SYN=1的报文段不能携带数据, 但是要消耗掉一个序号
  • 普通的确认报文段如果不携带数据, 则不消耗序号

image-20230308200313754

不能简化为两报文握手

image-20230308200553204

不能简化, 这是为了防止已经失效的连接请求报文段突然又传送到了TCP服务器, 因而导致服务器资源浪费

例题

image-20230308201216767

TCP连接释放「四次挥手」

常规四次挥手流程

image-20230308211347641

为什么有时间等待状态, 其目的是什么?

image-20230308211649589

  • 确保TCP服务器进程可以收到最后一个TCP确认报文段而进入关闭状态

  • 使新的TCP连接中不会出现旧的报文段

TCP客户端出现故障 => 保活计时器

image-20230308211821160

TCP报文段的首部格式

5.9 TCP报文段的首部格式_哔哩哔哩_bilibili

  • 为了实现可靠传输, TCP采用了面向字节流的方式
  • 但TCP在发送数据时, 是从发送缓存中取出一部分或者全部字节并给其添加一个首部使之成为TCP报文段后进行发送
    • 一个TCP报文段由首部数据载荷两部分构成
    • TCP的全部功能都体现在它首部中各字段的作用

image-20230308213621162

扩展首部的填充部分: 确保报文段首部能被4整除 (因为数据偏移字段, 也就是首部长度字段, 是以4字节为单位的)

image-20230308213800508

其他

单工,半双工,全双工

单工、半双工和全双工是三种不同的数据传输模式。

  • 单工:数据只能在一个方向上传输,有固定的发送者和接收者。例如:电视、广播。
  • 半双工:数据可以在两个方向上传输,但是同一时间只能在一个方向上传输,实际上是切换的单工。例如:对讲机、集线器。
  • 全双工:数据可以在两个方向上同时传输,需要独立的发送端和接收端。例如:电话、交换机。

加密算法

对称加密和非对称加密算法

  • 对称加密中加密和解密使用的秘钥是同一个;非对称加密中采用两个密钥,一般使用公钥进行加密,私钥进行解密
  • 对称加密解密的速度比较快,非对称加密和解密花费的时间长、速度相对较慢
  • 非对称加密可以用于数字签名和数字鉴别,而对称加密不可以。

你可以参考下面这个表格来比较两种算法的优缺点:

对称加密非对称加密
优点:速度快、效率高优点:安全性高、可用于认证
缺点:秘钥传输不安全、无法鉴别身份缺点:计算复杂、速度慢

数字签名和数字鉴别

数字签名和数字鉴别分别用于

  • 保证数字信息的真实性
  • 保证数字信息完整性
项目数字签名数字鉴别
定义一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术实现,用于鉴别数字信息的方法。一种利用对称加密或单向散列函数生成一个固定长度的信息摘要,附在原始信息后面,用于验证信息是否被篡改或伪造的方法。
加密方式非对称加密对称加密或单向散列函数
加密和解密方法私钥进行加密,公钥进行解密秘钥或散列函数进行加密和解密
功能作用证明信息的来源、防止抵赖和伪造。证明信息的完整性。
应用场景电子邮件、电子合同、电子发票等都可以使用数字签名来确认发送者的身份和信息的真实性。网络传输、文件存储、数据备份等都可以使用数字鉴别来验证信息是否被修改或损坏。

中间人攻击

攻击者作为客户端和服务器之间的桥梁、双向获取信息并且篡改其内容

中间人攻击是指攻击者通过与客户端和目标服务器「同时」建立连接,作为客户端和服务器的桥梁,处理双方的数据,整个会话期间的内容几乎是完全被攻击者控制的。攻击者可以拦截双方的会话并且插入新的数据内容

中间人攻击的过程:

  1. 服务器向客户端发送公钥。
  2. 攻击者截获公钥,保留在自己手上。
  3. 然后攻击者自己生成一个伪造的公钥,发给客户端。
  4. 客户端收到伪造的公钥后,生成加密哈希(此时加密内容是对称加解密秘钥) 值发给攻击者服务器。
  5. 攻击者获得加密哈希值,用自己的私钥解密获得真秘钥。
  6. 同时生成假的加密哈希值,发给服务器。
  7. 服务器用私钥解密获得假秘钥。
  8. 服务器用假秘钥加密传输信息。

上次更新于:

- + \ No newline at end of file diff --git "a/Network/04.\347\275\221\347\273\234\345\261\202.html" "b/Network/04.\347\275\221\347\273\234\345\261\202.html" index 5eef8c6..6023d9c 100644 --- "a/Network/04.\347\275\221\347\273\234\345\261\202.html" +++ "b/Network/04.\347\275\221\347\273\234\345\261\202.html" @@ -15,8 +15,8 @@
Skip to content
On this page

网络层概述

更新ing...

上次更新于:

- + \ No newline at end of file diff --git "a/Network/05.\347\275\221\347\273\234\345\256\211\345\205\250.html" "b/Network/05.\347\275\221\347\273\234\345\256\211\345\205\250.html" index 6ae2b07..1027904 100644 --- "a/Network/05.\347\275\221\347\273\234\345\256\211\345\205\250.html" +++ "b/Network/05.\347\275\221\347\273\234\345\256\211\345\205\250.html" @@ -15,8 +15,8 @@
Skip to content
On this page

网络安全

网络安全概述

网络安全分为安全威胁,安全服务和安全机制三方面

安全威胁

分为被动攻击和主动攻击

  • 被动攻击:攻击者通过窃听手段仅观察和分析网络中传输数据流中的敏感信息, 而不对其进行干扰。

  • 主动攻击:攻击者对网络中传输着的数据流进行各种处理。

    • 中断
    • 篡改
    • 伪造
    • 恶意程序
      • 计算机病毒 计算机蠕虫 特洛伊木马 逻辑炸弹 后门入侵 流氓软件
    • 拒绝服务攻击(Dos)
      • image-20230314171338272

对于被动攻击:由于其不涉及对数据的更改,很难被发现,因此对付被动攻击主要采用各种数据加密技术进行预防,而不是主动检测。

对于主动攻击:由于主动攻击容易检测。对付主动攻击除要采取数据加密技术、访问控 制技术等预防措施,还需要采取各种检测技术及时发现井阻止攻击,同时还要对攻击源进行 追踪,井利用法律手段对其进行打击。

安全服务

安全服务作用对付攻击
保密性确保网络中传输的信息只有其发送方和接收方才能懂得其含义对付被动攻击
报文完整性确保网络中传输的信息不被攻击者篡改或伪造对付主动攻击
实体鉴别通信两端的实体能够相互验证对方的真实身份对付主动攻击
不可否认性防止发送方或接收方否认发送或接收过某信息保护电子商务
访问控制对实体的访问权限进行控制防止未授权实体访问
可用性确保授权用户能够正常访问系统信息和资源拒绝服务攻击

对称加密与非对称加密

密码学基本概念

将发送的数据变换成对任何不知道如何做逆变换的人都不可理解的形式,从而保证数据的机密性,这种变换称为加密 (Encryption)

  • 加密前的数据被称为明文 (Plaintext)
  • 加密后的数据被称为密文 (Ciphertext)

通过某种逆变换将密文重新变换回明文,这种逆变换称为解密(Decryption)

加密和解密过程可以使用密钥(Key)作为参数。

  • 密钥必须保密,但加密和解密的过程可以公开。
  • 只有知道密钥的人才能解密密文,否则即使知道加密或解密算法也无法解密密文。

为什么依靠密钥进行保密而不依靠密码算法进行保密呢?

  • 一旦算法失密就必须放弃该算法,这意味着需 要频繁地修改密码算法,而开发一个新的密码 算法是非常困难的事情。
  • 密钥空间可以很大,用密钥将密码算法参数化, 同一个密码算法可以为大量用户提供加密服务。

image-20230314172659326

image-20230314172753299

对称密钥密码体制

对称密钥密码体制是指加密密钥与解密密钥相同的密码体制。 数据加密的一般模型使用对称密钥

image-20230314173359395

数据加密标准的发展:

DES->三重DES->高级加密标准ANS

高级加密标准AES

  • 高级加密标准(Advanced Encryption Standard, AES)支持128比特、192比特和256比比特的密钥长度,用硬件和软件都可以快速实现。
  • AES不需要太多内存,因此适用于小型移动设备。
  • 据美国国家标准与技术研究院NIST估计,如果用1秒即可破解56比特密钥长度的DES的计算机,来破解128比特密钥长度的AES密钥,要用大约149万亿年的时间才有可能完成破解。

公钥密码体制

上次更新于:

- + \ No newline at end of file diff --git "a/Network/06.\350\256\241\347\275\221\345\277\253\351\227\256\345\277\253\347\255\224.html" "b/Network/06.\350\256\241\347\275\221\345\277\253\351\227\256\345\277\253\347\255\224.html" index c71daa1..825adb7 100644 --- "a/Network/06.\350\256\241\347\275\221\345\277\253\351\227\256\345\277\253\347\255\224.html" +++ "b/Network/06.\350\256\241\347\275\221\345\277\253\351\227\256\345\277\253\347\255\224.html" @@ -15,8 +15,8 @@
Skip to content
On this page

网络体系

img

实际使用的是应用层,传输层,网络层和网络接口层

在五层协议中,是把TCP/IP的网络接口层重新划分为了物理层和数据链路层

在OSI七层协议中,是把TCP/IP的应用层划分为了会话层,表示层和应用层

OSI七层

img

应用层

状态码

https://http.devtool.tech/ 「强烈推荐:状态码速查」

传输层

传输层主要作用:建立端到端的连接,比如我们的客户端到服务端就是端到端

TCP为什么要三次握手?

  • 面向连接
  • 可靠
  • 面向字节流
  • 全双工

首先,明确三次握手的过程

image-20230327233745045

三次握手是为了防止已经失效的请求报文,突然又传到服务器引起错误,防止由于网络滞留重发SYN包的情况

本质来说,三次握手是为了解决网络信道不可靠的问题,为了在不可靠的信道上,建立起可靠的连接

四次挥手

image-20230327234300572

保证对方已收到ACK 包,假设客户端送完最后一包 ACK包后就释放了连接,一旦ACK包在网络中丢失,服务端将一直停留在最后确认状态

为了在不可靠的信道上,建立起可靠的连接断开确认

网络层

网络接口层

  • arp协议:
    • ARP广播报文,询问设备的mac地址

在 JavaScript 中,this 关键字用于引用当前执行函数的对象。this 的值取决于函数的调用方式。this 可能是任何东西,这取决于它出现在代码中的上下文。

缓存

强制缓存和协商缓存是 HTTP 缓存机制中的两种不同方式。它们有以下异同点:

强制缓存协商缓存
说明浏览器在请求资源时,直接从本地缓存中获取,不向服务器发送请求浏览器在请求资源时,向服务器发送请求,根据服务器返回的响应头信息来判断是否从本地缓存中获取
缓存过期时间由 Cache-Control 和 Expires 响应头控制由 Last-Modified 和 ETag 响应头控制
缓存命中命中缓存时,请求不会发到服务器命中缓存时,请求会发到服务器,但服务器会返回 304 Not Modified 状态码,告诉浏览器可以使用本地缓存
更新缓存无法立即更新缓存,除非强制刷新或过期时间到了可以通过服务器控制缓存的更新,根据需要返回新的资源或 304 状态码

需要注意的是,虽然强制缓存和协商缓存是不同的缓存方式,但它们并不互斥。在浏览器缓存机制中,通常会先使用强制缓存,如果缓存过期或者需要强制刷新,再使用协商缓存。

简单请求和复杂请求

简单请求和复杂请求是指浏览器在发送跨域请求时,根据请求的方式、请求头和数据等内容,进行的一种分类。

简单请求满足以下条件:

  1. 请求方式为 GET、HEAD、POST 中的一种。
  2. 请求头只包含 Content-Type、Accept、Accept-Language、Content-Language、Content-Encoding、Last-Event-ID 中的一种或几种。
  3. Content-Type 只能是下面三种类型之一:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  4. 请求中的任意 XMLHttpRequestUpload 对象均没有被使用。

如果请求不满足上述所有条件,则被认为是复杂请求。

复杂请求需要在正式请求前,先发送一个 OPTIONS 请求,称为“预检请求”。预检请求的作用是询问服务器,是否允许跨域请求。如果服务器允许,才会发送正式请求。预检请求中包含了一个 Access-Control-Request-Headers 头部,标明了实际请求中会使用的自定义头部字段。

在实际请求中,还需要添加一个 Origin 头部,表示请求的源地址。服务器在响应时,需要添加一个 Access-Control-Allow-Origin 头部,表示允许跨域请求的源地址。同时,还需要添加一个 Access-Control-Allow-Credentials 头部,表示允许携带凭证信息(如 Cookie)。

需要注意的是,跨域请求存在一定的安全风险,因此必须在服务器端进行一定的安全措施,如限制跨域请求的来源、仅允许特定的请求类型等。

PUT请求

PUT 请求方法通常用于更新服务器上的资源。PUT 请求可以覆盖服务器上的指定资源或者创建一个新的资源。PUT 请求的语义是幂等的,即多次请求相同的 URL,返回的结果是一致的,不会对资源造成影响。

PUT 请求的操作过程如下:

  1. 客户端向服务器发送一个 PUT 请求,请求修改指定的资源。
  2. 服务器返回一个状态码表示请求的成功或失败。
  3. 如果请求成功,服务器将修改后的资源存储在指定位置。

需要注意的是,PUT 请求通常用于修改整个资源,而不是部分资源。如果需要修改部分资源,可以使用 PATCH 请求。

此外,PUT 请求还有一些限制。PUT 请求的请求体中应该包含完整的修改后的资源,因此客户端必须知道修改后的资源是什么。如果客户端不知道修改后的资源是什么,就应该使用 POST 请求。另外,PUT 请求通常需要进行身份验证,以确保只有授权用户才能修改资源。

上次更新于:

- + \ No newline at end of file diff --git a/Projects/Wecqu.html b/Projects/Wecqu.html index 4ec43e1..d54f246 100644 --- a/Projects/Wecqu.html +++ b/Projects/Wecqu.html @@ -15,8 +15,8 @@
Skip to content
On this page

WeCQU微信小程序

Uniapp + GraceUI 5 用户画像:在校大学生

项目介绍

我们的项目是一个智慧校园一体化查询微信小程序,专为大学生打造,旨在提高学生在校内的信息查询效率。

具体的实现方式是利用”微信小程序+后端“的技术,实现了一次登录,多次查询的功能,无需每次都输入账号和密码,也无需繁琐的点击操作,只需简单地访问想要查询的页面,就能快速得到结果。

项目数据

单日累计打开次数约10万+,单日访问人数1万+

image-20230225154558704

image-20230225154639099

页面展示

这里仅展示一些用户使用频率较高的页面

1.首页

image-20230225220453325

2.课表

image-20230225220529292

3.账户中心

image-20230225220730213

4.一卡通与电费

image-20230225220807931

5.成绩单

image-20230225220836320

6.常用电话

image-20230225220901184

核心功能

  • 课表查询🔥
  • 成绩查询🔥
  • 校园卡余额与消费记录查询🔥
  • 考试安排查询
  • 志愿时长查询
  • 空教室查询
  • 校车时刻查询
  • 历届课程信息查询
  • 常用电话查询

我的工作⛱

持续时间:2022.9 - 至今

  • 页面

    • 使用GraceUI组件库, 进行页面UI的重构, 重构率约40%
    • 对组件库中的轮播图组件进行二次封装,实现点击跳转webview页面的功能
  • 逻辑

    • 优化原有的javascript逻辑, 进行部分页面逻辑的重写, 重写率约50%

    • 封装部分工具类函数, 例如:

      • 使用promise封装wx.request, 避免回调地狱的产生
      • 使用promise封装wx.showModal请求, 简化模态对话框的使用方式
      • 封装将临时图片转换为base64图片的函数, 获取用户头像时调用
    • 通过变量加锁的方式防止用户多次发起登录请求, 减轻服务器压力

    • 通过setStorage和getStorage方式实现数据持久化,包括base64用户头像的存取,课表数据的存取等

前端特色

  • 充分利用微信小程序提供的 API

    • 微信小程序本身目录结构非常的清晰,分为四种主要的文件类型 .wxml/.wxss/.js/.json
    • 第一类文件主要是类似于 html 的页面结构
    • 第二类文件是类似于 CSS 的样式文件
    • 第三类文件是传统的 js 文件,但是在这个 js 文件中会 实例化一个 Page 对象作为入口函数,
    • 第四类文件是配置文件
    • 此外,微信小程序提供很多已经包装好的函数,例如 Loading加载动画,request 请求等等。
  • Promise 无阻塞请求

    • 由于一个页面中存在类似于课表信息,一卡通信息等许多类型的数据需要请求
    • 如果同时使用串行函数请求,会有阻塞发生
    • 为了避免这种情况, 我们引入了 es6 中的 Promise 函数,将每一个请求包装成一个 Promise 对象,然后并行无阻塞请求,这样就不会发生,一个请求等另一个请求这种影响性能的情况。
  • 针对通用函数采用了工具包的形式, 工具包使用的是模块化接口设计对外提供访问函数, 将通用逻辑统一封装到utils工具文件夹下, 页面逻辑中直接导入使用即可

  • 采用前后端完全分离的模式, 后端只需要向前端提供接口即可

上次更新于:

- + \ No newline at end of file diff --git a/Projects/ecosystem.html b/Projects/ecosystem.html index 14168d3..acfb847 100644 --- a/Projects/ecosystem.html +++ b/Projects/ecosystem.html @@ -45,8 +45,8 @@ },

亮点

component标签+KeepAlive标签,降低了顶部tab栏的切换开销

component标签的核心就是动态绑定,项目通过动态绑定is属性的方式, 用来切换不同组件的显示或隐藏

当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载,当频繁切换时会产生不必要的渲染开销。我们可以通过KeepAlive强制被切换掉的组件仍然保持“存活”的状态。

vue
<KeepAlive>
   <component :is="curComponent"></component>
 </KeepAlive>
- + \ No newline at end of file diff --git a/React/Quick Start.html b/React/Quick Start.html index 637c8b2..7e7c2a2 100644 --- a/React/Quick Start.html +++ b/React/Quick Start.html @@ -10,13 +10,13 @@ - + -
Skip to content
On this page

Quick Start

About what in this section ?

  • Component && Props
  • States
  • Hooks
  • Redux
  • Typescript
  • Forms
  • State Management
  • Firebase Project
- +
Skip to content
On this page

Quick Start

About what in this section ?

  • Component && Props
  • States
  • Hooks
  • Redux
  • Typescript
  • Forms
  • State Management
  • Firebase Project

上次更新于:

+ \ No newline at end of file diff --git a/React/React Hooks.html b/React/React Hooks.html index 2082b1f..68059f7 100644 --- a/React/React Hooks.html +++ b/React/React Hooks.html @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

React Hooks

一、产生原因

React Hooks是React v16.8.0版本引入的新特性,它可以让我们在函数组件中使用state和其他React特性,从而避免使用类组件。

在使用React Hooks时,有一些注意事项和设计依据需要遵循。

二、常用 Hooks

useState:状态定义

不可变状态,只能通过创建状态时提供的方法,进行状态的修改,而不能直接对状态进行赋值

  • useState返回的第一个参数是当前state,第二个参数是更新state的函数。
  • 在useState中如果需要更新state,不能基于当前的state进行更新,而是需要使用回调函数的方式进行更新,因为useState是异步更新state的。
  • 不能在条件语句中使用useState,因为条件语句在每次渲染时都会执行,如果在条件语句中使用useState,会导致state的混乱。

请记住:使用 useState 会触发组件的重新渲染。

例如:在一个组件中,引入了一个不需要改变的组件,同时使用useState定义并触发数据更改,则会造成这个被引入不必要的重渲染

useEffect:副作用处理

  • useEffect是React Hooks中用于处理副作用的钩子函数。
  • useEffect会在组件渲染完成后执行,并在每次state或props发生变化时重新执行。
  • 在useEffect中可以返回一个函数,这个函数会在组件被销毁时执行。

useLayoutEffect:副作用处理

useLayoutEffect是React提供的一个Hook函数,它和useEffect非常相似,都是用来处理副作用的,但是它们的执行时机不同。

useEffect 执行时机

useEffect 在内容重绘到页面之后 runs,useLayoutEffect在之前 runs

useEffect的执行时机是在DOM更新之后、浏览器绘制之后,也就是在useLayoutEffect之后执行。这个时机适合用来处理一些不需要立即执行的副作用,比如发送网络请求、修改全局状态等。

在组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入异步队列等待执行。

useLayoutEffect 执行时机

useLayoutEffect的执行时机是在DOM更新之后、浏览器绘制之前,也就是在useEffect之前执行。这个时机非常适合用来读取DOM节点的尺寸、位置等信息,因为这些信息需要在浏览器绘制之前就要计算好,否则可能会导致页面闪烁等问题。

组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入同步队列等待执行,这意味着useLayouEffect会完全阻塞后续的更新工作,也就是说,组件首次渲染完成后,会立即执行useLayoutEffect的回调函数,而不用等待主线程中其他未完成的工作。

总结

useLayoutEffect的原理和useEffect类似,都是通过在组件渲染时注册副作用函数,然后在组件卸载时执行清除函数来实现的。不同的是,useLayoutEffect会在浏览器绘制之前执行副作用函数,因此它会阻塞浏览器的渲染过程,可能会导致性能问题。

简单的理解就是,useLayoutEffect是立即执行,useEffect是推入异步队列,最终执行时间不确定。

因此,一般情况下,我们应该优先使用useEffect,只有在需要读取DOM节点信息时才使用useLayoutEffect。

useMemo:计算属性

产生依据

可以使用useMemo实现useCallback

useMemo类似于Vue3的计算属性,是针对于状态的,useCallback是针对函数的。。useCallback用来缓存函数,第一个参数是一个函数,但是他的这个函数不会被React执行,而是直接进入缓存;useMemo用来缓存状态,第一个参数也是一个函数,组件初始化时这个函数会被React直接执行,然后将其返回值进行缓存,第二个参数是依赖项,当依赖项变化时,React会重新执行对应的第一个参数,然后拿到最新的返回值,再次进行缓存。

参数说明

  • useMemo接收两个参数,第一个参数是需要进行计算的函数,第二个参数是依赖项数组。
  • 当依赖项数组中的值发生变化时,useMemo会重新执行计算函数,否则会使用上一次的计算结果。

useCallback:优化函数性能

产生依据

useCallback用于减少函数引用的创建次数。我们知道,每次组件的重新渲染都意味着内部所有的引用值都会被重新构建,每次函数组件的重新渲染都对应函数的重新执行。当不使用useCallback时,每一次状态变化,比如list或name状态变化,都会重新创建一个fetchData函数的引用,这是十分浪费性能的。实际上,fetchData函数只和url这个状态有关,当url这个状态不改变时,就不需要创建新的函数引用,因此就有了useCallback。

使用useCallback并且指定依赖项时,只有当url这个状态变化时,才会在新的时间切片里创建新的函数引用,否则就使用原先时间切片里的函数引用而无需创建新的引用。

参数说明

第一个参数是函数声明,第二个参数是依赖项,当依赖项发生了变动以后,对应的函数引用会被重新生成。若依赖项为空数组表示该回调函数不依赖于任何状态或属性,只会在组件挂载时创建一次,并在整个组件的生命周期内保持不变。这种情况下,useCallback的作用类似于普通的函数定义,但是由于它是在组件内部定义的,因此可以访问组件的状态和属性。使用空数组作为依赖项数组可以避免不必要的重新创建函数,从而提高性能。

注意事项

  • useCallback接收两个参数,第一个参数是需要缓存的函数,第二个参数是依赖项数组。
  • 当依赖项数组中的值发生变化时,useCallback会返回一个新的函数,否则会返回上一次缓存的函数。

useRef:构建出脱离React控制的状态

产生依据

useState用于构建组件状态,当状态变更的时候组件必定重新渲染;useRef出现的目的是构建一个状态出来,但是这个状态是直接脱离React控制的,也不会造成重新渲染,同时状态还不会因为组件的重新渲染而被初始化。useRef完全脱离React控制,意味着更改refInstance.current的值,不会像修改useState定义的状态一样,造成页面的重新渲染。refInstance是可以直接读写的,不需要像useState一样使用给定的方法来修改。

注意事项

  • useRef是React Hooks中用于获取DOM节点和存储任意值的钩子函数。
  • useRef返回的是一个对象,其中current属性是一个可变的变量,可以存储任意值。
  • 在函数组件中,使用useRef可以获取DOM节点的引用。

useContext:祖孙组件传值

产生依据

类似于Vue3的provide和inject,允许组件之间通过除 props 之外的方式共享数据,多用于祖孙组件之间的数据传递问题。

核心实现

  • useContext是React Hooks中用于获取上下文的钩子函数。
  • 使用useContext可以在函数组件中获取上下文的值,避免了使用类组件中的static contextType和Consumer。

useContext 和 createContext 是 React 中用于跨组件传递数据的两个 API,通常用于全局数据管理。很多著名的库,例如 react-router, redux 都是基于 useContext 来做的;

createContext 用于创建一个上下文对象,useContext 用于在组件中获取上下文对象中的数据。

使用场景

当多个组件需要共享同一个数据时,可以使用 createContext 创建一个上下文对象,并将数据传递给上下文对象。然后在需要使用数据的组件中使用 useContext 获取上下文对象中的数据。

注意事项

  1. 上下文对象中的数据应该是不可变的,避免直接修改上下文对象中的数据。
  2. 上下文对象中的数据应该是全局共享的,避免在组件中使用 useContext 获取到的数据与其他组件不一致。
  3. 上下文对象中的数据应该是简单的数据类型,避免在组件中使用 useContext 获取到的数据过于复杂。

useReducer:加强版 useState

useReducer是React Hooks中用于管理组件状态的钩子函数。

  • 接收三个参数:第一个参数是reducer函数,第二个参数是初始状态,第三个参数是初始化行为函数
    • 第一个参数reducer函数接收两个参数,一个是state,一个是action(也就是dispatch函数的参数)
    • 第二个参数通常是一个对象,作为初始值
    • 第三个参数成为初始化行为,可以在此处做数据持久化的处理,详见代码示例
  • 返回值:useReducer返回的是一个包含state和dispatch函数的数组。此处的dispatch可以类比理解为 useState 的 setState,不过他比 useState 具有更多的功能

下面是一个使用 useReducer 和 RooUI 实现的计数器功能,并实现了数据持久化:

jsx
import { Button } from '@roo/roo';
+    
Skip to content
On this page

React Hooks

一、产生原因

React Hooks是React v16.8.0版本引入的新特性,它可以让我们在函数组件中使用state和其他React特性,从而避免使用类组件。

在使用React Hooks时,有一些注意事项和设计依据需要遵循。

二、常用 Hooks

useState:状态定义

不可变状态,只能通过创建状态时提供的方法,进行状态的修改,而不能直接对状态进行赋值

  • useState返回的第一个参数是当前state,第二个参数是更新state的函数。
  • 在useState中如果需要更新state,不能基于当前的state进行更新,而是需要使用回调函数的方式进行更新,因为useState是异步更新state的。
  • 不能在条件语句中使用useState,因为条件语句在每次渲染时都会执行,如果在条件语句中使用useState,会导致state的混乱。

请记住:使用 useState 会触发组件的重新渲染。

例如:在一个组件中,引入了一个不需要改变的组件,同时使用useState定义并触发数据更改,则会造成这个被引入不必要的重渲染

useEffect:副作用处理

  • useEffect是React Hooks中用于处理副作用的钩子函数。
  • useEffect会在组件渲染完成后执行,并在每次state或props发生变化时重新执行。
  • 在useEffect中可以返回一个函数,这个函数会在组件被销毁时执行。

useLayoutEffect:副作用处理

useLayoutEffect是React提供的一个Hook函数,它和useEffect非常相似,都是用来处理副作用的,但是它们的执行时机不同。

useEffect 执行时机

useEffect 在内容重绘到页面之后 runs,useLayoutEffect在之前 runs

useEffect的执行时机是在DOM更新之后、浏览器绘制之后,也就是在useLayoutEffect之后执行。这个时机适合用来处理一些不需要立即执行的副作用,比如发送网络请求、修改全局状态等。

在组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入异步队列等待执行。

useLayoutEffect 执行时机

useLayoutEffect的执行时机是在DOM更新之后、浏览器绘制之前,也就是在useEffect之前执行。这个时机非常适合用来读取DOM节点的尺寸、位置等信息,因为这些信息需要在浏览器绘制之前就要计算好,否则可能会导致页面闪烁等问题。

组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入同步队列等待执行,这意味着useLayouEffect会完全阻塞后续的更新工作,也就是说,组件首次渲染完成后,会立即执行useLayoutEffect的回调函数,而不用等待主线程中其他未完成的工作。

总结

useLayoutEffect的原理和useEffect类似,都是通过在组件渲染时注册副作用函数,然后在组件卸载时执行清除函数来实现的。不同的是,useLayoutEffect会在浏览器绘制之前执行副作用函数,因此它会阻塞浏览器的渲染过程,可能会导致性能问题。

简单的理解就是,useLayoutEffect是立即执行,useEffect是推入异步队列,最终执行时间不确定。

因此,一般情况下,我们应该优先使用useEffect,只有在需要读取DOM节点信息时才使用useLayoutEffect。

useMemo:计算属性

产生依据

可以使用useMemo实现useCallback

useMemo类似于Vue3的计算属性,是针对于状态的,useCallback是针对函数的。。useCallback用来缓存函数,第一个参数是一个函数,但是他的这个函数不会被React执行,而是直接进入缓存;useMemo用来缓存状态,第一个参数也是一个函数,组件初始化时这个函数会被React直接执行,然后将其返回值进行缓存,第二个参数是依赖项,当依赖项变化时,React会重新执行对应的第一个参数,然后拿到最新的返回值,再次进行缓存。

参数说明

  • useMemo接收两个参数,第一个参数是需要进行计算的函数,第二个参数是依赖项数组。
  • 当依赖项数组中的值发生变化时,useMemo会重新执行计算函数,否则会使用上一次的计算结果。

useCallback:优化函数性能

产生依据

useCallback用于减少函数引用的创建次数。我们知道,每次组件的重新渲染都意味着内部所有的引用值都会被重新构建,每次函数组件的重新渲染都对应函数的重新执行。当不使用useCallback时,每一次状态变化,比如list或name状态变化,都会重新创建一个fetchData函数的引用,这是十分浪费性能的。实际上,fetchData函数只和url这个状态有关,当url这个状态不改变时,就不需要创建新的函数引用,因此就有了useCallback。

使用useCallback并且指定依赖项时,只有当url这个状态变化时,才会在新的时间切片里创建新的函数引用,否则就使用原先时间切片里的函数引用而无需创建新的引用。

参数说明

第一个参数是函数声明,第二个参数是依赖项,当依赖项发生了变动以后,对应的函数引用会被重新生成。若依赖项为空数组表示该回调函数不依赖于任何状态或属性,只会在组件挂载时创建一次,并在整个组件的生命周期内保持不变。这种情况下,useCallback的作用类似于普通的函数定义,但是由于它是在组件内部定义的,因此可以访问组件的状态和属性。使用空数组作为依赖项数组可以避免不必要的重新创建函数,从而提高性能。

注意事项

  • useCallback接收两个参数,第一个参数是需要缓存的函数,第二个参数是依赖项数组。
  • 当依赖项数组中的值发生变化时,useCallback会返回一个新的函数,否则会返回上一次缓存的函数。

useRef:构建出脱离React控制的状态

产生依据

useState用于构建组件状态,当状态变更的时候组件必定重新渲染;useRef出现的目的是构建一个状态出来,但是这个状态是直接脱离React控制的,也不会造成重新渲染,同时状态还不会因为组件的重新渲染而被初始化。useRef完全脱离React控制,意味着更改refInstance.current的值,不会像修改useState定义的状态一样,造成页面的重新渲染。refInstance是可以直接读写的,不需要像useState一样使用给定的方法来修改。

注意事项

  • useRef是React Hooks中用于获取DOM节点和存储任意值的钩子函数。
  • useRef返回的是一个对象,其中current属性是一个可变的变量,可以存储任意值。
  • 在函数组件中,使用useRef可以获取DOM节点的引用。

useContext:祖孙组件传值

产生依据

类似于Vue3的provide和inject,允许组件之间通过除 props 之外的方式共享数据,多用于祖孙组件之间的数据传递问题。

核心实现

  • useContext是React Hooks中用于获取上下文的钩子函数。
  • 使用useContext可以在函数组件中获取上下文的值,避免了使用类组件中的static contextType和Consumer。

useContext 和 createContext 是 React 中用于跨组件传递数据的两个 API,通常用于全局数据管理。很多著名的库,例如 react-router, redux 都是基于 useContext 来做的;

createContext 用于创建一个上下文对象,useContext 用于在组件中获取上下文对象中的数据。

使用场景

当多个组件需要共享同一个数据时,可以使用 createContext 创建一个上下文对象,并将数据传递给上下文对象。然后在需要使用数据的组件中使用 useContext 获取上下文对象中的数据。

注意事项

  1. 上下文对象中的数据应该是不可变的,避免直接修改上下文对象中的数据。
  2. 上下文对象中的数据应该是全局共享的,避免在组件中使用 useContext 获取到的数据与其他组件不一致。
  3. 上下文对象中的数据应该是简单的数据类型,避免在组件中使用 useContext 获取到的数据过于复杂。

useReducer:加强版 useState

useReducer是React Hooks中用于管理组件状态的钩子函数。

  • 接收三个参数:第一个参数是reducer函数,第二个参数是初始状态,第三个参数是初始化行为函数
    • 第一个参数reducer函数接收两个参数,一个是state,一个是action(也就是dispatch函数的参数)
    • 第二个参数通常是一个对象,作为初始值
    • 第三个参数成为初始化行为,可以在此处做数据持久化的处理,详见代码示例
  • 返回值:useReducer返回的是一个包含state和dispatch函数的数组。此处的dispatch可以类比理解为 useState 的 setState,不过他比 useState 具有更多的功能

下面是一个使用 useReducer 和 RooUI 实现的计数器功能,并实现了数据持久化:

jsx
import { Button } from '@roo/roo';
 
 const initialState = {
   name: 'Conny',
@@ -55,9 +55,9 @@
 return () => {
   document.removeEventListener('mousemove', handleMouseMove);
 };
-

}, []);

return position; }

export default useMouseMove;`

useDownLoadUrl

`/**

  • 从后端的通用下载接口获取模版下载信息
  • @param type */ import { useCallback, useEffect, useState } from 'react'; import { Toast } from '@roo/roo'; import { thfDownloadTemplate } from '../services/api';

const useDownLoadUrl = (type) => { const [templateUrl, setTemplateUrl] = useState(''); const fetchDownTemplate = useCallback(async () => { try { const res: any = await thfDownloadTemplate({ type }); const { code, msg, data } = res; if (code === 0) { setTemplateUrl(data.url); } else { Toast.fail({ title: '获取模版信息' }); setTemplateUrl(''); } } catch (e) { Toast.fail({ title: '获取模版信息' }); setTemplateUrl(''); } }, [type]);

useEffect(() => { fetchDownTemplate(); }, []);

return { templateUrl }; }; export default useDownLoadUrl;`

四、不常见Hook

useImperativeHandle

参数

  • 第一个参数是 ref
  • 第二个参数是一个函数,这个函数的返回值最终被赋值给ref.current
  • 第三个参数是依赖项,依赖项不变的话,ref.current不会被赋值

useLayoutEffect

useEffect vs useLayoutEffect

二者基本一致,只有运行机制不一样

大多业务都用 useEffect,很少用 useLayoutEffect

useEffect的运行规则:组件首次渲染工作完成并将真实dom生成到页面以后 将对应的回调函数推入异步队列等待执行

useLayoutEffect的运行规则:组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入同步队列等待执行【意味着useLayouEffect会完

全阻塞后续的更新工作,也就是说,组件首次渲染完成后,会立即执行useLayoutEffect的回调函数,而不用等待主线程中其他未完成的工作】

简单的理解就是,useLayoutEffect是立即执行,useEffect是推入异步队列,最终执行时间不确定

useLayoutEffect是React提供的一个Hook函数,它和useEffect非常相似,都是用来处理副作用的,但是它们的执行时机不同。

useLayoutEffect的执行时机是在DOM更新之后、浏览器绘制之前,也就是在useEffect之前执行。这个时机非常适合用来读取DOM节点的尺寸、位置等信息,因为这些信息需要在浏览器绘制之前就要计算好,否则可能会导致页面闪烁等问题。

useEffect的执行时机是在DOM更新之后、浏览器绘制之后,也就是在useLayoutEffect之后执行。这个时机适合用来处理一些不需要立即执行的副作用,比如发送网络请求、修改全局状态等。

useLayoutEffect的原理和useEffect类似,都是通过在组件渲染时注册副作用函数,然后在组件卸载时执行清除函数来实现的。不同的是,useLayoutEffect会在浏览器绘制之前执行副作用函数,因此它会阻塞浏览器的渲染过程,可能会导致性能问题。

因此,一般情况下,我们应该优先使用useEffect,只有在需要读取DOM节点信息时才使用useLayoutEffect。

- +

}, []);

return position; }

export default useMouseMove;`

useDownLoadUrl

`/**

  • 从后端的通用下载接口获取模版下载信息
  • @param type */ import { useCallback, useEffect, useState } from 'react'; import { Toast } from '@roo/roo'; import { thfDownloadTemplate } from '../services/api';

const useDownLoadUrl = (type) => { const [templateUrl, setTemplateUrl] = useState(''); const fetchDownTemplate = useCallback(async () => { try { const res: any = await thfDownloadTemplate({ type }); const { code, msg, data } = res; if (code === 0) { setTemplateUrl(data.url); } else { Toast.fail({ title: '获取模版信息' }); setTemplateUrl(''); } } catch (e) { Toast.fail({ title: '获取模版信息' }); setTemplateUrl(''); } }, [type]);

useEffect(() => { fetchDownTemplate(); }, []);

return { templateUrl }; }; export default useDownLoadUrl;`

四、不常见Hook

useImperativeHandle

参数

  • 第一个参数是 ref
  • 第二个参数是一个函数,这个函数的返回值最终被赋值给ref.current
  • 第三个参数是依赖项,依赖项不变的话,ref.current不会被赋值

useLayoutEffect

useEffect vs useLayoutEffect

二者基本一致,只有运行机制不一样

大多业务都用 useEffect,很少用 useLayoutEffect

useEffect的运行规则:组件首次渲染工作完成并将真实dom生成到页面以后 将对应的回调函数推入异步队列等待执行

useLayoutEffect的运行规则:组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入同步队列等待执行【意味着useLayouEffect会完

全阻塞后续的更新工作,也就是说,组件首次渲染完成后,会立即执行useLayoutEffect的回调函数,而不用等待主线程中其他未完成的工作】

简单的理解就是,useLayoutEffect是立即执行,useEffect是推入异步队列,最终执行时间不确定

useLayoutEffect是React提供的一个Hook函数,它和useEffect非常相似,都是用来处理副作用的,但是它们的执行时机不同。

useLayoutEffect的执行时机是在DOM更新之后、浏览器绘制之前,也就是在useEffect之前执行。这个时机非常适合用来读取DOM节点的尺寸、位置等信息,因为这些信息需要在浏览器绘制之前就要计算好,否则可能会导致页面闪烁等问题。

useEffect的执行时机是在DOM更新之后、浏览器绘制之后,也就是在useLayoutEffect之后执行。这个时机适合用来处理一些不需要立即执行的副作用,比如发送网络请求、修改全局状态等。

useLayoutEffect的原理和useEffect类似,都是通过在组件渲染时注册副作用函数,然后在组件卸载时执行清除函数来实现的。不同的是,useLayoutEffect会在浏览器绘制之前执行副作用函数,因此它会阻塞浏览器的渲染过程,可能会导致性能问题。

因此,一般情况下,我们应该优先使用useEffect,只有在需要读取DOM节点信息时才使用useLayoutEffect。

上次更新于:

+ \ No newline at end of file diff --git a/React/React Router v6.html b/React/React Router v6.html index 2410c0d..5fa5c83 100644 --- a/React/React Router v6.html +++ b/React/React Router v6.html @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

React Router v6

快速复习

  • BrowerRouter
  • NavLink vs Link (可以传递参数)
  • Routes && Route
  • useNavigate 强制路由跳转
  • Link && Navigate && useNavigate 通过 state 传递状态,通过 useLocation 接收
  • 通过 useParams 接收 URL 参数

一、概述

如何安装

pnpm add react-router-dom@6

概念

  • react-router:为 React 应用提供了路由的核心功能;
  • react-router-dom:基于 react-router,加入了在浏览器运行环境下的一些功能。

二、基本使用

BrowserRouter

要想在 React 应用中使用 React Router,就需要在 React 项目的根文件(index.tsx)中导入 Router 组件

javascript
import { StrictMode } from "react";
+    
Skip to content
On this page

React Router v6

快速复习

  • BrowerRouter
  • NavLink vs Link (可以传递参数)
  • Routes && Route
  • useNavigate 强制路由跳转
  • Link && Navigate && useNavigate 通过 state 传递状态,通过 useLocation 接收
  • 通过 useParams 接收 URL 参数

一、概述

如何安装

pnpm add react-router-dom@6

概念

  • react-router:为 React 应用提供了路由的核心功能;
  • react-router-dom:基于 react-router,加入了在浏览器运行环境下的一些功能。

二、基本使用

BrowserRouter

要想在 React 应用中使用 React Router,就需要在 React 项目的根文件(index.tsx)中导入 Router 组件

javascript
import { StrictMode } from "react";
 import * as ReactDOMClient from "react-dom/client";
 import { BrowserRouter } from "react-router-dom";
 
@@ -354,9 +354,9 @@
       {routes}
     《/div》
   );
-}`
- +}`

上次更新于:

+ \ No newline at end of file diff --git "a/React/React \344\272\213\344\273\266\346\234\272\345\210\266.html" "b/React/React \344\272\213\344\273\266\346\234\272\345\210\266.html" index 424d495..8ce5b56 100644 --- "a/React/React \344\272\213\344\273\266\346\234\272\345\210\266.html" +++ "b/React/React \344\272\213\344\273\266\346\234\272\345\210\266.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

React 事件机制

前置知识

  • 事件冒泡
  • 事件委托
  • 函数式编程「React 官方是十分推崇函数式编程的」
    • 纯函数
    • 了解什么是副作用
    • 不可变状态
      • 可以使用一些npm包,使用可变的代码来操作状态
  • 拓展运算符 ...

React 事件机制「原理」

事件机制

事件机制,fiber架构,react调度机制,优先级概念,commit 以及 render两个阶段 以及hooks原理 -> 重难点

  • React的JSX 写的代码不是真实dom,经过babel编译成React.createElement,ReactElement 之后被react执行,从而在页面中生成真实dom

  • 所有的标签属性都不是真实的dom属性,而是会被react进行处理 最终反应到真实dom身上去

    • React中的属性分为标签属性和组件属性
  • react的jsx上的标签不是真实标签,它会被babel编译为ReactElement,ReactDom再将ReactElement转换为真实dom

  • React 事件和原生的事件行为差不多,基本上原生事件能做到的事情,React 都复刻了一遍

  • react 为了节约性能以及实现动态监听,react使用事件委托的机制

    假设我现在有1000个dom,与其我绑定1000个dom事件

    不如我给这1000个dom的父级绑定事件,给父级绑定的话 只需要绑定一个事件就ok了,event.target--—>指向真正触发事件的元素

    react把事件绑定在了对应的 root 元素上,当某个真实dom触发事件以后,dom事件会随着事件冒泡 一直冒到root元素上,root元素对应的事件处理函数又可以通过

    event.target知道真正触发事件的元素是谁,进而执行处理

    那其实就意味着 对应的jsx所转化的真实dom身上不会绑定任何的真实事件,react会把jsx上所书写的对应的和事件有关的标签属性收集起来 找个地方存起来

    最终真实dom在页面生成,当我们点击对应的真实dom时 事件会冒泡 事件冒泡是不需要绑定真实dom事件也会冒泡的 最终会冒泡到root 然后root来进行事件的处理

事件池机制

React 17以上的版本取消了事件池机制

事件池机制的本质就是保存对event的引用而不是重新创建另一个event

  • react里的标签属性事件 对应的event 是哪来的??【react 捏给你的,和真实dom没有半毛钱关系】
  • 在16.8以及之前的版本 react为了更好的性能考虑会尝试重用事件
  • react会保存引用 只是修改对应的属性值

⚠️:

  • 基于React的事件池机制,只要公司用的还是17以下的代码,都要注意不要在异步环境下访问事件源对象的属性,例如在调试中发现event.target===null这种情况,就要考虑是否不小心触发了事件池机制
  • 使用 e. persist() 取消事件池机制

受控组件和非受控组件

React中,受控和非受控 我们只在表单组件中去谈【因为只有表单组件才涉及到交互】

一个组件如果不涉及到交互,他就是一个渲染组件 UI 组件,不用考虑受控和非受控的问题

在表单组件中,判定受控和非受控的标准是什么?【受控标签属性的植入】【标签受控展性 —也可以理解为是一个标记,出现了这个标记则该组件为受控组件]

在input里面 受控标签属性 是 value 属性

当你给input设置上value的值以后,input框里面出现什么文字 不再由用户输入说了算,而是由你的这个value值决定

所有的表单组件都分为受控和非受控:checkbox,radio

举个例子:

js
import { useState } from "react";
+    
Skip to content
On this page

React 事件机制

前置知识

  • 事件冒泡
  • 事件委托
  • 函数式编程「React 官方是十分推崇函数式编程的」
    • 纯函数
    • 了解什么是副作用
    • 不可变状态
      • 可以使用一些npm包,使用可变的代码来操作状态
  • 拓展运算符 ...

React 事件机制「原理」

事件机制

事件机制,fiber架构,react调度机制,优先级概念,commit 以及 render两个阶段 以及hooks原理 -> 重难点

  • React的JSX 写的代码不是真实dom,经过babel编译成React.createElement,ReactElement 之后被react执行,从而在页面中生成真实dom

  • 所有的标签属性都不是真实的dom属性,而是会被react进行处理 最终反应到真实dom身上去

    • React中的属性分为标签属性和组件属性
  • react的jsx上的标签不是真实标签,它会被babel编译为ReactElement,ReactDom再将ReactElement转换为真实dom

  • React 事件和原生的事件行为差不多,基本上原生事件能做到的事情,React 都复刻了一遍

  • react 为了节约性能以及实现动态监听,react使用事件委托的机制

    假设我现在有1000个dom,与其我绑定1000个dom事件

    不如我给这1000个dom的父级绑定事件,给父级绑定的话 只需要绑定一个事件就ok了,event.target--—>指向真正触发事件的元素

    react把事件绑定在了对应的 root 元素上,当某个真实dom触发事件以后,dom事件会随着事件冒泡 一直冒到root元素上,root元素对应的事件处理函数又可以通过

    event.target知道真正触发事件的元素是谁,进而执行处理

    那其实就意味着 对应的jsx所转化的真实dom身上不会绑定任何的真实事件,react会把jsx上所书写的对应的和事件有关的标签属性收集起来 找个地方存起来

    最终真实dom在页面生成,当我们点击对应的真实dom时 事件会冒泡 事件冒泡是不需要绑定真实dom事件也会冒泡的 最终会冒泡到root 然后root来进行事件的处理

事件池机制

React 17以上的版本取消了事件池机制

事件池机制的本质就是保存对event的引用而不是重新创建另一个event

  • react里的标签属性事件 对应的event 是哪来的??【react 捏给你的,和真实dom没有半毛钱关系】
  • 在16.8以及之前的版本 react为了更好的性能考虑会尝试重用事件
  • react会保存引用 只是修改对应的属性值

⚠️:

  • 基于React的事件池机制,只要公司用的还是17以下的代码,都要注意不要在异步环境下访问事件源对象的属性,例如在调试中发现event.target===null这种情况,就要考虑是否不小心触发了事件池机制
  • 使用 e. persist() 取消事件池机制

受控组件和非受控组件

React中,受控和非受控 我们只在表单组件中去谈【因为只有表单组件才涉及到交互】

一个组件如果不涉及到交互,他就是一个渲染组件 UI 组件,不用考虑受控和非受控的问题

在表单组件中,判定受控和非受控的标准是什么?【受控标签属性的植入】【标签受控展性 —也可以理解为是一个标记,出现了这个标记则该组件为受控组件]

在input里面 受控标签属性 是 value 属性

当你给input设置上value的值以后,input框里面出现什么文字 不再由用户输入说了算,而是由你的这个value值决定

所有的表单组件都分为受控和非受控:checkbox,radio

举个例子:

js
import { useState } from "react";
 
 export default function Test(){
   const [val,setVal] = useState("")
@@ -63,9 +63,9 @@
     }
   }
 },`
-

useRequestLoadingDispatch

定义

`import { useState } from "react";

export default function useRequestLoadingDispatch() { const [loading,setLoading] = useState(false) const excuteRequest = async (promiseFn) => { setLoading(true) await promiseFn() setLoading(false) }

return { loading, excuteRequest } }`

使用

`import { getStudentList } from '../../request'; import { useState,useEffect } from 'react'; import StudentItem from './components/StudentItem'; import useRequestLoadingDispatch from '../../hooks/useRequestLoadingDispatcher';

export default function StudentList(){ const [list,setList] = useState([]) const {loading,excuteRequest} = useRequestLoadingDispatch()

const fetchData = async ()=>{ excuteRequest(async ()=>{ const res = await getStudentList() setList(res.data) }) }

useEffect(() => { fetchData() },[])

return (

{ loading ?

加载ing

: list.map(student=><StudentItem {...student}/>) }
) }`

useForceUpdate

强制更新

`import { useState } from "react";

export default function useForceUpdate() { const [_,setVal] = useState({}) const forceUpdate = ()=>{ setVal({}) } return { _, forceUpdate } }`

useWindowScrollWatcher

`import { useEffect } from "react";

export default function useWindowScrollWatcher(scrollCallback) { useEffect(() => { document.addEventListener('scroll',scrollCallback) return () => { document.removeEventListener('scroll',scrollCallback) } }) }`

- +

useRequestLoadingDispatch

定义

`import { useState } from "react";

export default function useRequestLoadingDispatch() { const [loading,setLoading] = useState(false) const excuteRequest = async (promiseFn) => { setLoading(true) await promiseFn() setLoading(false) }

return { loading, excuteRequest } }`

使用

`import { getStudentList } from '../../request'; import { useState,useEffect } from 'react'; import StudentItem from './components/StudentItem'; import useRequestLoadingDispatch from '../../hooks/useRequestLoadingDispatcher';

export default function StudentList(){ const [list,setList] = useState([]) const {loading,excuteRequest} = useRequestLoadingDispatch()

const fetchData = async ()=>{ excuteRequest(async ()=>{ const res = await getStudentList() setList(res.data) }) }

useEffect(() => { fetchData() },[])

return (

{ loading ?

加载ing

: list.map(student=><StudentItem {...student}/>) }
) }`

useForceUpdate

强制更新

`import { useState } from "react";

export default function useForceUpdate() { const [_,setVal] = useState({}) const forceUpdate = ()=>{ setVal({}) } return { _, forceUpdate } }`

useWindowScrollWatcher

`import { useEffect } from "react";

export default function useWindowScrollWatcher(scrollCallback) { useEffect(() => { document.addEventListener('scroll',scrollCallback) return () => { document.removeEventListener('scroll',scrollCallback) } }) }`

上次更新于:

+ \ No newline at end of file diff --git "a/React/React \345\237\272\347\241\200\345\255\246\344\271\240.html" "b/React/React \345\237\272\347\241\200\345\255\246\344\271\240.html" index 13b3f78..5a49734 100644 --- "a/React/React \345\237\272\347\241\200\345\255\246\344\271\240.html" +++ "b/React/React \345\237\272\347\241\200\345\255\246\344\271\240.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

React 基础学习

推荐阅读:https://juejin.cn/post/7118937685653192735

起步

本质

在React jsx文件中每一次对组件的使用<App />都是在直接调用对应的组件函数

  • component state组件状态,实际上直接翻译过来不准确,因此应该理解为“组件数据”

  • 页面渲染 === 组件执行 / 重新渲染 === 函数组件的重新执行

  • 我们最终执行的代码是通过babel编译以后的代码

    React. createElement("span",{},count)--->调用document上面的一些方法去改变真实dom的显示状态

    count->0 如果我们想要页面里发生一点显示效果的变化,我们得让React.createElement这段代码重复执行

  • 如果想尝试让函数组建重新渲染,只有两种方式

    • 组件状态发生变化,注意此处是指通过useState定义的状态
    • 父组件重新渲染

解决了什么问题

  • 组件的逻辑复用
  • 解决了mixins混入的数据来源不清晰,HOC高阶组件的嵌套问题
  • 让函数组件拥有了类组件的特性,例如组件内的状态、生命周期等

理解脚手架

脚手架:在工程学里,脚手架提供了一系列预设,让施工者无需再考虑除了建造以外的其他外部问题,在编程学里,脚手架同样提供了一系列的预设,

让开发者无需再考虑除了自身业务代码以外的其他外部问题:

  1. 我们要发生产环境,代码压缩,用webpack,脚手架:我直接给你集成webpack,不仅如此 我还帮你把webpack的这个配置写好了 你不用管

  2. 组件化,我该怎么划分我的文件夹??图片该放哪 我的组件又该放哪 脚手架:你别管,我也处理好了 你只需要用我这个脚手架 我直接帮你把项

目目录直接生成

  1. 网络:跨域———>浏览器他不会让你随便请求别的服务器的数据的,如果不是同域名 同协议同端口 浏览器会进行拦截 我们就要跨域,那这个时

候我们就要去搭建临时的跨域服务器,脚手架:小意思辣 你别管 我来

  1. ...

规范

React对组件有一些要求:

  1. 组件名必须大写

  2. React组件必须要返回可以渲染的东西

  • null
  • React元素
  • 组件
  • 可以被迭代的对象【包括数组,set,map..】,只要一个对象具备迭代接口,那他就可以被渲染
  • 状态 以及属性。

组件属性

函数参数是让一个函数变得更加灵活的关键,同样,组件属性是让一个组件变得更加灵活的关键,组件属性和函数参数的原理大差不差,你给组件传递属性也就是意味着你在给对应的组件函数传递参数

但凡一个组件要重新渲染,都必须是满足以下两个条件之一:

  • 自身状态发生变化
  • 父组件重新渲染,自身就会重新渲染✨

React中的属性分为 标签属性 以及 组件属性

  • 传递给组件的自然而然就是组件属性
  • 传递给]SX的标签元素的属性就叫做标签属性【标签元素:在htmL中有明确的对标元素就叫做标签元素】,标签属性会被React自行处理对应到底层的事件或者属性
    • JSX最终都会被babel转换为React.createElement
      • 如果是标签元素,则会将对应的标签属性全部传递给React.createElement,然后React内部会自行处理
      • 如果是组件元素,他的这些组件属性会被作为参数传递给对应组件函数

hooks分类

https://km.sankuai.com/api/file/cdn/1728403390/43950220759?contentType=1&isNewContent=false

自变量

  • useState
  • useReducer
  • useContext

因变量,含有依赖项

  • useEffect ==> watchEffect
  • useMemo ==> computed计算属性
  • useCallback ==> 减少函数创建的次数

其他

  • useRef

useState

在我们使用usestate的时候 【我们为什么需要使用usestate来构建状态?因为使用usestate构建的状态会返回一个更新状态的函数,当调用这个

函数去修改状态时,React会通知组件去进行重新渲染】

  • 组件状态的更新是异步的【这意味着当更改状态的函数执行以后我们没有办法同步的马上得到他更新以后的值】

    那我如何拿到最新的状态呢?useEffect / useLayoutEffect

useState在调用的时候,可以给具体值,也可以给一个函数,这个函数的返回值被作为初始值,但是不推荐这种写法

为什么?✨

拿计数器Counter组件举例,

  • Counter函数的重新执行意味着Counter函数内部的代码要全部执行一遍,包括useState()
  • 但是useState内部对初始化操作有区分,只要不是在该函数组件内第一次调用useState,就不会进行初始化操作
  • 不会进行初始化操作的意思是不会将你传递给useState的值去重新赋值,也就意味着如果你传递给useState的是一个函数,这个函数的计算只在初始化时有意义,后续函数计算的结果没有意义✨

请看一段伪代码,便于理解useState的工作原理

function useState(initialState) { let state; if (isFirstIn) { state = initialState } const dispatch = (newState)=>{ state = newState render() //重新渲染 } return [state, dispatch] }

推荐写法

[state, setState] = useState(initialValue)

  • initialValue写成具体值而不是一个函数
  • setState(prev=>{return something}),setState函数的参数推荐写成函数的形式而不是具体值

总结

  • 组件状态的更新是异步的,意味着当更改状态的函数执行以后我们没有办法同步的马上得到他更新以后的值,那我如何拿到最新的状态呢?useEffect / uselLayoutEffect
  • usestate在调用的时候可以传递函数,也可以传递具体的值,如果传递的是函数,则会直接将函数的返回值作为初始化状态,但是虽然在初始化的时候他允许你传递函数,我们也尽量不要传递函数,因为初始化工作只会进行一次
  • usestate会返回一个数组,数组里面有两个成员
    • 以初始化为值的变量
    • 修改该变量的函數,这个函数的调用会造成函数组件的重新运行
      • 调用该函数的时候可以直接传递一个值,也可以传递一个函数,如果你传递一个函数进去,则React会将上一次的状态传递给你,帮助你进行计算;如果你传递的是一个函数,React会将这个函数放到一个队列里等待执行,那也就是如果我们想每次都稳稳的拿到上一次的值,我们得写成一个函数
      • 推荐写成函数的形式
      • 状态的更新是批量进行的,而不是一个一个的进行,这是为了性能考虑,成为auto batching

useEffect

函数副作用

副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用。

常见副作用

  • 数据请求 ajax 发送手动修改 domlocalstorage 操作......useEffect 函数的作用就是为 react 函数组件提供副作用处理的!

案例1:useEffect(fn)

默认是全部属性的副作用都会调用该函数,很少使用

javascript
import React, { useEffect, useState } from "react";
+    
Skip to content
On this page

React 基础学习

推荐阅读:https://juejin.cn/post/7118937685653192735

起步

本质

在React jsx文件中每一次对组件的使用<App />都是在直接调用对应的组件函数

  • component state组件状态,实际上直接翻译过来不准确,因此应该理解为“组件数据”

  • 页面渲染 === 组件执行 / 重新渲染 === 函数组件的重新执行

  • 我们最终执行的代码是通过babel编译以后的代码

    React. createElement("span",{},count)--->调用document上面的一些方法去改变真实dom的显示状态

    count->0 如果我们想要页面里发生一点显示效果的变化,我们得让React.createElement这段代码重复执行

  • 如果想尝试让函数组建重新渲染,只有两种方式

    • 组件状态发生变化,注意此处是指通过useState定义的状态
    • 父组件重新渲染

解决了什么问题

  • 组件的逻辑复用
  • 解决了mixins混入的数据来源不清晰,HOC高阶组件的嵌套问题
  • 让函数组件拥有了类组件的特性,例如组件内的状态、生命周期等

理解脚手架

脚手架:在工程学里,脚手架提供了一系列预设,让施工者无需再考虑除了建造以外的其他外部问题,在编程学里,脚手架同样提供了一系列的预设,

让开发者无需再考虑除了自身业务代码以外的其他外部问题:

  1. 我们要发生产环境,代码压缩,用webpack,脚手架:我直接给你集成webpack,不仅如此 我还帮你把webpack的这个配置写好了 你不用管

  2. 组件化,我该怎么划分我的文件夹??图片该放哪 我的组件又该放哪 脚手架:你别管,我也处理好了 你只需要用我这个脚手架 我直接帮你把项

目目录直接生成

  1. 网络:跨域———>浏览器他不会让你随便请求别的服务器的数据的,如果不是同域名 同协议同端口 浏览器会进行拦截 我们就要跨域,那这个时

候我们就要去搭建临时的跨域服务器,脚手架:小意思辣 你别管 我来

  1. ...

规范

React对组件有一些要求:

  1. 组件名必须大写

  2. React组件必须要返回可以渲染的东西

  • null
  • React元素
  • 组件
  • 可以被迭代的对象【包括数组,set,map..】,只要一个对象具备迭代接口,那他就可以被渲染
  • 状态 以及属性。

组件属性

函数参数是让一个函数变得更加灵活的关键,同样,组件属性是让一个组件变得更加灵活的关键,组件属性和函数参数的原理大差不差,你给组件传递属性也就是意味着你在给对应的组件函数传递参数

但凡一个组件要重新渲染,都必须是满足以下两个条件之一:

  • 自身状态发生变化
  • 父组件重新渲染,自身就会重新渲染✨

React中的属性分为 标签属性 以及 组件属性

  • 传递给组件的自然而然就是组件属性
  • 传递给]SX的标签元素的属性就叫做标签属性【标签元素:在htmL中有明确的对标元素就叫做标签元素】,标签属性会被React自行处理对应到底层的事件或者属性
    • JSX最终都会被babel转换为React.createElement
      • 如果是标签元素,则会将对应的标签属性全部传递给React.createElement,然后React内部会自行处理
      • 如果是组件元素,他的这些组件属性会被作为参数传递给对应组件函数

hooks分类

https://km.sankuai.com/api/file/cdn/1728403390/43950220759?contentType=1&isNewContent=false

自变量

  • useState
  • useReducer
  • useContext

因变量,含有依赖项

  • useEffect ==> watchEffect
  • useMemo ==> computed计算属性
  • useCallback ==> 减少函数创建的次数

其他

  • useRef

useState

在我们使用usestate的时候 【我们为什么需要使用usestate来构建状态?因为使用usestate构建的状态会返回一个更新状态的函数,当调用这个

函数去修改状态时,React会通知组件去进行重新渲染】

  • 组件状态的更新是异步的【这意味着当更改状态的函数执行以后我们没有办法同步的马上得到他更新以后的值】

    那我如何拿到最新的状态呢?useEffect / useLayoutEffect

useState在调用的时候,可以给具体值,也可以给一个函数,这个函数的返回值被作为初始值,但是不推荐这种写法

为什么?✨

拿计数器Counter组件举例,

  • Counter函数的重新执行意味着Counter函数内部的代码要全部执行一遍,包括useState()
  • 但是useState内部对初始化操作有区分,只要不是在该函数组件内第一次调用useState,就不会进行初始化操作
  • 不会进行初始化操作的意思是不会将你传递给useState的值去重新赋值,也就意味着如果你传递给useState的是一个函数,这个函数的计算只在初始化时有意义,后续函数计算的结果没有意义✨

请看一段伪代码,便于理解useState的工作原理

function useState(initialState) { let state; if (isFirstIn) { state = initialState } const dispatch = (newState)=>{ state = newState render() //重新渲染 } return [state, dispatch] }

推荐写法

[state, setState] = useState(initialValue)

  • initialValue写成具体值而不是一个函数
  • setState(prev=>{return something}),setState函数的参数推荐写成函数的形式而不是具体值

总结

  • 组件状态的更新是异步的,意味着当更改状态的函数执行以后我们没有办法同步的马上得到他更新以后的值,那我如何拿到最新的状态呢?useEffect / uselLayoutEffect
  • usestate在调用的时候可以传递函数,也可以传递具体的值,如果传递的是函数,则会直接将函数的返回值作为初始化状态,但是虽然在初始化的时候他允许你传递函数,我们也尽量不要传递函数,因为初始化工作只会进行一次
  • usestate会返回一个数组,数组里面有两个成员
    • 以初始化为值的变量
    • 修改该变量的函數,这个函数的调用会造成函数组件的重新运行
      • 调用该函数的时候可以直接传递一个值,也可以传递一个函数,如果你传递一个函数进去,则React会将上一次的状态传递给你,帮助你进行计算;如果你传递的是一个函数,React会将这个函数放到一个队列里等待执行,那也就是如果我们想每次都稳稳的拿到上一次的值,我们得写成一个函数
      • 推荐写成函数的形式
      • 状态的更新是批量进行的,而不是一个一个的进行,这是为了性能考虑,成为auto batching

useEffect

函数副作用

副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用。

常见副作用

  • 数据请求 ajax 发送手动修改 domlocalstorage 操作......useEffect 函数的作用就是为 react 函数组件提供副作用处理的!

案例1:useEffect(fn)

默认是全部属性的副作用都会调用该函数,很少使用

javascript
import React, { useEffect, useState } from "react";
 
 // 函数组件
 function Sub () {
@@ -69,9 +69,9 @@
       <button onClick={() => setName('test')}>{name}</button>
     </>
   )
-}

清理副作用✨

使用场景:在组件被销毁时,如果有些副作用操作需要被清理,就可以使用这种方式(比如常见的 定时器)。

如果想要清理副作用,可以在副作用函数中的末尾 return 一个新的函数,在新的函数中编写清理副作用的逻辑,注意执行时机为:

  • 组件卸载时自动执行
  • 组件更新时,下一个 useEffect 副作用函数执行之前自动执行

`import { useEffect, useState } from "react"

const App = () => { const [count, setCount] = useState(0) useEffect(() => { const timer = setInterval(() => { setCount(count + 1) }, 1000) return () => { // 用来清理副作用的事情 clearInterval(timer) } }, [count]) return (

{count}
) }

export default App`

- +}

清理副作用✨

使用场景:在组件被销毁时,如果有些副作用操作需要被清理,就可以使用这种方式(比如常见的 定时器)。

如果想要清理副作用,可以在副作用函数中的末尾 return 一个新的函数,在新的函数中编写清理副作用的逻辑,注意执行时机为:

  • 组件卸载时自动执行
  • 组件更新时,下一个 useEffect 副作用函数执行之前自动执行

`import { useEffect, useState } from "react"

const App = () => { const [count, setCount] = useState(0) useEffect(() => { const timer = setInterval(() => { setCount(count + 1) }, 1000) return () => { // 用来清理副作用的事情 clearInterval(timer) } }, [count]) return (

{count}
) }

export default App`

上次更新于:

+ \ No newline at end of file diff --git "a/React/\346\241\206\346\236\266\346\212\275\350\261\241.html" "b/React/\346\241\206\346\236\266\346\212\275\350\261\241.html" index c859209..546808a 100644 --- "a/React/\346\241\206\346\236\266\346\212\275\350\261\241.html" +++ "b/React/\346\241\206\346\236\266\346\212\275\350\261\241.html" @@ -10,18 +10,18 @@ - + -
Skip to content
On this page

框架抽象

层级划分(三层):

  • 应用层面
    • 组件层面
      • 节点层面

工作原理

UI = f(state)

视图 = 框架内部运行机制(状态)

框架内部的运行机制根据状态渲染视图

不同框架的区别

不同框架的区别主要是更新粒度的区别

1. 节点级更新粒度 - Svelte

关键词: 预编译 细粒度更新

原理:

  1. 将状态变化可能导致的节点变化编译为具体方法
  2. 监听状态变化
  3. 当交互导致状态变化后直接调用具体方法, 改变对应视图

例子:

<h1 on:click={handleClick}>{}count</h1>
  1. 将cout状态变化可能导致h1内的文本节点变化, 编译为update方法, 即:预编译过程

    // 每次调用update方法, 如果count状态变化, 就更新视图中对应的文本节点
    +    
    Skip to content
    On this page

    框架抽象

    层级划分(三层):

    • 应用层面
      • 组件层面
        • 节点层面

    工作原理

    UI = f(state)

    视图 = 框架内部运行机制(状态)

    框架内部的运行机制根据状态渲染视图

    不同框架的区别

    不同框架的区别主要是更新粒度的区别

    1. 节点级更新粒度 - Svelte

    关键词: 预编译 细粒度更新

    原理:

    1. 将状态变化可能导致的节点变化编译为具体方法
    2. 监听状态变化
    3. 当交互导致状态变化后直接调用具体方法, 改变对应视图

    例子:

    <h1 on:click={handleClick}>{}count</h1>
    1. 将cout状态变化可能导致h1内的文本节点变化, 编译为update方法, 即:预编译过程

      // 每次调用update方法, 如果count状态变化, 就更新视图中对应的文本节点
        function update(ctx, [dirty]) {
          if (dirty & /*count*/ 1) {
            set_data_dev(t0, /*count*/ ctx[0]);
          }
      - }
    2. 监听状态变化, 采用"发布订阅"的设计模式, 通过这种方式, 框架能对每个状态变化作出反应, 即实现细粒度更新

      1. 订阅: 每当创建一个状态后, 会为该状态维护一个订阅该状态变化的表, 所有需要监听该状态变化的回调函数都会在该表中注册
      2. 发布: 每当状态变化, 会遍历这个表, 将"状态变了"这一消息发布出去, 每个订阅该状态的回调函数都会接受到通知并执行

    代表: Svelte, Solid.js

    2. 应用级更新框架 - React

    关键词: 虚拟DOM

    节点级框架需要监听状态的变化, 应用级框架则不关心状态的变化, 因为应用级框架中, 任何一个状态变化, 都会创建一颗完整的虚拟DOM树, 框架会通过前后虚拟DOM的对比找到变化的部分, 最终将变化的状态更新到视图

    1. 状态变化
    2. 创建一棵完整的虚拟DOM树
    3. 比较, 并将变化的部分更新到视图

    3. 组件级更新框架 - Vue

    关键词:

    Vue2 虚拟DOM+细粒度更新

    Vue3 虚拟DOM+预编译+细粒度更新

    1. 状态变化
    2. 针对组件层面, 创建一棵组件级虚拟DOM树
    3. 比较, 并将变化的部分更新到视图
    - + }
  2. 监听状态变化, 采用"发布订阅"的设计模式, 通过这种方式, 框架能对每个状态变化作出反应, 即实现细粒度更新

    1. 订阅: 每当创建一个状态后, 会为该状态维护一个订阅该状态变化的表, 所有需要监听该状态变化的回调函数都会在该表中注册
    2. 发布: 每当状态变化, 会遍历这个表, 将"状态变了"这一消息发布出去, 每个订阅该状态的回调函数都会接受到通知并执行

代表: Svelte, Solid.js

2. 应用级更新框架 - React

关键词: 虚拟DOM

节点级框架需要监听状态的变化, 应用级框架则不关心状态的变化, 因为应用级框架中, 任何一个状态变化, 都会创建一颗完整的虚拟DOM树, 框架会通过前后虚拟DOM的对比找到变化的部分, 最终将变化的状态更新到视图

  1. 状态变化
  2. 创建一棵完整的虚拟DOM树
  3. 比较, 并将变化的部分更新到视图

3. 组件级更新框架 - Vue

关键词:

Vue2 虚拟DOM+细粒度更新

Vue3 虚拟DOM+预编译+细粒度更新

  1. 状态变化
  2. 针对组件层面, 创建一棵组件级虚拟DOM树
  3. 比较, 并将变化的部分更新到视图

上次更新于:

+ \ No newline at end of file diff --git a/TS/Quick Start.html b/TS/Quick Start.html index eff75f9..8dc731f 100644 --- a/TS/Quick Start.html +++ b/TS/Quick Start.html @@ -10,13 +10,13 @@ - + -
Skip to content
On this page

Quick Start

TS和JS的区别

TS和JS的区别是,TS是JS的一个超集,也就是说,JS有的TS都有,而TS还有一些JS没有的特性。最大的特性就是TS提供了类型系统³⁵,可以在编译时检查代码中的错误和不匹配。而JS是一个弱类型语言,只能在运行时发现错误。

另外,TS还支持ES6标准,可以编译成任意版本的JS代码³,从而解决不同浏览器和平台的兼容问题, TS还能获得更好的代码提示

TS中type和interface的区别

TS中type和interface的区别是一个常见的问题:

  • type和interface都可以用来定义类型,但type还可以用来定义别名、联合类型、交叉类型等。
  • interface可以被多次声明并合并,而type只能被声明一次。
  • interface可以继承其他interface或class,而type不能继承class。
  • interface更符合面向对象的思想,而type更适合函数式编程。

我来给你举几个例子。

  • type可以定义别名,比如type Name = string,就是把string类型取一个别名叫Name。interface不能这样做。
  • type可以定义联合类型,比如type Color = 'red' | 'green' | 'blue',就是把三个字符串字面量类型合并成一个类型叫Color。interface不能这样做。
  • type可以定义交叉类型,比如type Person = {name: string} & {age: number},就是把两个对象类型合并成一个类型叫Person。interface不能这样做。
  • interface可以被多次声明并合并,比如interface Animal {name: string}interface Animal {age: number}会被合并成一个接口Animal。type只能被声明一次。
  • interface可以继承其他interface或class,比如interface Dog extends Animal {bark(): void}或者interface Car extends Vehicle {drive(): void}。type不能继承class。

我再尝试解释一下:

type和interface都是用来定义类型的,但是它们有不同的用途和特点。

type更像是一个工具,它可以让你给一个已有的类型取一个新的名字,或者把多个类型组合成一个新的类型。这样可以让你写出更简洁和灵活的代码。

interface更像是一个契约,它可以让你规定一个对象或函数必须具备哪些属性或方法。这样可以让你写出更清晰和安全的代码。

type和interface之间有一些重叠的功能,比如都可以定义对象类型。但是它们也有一些互斥的功能,比如type可以定义别名、联合类型、交叉类型等,而interface可以被多次声明并合并、继承其他interface或class等。

- +
Skip to content
On this page

Quick Start

TS和JS的区别

TS和JS的区别是,TS是JS的一个超集,也就是说,JS有的TS都有,而TS还有一些JS没有的特性。最大的特性就是TS提供了类型系统³⁵,可以在编译时检查代码中的错误和不匹配。而JS是一个弱类型语言,只能在运行时发现错误。

另外,TS还支持ES6标准,可以编译成任意版本的JS代码³,从而解决不同浏览器和平台的兼容问题, TS还能获得更好的代码提示

TS中type和interface的区别

TS中type和interface的区别是一个常见的问题:

  • type和interface都可以用来定义类型,但type还可以用来定义别名、联合类型、交叉类型等。
  • interface可以被多次声明并合并,而type只能被声明一次。
  • interface可以继承其他interface或class,而type不能继承class。
  • interface更符合面向对象的思想,而type更适合函数式编程。

我来给你举几个例子。

  • type可以定义别名,比如type Name = string,就是把string类型取一个别名叫Name。interface不能这样做。
  • type可以定义联合类型,比如type Color = 'red' | 'green' | 'blue',就是把三个字符串字面量类型合并成一个类型叫Color。interface不能这样做。
  • type可以定义交叉类型,比如type Person = {name: string} & {age: number},就是把两个对象类型合并成一个类型叫Person。interface不能这样做。
  • interface可以被多次声明并合并,比如interface Animal {name: string}interface Animal {age: number}会被合并成一个接口Animal。type只能被声明一次。
  • interface可以继承其他interface或class,比如interface Dog extends Animal {bark(): void}或者interface Car extends Vehicle {drive(): void}。type不能继承class。

我再尝试解释一下:

type和interface都是用来定义类型的,但是它们有不同的用途和特点。

type更像是一个工具,它可以让你给一个已有的类型取一个新的名字,或者把多个类型组合成一个新的类型。这样可以让你写出更简洁和灵活的代码。

interface更像是一个契约,它可以让你规定一个对象或函数必须具备哪些属性或方法。这样可以让你写出更清晰和安全的代码。

type和interface之间有一些重叠的功能,比如都可以定义对象类型。但是它们也有一些互斥的功能,比如type可以定义别名、联合类型、交叉类型等,而interface可以被多次声明并合并、继承其他interface或class等。

上次更新于:

+ \ No newline at end of file diff --git "a/TS/\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.html" "b/TS/\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.html" index b4025c1..27d2e88 100644 --- "a/TS/\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.html" +++ "b/TS/\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

产生

出现的原因:

  • 随着算力的不断增大,传统代码过程式编程的思想会使得代码总体难以维护
  • 提出函数式编程的思想,试图从思维上让程序员进化

三个境界:

  1. 用函数写程序
  2. 实现最小粒度的函数封装,组合,复用(积木逻辑
    • 另一种理解:每个函数只做一件事,达到最小粒度
    • 函数没有副作用:便于测试
  3. 对程序员思维的改变:用表达式来描述程序,而不是用过程组织计算
    • 过程式编程
    • 面向对象编程,最典型的就是java
    • 函数式编程

例子一:全排列

ts
function remove(set: Set<string>, item: string): Set<string> {
+    
Skip to content
On this page

产生

出现的原因:

  • 随着算力的不断增大,传统代码过程式编程的思想会使得代码总体难以维护
  • 提出函数式编程的思想,试图从思维上让程序员进化

三个境界:

  1. 用函数写程序
  2. 实现最小粒度的函数封装,组合,复用(积木逻辑
    • 另一种理解:每个函数只做一件事,达到最小粒度
    • 函数没有副作用:便于测试
  3. 对程序员思维的改变:用表达式来描述程序,而不是用过程组织计算
    • 过程式编程
    • 面向对象编程,最典型的就是java
    • 函数式编程

例子一:全排列

ts
function remove(set: Set<string>, item: string): Set<string> {
   const newSet = new Set<string>(...set)
   newSet.delete(item)
   return newSet
@@ -35,9 +35,9 @@
   }
   return R(new Set([...str]))
 }
-console.log(permutation('abc'));
- +console.log(permutation('abc'));

上次更新于:

+ \ No newline at end of file diff --git "a/TS/\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.html" "b/TS/\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.html" index 7c26ee9..6179c9f 100644 --- "a/TS/\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.html" +++ "b/TS/\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

泛型工具类

实现8种常见的泛型工具类

typescript
import { IPerson } from "../interfaces/IPerson";
+    
Skip to content
On this page

泛型工具类

实现8种常见的泛型工具类

typescript
import { IPerson } from "../interfaces/IPerson";
 
 // Partial<T>: 将T中所有属性变为可选项
 type TPatrial = Partial<IPerson>
@@ -63,9 +63,9 @@
   miffy: { age: 10, breed: "Persian" },
   boris: { age: 5, breed: "Maine Coon" },
   mordred: { age: 16, breed: "British Shorthair" },
-};
- +};

上次更新于:

+ \ No newline at end of file diff --git "a/TS/\347\261\273\345\236\213\347\263\273\347\273\237.html" "b/TS/\347\261\273\345\236\213\347\263\273\347\273\237.html" index 6022ded..5d70bbb 100644 --- "a/TS/\347\261\273\345\236\213\347\263\273\347\273\237.html" +++ "b/TS/\347\261\273\345\236\213\347\263\273\347\273\237.html" @@ -10,13 +10,13 @@ - + -
Skip to content
On this page

类型系统

1.类型系统

核心:never 字面量类型 原始类型 any/unknown (赋值时只能是层级低的赋值给层级高的)

如何理解呢?

通过人往高处走这句俗语来理解,层级高的赋值给层级低的,就相当于水往低处流,这是不符合ts的规范的

层级低的赋值给层级高的,就是人往高处走,是符合ts的规范的

`// never 字面量类型 原始类型 any/unknown (赋值时只能是层级低的赋值给层级高的)

let t1: boolean let bool1:true = true // t1 = bool1 bool1 = t1 // 原始类型,赋值给字面量类型 不可以

let str1:"132" = '132' // 字面量类型 let str:string = 'bart' str1 = str`

结构化类型 VS 标称类型(nominal)

ts是基于类型的结构而不是类型的名称,进行类型的检查,因此会带来一些问题

开发中需要增添额外代码,来实现 nominal 标称类型系统,允许基于类型的名称而不是其结构进行类型检查

`// 有一种特殊情况 // export type CNY = Nonimal小于number, 'CNY'> // export type USD = Nonimal小于number, 'USD'> // const addCNY = (source:CNY,target:CNY) => { // return source+target // } // const CNYCount = 100 as CNY // const USDCount = 100 as USD // console.log(addCNY(CNYCount,CNYCount)) // console.log(addCNY(CNYCount,USDCount)) // 此处会打印输出200,但显然这种情况是不合理的,不能直接把USD和CNY相加

// 使用structre来模拟nonimal可以解决这种问题 export declare class TaggedProtector小于Tag extends string> { protected __tag: Tag } export type Nonimal小于T,Tag extends string> = T & TaggedProtector小于Tag> // 交叉类型 // 用于在 TypeScript 中创建一个名义类型系统,允许基于类型的名称而不是其结构进行类型检查。

export type CNY = Nonimal小于number, 'CNY'> export type USD = Nonimal小于number, 'USD'> const addCNY = (source:CNY,target:CNY) => { return source+target } const CNYCount = 100 as CNY const USDCount = 100 as USD console.log(addCNY(CNYCount,CNYCount)) console.log(addCNY(CNYCount,USDCount))`

any,unknown,never

any vs unknown

any >>> 具有传染性

unknown >>> 类型安全

在js->ts重构时,推荐全部使用unknown,通过增添开发者的心智负担确保类型安全

never

never 小于 字面量类型 小于 原始类型 小于 any/unknown

也就是说,允许下层对上层进行赋值,例如将原始类型赋值给any/unknown,而不允许上层对下层进行赋值,例如将原始类型赋值给never类型

never的妙用

在开发中,常常会遇到需要对不同数据类型的变量,做出不同的处理这种情况;此时,可以利用never处于类型系统底层的特性,在判断的结尾(也就是else处),试图将变量赋值给never类型,如果之前已经对变量的所有类型都做了处理,判断就不会走到else处,也就不会报错,反之就会报错。

通过这种特性,可以增强代码的规范性,通过增添开发者的心智负担保证了系统的稳定性。

理解extends链

`// extends意味着什么,底层类型逐级extends上层类型,进而形成链条

// 类型系统的层级关系:构造一条extends链

// 从层级关系看类型断言的具体原理 class Base { name!: string } class DerivedBar extends Base { bar!: string } class DerivedBaz extends Base { baz!: string }

// 父类 >>> 子类的类型断言 类型系统向下转型 const bar = new Base() as DerivedBar bar.name = '23' bar.bar = '45'

// 子类 >>> 父类 // as 只用于转换存在子类型关系的两个类型 // extends通过结构化类型系统判断得到的兼容关系 const b = new DerivedBaz() as DerivedBar const b = new DerivedBaz() as Base as DerivedBar // 先向上转换,再向下转型

// 此外,还有一些其他类型系统知识,与类型编程相关性小,但同样重要 // - 协变和逆变 // - 类型控制流分析 // - 上下文类型`

基本数据类型

在Typescript中,基本数据类型有以下几种:

  1. 布尔值(Boolean):布尔值表示真或假,只有两个值:true和false。
  2. 数字(Number):数字包括整数和浮点数,例如:10,10.5等。
  3. 字符串(String):字符串表示文本数据,可以使用单引号、双引号或反引号来表示。
  4. 数组(Array):数组表示一组同类型的数据集合,可以使用[]或Array小于elementType>来定义。
  5. 元组(Tuple):元组表示已知元素数量和类型的数组,例如:[string, number]。
  6. 枚举(Enum):枚举表示具有命名的常量集合,例如:enum Color {Red, Green, Blue}。
  7. 任意值(Any):任意值表示可以被赋值为任意类型的数据,例如:let a: any = 'hello'。
  8. 空值(Void):空值表示没有任何返回值的函数,例如:function test(): void {console.log('hello') }。
  9. Null和Undefined:Null和Undefined表示没有值的数据类型,它们是所有类型的子类型。

交叉类型

交叉类型和联合类型实际上就是指类型操作符。

交叉类型可以看作是将多个类型合并为一个类型的操作。使用“&”符号来表示交叉类型。例如:

`type Person = { name: string; age: number; };

type Employee = { company: string; workId: string; };

type PersonEmployee = Person & Employee;`

在上面的例子中,定义了两个类型:Person和Employee。然后使用“&”符号将它们合并为一个新的类型:PersonEmployee。这个新的类型同时具有Person和Employee类型的所有属性。

交叉类型的使用场景包括:

  • 合并多个对象的属性⭐️
  • 合并多个函数的参数和返回值

联合类型

联合类型可以看作是将多个类型中的一个类型赋值给一个变量的操作。使用“|”符号来表示联合类型。例如:

`type Status = "success" | "error" | "loading";

function getStatusMessage(status: Status) { switch (status) { case "success": return "Operation successful"; case "error": return "Operation failed"; case "loading": return "Operation in progress"; } }`

在上面的例子中,我们定义了一个Status类型,它只能取“success”、“error”或“loading”这三个值中的一个。然后我们定义了一个函数getStatusMessage,它接受一个Status类型的参数,并根据参数的值返回不同的字符串。

联合类型的使用场景包括:

  • 定义一个变量可以是多个类型中的一种
  • 定义一个函数可以接受多个类型中的一种作为参数⭐️

泛型

本质:接受类型作为参数,并且返回一个类型

常用的泛型工具类基本上都是这种实现

为什么要有泛型?

泛型的本质:类型参数化,让我们可以通过参数来控制类型,让代码具有更强的可拓展性。

具体体现有泛型函数、泛型类以及泛型约束

常用泛型工具类

`// 1.Partial:将传入的属性变为可选项 interface IPeople { title: string; name: string; }

type MyPartial小于T> = { [P in keyof T]?:T[P] }

const people: MyPartial小于IPeople> = { title: 'Delete inactive users' };

// 2.Readonly interface Person { name: string; age: number; }

type MyReadonly小于T> = { readonly [P in keyof T]: T[P] }

const p: MyReadonly小于Person> = { name: '张三', age: 22 }

// p.name = '李四'; // 无法分配到 "name" ,因为它是只读属性

// 3.Required interface ICar { weight?:number, height?:number } type MyRequired小于T> = { [P in keyof T]-?:T[P] } const car:Required小于ICar> = { weight: 30, height: 20 }

// 4.Pick interface IPerson { name: string; age: number; salary: number }

type MyPick小于T,K extends keyof T> = { // 使用extends进行泛型约束,保证第二个参数(联合类型)肯定包含在T的keys里 [P in K]:T[P] // 通过映射对象类型,约束每一个属性成员 }

type TP = MyPick小于IPerson, 'name'|'salary'>;

const tp: TP = { // age: 22, // 对象文字可以只指定已知属性,并且“age”不在类型“TP”中 name: '张三', salary:10000 }`

- +
Skip to content
On this page

类型系统

1.类型系统

核心:never 字面量类型 原始类型 any/unknown (赋值时只能是层级低的赋值给层级高的)

如何理解呢?

通过人往高处走这句俗语来理解,层级高的赋值给层级低的,就相当于水往低处流,这是不符合ts的规范的

层级低的赋值给层级高的,就是人往高处走,是符合ts的规范的

`// never 字面量类型 原始类型 any/unknown (赋值时只能是层级低的赋值给层级高的)

let t1: boolean let bool1:true = true // t1 = bool1 bool1 = t1 // 原始类型,赋值给字面量类型 不可以

let str1:"132" = '132' // 字面量类型 let str:string = 'bart' str1 = str`

结构化类型 VS 标称类型(nominal)

ts是基于类型的结构而不是类型的名称,进行类型的检查,因此会带来一些问题

开发中需要增添额外代码,来实现 nominal 标称类型系统,允许基于类型的名称而不是其结构进行类型检查

`// 有一种特殊情况 // export type CNY = Nonimal小于number, 'CNY'> // export type USD = Nonimal小于number, 'USD'> // const addCNY = (source:CNY,target:CNY) => { // return source+target // } // const CNYCount = 100 as CNY // const USDCount = 100 as USD // console.log(addCNY(CNYCount,CNYCount)) // console.log(addCNY(CNYCount,USDCount)) // 此处会打印输出200,但显然这种情况是不合理的,不能直接把USD和CNY相加

// 使用structre来模拟nonimal可以解决这种问题 export declare class TaggedProtector小于Tag extends string> { protected __tag: Tag } export type Nonimal小于T,Tag extends string> = T & TaggedProtector小于Tag> // 交叉类型 // 用于在 TypeScript 中创建一个名义类型系统,允许基于类型的名称而不是其结构进行类型检查。

export type CNY = Nonimal小于number, 'CNY'> export type USD = Nonimal小于number, 'USD'> const addCNY = (source:CNY,target:CNY) => { return source+target } const CNYCount = 100 as CNY const USDCount = 100 as USD console.log(addCNY(CNYCount,CNYCount)) console.log(addCNY(CNYCount,USDCount))`

any,unknown,never

any vs unknown

any >>> 具有传染性

unknown >>> 类型安全

在js->ts重构时,推荐全部使用unknown,通过增添开发者的心智负担确保类型安全

never

never 小于 字面量类型 小于 原始类型 小于 any/unknown

也就是说,允许下层对上层进行赋值,例如将原始类型赋值给any/unknown,而不允许上层对下层进行赋值,例如将原始类型赋值给never类型

never的妙用

在开发中,常常会遇到需要对不同数据类型的变量,做出不同的处理这种情况;此时,可以利用never处于类型系统底层的特性,在判断的结尾(也就是else处),试图将变量赋值给never类型,如果之前已经对变量的所有类型都做了处理,判断就不会走到else处,也就不会报错,反之就会报错。

通过这种特性,可以增强代码的规范性,通过增添开发者的心智负担保证了系统的稳定性。

理解extends链

`// extends意味着什么,底层类型逐级extends上层类型,进而形成链条

// 类型系统的层级关系:构造一条extends链

// 从层级关系看类型断言的具体原理 class Base { name!: string } class DerivedBar extends Base { bar!: string } class DerivedBaz extends Base { baz!: string }

// 父类 >>> 子类的类型断言 类型系统向下转型 const bar = new Base() as DerivedBar bar.name = '23' bar.bar = '45'

// 子类 >>> 父类 // as 只用于转换存在子类型关系的两个类型 // extends通过结构化类型系统判断得到的兼容关系 const b = new DerivedBaz() as DerivedBar const b = new DerivedBaz() as Base as DerivedBar // 先向上转换,再向下转型

// 此外,还有一些其他类型系统知识,与类型编程相关性小,但同样重要 // - 协变和逆变 // - 类型控制流分析 // - 上下文类型`

基本数据类型

在Typescript中,基本数据类型有以下几种:

  1. 布尔值(Boolean):布尔值表示真或假,只有两个值:true和false。
  2. 数字(Number):数字包括整数和浮点数,例如:10,10.5等。
  3. 字符串(String):字符串表示文本数据,可以使用单引号、双引号或反引号来表示。
  4. 数组(Array):数组表示一组同类型的数据集合,可以使用[]或Array小于elementType>来定义。
  5. 元组(Tuple):元组表示已知元素数量和类型的数组,例如:[string, number]。
  6. 枚举(Enum):枚举表示具有命名的常量集合,例如:enum Color {Red, Green, Blue}。
  7. 任意值(Any):任意值表示可以被赋值为任意类型的数据,例如:let a: any = 'hello'。
  8. 空值(Void):空值表示没有任何返回值的函数,例如:function test(): void {console.log('hello') }。
  9. Null和Undefined:Null和Undefined表示没有值的数据类型,它们是所有类型的子类型。

交叉类型

交叉类型和联合类型实际上就是指类型操作符。

交叉类型可以看作是将多个类型合并为一个类型的操作。使用“&”符号来表示交叉类型。例如:

`type Person = { name: string; age: number; };

type Employee = { company: string; workId: string; };

type PersonEmployee = Person & Employee;`

在上面的例子中,定义了两个类型:Person和Employee。然后使用“&”符号将它们合并为一个新的类型:PersonEmployee。这个新的类型同时具有Person和Employee类型的所有属性。

交叉类型的使用场景包括:

  • 合并多个对象的属性⭐️
  • 合并多个函数的参数和返回值

联合类型

联合类型可以看作是将多个类型中的一个类型赋值给一个变量的操作。使用“|”符号来表示联合类型。例如:

`type Status = "success" | "error" | "loading";

function getStatusMessage(status: Status) { switch (status) { case "success": return "Operation successful"; case "error": return "Operation failed"; case "loading": return "Operation in progress"; } }`

在上面的例子中,我们定义了一个Status类型,它只能取“success”、“error”或“loading”这三个值中的一个。然后我们定义了一个函数getStatusMessage,它接受一个Status类型的参数,并根据参数的值返回不同的字符串。

联合类型的使用场景包括:

  • 定义一个变量可以是多个类型中的一种
  • 定义一个函数可以接受多个类型中的一种作为参数⭐️

泛型

本质:接受类型作为参数,并且返回一个类型

常用的泛型工具类基本上都是这种实现

为什么要有泛型?

泛型的本质:类型参数化,让我们可以通过参数来控制类型,让代码具有更强的可拓展性。

具体体现有泛型函数、泛型类以及泛型约束

常用泛型工具类

`// 1.Partial:将传入的属性变为可选项 interface IPeople { title: string; name: string; }

type MyPartial小于T> = { [P in keyof T]?:T[P] }

const people: MyPartial小于IPeople> = { title: 'Delete inactive users' };

// 2.Readonly interface Person { name: string; age: number; }

type MyReadonly小于T> = { readonly [P in keyof T]: T[P] }

const p: MyReadonly小于Person> = { name: '张三', age: 22 }

// p.name = '李四'; // 无法分配到 "name" ,因为它是只读属性

// 3.Required interface ICar { weight?:number, height?:number } type MyRequired小于T> = { [P in keyof T]-?:T[P] } const car:Required小于ICar> = { weight: 30, height: 20 }

// 4.Pick interface IPerson { name: string; age: number; salary: number }

type MyPick小于T,K extends keyof T> = { // 使用extends进行泛型约束,保证第二个参数(联合类型)肯定包含在T的keys里 [P in K]:T[P] // 通过映射对象类型,约束每一个属性成员 }

type TP = MyPick小于IPerson, 'name'|'salary'>;

const tp: TP = { // age: 22, // 对象文字可以只指定已知属性,并且“age”不在类型“TP”中 name: '张三', salary:10000 }`

上次更新于:

+ \ No newline at end of file diff --git "a/TS/\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.html" "b/TS/\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.html" index 95b532a..01071d5 100644 --- "a/TS/\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.html" +++ "b/TS/\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page

面向对象

在 TypeScript 中,面向对象编程有三大特性:封装、继承和多态。

封装

封装是指将数据和方法相结合,形成一个有机的整体,同时隐藏了对象的内部细节,只向外部暴露必要的接口。这样可以保护对象的数据不被随意修改,同时也提高了代码的可维护性。

在 TypeScript 中,可以使用 publicprivateprotected 访问修饰符来实现封装。其中,public 表示公有,可以在任何地方访问;private 表示私有,只能在类内部访问;protected 表示受保护的,可以在类内及其子类中访问。

下面是一个使用封装的例子:

class Person {
+    
Skip to content
On this page

面向对象

在 TypeScript 中,面向对象编程有三大特性:封装、继承和多态。

封装

封装是指将数据和方法相结合,形成一个有机的整体,同时隐藏了对象的内部细节,只向外部暴露必要的接口。这样可以保护对象的数据不被随意修改,同时也提高了代码的可维护性。

在 TypeScript 中,可以使用 publicprivateprotected 访问修饰符来实现封装。其中,public 表示公有,可以在任何地方访问;private 表示私有,只能在类内部访问;protected 表示受保护的,可以在类内及其子类中访问。

下面是一个使用封装的例子:

class Person {
   private name: string;
 
   public setName(name: string): void {
@@ -95,9 +95,9 @@
 const circle = new Circle(5);
 
 printArea(rectangle); // 输出:The area is 50
-printArea(circle); // 输出:The area is 78.53981633974483

在上面的例子中,Shape 类是一个抽象类,定义了一个抽象方法 getAreaRectangleCircle 类继承了 Shape 类,并实现了 getArea 方法。printArea 函数接受一个 Shape 类型的参数,可以接受任何继承了 Shape 类的子类。这样就实现了多态。

- +printArea(circle); // 输出:The area is 78.53981633974483

在上面的例子中,Shape 类是一个抽象类,定义了一个抽象方法 getAreaRectangleCircle 类继承了 Shape 类,并实现了 getArea 方法。printArea 函数接受一个 Shape 类型的参数,可以接受任何继承了 Shape 类的子类。这样就实现了多态。

上次更新于:

+ \ No newline at end of file diff --git "a/Uniapp/Uniapp\345\237\272\347\241\200.html" "b/Uniapp/Uniapp\345\237\272\347\241\200.html" index f8b5ff8..2dcf844 100644 --- "a/Uniapp/Uniapp\345\237\272\347\241\200.html" +++ "b/Uniapp/Uniapp\345\237\272\347\241\200.html" @@ -19,8 +19,8 @@ font-family: graceIconfont; } /* #endif */

同样, 也可以实现整体目录条件编译:

在根目录下创建platforms目录, 然后进一步创建app-plus,mp-wexin等子目录, 用来存放不同平台的文件

css变量

- + \ No newline at end of file diff --git a/Vue/VueRouter.html b/Vue/VueRouter.html index f95e925..ee9341f 100644 --- a/Vue/VueRouter.html +++ b/Vue/VueRouter.html @@ -75,8 +75,8 @@ get, post, }; - + \ No newline at end of file diff --git "a/Vue/Vue\350\277\233\351\230\266.html" "b/Vue/Vue\350\277\233\351\230\266.html" index dfae68c..21250e7 100644 --- "a/Vue/Vue\350\277\233\351\230\266.html" +++ "b/Vue/Vue\350\277\233\351\230\266.html" @@ -15,8 +15,8 @@
Skip to content
On this page

生命周期

Vue2和Vue3的生命周期都是指Vue实例从创建到销毁的一系列过程,其中有一些特定的事件或钩子函数可以让我们在不同阶段执行自定义的逻辑

Vue2和Vue3的生命周期有以下区别:

  • Vue3新增了一个setup钩子函数,它是组合式API(Composition API)的入口,可以在其中使用响应式数据、计算属性、侦听器、生命周期等功能
  • Vue3中除了setup外,其他的生命周期钩子函数都需要按需引入才能使用,而且名称上都加了on前缀,比如onCreated, onMounted等
  • Vue3中移除了beforeDestroy和destroyed钩子函数,取而代之的是beforeUnmount和unmounted

具体到每一个生命周期的功能,请参考以下表格:

钩子函数Vue2Vue3功能
beforeCreate✔️✔️实例初始化之后,数据观测和事件配置之前调用
created✔️✔️实例创建完成后调用,可访问data和methods
beforeMount✔️✔️模板编译/挂载之前调用
mounted✔️✔️模板编译/挂载之后调用
beforeUpdate✔️✔️数据更新时调用,发生在虚拟DOM打补丁之前
updated✔️✔️数据更新后调用,发生在虚拟DOM打补丁之后
beforeUnmount✔️实例卸载之前调用,可以做一些清理工作
unmounted✔️实例卸载之后调用,实例的所有指令解绑,所有的事件监听器被移除,所有的子组件也被销毁

此外,还有一些不那么常用的生命周期钩子

钩子函数Vue2Vue3功能
activated✔️✔️keep-alive组件激活时调用
deactivated✔️✔️keep-alive组件停用时调用
errorCaptured✔️✔️捕获一个来自子孙组件的错误时被调用

响应式原理

VUE的响应式原理是指当数据发生变化时,视图会自动更新。

Vue2

VUE2是利用了Object.defineProperty的方法里面的setter和getter方法的观察者模式来实现的;

具体过程如下:

  1. 当创建一个VUE实例时,会对data中的属性进行数据劫持,即用Object.defineProperty定义属性的getter和setter方法。
  2. 在getter方法中,会收集依赖,即把当前访问该属性的Watcher对象添加到Dep对象中。
  3. 在setter方法中,会触发依赖,即通知Dep对象中存储的所有Watcher对象执行更新操作。
  4. Watcher对象是一个订阅者,它有一个update方法,用来更新视图或执行回调函数。
  5. Dep对象是一个发布者,它有一个notify方法,用来通知所有订阅者更新。
  6. VUE中有三种Watcher对象:渲染Watcher、计算属性Watcher和侦听器Watcher。它们之间有着复杂的收集关系

Vue3

VUE3的响应式原理是基于现代浏览器所支持的代理对象Proxy实现的。Proxy可以拦截对象的读取、修改和删除操作,从而实现数据劫持和依赖收集。

具体过程如下:

  1. 当创建一个VUE实例时,会对data中的属性进行数据劫持,即用Proxy定义对象的get和set方法。
  2. 在get方法中,会收集依赖,即把当前访问该属性的effect函数添加到activeEffectStack中,并且建立属性和effect函数之间的映射关系。
  3. 在set方法中,会触发依赖,即根据属性找到对应的effect函数,并执行它们。
  4. effect函数是一个副作用函数,它可以更新视图或执行回调函数。
  5. VUE3中有两种effect函数:渲染effect和计算属性effect。渲染effect用来更新视图,计算属性effect用来缓存计算结果。

v-for中key的作用

h函数的key是diff阶段唯一标识,一个是isSameVnode需要,另一个是keyToNewIndexMap做映射表取出key做新老节点diff.

vue的v-for指令用来渲染一个列表,它需要一个key属性来标识每个元素。

key属性的作用是给vue的虚拟DOM算法一个提示,让它能够在更新列表时识别哪些节点发生了变化,哪些没有。

这样可以提高渲染性能,避免不必要的DOM操作。

如果不使用key属性,或者使用不唯一的值作为key,可能会导致一些问题,比如数据错乱、动画失效、组件状态丢失等。

因此,建议使用唯一且稳定的值作为key,比如id等

上次更新于:

- + \ No newline at end of file diff --git "a/Vue/\346\211\213\345\206\231\347\263\273\345\210\227.html" "b/Vue/\346\211\213\345\206\231\347\263\273\345\210\227.html" index 6a5067e..5c20859 100644 --- "a/Vue/\346\211\213\345\206\231\347\263\273\345\210\227.html" +++ "b/Vue/\346\211\213\345\206\231\347\263\273\345\210\227.html" @@ -166,8 +166,8 @@ </body> </html> - + \ No newline at end of file diff --git "a/WeChatMiniprogram/01.\345\260\217\347\250\213\345\272\217\350\265\267\346\255\245.html" "b/WeChatMiniprogram/01.\345\260\217\347\250\213\345\272\217\350\265\267\346\255\245.html" index 757d155..00dfc1d 100644 --- "a/WeChatMiniprogram/01.\345\260\217\347\250\213\345\272\217\350\265\267\346\255\245.html" +++ "b/WeChatMiniprogram/01.\345\260\217\347\250\213\345\272\217\350\265\267\346\255\245.html" @@ -15,8 +15,8 @@
Skip to content
On this page

小程序入门

缘起

我于2022年8月份开始负责一款校园课表查询小程序的线上维护。在这个过程中,我不仅进行了新功能的开发和旧功能的维护,还遇到并解决了许多bug。为了分享我的经验和心得,我将这些问题和总结整理成了这篇专栏,希望能给正在学习小程序的朋友们提供一些参考和启发。

小程序发展

小程序的价值

小程序带来的最大的价值就是它的产品价值, 例如它帮助微信这种超级app构筑了更加完整的生态, 扩充了更多的业务场景

无论什么技术都是为业务服务的, 技术不存在优劣, 只存在是否合适

小程序的特点

  • 数量众多(超过app的数量)
  • 应用场景广泛

小程序生态

  • 围绕大厂app的核心业务开发

小程序与传统Web技术的区别:

  • 有固定的语法和统一的版本管理
  • 平台能够控制各个入口, 如二维码/文章内嵌/端内分享
  • 基于特殊架构, 流畅度更好

小程序三大价值

  • 渠道价值: 依托于超级平台, 小程序能够充分为很多场景导流
  • 业务探索价值: 相比于原生app, 开发成本低
  • 数字升级价值:线下到线上, 轻度消费到大宗消费, 小程序都展现了良好的容错空间

小程序原理

第三方开发应用最简单方便的方式---WebView+JSBridge

问题

  • 无网络的情况下体验不佳
  • 网页切换体验不佳
  • 管控安全无法保证

针对于这三大问题, 微信小程序具有如下三个特点

  • 开发门槛低
    • HTML+CSS+JS
  • 接近原生的使用体验
    • 资源加载+渲染+页面切换(每次切换之前保留原先的页面结构)
  • 能够保证安全可控
    • 独立的JSCore

问题1: 不操作DOM如何渲染页面, 这个问题许多框架都有, 例如React

解决思路为: 只关心数据流, 而不需要操作具体的DOM, 就可以根据数据渲染页面

问题2: 浏览器中, JS操作频繁时我们动画会卡顿, 因为它们是在同一个进程中的

解决思路为: 利用小程序的特殊结构将js和渲染分离, 这样的通信结构, 决定了小程序的性能问题在数据传递

上次更新于:

- + \ No newline at end of file diff --git "a/WeChatMiniprogram/02.\350\216\267\345\217\226\347\224\250\346\210\267\345\244\264\345\203\217.html" "b/WeChatMiniprogram/02.\350\216\267\345\217\226\347\224\250\346\210\267\345\244\264\345\203\217.html" index cd28e09..3f24476 100644 --- "a/WeChatMiniprogram/02.\350\216\267\345\217\226\347\224\250\346\210\267\345\244\264\345\203\217.html" +++ "b/WeChatMiniprogram/02.\350\216\267\345\217\226\347\224\250\346\210\267\345\244\264\345\203\217.html" @@ -115,8 +115,8 @@ }) }, })

可以使用下面的网址检验临时url是否成功转换为base64格式

BASE64转图片 - 站长工具 - 极速数据 (jisuapi.com)

- + \ No newline at end of file diff --git "a/WeChatMiniprogram/03.\345\217\230\351\207\217\345\212\240\351\224\201\344\270\216\350\257\267\346\261\202\345\260\201\350\243\205.html" "b/WeChatMiniprogram/03.\345\217\230\351\207\217\345\212\240\351\224\201\344\270\216\350\257\267\346\261\202\345\260\201\350\243\205.html" index 94b3a86..edba64a 100644 --- "a/WeChatMiniprogram/03.\345\217\230\351\207\217\345\212\240\351\224\201\344\270\216\350\257\267\346\261\202\345\260\201\350\243\205.html" +++ "b/WeChatMiniprogram/03.\345\217\230\351\207\217\345\212\240\351\224\201\344\270\216\350\257\267\346\261\202\345\260\201\350\243\205.html" @@ -119,8 +119,8 @@ console.log('用户点击取消') }) }, - + \ No newline at end of file diff --git "a/WeChatMiniprogram/04.Unionid\344\270\216\345\205\266\350\277\221\344\272\262.html" "b/WeChatMiniprogram/04.Unionid\344\270\216\345\205\266\350\277\221\344\272\262.html" index 64c53fd..00f5b14 100644 --- "a/WeChatMiniprogram/04.Unionid\344\270\216\345\205\266\350\277\221\344\272\262.html" +++ "b/WeChatMiniprogram/04.Unionid\344\270\216\345\205\266\350\277\221\344\272\262.html" @@ -15,8 +15,8 @@
Skip to content
On this page

AppId,UnionId和OpenId

以一首诗作为开篇

微信生态,博大精深, 开放平台,公众平台,小程序, 各有特色,各有用途。

AppID 是应用的身份标识, UnionID 是用户的唯一标识, OpenID 是用户在应用中的标识, 三者之间,要分清楚。

微信生态,连接亿万人, 提供服务,传递信息,创造价值, 让开发者,更好地做人。

AppID(应用端)

从打油诗中可以看出,appid是应用的身份标识,不同的小程序/公众号/APP各自在微信平台下有着不同的AppID,这一部分大多时候是不需要开发者关心的

UnionID和OpenID(用户端)

UnionID是用户的唯一标识,对于从属于同一个微信开发平台下的公众号和小程序,

二者对同一个用户分配相同的UnionID(换句话说,二者可以拿到用户的UnionID是相同的)

然而,公众号和小程序会对同一个用户分配不同的OpenID

通常来说,UnionID比OpenID更实用,那么为什么还需要OpenID呢?

原因如下:

  • 微信 SDK 的某些 api 可能会用到 OpenID 作为参数;
  • 之前,OpenID 的获取不需要用户授权就能拿到,但是 UnionID 的获取就需要用户授权同意了之后才能获取到,你不能排除公司的某些业务为了缩短流程链路,不想进行用户授权,所以这个时候就需要用 OpenID 作为当前平台用户的唯一标识来使用了;
  • 补充一点:现在微信对 UnionID 的获取做了调整,无需授权也可以直接获取到了;
  • 还有最重要一点,涉及到开放平台主体迁移的时候,OpenID 能派上大用场

开放平台主体迁移

待续...

上次更新于:

- + \ No newline at end of file diff --git "a/WeChatMiniprogram/05.\345\276\256\344\277\241\344\272\221\345\274\200\345\217\221.html" "b/WeChatMiniprogram/05.\345\276\256\344\277\241\344\272\221\345\274\200\345\217\221.html" index c1ba1ec..08635b7 100644 --- "a/WeChatMiniprogram/05.\345\276\256\344\277\241\344\272\221\345\274\200\345\217\221.html" +++ "b/WeChatMiniprogram/05.\345\276\256\344\277\241\344\272\221\345\274\200\345\217\221.html" @@ -38,8 +38,8 @@ } }) } - + \ No newline at end of file diff --git a/_pagefind/fragment/zh-cn_bf41949.pf_fragment b/_pagefind/fragment/zh-cn_bf41949.pf_fragment new file mode 100644 index 0000000..4b09638 Binary files /dev/null and b/_pagefind/fragment/zh-cn_bf41949.pf_fragment differ diff --git a/_pagefind/fragment/zh-cn_dfa7013.pf_fragment b/_pagefind/fragment/zh-cn_dfa7013.pf_fragment deleted file mode 100644 index a415a17..0000000 Binary files a/_pagefind/fragment/zh-cn_dfa7013.pf_fragment and /dev/null differ diff --git a/_pagefind/index/zh-cn_2faeb3f.pf_index b/_pagefind/index/zh-cn_2faeb3f.pf_index new file mode 100644 index 0000000..bc91f52 Binary files /dev/null and b/_pagefind/index/zh-cn_2faeb3f.pf_index differ diff --git a/_pagefind/index/zh-cn_51443c9.pf_index b/_pagefind/index/zh-cn_51443c9.pf_index deleted file mode 100644 index c262834..0000000 Binary files a/_pagefind/index/zh-cn_51443c9.pf_index and /dev/null differ diff --git a/_pagefind/index/zh-cn_7e60e0d.pf_index b/_pagefind/index/zh-cn_7e60e0d.pf_index deleted file mode 100644 index 7cfa191..0000000 Binary files a/_pagefind/index/zh-cn_7e60e0d.pf_index and /dev/null differ diff --git a/_pagefind/index/zh-cn_82efdc3.pf_index b/_pagefind/index/zh-cn_82efdc3.pf_index deleted file mode 100644 index 7319947..0000000 Binary files a/_pagefind/index/zh-cn_82efdc3.pf_index and /dev/null differ diff --git a/_pagefind/index/zh-cn_91ba8a6.pf_index b/_pagefind/index/zh-cn_91ba8a6.pf_index deleted file mode 100644 index 2d4b609..0000000 Binary files a/_pagefind/index/zh-cn_91ba8a6.pf_index and /dev/null differ diff --git a/_pagefind/index/zh-cn_b723c82.pf_index b/_pagefind/index/zh-cn_b723c82.pf_index new file mode 100644 index 0000000..670bbeb Binary files /dev/null and b/_pagefind/index/zh-cn_b723c82.pf_index differ diff --git a/_pagefind/index/zh-cn_cf211cc.pf_index b/_pagefind/index/zh-cn_cf211cc.pf_index new file mode 100644 index 0000000..6cd773b Binary files /dev/null and b/_pagefind/index/zh-cn_cf211cc.pf_index differ diff --git a/_pagefind/index/zh-cn_f4c6382.pf_index b/_pagefind/index/zh-cn_f4c6382.pf_index new file mode 100644 index 0000000..873b703 Binary files /dev/null and b/_pagefind/index/zh-cn_f4c6382.pf_index differ diff --git a/_pagefind/pagefind-entry.json b/_pagefind/pagefind-entry.json index 8906175..c393601 100644 --- a/_pagefind/pagefind-entry.json +++ b/_pagefind/pagefind-entry.json @@ -1 +1 @@ -{"version":"0.12.0","languages":{"zh-cn":{"hash":"zh-cn_7111425a59128","wasm":null,"page_count":51}}} \ No newline at end of file +{"version":"0.12.0","languages":{"zh-cn":{"hash":"zh-cn_a798d0c3d1cdf","wasm":null,"page_count":51}}} \ No newline at end of file diff --git a/_pagefind/pagefind.zh-cn_7111425a59128.pf_meta b/_pagefind/pagefind.zh-cn_7111425a59128.pf_meta deleted file mode 100644 index b2e8195..0000000 Binary files a/_pagefind/pagefind.zh-cn_7111425a59128.pf_meta and /dev/null differ diff --git a/_pagefind/pagefind.zh-cn_a798d0c3d1cdf.pf_meta b/_pagefind/pagefind.zh-cn_a798d0c3d1cdf.pf_meta new file mode 100644 index 0000000..d9f32e2 Binary files /dev/null and b/_pagefind/pagefind.zh-cn_a798d0c3d1cdf.pf_meta differ diff --git "a/assets/Algo_01.\346\261\207\346\200\273.md.6fd127cc.lean.js" "b/assets/Algo_01.\346\261\207\346\200\273.md.6fd127cc.lean.js" deleted file mode 100644 index 54482a3..0000000 --- "a/assets/Algo_01.\346\261\207\346\200\273.md.6fd127cc.lean.js" +++ /dev/null @@ -1 +0,0 @@ -import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const p="/my-blog/assets/image-20230310224909873.5c87e648.png",b=JSON.parse('{"title":"题型汇总 🔥","description":"","frontmatter":{"tag":["DS"],"sticky":200},"headers":[],"relativePath":"Algo/01.汇总.md","filePath":"Algo/01.汇总.md","lastUpdated":1689602218000}'),o={name:"Algo/01.汇总.md"},e=l("",88),t=[e];function r(c,y,F,D,i,A){return n(),a("div",null,t)}const u=s(o,[["render",r]]);export{b as __pageData,u as default}; diff --git "a/assets/Algo_01.\346\261\207\346\200\273.md.6fd127cc.js" "b/assets/Algo_01.\346\261\207\346\200\273.md.ddcd8b4a.js" similarity index 99% rename from "assets/Algo_01.\346\261\207\346\200\273.md.6fd127cc.js" rename to "assets/Algo_01.\346\261\207\346\200\273.md.ddcd8b4a.js" index 1a05dbe..79fd196 100644 --- "a/assets/Algo_01.\346\261\207\346\200\273.md.6fd127cc.js" +++ "b/assets/Algo_01.\346\261\207\346\200\273.md.ddcd8b4a.js" @@ -1,4 +1,4 @@ -import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const p="/my-blog/assets/image-20230310224909873.5c87e648.png",b=JSON.parse('{"title":"题型汇总 🔥","description":"","frontmatter":{"tag":["DS"],"sticky":200},"headers":[],"relativePath":"Algo/01.汇总.md","filePath":"Algo/01.汇总.md","lastUpdated":1689602218000}'),o={name:"Algo/01.汇总.md"},e=l(`

题型汇总 🔥

  1. 算法的复杂度分析。
  2. 排序算法,以及他们的区别和优化。
  3. 数组中的双指针、滑动窗口思想。
  4. 利用 Map 和 Set 处理查找表问题。
  5. 链表的各种问题。
  6. 利用递归和迭代法解决二叉树问题。
  7. 栈、队列、DFS、BFS。
  8. 回溯法、贪心算法、动态规划。

滑动窗口问题

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

利用set()自动去重的特点

js
/**
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const p="/my-blog/assets/image-20230310224909873.5c87e648.png",b=JSON.parse('{"title":"常见算法题型 🔥","description":"","frontmatter":{"tag":["DataStructure"],"sticky":100},"headers":[],"relativePath":"Algo/01.汇总.md","filePath":"Algo/01.汇总.md","lastUpdated":1692542168000}'),o={name:"Algo/01.汇总.md"},e=l(`

常见算法题型 🔥

  1. 算法的复杂度分析。
  2. 排序算法,以及他们的区别和优化。
  3. 数组中的双指针、滑动窗口思想。
  4. 利用 Map 和 Set 处理查找表问题。
  5. 链表的各种问题。
  6. 利用递归和迭代法解决二叉树问题。
  7. 栈、队列、DFS、BFS。
  8. 回溯法、贪心算法、动态规划。

滑动窗口问题

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

利用set()自动去重的特点

js
/**
  * @param {string} s
  * @return {number}
  */
diff --git "a/assets/Algo_01.\346\261\207\346\200\273.md.ddcd8b4a.lean.js" "b/assets/Algo_01.\346\261\207\346\200\273.md.ddcd8b4a.lean.js"
new file mode 100644
index 0000000..87e79af
--- /dev/null
+++ "b/assets/Algo_01.\346\261\207\346\200\273.md.ddcd8b4a.lean.js"
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const p="/my-blog/assets/image-20230310224909873.5c87e648.png",b=JSON.parse('{"title":"常见算法题型 🔥","description":"","frontmatter":{"tag":["DataStructure"],"sticky":100},"headers":[],"relativePath":"Algo/01.汇总.md","filePath":"Algo/01.汇总.md","lastUpdated":1692542168000}'),o={name:"Algo/01.汇总.md"},e=l("",88),t=[e];function r(c,y,F,D,i,A){return n(),a("div",null,t)}const u=s(o,[["render",r]]);export{b as __pageData,u as default};
diff --git "a/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.32f9e142.lean.js" "b/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.32f9e142.lean.js"
deleted file mode 100644
index e5f9380..0000000
--- "a/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.32f9e142.lean.js"
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"双指针问题","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/02.双指针.md","filePath":"Algo/02.双指针.md","lastUpdated":1689602218000}'),p={name:"Algo/02.双指针.md"},o=l("",12),e=[o];function t(c,r,y,F,i,D){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.32f9e142.js" "b/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.7f81d5b0.js"
similarity index 92%
rename from "assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.32f9e142.js"
rename to "assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.7f81d5b0.js"
index 4773fbe..7b22cd2 100644
--- "a/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.32f9e142.js"
+++ "b/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.7f81d5b0.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"双指针问题","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/02.双指针.md","filePath":"Algo/02.双指针.md","lastUpdated":1689602218000}'),p={name:"Algo/02.双指针.md"},o=l(`

双指针问题

16. 最接近的三数之和

先按照升序排序,然后分别从左往右依次选择一个基础点 i0 <= i <= nums.length - 3),在基础点的右侧用双指针去不断的找最小的差值。

假设基础点是 i,初始化的时候,双指针分别是:

  • lefti + 1,基础点右边一位。
  • right: nums.length - 1 数组最后一位。

然后求此时的和,如果和大于 target,那么可以把右指针左移一位,去试试更小一点的值,反之则把左指针右移。

在这个过程中,不断更新全局的最小差值 min,和此时记录下来的和 res

最后返回 res 即可。

js
/**
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"双指针问题","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/02.双指针.md","filePath":"Algo/02.双指针.md","lastUpdated":1692542168000}'),p={name:"Algo/02.双指针.md"},o=l(`

双指针问题

16. 最接近的三数之和

先按照升序排序,然后分别从左往右依次选择一个基础点 i0 <= i <= nums.length - 3),在基础点的右侧用双指针去不断的找最小的差值。

假设基础点是 i,初始化的时候,双指针分别是:

  • lefti + 1,基础点右边一位。
  • right: nums.length - 1 数组最后一位。

然后求此时的和,如果和大于 target,那么可以把右指针左移一位,去试试更小一点的值,反之则把左指针右移。

在这个过程中,不断更新全局的最小差值 min,和此时记录下来的和 res

最后返回 res 即可。

js
/**
  * @param {number[]} nums
  * @param {number} target
  * @return {number}
diff --git "a/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.7f81d5b0.lean.js" "b/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.7f81d5b0.lean.js"
new file mode 100644
index 0000000..3bc38d9
--- /dev/null
+++ "b/assets/Algo_02.\345\217\214\346\214\207\351\222\210.md.7f81d5b0.lean.js"
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"双指针问题","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/02.双指针.md","filePath":"Algo/02.双指针.md","lastUpdated":1692542168000}'),p={name:"Algo/02.双指针.md"},o=l("",12),e=[o];function t(c,r,y,F,i,D){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.9afad74a.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.601bbf48.js"
similarity index 99%
rename from "assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.9afad74a.js"
rename to "assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.601bbf48.js"
index 2cb107c..798eb77 100644
--- "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.9afad74a.js"
+++ "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.601bbf48.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记1","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/刷题笔记1.md","filePath":"Algo/刷题笔记1.md","lastUpdated":1689602218000}'),p={name:"Algo/刷题笔记1.md"},o=l(`

刷题笔记1

1 两数之和

js
/**
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记1","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/刷题笔记1.md","filePath":"Algo/刷题笔记1.md","lastUpdated":1692542168000}'),p={name:"Algo/刷题笔记1.md"},o=l(`

刷题笔记1

1 两数之和

js
/**
  * @param {number[]} nums
  * @param {number} target
  * @return {number[]}
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.601bbf48.lean.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.601bbf48.lean.js"
new file mode 100644
index 0000000..725b960
--- /dev/null
+++ "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.601bbf48.lean.js"
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记1","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/刷题笔记1.md","filePath":"Algo/刷题笔记1.md","lastUpdated":1692542168000}'),p={name:"Algo/刷题笔记1.md"},o=l("",70),e=[o];function t(c,r,y,F,D,i){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.9afad74a.lean.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.9afad74a.lean.js"
deleted file mode 100644
index bc3cf03..0000000
--- "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2601.md.9afad74a.lean.js"
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记1","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/刷题笔记1.md","filePath":"Algo/刷题笔记1.md","lastUpdated":1689602218000}'),p={name:"Algo/刷题笔记1.md"},o=l("",70),e=[o];function t(c,r,y,F,D,i){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.fce6ed95.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.37b13e79.js"
similarity index 99%
rename from "assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.fce6ed95.js"
rename to "assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.37b13e79.js"
index a900ff8..78ce4e8 100644
--- "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.fce6ed95.js"
+++ "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.37b13e79.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记2","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/刷题笔记2.md","filePath":"Algo/刷题笔记2.md","lastUpdated":1689602218000}'),p={name:"Algo/刷题笔记2.md"},o=l(`

刷题笔记2

90 子集 II

本题目的关键在于对子集中的元素进行去重, 关键点是要在for循环内部去重

js
/**
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记2","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/刷题笔记2.md","filePath":"Algo/刷题笔记2.md","lastUpdated":1692542168000}'),p={name:"Algo/刷题笔记2.md"},o=l(`

刷题笔记2

90 子集 II

本题目的关键在于对子集中的元素进行去重, 关键点是要在for循环内部去重

js
/**
  * @param {number[]} nums
  * @return {number[][]}
  */
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.37b13e79.lean.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.37b13e79.lean.js"
new file mode 100644
index 0000000..eb3a66e
--- /dev/null
+++ "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.37b13e79.lean.js"
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记2","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/刷题笔记2.md","filePath":"Algo/刷题笔记2.md","lastUpdated":1692542168000}'),p={name:"Algo/刷题笔记2.md"},o=l("",50),e=[o];function t(c,r,y,F,D,i){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.fce6ed95.lean.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.fce6ed95.lean.js"
deleted file mode 100644
index 94a613c..0000000
--- "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2602.md.fce6ed95.lean.js"
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记2","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/刷题笔记2.md","filePath":"Algo/刷题笔记2.md","lastUpdated":1689602218000}'),p={name:"Algo/刷题笔记2.md"},o=l("",50),e=[o];function t(c,r,y,F,D,i){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.400a90c9.lean.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.400a90c9.lean.js"
deleted file mode 100644
index 06919f0..0000000
--- "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.400a90c9.lean.js"
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记3","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/刷题笔记3.md","filePath":"Algo/刷题笔记3.md","lastUpdated":1689602218000}'),p={name:"Algo/刷题笔记3.md"},o=l("",28),e=[o];function t(c,r,y,F,D,i){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.400a90c9.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.7fcd0a6a.js"
similarity index 98%
rename from "assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.400a90c9.js"
rename to "assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.7fcd0a6a.js"
index b2daed9..43ea719 100644
--- "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.400a90c9.js"
+++ "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.7fcd0a6a.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记3","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/刷题笔记3.md","filePath":"Algo/刷题笔记3.md","lastUpdated":1689602218000}'),p={name:"Algo/刷题笔记3.md"},o=l(`

刷题笔记3

242 有效的字母异位词

使用Map统计每个character出现的次数即可

js
/**
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记3","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/刷题笔记3.md","filePath":"Algo/刷题笔记3.md","lastUpdated":1692542168000}'),p={name:"Algo/刷题笔记3.md"},o=l(`

刷题笔记3

242 有效的字母异位词

使用Map统计每个character出现的次数即可

js
/**
  * @param {string} s
  * @param {string} t
  * @return {boolean}
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.7fcd0a6a.lean.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.7fcd0a6a.lean.js"
new file mode 100644
index 0000000..efaed26
--- /dev/null
+++ "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2603.md.7fcd0a6a.lean.js"
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记3","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/刷题笔记3.md","filePath":"Algo/刷题笔记3.md","lastUpdated":1692542168000}'),p={name:"Algo/刷题笔记3.md"},o=l("",28),e=[o];function t(c,r,y,F,D,i){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.96af88dd.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.65097491.js"
similarity index 93%
rename from "assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.96af88dd.js"
rename to "assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.65097491.js"
index bcc5fa5..b355612 100644
--- "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.96af88dd.js"
+++ "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.65097491.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记4","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/刷题笔记4.md","filePath":"Algo/刷题笔记4.md","lastUpdated":1689602218000}'),p={name:"Algo/刷题笔记4.md"},o=l(`

刷题笔记4

多多的数字组合

多多君最近在研究某种数字组合: 定义为:每个数字的十进制表示中(0~9),每个数位各不相同且各个数位之和等于N。 满足条件的数字可能很多,找到其中的最小值即可。

多多君还有很多研究课题,于是多多君找到了你--未来的计算机科学家寻求帮助。

共一行,一个正整数N,如题意所示,表示组合中数字不同数位之和。
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记4","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/刷题笔记4.md","filePath":"Algo/刷题笔记4.md","lastUpdated":1692542168000}'),p={name:"Algo/刷题笔记4.md"},o=l(`

刷题笔记4

多多的数字组合

多多君最近在研究某种数字组合: 定义为:每个数字的十进制表示中(0~9),每个数位各不相同且各个数位之和等于N。 满足条件的数字可能很多,找到其中的最小值即可。

多多君还有很多研究课题,于是多多君找到了你--未来的计算机科学家寻求帮助。

共一行,一个正整数N,如题意所示,表示组合中数字不同数位之和。
 (1 <= N <= 1,000)
共一行,一个整数,表示该组合中的最小值。
 如果组合中没有任何符合条件的数字,那么输出-1即可。

输入例子:

5

输出例子:

5

例子说明:

符合条件的数字有:5,14,23,32,41
 其中最小值为5
js
const rl = require("readline").createInterface({ input: process.stdin });
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.65097491.lean.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.65097491.lean.js"
new file mode 100644
index 0000000..4ab2bb3
--- /dev/null
+++ "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.65097491.lean.js"
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记4","description":"","frontmatter":{"tag":["DataStructure"]},"headers":[],"relativePath":"Algo/刷题笔记4.md","filePath":"Algo/刷题笔记4.md","lastUpdated":1692542168000}'),p={name:"Algo/刷题笔记4.md"},o=l("",13),e=[o];function t(r,c,y,F,i,D){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.96af88dd.lean.js" "b/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.96af88dd.lean.js"
deleted file mode 100644
index 08aaef5..0000000
--- "a/assets/Algo_\345\210\267\351\242\230\347\254\224\350\256\2604.md.96af88dd.lean.js"
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"刷题笔记4","description":"","frontmatter":{"tag":["DS"]},"headers":[],"relativePath":"Algo/刷题笔记4.md","filePath":"Algo/刷题笔记4.md","lastUpdated":1689602218000}'),p={name:"Algo/刷题笔记4.md"},o=l("",13),e=[o];function t(r,c,y,F,i,D){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git a/assets/React_Quick Start.md.2fc9061e.js b/assets/React_Quick Start.md.2fc9061e.js
new file mode 100644
index 0000000..9dab22a
--- /dev/null
+++ b/assets/React_Quick Start.md.2fc9061e.js	
@@ -0,0 +1 @@
+import{_ as t,o as a,c as e,U as i}from"./chunks/framework.a7041386.js";const m=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/Quick Start.md","filePath":"React/Quick Start.md","lastUpdated":1690701222000}'),c={name:"React/Quick Start.md"},o=i('

Quick Start

About what in this section ?

  • Component && Props
  • States
  • Hooks
  • Redux
  • Typescript
  • Forms
  • State Management
  • Firebase Project
',3),r=[o];function l(s,_,n,u,p,d){return a(),e("div",null,r)}const S=t(c,[["render",l]]);export{m as __pageData,S as default}; diff --git a/assets/React_Quick Start.md.839528c9.lean.js b/assets/React_Quick Start.md.2fc9061e.lean.js similarity index 57% rename from assets/React_Quick Start.md.839528c9.lean.js rename to assets/React_Quick Start.md.2fc9061e.lean.js index 8abf1ec..53abef9 100644 --- a/assets/React_Quick Start.md.839528c9.lean.js +++ b/assets/React_Quick Start.md.2fc9061e.lean.js @@ -1 +1 @@ -import{_ as t,o as a,c as e,U as i}from"./chunks/framework.a7041386.js";const m=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/Quick Start.md","filePath":"React/Quick Start.md","lastUpdated":null}'),c={name:"React/Quick Start.md"},o=i("",3),r=[o];function l(s,n,_,u,p,d){return a(),e("div",null,r)}const S=t(c,[["render",l]]);export{m as __pageData,S as default}; +import{_ as t,o as a,c as e,U as i}from"./chunks/framework.a7041386.js";const m=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/Quick Start.md","filePath":"React/Quick Start.md","lastUpdated":1690701222000}'),c={name:"React/Quick Start.md"},o=i("",3),r=[o];function l(s,_,n,u,p,d){return a(),e("div",null,r)}const S=t(c,[["render",l]]);export{m as __pageData,S as default}; diff --git a/assets/React_Quick Start.md.839528c9.js b/assets/React_Quick Start.md.839528c9.js deleted file mode 100644 index f734f6b..0000000 --- a/assets/React_Quick Start.md.839528c9.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as t,o as a,c as e,U as i}from"./chunks/framework.a7041386.js";const m=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/Quick Start.md","filePath":"React/Quick Start.md","lastUpdated":null}'),c={name:"React/Quick Start.md"},o=i('

Quick Start

About what in this section ?

  • Component && Props
  • States
  • Hooks
  • Redux
  • Typescript
  • Forms
  • State Management
  • Firebase Project
',3),r=[o];function l(s,n,_,u,p,d){return a(),e("div",null,r)}const S=t(c,[["render",l]]);export{m as __pageData,S as default}; diff --git a/assets/React_React Hooks.md.8d73e4ec.js b/assets/React_React Hooks.md.3560e5e9.js similarity index 56% rename from assets/React_React Hooks.md.8d73e4ec.js rename to assets/React_React Hooks.md.3560e5e9.js index 96b5088..fa4c0f6 100644 --- a/assets/React_React Hooks.md.8d73e4ec.js +++ b/assets/React_React Hooks.md.3560e5e9.js @@ -1,4 +1,4 @@ -import{_ as s,o as a,c as e,U as n}from"./chunks/framework.a7041386.js";const f=JSON.parse('{"title":"React Hooks","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React Hooks.md","filePath":"React/React Hooks.md","lastUpdated":null}'),l={name:"React/React Hooks.md"},o=n(`

React Hooks

一、产生原因

React Hooks是React v16.8.0版本引入的新特性,它可以让我们在函数组件中使用state和其他React特性,从而避免使用类组件。

在使用React Hooks时,有一些注意事项和设计依据需要遵循。

二、常用 Hooks

useState:状态定义

不可变状态,只能通过创建状态时提供的方法,进行状态的修改,而不能直接对状态进行赋值

  • useState返回的第一个参数是当前state,第二个参数是更新state的函数。
  • 在useState中如果需要更新state,不能基于当前的state进行更新,而是需要使用回调函数的方式进行更新,因为useState是异步更新state的。
  • 不能在条件语句中使用useState,因为条件语句在每次渲染时都会执行,如果在条件语句中使用useState,会导致state的混乱。

请记住:使用 useState 会触发组件的重新渲染。

例如:在一个组件中,引入了一个不需要改变的组件,同时使用useState定义并触发数据更改,则会造成这个被引入不必要的重渲染

useEffect:副作用处理

  • useEffect是React Hooks中用于处理副作用的钩子函数。
  • useEffect会在组件渲染完成后执行,并在每次state或props发生变化时重新执行。
  • 在useEffect中可以返回一个函数,这个函数会在组件被销毁时执行。

useLayoutEffect:副作用处理

useLayoutEffect是React提供的一个Hook函数,它和useEffect非常相似,都是用来处理副作用的,但是它们的执行时机不同。

useEffect 执行时机

useEffect 在内容重绘到页面之后 runs,useLayoutEffect在之前 runs

useEffect的执行时机是在DOM更新之后、浏览器绘制之后,也就是在useLayoutEffect之后执行。这个时机适合用来处理一些不需要立即执行的副作用,比如发送网络请求、修改全局状态等。

在组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入异步队列等待执行。

useLayoutEffect 执行时机

useLayoutEffect的执行时机是在DOM更新之后、浏览器绘制之前,也就是在useEffect之前执行。这个时机非常适合用来读取DOM节点的尺寸、位置等信息,因为这些信息需要在浏览器绘制之前就要计算好,否则可能会导致页面闪烁等问题。

组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入同步队列等待执行,这意味着useLayouEffect会完全阻塞后续的更新工作,也就是说,组件首次渲染完成后,会立即执行useLayoutEffect的回调函数,而不用等待主线程中其他未完成的工作。

总结

useLayoutEffect的原理和useEffect类似,都是通过在组件渲染时注册副作用函数,然后在组件卸载时执行清除函数来实现的。不同的是,useLayoutEffect会在浏览器绘制之前执行副作用函数,因此它会阻塞浏览器的渲染过程,可能会导致性能问题。

简单的理解就是,useLayoutEffect是立即执行,useEffect是推入异步队列,最终执行时间不确定。

因此,一般情况下,我们应该优先使用useEffect,只有在需要读取DOM节点信息时才使用useLayoutEffect。

useMemo:计算属性

产生依据

可以使用useMemo实现useCallback

useMemo类似于Vue3的计算属性,是针对于状态的,useCallback是针对函数的。。useCallback用来缓存函数,第一个参数是一个函数,但是他的这个函数不会被React执行,而是直接进入缓存;useMemo用来缓存状态,第一个参数也是一个函数,组件初始化时这个函数会被React直接执行,然后将其返回值进行缓存,第二个参数是依赖项,当依赖项变化时,React会重新执行对应的第一个参数,然后拿到最新的返回值,再次进行缓存。

参数说明

  • useMemo接收两个参数,第一个参数是需要进行计算的函数,第二个参数是依赖项数组。
  • 当依赖项数组中的值发生变化时,useMemo会重新执行计算函数,否则会使用上一次的计算结果。

useCallback:优化函数性能

产生依据

useCallback用于减少函数引用的创建次数。我们知道,每次组件的重新渲染都意味着内部所有的引用值都会被重新构建,每次函数组件的重新渲染都对应函数的重新执行。当不使用useCallback时,每一次状态变化,比如list或name状态变化,都会重新创建一个fetchData函数的引用,这是十分浪费性能的。实际上,fetchData函数只和url这个状态有关,当url这个状态不改变时,就不需要创建新的函数引用,因此就有了useCallback。

使用useCallback并且指定依赖项时,只有当url这个状态变化时,才会在新的时间切片里创建新的函数引用,否则就使用原先时间切片里的函数引用而无需创建新的引用。

参数说明

第一个参数是函数声明,第二个参数是依赖项,当依赖项发生了变动以后,对应的函数引用会被重新生成。若依赖项为空数组表示该回调函数不依赖于任何状态或属性,只会在组件挂载时创建一次,并在整个组件的生命周期内保持不变。这种情况下,useCallback的作用类似于普通的函数定义,但是由于它是在组件内部定义的,因此可以访问组件的状态和属性。使用空数组作为依赖项数组可以避免不必要的重新创建函数,从而提高性能。

注意事项

  • useCallback接收两个参数,第一个参数是需要缓存的函数,第二个参数是依赖项数组。
  • 当依赖项数组中的值发生变化时,useCallback会返回一个新的函数,否则会返回上一次缓存的函数。

useRef:构建出脱离React控制的状态

产生依据

useState用于构建组件状态,当状态变更的时候组件必定重新渲染;useRef出现的目的是构建一个状态出来,但是这个状态是直接脱离React控制的,也不会造成重新渲染,同时状态还不会因为组件的重新渲染而被初始化。useRef完全脱离React控制,意味着更改refInstance.current的值,不会像修改useState定义的状态一样,造成页面的重新渲染。refInstance是可以直接读写的,不需要像useState一样使用给定的方法来修改。

注意事项

  • useRef是React Hooks中用于获取DOM节点和存储任意值的钩子函数。
  • useRef返回的是一个对象,其中current属性是一个可变的变量,可以存储任意值。
  • 在函数组件中,使用useRef可以获取DOM节点的引用。

useContext:祖孙组件传值

产生依据

类似于Vue3的provide和inject,允许组件之间通过除 props 之外的方式共享数据,多用于祖孙组件之间的数据传递问题。

核心实现

  • useContext是React Hooks中用于获取上下文的钩子函数。
  • 使用useContext可以在函数组件中获取上下文的值,避免了使用类组件中的static contextType和Consumer。

useContext 和 createContext 是 React 中用于跨组件传递数据的两个 API,通常用于全局数据管理。很多著名的库,例如 react-router, redux 都是基于 useContext 来做的;

createContext 用于创建一个上下文对象,useContext 用于在组件中获取上下文对象中的数据。

使用场景

当多个组件需要共享同一个数据时,可以使用 createContext 创建一个上下文对象,并将数据传递给上下文对象。然后在需要使用数据的组件中使用 useContext 获取上下文对象中的数据。

注意事项

  1. 上下文对象中的数据应该是不可变的,避免直接修改上下文对象中的数据。
  2. 上下文对象中的数据应该是全局共享的,避免在组件中使用 useContext 获取到的数据与其他组件不一致。
  3. 上下文对象中的数据应该是简单的数据类型,避免在组件中使用 useContext 获取到的数据过于复杂。

useReducer:加强版 useState

useReducer是React Hooks中用于管理组件状态的钩子函数。

  • 接收三个参数:第一个参数是reducer函数,第二个参数是初始状态,第三个参数是初始化行为函数
    • 第一个参数reducer函数接收两个参数,一个是state,一个是action(也就是dispatch函数的参数)
    • 第二个参数通常是一个对象,作为初始值
    • 第三个参数成为初始化行为,可以在此处做数据持久化的处理,详见代码示例
  • 返回值:useReducer返回的是一个包含state和dispatch函数的数组。此处的dispatch可以类比理解为 useState 的 setState,不过他比 useState 具有更多的功能

下面是一个使用 useReducer 和 RooUI 实现的计数器功能,并实现了数据持久化:

jsx
import { Button } from '@roo/roo';
+import{_ as s,o as a,c as e,U as n}from"./chunks/framework.a7041386.js";const f=JSON.parse('{"title":"React Hooks","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React Hooks.md","filePath":"React/React Hooks.md","lastUpdated":1690701222000}'),l={name:"React/React Hooks.md"},o=n(`

React Hooks

一、产生原因

React Hooks是React v16.8.0版本引入的新特性,它可以让我们在函数组件中使用state和其他React特性,从而避免使用类组件。

在使用React Hooks时,有一些注意事项和设计依据需要遵循。

二、常用 Hooks

useState:状态定义

不可变状态,只能通过创建状态时提供的方法,进行状态的修改,而不能直接对状态进行赋值

  • useState返回的第一个参数是当前state,第二个参数是更新state的函数。
  • 在useState中如果需要更新state,不能基于当前的state进行更新,而是需要使用回调函数的方式进行更新,因为useState是异步更新state的。
  • 不能在条件语句中使用useState,因为条件语句在每次渲染时都会执行,如果在条件语句中使用useState,会导致state的混乱。

请记住:使用 useState 会触发组件的重新渲染。

例如:在一个组件中,引入了一个不需要改变的组件,同时使用useState定义并触发数据更改,则会造成这个被引入不必要的重渲染

useEffect:副作用处理

  • useEffect是React Hooks中用于处理副作用的钩子函数。
  • useEffect会在组件渲染完成后执行,并在每次state或props发生变化时重新执行。
  • 在useEffect中可以返回一个函数,这个函数会在组件被销毁时执行。

useLayoutEffect:副作用处理

useLayoutEffect是React提供的一个Hook函数,它和useEffect非常相似,都是用来处理副作用的,但是它们的执行时机不同。

useEffect 执行时机

useEffect 在内容重绘到页面之后 runs,useLayoutEffect在之前 runs

useEffect的执行时机是在DOM更新之后、浏览器绘制之后,也就是在useLayoutEffect之后执行。这个时机适合用来处理一些不需要立即执行的副作用,比如发送网络请求、修改全局状态等。

在组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入异步队列等待执行。

useLayoutEffect 执行时机

useLayoutEffect的执行时机是在DOM更新之后、浏览器绘制之前,也就是在useEffect之前执行。这个时机非常适合用来读取DOM节点的尺寸、位置等信息,因为这些信息需要在浏览器绘制之前就要计算好,否则可能会导致页面闪烁等问题。

组件首次渲染工作完成并将真实dom生成到页面以后,将对应的回调函数推入同步队列等待执行,这意味着useLayouEffect会完全阻塞后续的更新工作,也就是说,组件首次渲染完成后,会立即执行useLayoutEffect的回调函数,而不用等待主线程中其他未完成的工作。

总结

useLayoutEffect的原理和useEffect类似,都是通过在组件渲染时注册副作用函数,然后在组件卸载时执行清除函数来实现的。不同的是,useLayoutEffect会在浏览器绘制之前执行副作用函数,因此它会阻塞浏览器的渲染过程,可能会导致性能问题。

简单的理解就是,useLayoutEffect是立即执行,useEffect是推入异步队列,最终执行时间不确定。

因此,一般情况下,我们应该优先使用useEffect,只有在需要读取DOM节点信息时才使用useLayoutEffect。

useMemo:计算属性

产生依据

可以使用useMemo实现useCallback

useMemo类似于Vue3的计算属性,是针对于状态的,useCallback是针对函数的。。useCallback用来缓存函数,第一个参数是一个函数,但是他的这个函数不会被React执行,而是直接进入缓存;useMemo用来缓存状态,第一个参数也是一个函数,组件初始化时这个函数会被React直接执行,然后将其返回值进行缓存,第二个参数是依赖项,当依赖项变化时,React会重新执行对应的第一个参数,然后拿到最新的返回值,再次进行缓存。

参数说明

  • useMemo接收两个参数,第一个参数是需要进行计算的函数,第二个参数是依赖项数组。
  • 当依赖项数组中的值发生变化时,useMemo会重新执行计算函数,否则会使用上一次的计算结果。

useCallback:优化函数性能

产生依据

useCallback用于减少函数引用的创建次数。我们知道,每次组件的重新渲染都意味着内部所有的引用值都会被重新构建,每次函数组件的重新渲染都对应函数的重新执行。当不使用useCallback时,每一次状态变化,比如list或name状态变化,都会重新创建一个fetchData函数的引用,这是十分浪费性能的。实际上,fetchData函数只和url这个状态有关,当url这个状态不改变时,就不需要创建新的函数引用,因此就有了useCallback。

使用useCallback并且指定依赖项时,只有当url这个状态变化时,才会在新的时间切片里创建新的函数引用,否则就使用原先时间切片里的函数引用而无需创建新的引用。

参数说明

第一个参数是函数声明,第二个参数是依赖项,当依赖项发生了变动以后,对应的函数引用会被重新生成。若依赖项为空数组表示该回调函数不依赖于任何状态或属性,只会在组件挂载时创建一次,并在整个组件的生命周期内保持不变。这种情况下,useCallback的作用类似于普通的函数定义,但是由于它是在组件内部定义的,因此可以访问组件的状态和属性。使用空数组作为依赖项数组可以避免不必要的重新创建函数,从而提高性能。

注意事项

  • useCallback接收两个参数,第一个参数是需要缓存的函数,第二个参数是依赖项数组。
  • 当依赖项数组中的值发生变化时,useCallback会返回一个新的函数,否则会返回上一次缓存的函数。

useRef:构建出脱离React控制的状态

产生依据

useState用于构建组件状态,当状态变更的时候组件必定重新渲染;useRef出现的目的是构建一个状态出来,但是这个状态是直接脱离React控制的,也不会造成重新渲染,同时状态还不会因为组件的重新渲染而被初始化。useRef完全脱离React控制,意味着更改refInstance.current的值,不会像修改useState定义的状态一样,造成页面的重新渲染。refInstance是可以直接读写的,不需要像useState一样使用给定的方法来修改。

注意事项

  • useRef是React Hooks中用于获取DOM节点和存储任意值的钩子函数。
  • useRef返回的是一个对象,其中current属性是一个可变的变量,可以存储任意值。
  • 在函数组件中,使用useRef可以获取DOM节点的引用。

useContext:祖孙组件传值

产生依据

类似于Vue3的provide和inject,允许组件之间通过除 props 之外的方式共享数据,多用于祖孙组件之间的数据传递问题。

核心实现

  • useContext是React Hooks中用于获取上下文的钩子函数。
  • 使用useContext可以在函数组件中获取上下文的值,避免了使用类组件中的static contextType和Consumer。

useContext 和 createContext 是 React 中用于跨组件传递数据的两个 API,通常用于全局数据管理。很多著名的库,例如 react-router, redux 都是基于 useContext 来做的;

createContext 用于创建一个上下文对象,useContext 用于在组件中获取上下文对象中的数据。

使用场景

当多个组件需要共享同一个数据时,可以使用 createContext 创建一个上下文对象,并将数据传递给上下文对象。然后在需要使用数据的组件中使用 useContext 获取上下文对象中的数据。

注意事项

  1. 上下文对象中的数据应该是不可变的,避免直接修改上下文对象中的数据。
  2. 上下文对象中的数据应该是全局共享的,避免在组件中使用 useContext 获取到的数据与其他组件不一致。
  3. 上下文对象中的数据应该是简单的数据类型,避免在组件中使用 useContext 获取到的数据过于复杂。

useReducer:加强版 useState

useReducer是React Hooks中用于管理组件状态的钩子函数。

  • 接收三个参数:第一个参数是reducer函数,第二个参数是初始状态,第三个参数是初始化行为函数
    • 第一个参数reducer函数接收两个参数,一个是state,一个是action(也就是dispatch函数的参数)
    • 第二个参数通常是一个对象,作为初始值
    • 第三个参数成为初始化行为,可以在此处做数据持久化的处理,详见代码示例
  • 返回值:useReducer返回的是一个包含state和dispatch函数的数组。此处的dispatch可以类比理解为 useState 的 setState,不过他比 useState 具有更多的功能

下面是一个使用 useReducer 和 RooUI 实现的计数器功能,并实现了数据持久化:

jsx
import { Button } from '@roo/roo';
 
 const initialState = {
   name: 'Conny',
diff --git a/assets/React_React Hooks.md.8d73e4ec.lean.js b/assets/React_React Hooks.md.3560e5e9.lean.js
similarity index 57%
rename from assets/React_React Hooks.md.8d73e4ec.lean.js
rename to assets/React_React Hooks.md.3560e5e9.lean.js
index c539f7b..7b37c30 100644
--- a/assets/React_React Hooks.md.8d73e4ec.lean.js	
+++ b/assets/React_React Hooks.md.3560e5e9.lean.js	
@@ -1 +1 @@
-import{_ as s,o as a,c as e,U as n}from"./chunks/framework.a7041386.js";const f=JSON.parse('{"title":"React Hooks","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React Hooks.md","filePath":"React/React Hooks.md","lastUpdated":null}'),l={name:"React/React Hooks.md"},o=n("",94),t=[o];function p(r,c,i,u,y,D){return a(),e("div",null,t)}const h=s(l,[["render",p]]);export{f as __pageData,h as default};
+import{_ as s,o as a,c as e,U as n}from"./chunks/framework.a7041386.js";const f=JSON.parse('{"title":"React Hooks","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React Hooks.md","filePath":"React/React Hooks.md","lastUpdated":1690701222000}'),l={name:"React/React Hooks.md"},o=n("",94),t=[o];function p(r,c,i,u,y,D){return a(),e("div",null,t)}const h=s(l,[["render",p]]);export{f as __pageData,h as default};
diff --git a/assets/React_React Router v6.md.49c0c8c5.js b/assets/React_React Router v6.md.760af9af.js
similarity index 96%
rename from assets/React_React Router v6.md.49c0c8c5.js
rename to assets/React_React Router v6.md.760af9af.js
index fadec0a..2e49442 100644
--- a/assets/React_React Router v6.md.49c0c8c5.js	
+++ b/assets/React_React Router v6.md.760af9af.js	
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const F=JSON.parse('{"title":"React Router v6","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React Router v6.md","filePath":"React/React Router v6.md","lastUpdated":null}'),p={name:"React/React Router v6.md"},e=l(`

React Router v6

快速复习

  • BrowerRouter
  • NavLink vs Link (可以传递参数)
  • Routes && Route
  • useNavigate 强制路由跳转
  • Link && Navigate && useNavigate 通过 state 传递状态,通过 useLocation 接收
  • 通过 useParams 接收 URL 参数

一、概述

如何安装

pnpm add react-router-dom@6

概念

  • react-router:为 React 应用提供了路由的核心功能;
  • react-router-dom:基于 react-router,加入了在浏览器运行环境下的一些功能。

二、基本使用

BrowserRouter

要想在 React 应用中使用 React Router,就需要在 React 项目的根文件(index.tsx)中导入 Router 组件

javascript
import { StrictMode } from "react";
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const F=JSON.parse('{"title":"React Router v6","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React Router v6.md","filePath":"React/React Router v6.md","lastUpdated":1690701222000}'),p={name:"React/React Router v6.md"},e=l(`

React Router v6

快速复习

  • BrowerRouter
  • NavLink vs Link (可以传递参数)
  • Routes && Route
  • useNavigate 强制路由跳转
  • Link && Navigate && useNavigate 通过 state 传递状态,通过 useLocation 接收
  • 通过 useParams 接收 URL 参数

一、概述

如何安装

pnpm add react-router-dom@6

概念

  • react-router:为 React 应用提供了路由的核心功能;
  • react-router-dom:基于 react-router,加入了在浏览器运行环境下的一些功能。

二、基本使用

BrowserRouter

要想在 React 应用中使用 React Router,就需要在 React 项目的根文件(index.tsx)中导入 Router 组件

javascript
import { StrictMode } from "react";
 import * as ReactDOMClient from "react-dom/client";
 import { BrowserRouter } from "react-router-dom";
 
diff --git a/assets/React_React Router v6.md.49c0c8c5.lean.js b/assets/React_React Router v6.md.760af9af.lean.js
similarity index 55%
rename from assets/React_React Router v6.md.49c0c8c5.lean.js
rename to assets/React_React Router v6.md.760af9af.lean.js
index 2571998..b88138e 100644
--- a/assets/React_React Router v6.md.49c0c8c5.lean.js	
+++ b/assets/React_React Router v6.md.760af9af.lean.js	
@@ -1 +1 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const F=JSON.parse('{"title":"React Router v6","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React Router v6.md","filePath":"React/React Router v6.md","lastUpdated":null}'),p={name:"React/React Router v6.md"},e=l("",54),o=[e];function r(t,c,i,y,D,C){return n(),a("div",null,o)}const u=s(p,[["render",r]]);export{F as __pageData,u as default};
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const F=JSON.parse('{"title":"React Router v6","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React Router v6.md","filePath":"React/React Router v6.md","lastUpdated":1690701222000}'),p={name:"React/React Router v6.md"},e=l("",54),o=[e];function r(t,c,i,y,D,C){return n(),a("div",null,o)}const u=s(p,[["render",r]]);export{F as __pageData,u as default};
diff --git "a/assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.5bb91d09.js" "b/assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.1787ad45.js"
similarity index 80%
rename from "assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.5bb91d09.js"
rename to "assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.1787ad45.js"
index 4983cab..b0732b9 100644
--- "a/assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.5bb91d09.js"	
+++ "b/assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.1787ad45.js"	
@@ -1,4 +1,4 @@
-import{_ as s,o as a,c as n,U as l}from"./chunks/framework.a7041386.js";const d=JSON.parse('{"title":"React 事件机制","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React 事件机制.md","filePath":"React/React 事件机制.md","lastUpdated":null}'),e={name:"React/React 事件机制.md"},o=l(`

React 事件机制

前置知识

  • 事件冒泡
  • 事件委托
  • 函数式编程「React 官方是十分推崇函数式编程的」
    • 纯函数
    • 了解什么是副作用
    • 不可变状态
      • 可以使用一些npm包,使用可变的代码来操作状态
  • 拓展运算符 ...

React 事件机制「原理」

事件机制

事件机制,fiber架构,react调度机制,优先级概念,commit 以及 render两个阶段 以及hooks原理 -> 重难点

  • React的JSX 写的代码不是真实dom,经过babel编译成React.createElement,ReactElement 之后被react执行,从而在页面中生成真实dom

  • 所有的标签属性都不是真实的dom属性,而是会被react进行处理 最终反应到真实dom身上去

    • React中的属性分为标签属性和组件属性
  • react的jsx上的标签不是真实标签,它会被babel编译为ReactElement,ReactDom再将ReactElement转换为真实dom

  • React 事件和原生的事件行为差不多,基本上原生事件能做到的事情,React 都复刻了一遍

  • react 为了节约性能以及实现动态监听,react使用事件委托的机制

    假设我现在有1000个dom,与其我绑定1000个dom事件

    不如我给这1000个dom的父级绑定事件,给父级绑定的话 只需要绑定一个事件就ok了,event.target--—>指向真正触发事件的元素

    react把事件绑定在了对应的 root 元素上,当某个真实dom触发事件以后,dom事件会随着事件冒泡 一直冒到root元素上,root元素对应的事件处理函数又可以通过

    event.target知道真正触发事件的元素是谁,进而执行处理

    那其实就意味着 对应的jsx所转化的真实dom身上不会绑定任何的真实事件,react会把jsx上所书写的对应的和事件有关的标签属性收集起来 找个地方存起来

    最终真实dom在页面生成,当我们点击对应的真实dom时 事件会冒泡 事件冒泡是不需要绑定真实dom事件也会冒泡的 最终会冒泡到root 然后root来进行事件的处理

事件池机制

React 17以上的版本取消了事件池机制

事件池机制的本质就是保存对event的引用而不是重新创建另一个event

  • react里的标签属性事件 对应的event 是哪来的??【react 捏给你的,和真实dom没有半毛钱关系】
  • 在16.8以及之前的版本 react为了更好的性能考虑会尝试重用事件
  • react会保存引用 只是修改对应的属性值

⚠️:

  • 基于React的事件池机制,只要公司用的还是17以下的代码,都要注意不要在异步环境下访问事件源对象的属性,例如在调试中发现event.target===null这种情况,就要考虑是否不小心触发了事件池机制
  • 使用 e. persist() 取消事件池机制

受控组件和非受控组件

React中,受控和非受控 我们只在表单组件中去谈【因为只有表单组件才涉及到交互】

一个组件如果不涉及到交互,他就是一个渲染组件 UI 组件,不用考虑受控和非受控的问题

在表单组件中,判定受控和非受控的标准是什么?【受控标签属性的植入】【标签受控展性 —也可以理解为是一个标记,出现了这个标记则该组件为受控组件]

在input里面 受控标签属性 是 value 属性

当你给input设置上value的值以后,input框里面出现什么文字 不再由用户输入说了算,而是由你的这个value值决定

所有的表单组件都分为受控和非受控:checkbox,radio

举个例子:

js
import { useState } from "react";
+import{_ as s,o as a,c as n,U as l}from"./chunks/framework.a7041386.js";const d=JSON.parse('{"title":"React 事件机制","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React 事件机制.md","filePath":"React/React 事件机制.md","lastUpdated":1690701222000}'),e={name:"React/React 事件机制.md"},o=l(`

React 事件机制

前置知识

  • 事件冒泡
  • 事件委托
  • 函数式编程「React 官方是十分推崇函数式编程的」
    • 纯函数
    • 了解什么是副作用
    • 不可变状态
      • 可以使用一些npm包,使用可变的代码来操作状态
  • 拓展运算符 ...

React 事件机制「原理」

事件机制

事件机制,fiber架构,react调度机制,优先级概念,commit 以及 render两个阶段 以及hooks原理 -> 重难点

  • React的JSX 写的代码不是真实dom,经过babel编译成React.createElement,ReactElement 之后被react执行,从而在页面中生成真实dom

  • 所有的标签属性都不是真实的dom属性,而是会被react进行处理 最终反应到真实dom身上去

    • React中的属性分为标签属性和组件属性
  • react的jsx上的标签不是真实标签,它会被babel编译为ReactElement,ReactDom再将ReactElement转换为真实dom

  • React 事件和原生的事件行为差不多,基本上原生事件能做到的事情,React 都复刻了一遍

  • react 为了节约性能以及实现动态监听,react使用事件委托的机制

    假设我现在有1000个dom,与其我绑定1000个dom事件

    不如我给这1000个dom的父级绑定事件,给父级绑定的话 只需要绑定一个事件就ok了,event.target--—>指向真正触发事件的元素

    react把事件绑定在了对应的 root 元素上,当某个真实dom触发事件以后,dom事件会随着事件冒泡 一直冒到root元素上,root元素对应的事件处理函数又可以通过

    event.target知道真正触发事件的元素是谁,进而执行处理

    那其实就意味着 对应的jsx所转化的真实dom身上不会绑定任何的真实事件,react会把jsx上所书写的对应的和事件有关的标签属性收集起来 找个地方存起来

    最终真实dom在页面生成,当我们点击对应的真实dom时 事件会冒泡 事件冒泡是不需要绑定真实dom事件也会冒泡的 最终会冒泡到root 然后root来进行事件的处理

事件池机制

React 17以上的版本取消了事件池机制

事件池机制的本质就是保存对event的引用而不是重新创建另一个event

  • react里的标签属性事件 对应的event 是哪来的??【react 捏给你的,和真实dom没有半毛钱关系】
  • 在16.8以及之前的版本 react为了更好的性能考虑会尝试重用事件
  • react会保存引用 只是修改对应的属性值

⚠️:

  • 基于React的事件池机制,只要公司用的还是17以下的代码,都要注意不要在异步环境下访问事件源对象的属性,例如在调试中发现event.target===null这种情况,就要考虑是否不小心触发了事件池机制
  • 使用 e. persist() 取消事件池机制

受控组件和非受控组件

React中,受控和非受控 我们只在表单组件中去谈【因为只有表单组件才涉及到交互】

一个组件如果不涉及到交互,他就是一个渲染组件 UI 组件,不用考虑受控和非受控的问题

在表单组件中,判定受控和非受控的标准是什么?【受控标签属性的植入】【标签受控展性 —也可以理解为是一个标记,出现了这个标记则该组件为受控组件]

在input里面 受控标签属性 是 value 属性

当你给input设置上value的值以后,input框里面出现什么文字 不再由用户输入说了算,而是由你的这个value值决定

所有的表单组件都分为受控和非受控:checkbox,radio

举个例子:

js
import { useState } from "react";
 
 export default function Test(){
   const [val,setVal] = useState("")
diff --git "a/assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.5bb91d09.lean.js" "b/assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.1787ad45.lean.js"
similarity index 54%
rename from "assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.5bb91d09.lean.js"
rename to "assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.1787ad45.lean.js"
index f66d625..3c9fc74 100644
--- "a/assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.5bb91d09.lean.js"	
+++ "b/assets/React_React \344\272\213\344\273\266\346\234\272\345\210\266.md.1787ad45.lean.js"	
@@ -1 +1 @@
-import{_ as s,o as a,c as n,U as l}from"./chunks/framework.a7041386.js";const d=JSON.parse('{"title":"React 事件机制","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React 事件机制.md","filePath":"React/React 事件机制.md","lastUpdated":null}'),e={name:"React/React 事件机制.md"},o=l("",84),p=[o];function t(r,c,i,F,D,y){return a(),n("div",null,p)}const C=s(e,[["render",t]]);export{d as __pageData,C as default};
+import{_ as s,o as a,c as n,U as l}from"./chunks/framework.a7041386.js";const d=JSON.parse('{"title":"React 事件机制","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React 事件机制.md","filePath":"React/React 事件机制.md","lastUpdated":1690701222000}'),e={name:"React/React 事件机制.md"},o=l("",84),p=[o];function t(r,c,i,F,D,y){return a(),n("div",null,p)}const C=s(e,[["render",t]]);export{d as __pageData,C as default};
diff --git "a/assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.8e95a85a.js" "b/assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.ddfa0dea.js"
similarity index 61%
rename from "assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.8e95a85a.js"
rename to "assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.ddfa0dea.js"
index d7bd5b5..083faeb 100644
--- "a/assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.8e95a85a.js"	
+++ "b/assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.ddfa0dea.js"	
@@ -1,4 +1,4 @@
-import{_ as s,o as a,c as n,U as l}from"./chunks/framework.a7041386.js";const b=JSON.parse('{"title":"React 基础学习","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React 基础学习.md","filePath":"React/React 基础学习.md","lastUpdated":null}'),e={name:"React/React 基础学习.md"},p=l(`

React 基础学习

推荐阅读:https://juejin.cn/post/7118937685653192735

起步

本质

在React jsx文件中每一次对组件的使用<App />都是在直接调用对应的组件函数

  • component state组件状态,实际上直接翻译过来不准确,因此应该理解为“组件数据”

  • 页面渲染 === 组件执行 / 重新渲染 === 函数组件的重新执行

  • 我们最终执行的代码是通过babel编译以后的代码

    React. createElement("span",{},count)--->调用document上面的一些方法去改变真实dom的显示状态

    count->0 如果我们想要页面里发生一点显示效果的变化,我们得让React.createElement这段代码重复执行

  • 如果想尝试让函数组建重新渲染,只有两种方式

    • 组件状态发生变化,注意此处是指通过useState定义的状态
    • 父组件重新渲染

解决了什么问题

  • 组件的逻辑复用
  • 解决了mixins混入的数据来源不清晰,HOC高阶组件的嵌套问题
  • 让函数组件拥有了类组件的特性,例如组件内的状态、生命周期等

理解脚手架

脚手架:在工程学里,脚手架提供了一系列预设,让施工者无需再考虑除了建造以外的其他外部问题,在编程学里,脚手架同样提供了一系列的预设,

让开发者无需再考虑除了自身业务代码以外的其他外部问题:

  1. 我们要发生产环境,代码压缩,用webpack,脚手架:我直接给你集成webpack,不仅如此 我还帮你把webpack的这个配置写好了 你不用管

  2. 组件化,我该怎么划分我的文件夹??图片该放哪 我的组件又该放哪 脚手架:你别管,我也处理好了 你只需要用我这个脚手架 我直接帮你把项

目目录直接生成

  1. 网络:跨域———>浏览器他不会让你随便请求别的服务器的数据的,如果不是同域名 同协议同端口 浏览器会进行拦截 我们就要跨域,那这个时

候我们就要去搭建临时的跨域服务器,脚手架:小意思辣 你别管 我来

  1. ...

规范

React对组件有一些要求:

  1. 组件名必须大写

  2. React组件必须要返回可以渲染的东西

  • null
  • React元素
  • 组件
  • 可以被迭代的对象【包括数组,set,map..】,只要一个对象具备迭代接口,那他就可以被渲染
  • 状态 以及属性。

组件属性

函数参数是让一个函数变得更加灵活的关键,同样,组件属性是让一个组件变得更加灵活的关键,组件属性和函数参数的原理大差不差,你给组件传递属性也就是意味着你在给对应的组件函数传递参数

但凡一个组件要重新渲染,都必须是满足以下两个条件之一:

  • 自身状态发生变化
  • 父组件重新渲染,自身就会重新渲染✨

React中的属性分为 标签属性 以及 组件属性

  • 传递给组件的自然而然就是组件属性
  • 传递给]SX的标签元素的属性就叫做标签属性【标签元素:在htmL中有明确的对标元素就叫做标签元素】,标签属性会被React自行处理对应到底层的事件或者属性
    • JSX最终都会被babel转换为React.createElement
      • 如果是标签元素,则会将对应的标签属性全部传递给React.createElement,然后React内部会自行处理
      • 如果是组件元素,他的这些组件属性会被作为参数传递给对应组件函数

hooks分类

https://km.sankuai.com/api/file/cdn/1728403390/43950220759?contentType=1&isNewContent=false

自变量

  • useState
  • useReducer
  • useContext

因变量,含有依赖项

  • useEffect ==> watchEffect
  • useMemo ==> computed计算属性
  • useCallback ==> 减少函数创建的次数

其他

  • useRef

useState

在我们使用usestate的时候 【我们为什么需要使用usestate来构建状态?因为使用usestate构建的状态会返回一个更新状态的函数,当调用这个

函数去修改状态时,React会通知组件去进行重新渲染】

  • 组件状态的更新是异步的【这意味着当更改状态的函数执行以后我们没有办法同步的马上得到他更新以后的值】

    那我如何拿到最新的状态呢?useEffect / useLayoutEffect

useState在调用的时候,可以给具体值,也可以给一个函数,这个函数的返回值被作为初始值,但是不推荐这种写法

为什么?✨

拿计数器Counter组件举例,

  • Counter函数的重新执行意味着Counter函数内部的代码要全部执行一遍,包括useState()
  • 但是useState内部对初始化操作有区分,只要不是在该函数组件内第一次调用useState,就不会进行初始化操作
  • 不会进行初始化操作的意思是不会将你传递给useState的值去重新赋值,也就意味着如果你传递给useState的是一个函数,这个函数的计算只在初始化时有意义,后续函数计算的结果没有意义✨

请看一段伪代码,便于理解useState的工作原理

function useState(initialState) { let state; if (isFirstIn) { state = initialState } const dispatch = (newState)=>{ state = newState render() //重新渲染 } return [state, dispatch] }

推荐写法

[state, setState] = useState(initialValue)

  • initialValue写成具体值而不是一个函数
  • setState(prev=>{return something}),setState函数的参数推荐写成函数的形式而不是具体值

总结

  • 组件状态的更新是异步的,意味着当更改状态的函数执行以后我们没有办法同步的马上得到他更新以后的值,那我如何拿到最新的状态呢?useEffect / uselLayoutEffect
  • usestate在调用的时候可以传递函数,也可以传递具体的值,如果传递的是函数,则会直接将函数的返回值作为初始化状态,但是虽然在初始化的时候他允许你传递函数,我们也尽量不要传递函数,因为初始化工作只会进行一次
  • usestate会返回一个数组,数组里面有两个成员
    • 以初始化为值的变量
    • 修改该变量的函數,这个函数的调用会造成函数组件的重新运行
      • 调用该函数的时候可以直接传递一个值,也可以传递一个函数,如果你传递一个函数进去,则React会将上一次的状态传递给你,帮助你进行计算;如果你传递的是一个函数,React会将这个函数放到一个队列里等待执行,那也就是如果我们想每次都稳稳的拿到上一次的值,我们得写成一个函数
      • 推荐写成函数的形式
      • 状态的更新是批量进行的,而不是一个一个的进行,这是为了性能考虑,成为auto batching

useEffect

函数副作用

副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用。

常见副作用

  • 数据请求 ajax 发送手动修改 domlocalstorage 操作......useEffect 函数的作用就是为 react 函数组件提供副作用处理的!

案例1:useEffect(fn)

默认是全部属性的副作用都会调用该函数,很少使用

javascript
import React, { useEffect, useState } from "react";
+import{_ as s,o as a,c as n,U as l}from"./chunks/framework.a7041386.js";const b=JSON.parse('{"title":"React 基础学习","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React 基础学习.md","filePath":"React/React 基础学习.md","lastUpdated":1690701222000}'),e={name:"React/React 基础学习.md"},p=l(`

React 基础学习

推荐阅读:https://juejin.cn/post/7118937685653192735

起步

本质

在React jsx文件中每一次对组件的使用<App />都是在直接调用对应的组件函数

  • component state组件状态,实际上直接翻译过来不准确,因此应该理解为“组件数据”

  • 页面渲染 === 组件执行 / 重新渲染 === 函数组件的重新执行

  • 我们最终执行的代码是通过babel编译以后的代码

    React. createElement("span",{},count)--->调用document上面的一些方法去改变真实dom的显示状态

    count->0 如果我们想要页面里发生一点显示效果的变化,我们得让React.createElement这段代码重复执行

  • 如果想尝试让函数组建重新渲染,只有两种方式

    • 组件状态发生变化,注意此处是指通过useState定义的状态
    • 父组件重新渲染

解决了什么问题

  • 组件的逻辑复用
  • 解决了mixins混入的数据来源不清晰,HOC高阶组件的嵌套问题
  • 让函数组件拥有了类组件的特性,例如组件内的状态、生命周期等

理解脚手架

脚手架:在工程学里,脚手架提供了一系列预设,让施工者无需再考虑除了建造以外的其他外部问题,在编程学里,脚手架同样提供了一系列的预设,

让开发者无需再考虑除了自身业务代码以外的其他外部问题:

  1. 我们要发生产环境,代码压缩,用webpack,脚手架:我直接给你集成webpack,不仅如此 我还帮你把webpack的这个配置写好了 你不用管

  2. 组件化,我该怎么划分我的文件夹??图片该放哪 我的组件又该放哪 脚手架:你别管,我也处理好了 你只需要用我这个脚手架 我直接帮你把项

目目录直接生成

  1. 网络:跨域———>浏览器他不会让你随便请求别的服务器的数据的,如果不是同域名 同协议同端口 浏览器会进行拦截 我们就要跨域,那这个时

候我们就要去搭建临时的跨域服务器,脚手架:小意思辣 你别管 我来

  1. ...

规范

React对组件有一些要求:

  1. 组件名必须大写

  2. React组件必须要返回可以渲染的东西

  • null
  • React元素
  • 组件
  • 可以被迭代的对象【包括数组,set,map..】,只要一个对象具备迭代接口,那他就可以被渲染
  • 状态 以及属性。

组件属性

函数参数是让一个函数变得更加灵活的关键,同样,组件属性是让一个组件变得更加灵活的关键,组件属性和函数参数的原理大差不差,你给组件传递属性也就是意味着你在给对应的组件函数传递参数

但凡一个组件要重新渲染,都必须是满足以下两个条件之一:

  • 自身状态发生变化
  • 父组件重新渲染,自身就会重新渲染✨

React中的属性分为 标签属性 以及 组件属性

  • 传递给组件的自然而然就是组件属性
  • 传递给]SX的标签元素的属性就叫做标签属性【标签元素:在htmL中有明确的对标元素就叫做标签元素】,标签属性会被React自行处理对应到底层的事件或者属性
    • JSX最终都会被babel转换为React.createElement
      • 如果是标签元素,则会将对应的标签属性全部传递给React.createElement,然后React内部会自行处理
      • 如果是组件元素,他的这些组件属性会被作为参数传递给对应组件函数

hooks分类

https://km.sankuai.com/api/file/cdn/1728403390/43950220759?contentType=1&isNewContent=false

自变量

  • useState
  • useReducer
  • useContext

因变量,含有依赖项

  • useEffect ==> watchEffect
  • useMemo ==> computed计算属性
  • useCallback ==> 减少函数创建的次数

其他

  • useRef

useState

在我们使用usestate的时候 【我们为什么需要使用usestate来构建状态?因为使用usestate构建的状态会返回一个更新状态的函数,当调用这个

函数去修改状态时,React会通知组件去进行重新渲染】

  • 组件状态的更新是异步的【这意味着当更改状态的函数执行以后我们没有办法同步的马上得到他更新以后的值】

    那我如何拿到最新的状态呢?useEffect / useLayoutEffect

useState在调用的时候,可以给具体值,也可以给一个函数,这个函数的返回值被作为初始值,但是不推荐这种写法

为什么?✨

拿计数器Counter组件举例,

  • Counter函数的重新执行意味着Counter函数内部的代码要全部执行一遍,包括useState()
  • 但是useState内部对初始化操作有区分,只要不是在该函数组件内第一次调用useState,就不会进行初始化操作
  • 不会进行初始化操作的意思是不会将你传递给useState的值去重新赋值,也就意味着如果你传递给useState的是一个函数,这个函数的计算只在初始化时有意义,后续函数计算的结果没有意义✨

请看一段伪代码,便于理解useState的工作原理

function useState(initialState) { let state; if (isFirstIn) { state = initialState } const dispatch = (newState)=>{ state = newState render() //重新渲染 } return [state, dispatch] }

推荐写法

[state, setState] = useState(initialValue)

  • initialValue写成具体值而不是一个函数
  • setState(prev=>{return something}),setState函数的参数推荐写成函数的形式而不是具体值

总结

  • 组件状态的更新是异步的,意味着当更改状态的函数执行以后我们没有办法同步的马上得到他更新以后的值,那我如何拿到最新的状态呢?useEffect / uselLayoutEffect
  • usestate在调用的时候可以传递函数,也可以传递具体的值,如果传递的是函数,则会直接将函数的返回值作为初始化状态,但是虽然在初始化的时候他允许你传递函数,我们也尽量不要传递函数,因为初始化工作只会进行一次
  • usestate会返回一个数组,数组里面有两个成员
    • 以初始化为值的变量
    • 修改该变量的函數,这个函数的调用会造成函数组件的重新运行
      • 调用该函数的时候可以直接传递一个值,也可以传递一个函数,如果你传递一个函数进去,则React会将上一次的状态传递给你,帮助你进行计算;如果你传递的是一个函数,React会将这个函数放到一个队列里等待执行,那也就是如果我们想每次都稳稳的拿到上一次的值,我们得写成一个函数
      • 推荐写成函数的形式
      • 状态的更新是批量进行的,而不是一个一个的进行,这是为了性能考虑,成为auto batching

useEffect

函数副作用

副作用是相对于主作用来说的,一个函数除了主作用,其他的作用就是副作用。对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用。

常见副作用

  • 数据请求 ajax 发送手动修改 domlocalstorage 操作......useEffect 函数的作用就是为 react 函数组件提供副作用处理的!

案例1:useEffect(fn)

默认是全部属性的副作用都会调用该函数,很少使用

javascript
import React, { useEffect, useState } from "react";
 
 // 函数组件
 function Sub () {
diff --git "a/assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.8e95a85a.lean.js" "b/assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.ddfa0dea.lean.js"
similarity index 54%
rename from "assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.8e95a85a.lean.js"
rename to "assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.ddfa0dea.lean.js"
index 2d617fc..7650103 100644
--- "a/assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.8e95a85a.lean.js"	
+++ "b/assets/React_React \345\237\272\347\241\200\345\255\246\344\271\240.md.ddfa0dea.lean.js"	
@@ -1 +1 @@
-import{_ as s,o as a,c as n,U as l}from"./chunks/framework.a7041386.js";const b=JSON.parse('{"title":"React 基础学习","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React 基础学习.md","filePath":"React/React 基础学习.md","lastUpdated":null}'),e={name:"React/React 基础学习.md"},p=l("",70),o=[p];function t(c,r,i,y,F,u){return a(),n("div",null,o)}const C=s(e,[["render",t]]);export{b as __pageData,C as default};
+import{_ as s,o as a,c as n,U as l}from"./chunks/framework.a7041386.js";const b=JSON.parse('{"title":"React 基础学习","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/React 基础学习.md","filePath":"React/React 基础学习.md","lastUpdated":1690701222000}'),e={name:"React/React 基础学习.md"},p=l("",70),o=[p];function t(c,r,i,y,F,u){return a(),n("div",null,o)}const C=s(e,[["render",t]]);export{b as __pageData,C as default};
diff --git "a/assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.62788972.js" "b/assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.4578d7b6.js"
similarity index 53%
rename from "assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.62788972.js"
rename to "assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.4578d7b6.js"
index 3a24da2..abe088c 100644
--- "a/assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.62788972.js"
+++ "b/assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.4578d7b6.js"
@@ -1,6 +1,6 @@
-import{_ as a,o as e,c as l,U as s}from"./chunks/framework.a7041386.js";const _=JSON.parse('{"title":"框架抽象","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/框架抽象.md","filePath":"React/框架抽象.md","lastUpdated":null}'),n={name:"React/框架抽象.md"},t=s(`

框架抽象

层级划分(三层):

  • 应用层面
    • 组件层面
      • 节点层面

工作原理

UI = f(state)

视图 = 框架内部运行机制(状态)

框架内部的运行机制根据状态渲染视图

不同框架的区别

不同框架的区别主要是更新粒度的区别

1. 节点级更新粒度 - Svelte

关键词: 预编译 细粒度更新

原理:

  1. 将状态变化可能导致的节点变化编译为具体方法
  2. 监听状态变化
  3. 当交互导致状态变化后直接调用具体方法, 改变对应视图

例子:

<h1 on:click={handleClick}>{}count</h1>
  1. 将cout状态变化可能导致h1内的文本节点变化, 编译为update方法, 即:预编译过程

    // 每次调用update方法, 如果count状态变化, 就更新视图中对应的文本节点
    +import{_ as a,o as e,c as l,U as s}from"./chunks/framework.a7041386.js";const _=JSON.parse('{"title":"框架抽象","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/框架抽象.md","filePath":"React/框架抽象.md","lastUpdated":1690701222000}'),t={name:"React/框架抽象.md"},n=s(`

    框架抽象

    层级划分(三层):

    • 应用层面
      • 组件层面
        • 节点层面

    工作原理

    UI = f(state)

    视图 = 框架内部运行机制(状态)

    框架内部的运行机制根据状态渲染视图

    不同框架的区别

    不同框架的区别主要是更新粒度的区别

    1. 节点级更新粒度 - Svelte

    关键词: 预编译 细粒度更新

    原理:

    1. 将状态变化可能导致的节点变化编译为具体方法
    2. 监听状态变化
    3. 当交互导致状态变化后直接调用具体方法, 改变对应视图

    例子:

    <h1 on:click={handleClick}>{}count</h1>
    1. 将cout状态变化可能导致h1内的文本节点变化, 编译为update方法, 即:预编译过程

      // 每次调用update方法, 如果count状态变化, 就更新视图中对应的文本节点
        function update(ctx, [dirty]) {
          if (dirty & /*count*/ 1) {
            set_data_dev(t0, /*count*/ ctx[0]);
          }
      - }
    2. 监听状态变化, 采用"发布订阅"的设计模式, 通过这种方式, 框架能对每个状态变化作出反应, 即实现细粒度更新

      1. 订阅: 每当创建一个状态后, 会为该状态维护一个订阅该状态变化的表, 所有需要监听该状态变化的回调函数都会在该表中注册
      2. 发布: 每当状态变化, 会遍历这个表, 将"状态变了"这一消息发布出去, 每个订阅该状态的回调函数都会接受到通知并执行

    代表: Svelte, Solid.js

    2. 应用级更新框架 - React

    关键词: 虚拟DOM

    节点级框架需要监听状态的变化, 应用级框架则不关心状态的变化, 因为应用级框架中, 任何一个状态变化, 都会创建一颗完整的虚拟DOM树, 框架会通过前后虚拟DOM的对比找到变化的部分, 最终将变化的状态更新到视图

    1. 状态变化
    2. 创建一棵完整的虚拟DOM树
    3. 比较, 并将变化的部分更新到视图

    3. 组件级更新框架 - Vue

    关键词:

    Vue2 虚拟DOM+细粒度更新

    Vue3 虚拟DOM+预编译+细粒度更新

    1. 状态变化
    2. 针对组件层面, 创建一棵组件级虚拟DOM树
    3. 比较, 并将变化的部分更新到视图
    `,24),o=[t];function i(p,r,c,u,d,h){return e(),l("div",null,o)}const m=a(n,[["render",i]]);export{_ as __pageData,m as default}; + }
  2. 监听状态变化, 采用"发布订阅"的设计模式, 通过这种方式, 框架能对每个状态变化作出反应, 即实现细粒度更新

    1. 订阅: 每当创建一个状态后, 会为该状态维护一个订阅该状态变化的表, 所有需要监听该状态变化的回调函数都会在该表中注册
    2. 发布: 每当状态变化, 会遍历这个表, 将"状态变了"这一消息发布出去, 每个订阅该状态的回调函数都会接受到通知并执行

代表: Svelte, Solid.js

2. 应用级更新框架 - React

关键词: 虚拟DOM

节点级框架需要监听状态的变化, 应用级框架则不关心状态的变化, 因为应用级框架中, 任何一个状态变化, 都会创建一颗完整的虚拟DOM树, 框架会通过前后虚拟DOM的对比找到变化的部分, 最终将变化的状态更新到视图

  1. 状态变化
  2. 创建一棵完整的虚拟DOM树
  3. 比较, 并将变化的部分更新到视图

3. 组件级更新框架 - Vue

关键词:

Vue2 虚拟DOM+细粒度更新

Vue3 虚拟DOM+预编译+细粒度更新

  1. 状态变化
  2. 针对组件层面, 创建一棵组件级虚拟DOM树
  3. 比较, 并将变化的部分更新到视图
`,24),o=[n];function i(p,r,c,u,d,h){return e(),l("div",null,o)}const m=a(t,[["render",i]]);export{_ as __pageData,m as default}; diff --git "a/assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.62788972.lean.js" "b/assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.4578d7b6.lean.js" similarity index 56% rename from "assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.62788972.lean.js" rename to "assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.4578d7b6.lean.js" index 1c968c0..5d6d860 100644 --- "a/assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.62788972.lean.js" +++ "b/assets/React_\346\241\206\346\236\266\346\212\275\350\261\241.md.4578d7b6.lean.js" @@ -1 +1 @@ -import{_ as a,o as e,c as l,U as s}from"./chunks/framework.a7041386.js";const _=JSON.parse('{"title":"框架抽象","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/框架抽象.md","filePath":"React/框架抽象.md","lastUpdated":null}'),n={name:"React/框架抽象.md"},t=s("",24),o=[t];function i(p,r,c,u,d,h){return e(),l("div",null,o)}const m=a(n,[["render",i]]);export{_ as __pageData,m as default}; +import{_ as a,o as e,c as l,U as s}from"./chunks/framework.a7041386.js";const _=JSON.parse('{"title":"框架抽象","description":"","frontmatter":{"tag":["React"]},"headers":[],"relativePath":"React/框架抽象.md","filePath":"React/框架抽象.md","lastUpdated":1690701222000}'),t={name:"React/框架抽象.md"},n=s("",24),o=[n];function i(p,r,c,u,d,h){return e(),l("div",null,o)}const m=a(t,[["render",i]]);export{_ as __pageData,m as default}; diff --git a/assets/TS_Quick Start.md.07656ad5.js b/assets/TS_Quick Start.md.07656ad5.js deleted file mode 100644 index 2a83f6f..0000000 --- a/assets/TS_Quick Start.md.07656ad5.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,o as t,c as a,U as r}from"./chunks/framework.a7041386.js";const _=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/Quick Start.md","filePath":"TS/Quick Start.md","lastUpdated":null}'),i={name:"TS/Quick Start.md"},n=r('

Quick Start

TS和JS的区别

TS和JS的区别是,TS是JS的一个超集,也就是说,JS有的TS都有,而TS还有一些JS没有的特性。最大的特性就是TS提供了类型系统³⁵,可以在编译时检查代码中的错误和不匹配。而JS是一个弱类型语言,只能在运行时发现错误。

另外,TS还支持ES6标准,可以编译成任意版本的JS代码³,从而解决不同浏览器和平台的兼容问题, TS还能获得更好的代码提示

TS中type和interface的区别

TS中type和interface的区别是一个常见的问题:

  • type和interface都可以用来定义类型,但type还可以用来定义别名、联合类型、交叉类型等。
  • interface可以被多次声明并合并,而type只能被声明一次。
  • interface可以继承其他interface或class,而type不能继承class。
  • interface更符合面向对象的思想,而type更适合函数式编程。

我来给你举几个例子。

  • type可以定义别名,比如type Name = string,就是把string类型取一个别名叫Name。interface不能这样做。
  • type可以定义联合类型,比如type Color = 'red' | 'green' | 'blue',就是把三个字符串字面量类型合并成一个类型叫Color。interface不能这样做。
  • type可以定义交叉类型,比如type Person = {name: string} & {age: number},就是把两个对象类型合并成一个类型叫Person。interface不能这样做。
  • interface可以被多次声明并合并,比如interface Animal {name: string}interface Animal {age: number}会被合并成一个接口Animal。type只能被声明一次。
  • interface可以继承其他interface或class,比如interface Dog extends Animal {bark(): void}或者interface Car extends Vehicle {drive(): void}。type不能继承class。

我再尝试解释一下:

type和interface都是用来定义类型的,但是它们有不同的用途和特点。

type更像是一个工具,它可以让你给一个已有的类型取一个新的名字,或者把多个类型组合成一个新的类型。这样可以让你写出更简洁和灵活的代码。

interface更像是一个契约,它可以让你规定一个对象或函数必须具备哪些属性或方法。这样可以让你写出更清晰和安全的代码。

type和interface之间有一些重叠的功能,比如都可以定义对象类型。但是它们也有一些互斥的功能,比如type可以定义别名、联合类型、交叉类型等,而interface可以被多次声明并合并、继承其他interface或class等。

',14),c=[n];function o(s,l,p,d,S,f){return t(),a("div",null,c)}const m=e(i,[["render",o]]);export{_ as __pageData,m as default}; diff --git a/assets/TS_Quick Start.md.07656ad5.lean.js b/assets/TS_Quick Start.md.07656ad5.lean.js deleted file mode 100644 index 99eeaab..0000000 --- a/assets/TS_Quick Start.md.07656ad5.lean.js +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,o as t,c as a,U as r}from"./chunks/framework.a7041386.js";const _=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/Quick Start.md","filePath":"TS/Quick Start.md","lastUpdated":null}'),i={name:"TS/Quick Start.md"},n=r("",14),c=[n];function o(s,l,p,d,S,f){return t(),a("div",null,c)}const m=e(i,[["render",o]]);export{_ as __pageData,m as default}; diff --git a/assets/TS_Quick Start.md.0be8b76a.js b/assets/TS_Quick Start.md.0be8b76a.js new file mode 100644 index 0000000..1a58a4e --- /dev/null +++ b/assets/TS_Quick Start.md.0be8b76a.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,U as r}from"./chunks/framework.a7041386.js";const u=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/Quick Start.md","filePath":"TS/Quick Start.md","lastUpdated":1690701222000}'),i={name:"TS/Quick Start.md"},c=r('

Quick Start

TS和JS的区别

TS和JS的区别是,TS是JS的一个超集,也就是说,JS有的TS都有,而TS还有一些JS没有的特性。最大的特性就是TS提供了类型系统³⁵,可以在编译时检查代码中的错误和不匹配。而JS是一个弱类型语言,只能在运行时发现错误。

另外,TS还支持ES6标准,可以编译成任意版本的JS代码³,从而解决不同浏览器和平台的兼容问题, TS还能获得更好的代码提示

TS中type和interface的区别

TS中type和interface的区别是一个常见的问题:

  • type和interface都可以用来定义类型,但type还可以用来定义别名、联合类型、交叉类型等。
  • interface可以被多次声明并合并,而type只能被声明一次。
  • interface可以继承其他interface或class,而type不能继承class。
  • interface更符合面向对象的思想,而type更适合函数式编程。

我来给你举几个例子。

  • type可以定义别名,比如type Name = string,就是把string类型取一个别名叫Name。interface不能这样做。
  • type可以定义联合类型,比如type Color = 'red' | 'green' | 'blue',就是把三个字符串字面量类型合并成一个类型叫Color。interface不能这样做。
  • type可以定义交叉类型,比如type Person = {name: string} & {age: number},就是把两个对象类型合并成一个类型叫Person。interface不能这样做。
  • interface可以被多次声明并合并,比如interface Animal {name: string}interface Animal {age: number}会被合并成一个接口Animal。type只能被声明一次。
  • interface可以继承其他interface或class,比如interface Dog extends Animal {bark(): void}或者interface Car extends Vehicle {drive(): void}。type不能继承class。

我再尝试解释一下:

type和interface都是用来定义类型的,但是它们有不同的用途和特点。

type更像是一个工具,它可以让你给一个已有的类型取一个新的名字,或者把多个类型组合成一个新的类型。这样可以让你写出更简洁和灵活的代码。

interface更像是一个契约,它可以让你规定一个对象或函数必须具备哪些属性或方法。这样可以让你写出更清晰和安全的代码。

type和interface之间有一些重叠的功能,比如都可以定义对象类型。但是它们也有一些互斥的功能,比如type可以定义别名、联合类型、交叉类型等,而interface可以被多次声明并合并、继承其他interface或class等。

',14),n=[c];function o(s,l,p,d,S,f){return t(),a("div",null,n)}const m=e(i,[["render",o]]);export{u as __pageData,m as default}; diff --git a/assets/TS_Quick Start.md.0be8b76a.lean.js b/assets/TS_Quick Start.md.0be8b76a.lean.js new file mode 100644 index 0000000..a22dfe5 --- /dev/null +++ b/assets/TS_Quick Start.md.0be8b76a.lean.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,U as r}from"./chunks/framework.a7041386.js";const u=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/Quick Start.md","filePath":"TS/Quick Start.md","lastUpdated":1690701222000}'),i={name:"TS/Quick Start.md"},c=r("",14),n=[c];function o(s,l,p,d,S,f){return t(),a("div",null,n)}const m=e(i,[["render",o]]);export{u as __pageData,m as default}; diff --git "a/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.609ef64b.lean.js" "b/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.609ef64b.lean.js" deleted file mode 100644 index ebe0c55..0000000 --- "a/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.609ef64b.lean.js" +++ /dev/null @@ -1 +0,0 @@ -import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const C=JSON.parse('{"title":"","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/函数式编程.md","filePath":"TS/函数式编程.md","lastUpdated":null}'),p={name:"TS/函数式编程.md"},o=l("",7),e=[o];function t(r,c,F,y,D,i){return n(),a("div",null,e)}const u=s(p,[["render",t]]);export{C as __pageData,u as default}; diff --git "a/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.609ef64b.js" "b/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.9add7ee4.js" similarity index 80% rename from "assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.609ef64b.js" rename to "assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.9add7ee4.js" index aaac9d7..4b04561 100644 --- "a/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.609ef64b.js" +++ "b/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.9add7ee4.js" @@ -1,4 +1,4 @@ -import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const C=JSON.parse('{"title":"","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/函数式编程.md","filePath":"TS/函数式编程.md","lastUpdated":null}'),p={name:"TS/函数式编程.md"},o=l(`

产生

出现的原因:

  • 随着算力的不断增大,传统代码过程式编程的思想会使得代码总体难以维护
  • 提出函数式编程的思想,试图从思维上让程序员进化

三个境界:

  1. 用函数写程序
  2. 实现最小粒度的函数封装,组合,复用(积木逻辑
    • 另一种理解:每个函数只做一件事,达到最小粒度
    • 函数没有副作用:便于测试
  3. 对程序员思维的改变:用表达式来描述程序,而不是用过程组织计算
    • 过程式编程
    • 面向对象编程,最典型的就是java
    • 函数式编程

例子一:全排列

ts
function remove(set: Set<string>, item: string): Set<string> {
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const C=JSON.parse('{"title":"","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/函数式编程.md","filePath":"TS/函数式编程.md","lastUpdated":1690701222000}'),p={name:"TS/函数式编程.md"},o=l(`

产生

出现的原因:

  • 随着算力的不断增大,传统代码过程式编程的思想会使得代码总体难以维护
  • 提出函数式编程的思想,试图从思维上让程序员进化

三个境界:

  1. 用函数写程序
  2. 实现最小粒度的函数封装,组合,复用(积木逻辑
    • 另一种理解:每个函数只做一件事,达到最小粒度
    • 函数没有副作用:便于测试
  3. 对程序员思维的改变:用表达式来描述程序,而不是用过程组织计算
    • 过程式编程
    • 面向对象编程,最典型的就是java
    • 函数式编程

例子一:全排列

ts
function remove(set: Set<string>, item: string): Set<string> {
   const newSet = new Set<string>(...set)
   newSet.delete(item)
   return newSet
diff --git "a/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.9add7ee4.lean.js" "b/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.9add7ee4.lean.js"
new file mode 100644
index 0000000..6594182
--- /dev/null
+++ "b/assets/TS_\345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md.9add7ee4.lean.js"
@@ -0,0 +1 @@
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const C=JSON.parse('{"title":"","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/函数式编程.md","filePath":"TS/函数式编程.md","lastUpdated":1690701222000}'),p={name:"TS/函数式编程.md"},o=l("",7),e=[o];function t(r,c,F,y,D,i){return n(),a("div",null,e)}const u=s(p,[["render",t]]);export{C as __pageData,u as default};
diff --git "a/assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.5db5adca.js" "b/assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.a3ff378a.js"
similarity index 95%
rename from "assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.5db5adca.js"
rename to "assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.a3ff378a.js"
index 731d24b..345f301 100644
--- "a/assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.5db5adca.js"
+++ "b/assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.a3ff378a.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const i=JSON.parse('{"title":"泛型工具类","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/泛型工具类.md","filePath":"TS/泛型工具类.md","lastUpdated":null}'),p={name:"TS/泛型工具类.md"},o=l(`

泛型工具类

实现8种常见的泛型工具类

typescript
import { IPerson } from "../interfaces/IPerson";
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const i=JSON.parse('{"title":"泛型工具类","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/泛型工具类.md","filePath":"TS/泛型工具类.md","lastUpdated":1690701222000}'),p={name:"TS/泛型工具类.md"},o=l(`

泛型工具类

实现8种常见的泛型工具类

typescript
import { IPerson } from "../interfaces/IPerson";
 
 // Partial<T>: 将T中所有属性变为可选项
 type TPatrial = Partial<IPerson>
diff --git "a/assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.5db5adca.lean.js" "b/assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.a3ff378a.lean.js"
similarity index 57%
rename from "assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.5db5adca.lean.js"
rename to "assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.a3ff378a.lean.js"
index c7adaad..e3fbd9b 100644
--- "a/assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.5db5adca.lean.js"
+++ "b/assets/TS_\346\263\233\345\236\213\345\267\245\345\205\267\347\261\273.md.a3ff378a.lean.js"
@@ -1 +1 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const i=JSON.parse('{"title":"泛型工具类","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/泛型工具类.md","filePath":"TS/泛型工具类.md","lastUpdated":null}'),p={name:"TS/泛型工具类.md"},o=l("",3),e=[o];function t(r,c,y,F,D,C){return n(),a("div",null,e)}const B=s(p,[["render",t]]);export{i as __pageData,B as default};
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const i=JSON.parse('{"title":"泛型工具类","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/泛型工具类.md","filePath":"TS/泛型工具类.md","lastUpdated":1690701222000}'),p={name:"TS/泛型工具类.md"},o=l("",3),e=[o];function t(r,c,y,F,D,C){return n(),a("div",null,e)}const B=s(p,[["render",t]]);export{i as __pageData,B as default};
diff --git "a/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.14c655f8.js" "b/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.14c655f8.js"
deleted file mode 100644
index bb50ef7..0000000
--- "a/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.14c655f8.js"
+++ /dev/null
@@ -1 +0,0 @@
-import{_ as e,o as t,c as n,U as a}from"./chunks/framework.a7041386.js";const m=JSON.parse('{"title":"类型系统","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/类型系统.md","filePath":"TS/类型系统.md","lastUpdated":null}'),o={name:"TS/类型系统.md"},r=a('

类型系统

1.类型系统

核心:never 字面量类型 原始类型 any/unknown (赋值时只能是层级低的赋值给层级高的)

如何理解呢?

通过人往高处走这句俗语来理解,层级高的赋值给层级低的,就相当于水往低处流,这是不符合ts的规范的

层级低的赋值给层级高的,就是人往高处走,是符合ts的规范的

`// never 字面量类型 原始类型 any/unknown (赋值时只能是层级低的赋值给层级高的)

let t1: boolean let bool1:true = true // t1 = bool1 bool1 = t1 // 原始类型,赋值给字面量类型 不可以

let str1:"132" = '132' // 字面量类型 let str:string = 'bart' str1 = str`

结构化类型 VS 标称类型(nominal)

ts是基于类型的结构而不是类型的名称,进行类型的检查,因此会带来一些问题

开发中需要增添额外代码,来实现 nominal 标称类型系统,允许基于类型的名称而不是其结构进行类型检查

`// 有一种特殊情况 // export type CNY = Nonimal小于number, 'CNY'> // export type USD = Nonimal小于number, 'USD'> // const addCNY = (source:CNY,target:CNY) => { // return source+target // } // const CNYCount = 100 as CNY // const USDCount = 100 as USD // console.log(addCNY(CNYCount,CNYCount)) // console.log(addCNY(CNYCount,USDCount)) // 此处会打印输出200,但显然这种情况是不合理的,不能直接把USD和CNY相加

// 使用structre来模拟nonimal可以解决这种问题 export declare class TaggedProtector小于Tag extends string> { protected __tag: Tag } export type Nonimal小于T,Tag extends string> = T & TaggedProtector小于Tag> // 交叉类型 // 用于在 TypeScript 中创建一个名义类型系统,允许基于类型的名称而不是其结构进行类型检查。

export type CNY = Nonimal小于number, 'CNY'> export type USD = Nonimal小于number, 'USD'> const addCNY = (source:CNY,target:CNY) => { return source+target } const CNYCount = 100 as CNY const USDCount = 100 as USD console.log(addCNY(CNYCount,CNYCount)) console.log(addCNY(CNYCount,USDCount))`

any,unknown,never

any vs unknown

any >>> 具有传染性

unknown >>> 类型安全

在js->ts重构时,推荐全部使用unknown,通过增添开发者的心智负担确保类型安全

never

never 小于 字面量类型 小于 原始类型 小于 any/unknown

也就是说,允许下层对上层进行赋值,例如将原始类型赋值给any/unknown,而不允许上层对下层进行赋值,例如将原始类型赋值给never类型

never的妙用

在开发中,常常会遇到需要对不同数据类型的变量,做出不同的处理这种情况;此时,可以利用never处于类型系统底层的特性,在判断的结尾(也就是else处),试图将变量赋值给never类型,如果之前已经对变量的所有类型都做了处理,判断就不会走到else处,也就不会报错,反之就会报错。

通过这种特性,可以增强代码的规范性,通过增添开发者的心智负担保证了系统的稳定性。

理解extends链

`// extends意味着什么,底层类型逐级extends上层类型,进而形成链条

// 类型系统的层级关系:构造一条extends链

// 从层级关系看类型断言的具体原理 class Base { name!: string } class DerivedBar extends Base { bar!: string } class DerivedBaz extends Base { baz!: string }

// 父类 >>> 子类的类型断言 类型系统向下转型 const bar = new Base() as DerivedBar bar.name = '23' bar.bar = '45'

// 子类 >>> 父类 // as 只用于转换存在子类型关系的两个类型 // extends通过结构化类型系统判断得到的兼容关系 const b = new DerivedBaz() as DerivedBar const b = new DerivedBaz() as Base as DerivedBar // 先向上转换,再向下转型

// 此外,还有一些其他类型系统知识,与类型编程相关性小,但同样重要 // - 协变和逆变 // - 类型控制流分析 // - 上下文类型`

基本数据类型

在Typescript中,基本数据类型有以下几种:

  1. 布尔值(Boolean):布尔值表示真或假,只有两个值:true和false。
  2. 数字(Number):数字包括整数和浮点数,例如:10,10.5等。
  3. 字符串(String):字符串表示文本数据,可以使用单引号、双引号或反引号来表示。
  4. 数组(Array):数组表示一组同类型的数据集合,可以使用[]或Array小于elementType>来定义。
  5. 元组(Tuple):元组表示已知元素数量和类型的数组,例如:[string, number]。
  6. 枚举(Enum):枚举表示具有命名的常量集合,例如:enum Color {Red, Green, Blue}。
  7. 任意值(Any):任意值表示可以被赋值为任意类型的数据,例如:let a: any = 'hello'。
  8. 空值(Void):空值表示没有任何返回值的函数,例如:function test(): void {console.log('hello') }。
  9. Null和Undefined:Null和Undefined表示没有值的数据类型,它们是所有类型的子类型。

交叉类型

交叉类型和联合类型实际上就是指类型操作符。

交叉类型可以看作是将多个类型合并为一个类型的操作。使用“&”符号来表示交叉类型。例如:

`type Person = { name: string; age: number; };

type Employee = { company: string; workId: string; };

type PersonEmployee = Person & Employee;`

在上面的例子中,定义了两个类型:Person和Employee。然后使用“&”符号将它们合并为一个新的类型:PersonEmployee。这个新的类型同时具有Person和Employee类型的所有属性。

交叉类型的使用场景包括:

  • 合并多个对象的属性⭐️
  • 合并多个函数的参数和返回值

联合类型

联合类型可以看作是将多个类型中的一个类型赋值给一个变量的操作。使用“|”符号来表示联合类型。例如:

`type Status = "success" | "error" | "loading";

function getStatusMessage(status: Status) { switch (status) { case "success": return "Operation successful"; case "error": return "Operation failed"; case "loading": return "Operation in progress"; } }`

在上面的例子中,我们定义了一个Status类型,它只能取“success”、“error”或“loading”这三个值中的一个。然后我们定义了一个函数getStatusMessage,它接受一个Status类型的参数,并根据参数的值返回不同的字符串。

联合类型的使用场景包括:

  • 定义一个变量可以是多个类型中的一种
  • 定义一个函数可以接受多个类型中的一种作为参数⭐️

泛型

本质:接受类型作为参数,并且返回一个类型

常用的泛型工具类基本上都是这种实现

为什么要有泛型?

泛型的本质:类型参数化,让我们可以通过参数来控制类型,让代码具有更强的可拓展性。

具体体现有泛型函数、泛型类以及泛型约束

常用泛型工具类

`// 1.Partial:将传入的属性变为可选项 interface IPeople { title: string; name: string; }

type MyPartial小于T> = { [P in keyof T]?:T[P] }

const people: MyPartial小于IPeople> = { title: 'Delete inactive users' };

// 2.Readonly interface Person { name: string; age: number; }

type MyReadonly小于T> = { readonly [P in keyof T]: T[P] }

const p: MyReadonly小于Person> = { name: '张三', age: 22 }

// p.name = '李四'; // 无法分配到 "name" ,因为它是只读属性

// 3.Required interface ICar { weight?:number, height?:number } type MyRequired小于T> = { [P in keyof T]-?:T[P] } const car:Required小于ICar> = { weight: 30, height: 20 }

// 4.Pick interface IPerson { name: string; age: number; salary: number }

type MyPick小于T,K extends keyof T> = { // 使用extends进行泛型约束,保证第二个参数(联合类型)肯定包含在T的keys里 [P in K]:T[P] // 通过映射对象类型,约束每一个属性成员 }

type TP = MyPick小于IPerson, 'name'|'salary'>;

const tp: TP = { // age: 22, // 对象文字可以只指定已知属性,并且“age”不在类型“TP”中 name: '张三', salary:10000 }`

',71),s=[r];function p(l,i,u,d,g,c){return t(),n("div",null,s)}const y=e(o,[["render",p]]);export{m as __pageData,y as default}; diff --git "a/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.6c1d1441.js" "b/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.6c1d1441.js" new file mode 100644 index 0000000..6f9966a --- /dev/null +++ "b/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.6c1d1441.js" @@ -0,0 +1 @@ +import{_ as e,o as t,c as n,U as a}from"./chunks/framework.a7041386.js";const m=JSON.parse('{"title":"类型系统","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/类型系统.md","filePath":"TS/类型系统.md","lastUpdated":1690701222000}'),o={name:"TS/类型系统.md"},r=a('

类型系统

1.类型系统

核心:never 字面量类型 原始类型 any/unknown (赋值时只能是层级低的赋值给层级高的)

如何理解呢?

通过人往高处走这句俗语来理解,层级高的赋值给层级低的,就相当于水往低处流,这是不符合ts的规范的

层级低的赋值给层级高的,就是人往高处走,是符合ts的规范的

`// never 字面量类型 原始类型 any/unknown (赋值时只能是层级低的赋值给层级高的)

let t1: boolean let bool1:true = true // t1 = bool1 bool1 = t1 // 原始类型,赋值给字面量类型 不可以

let str1:"132" = '132' // 字面量类型 let str:string = 'bart' str1 = str`

结构化类型 VS 标称类型(nominal)

ts是基于类型的结构而不是类型的名称,进行类型的检查,因此会带来一些问题

开发中需要增添额外代码,来实现 nominal 标称类型系统,允许基于类型的名称而不是其结构进行类型检查

`// 有一种特殊情况 // export type CNY = Nonimal小于number, 'CNY'> // export type USD = Nonimal小于number, 'USD'> // const addCNY = (source:CNY,target:CNY) => { // return source+target // } // const CNYCount = 100 as CNY // const USDCount = 100 as USD // console.log(addCNY(CNYCount,CNYCount)) // console.log(addCNY(CNYCount,USDCount)) // 此处会打印输出200,但显然这种情况是不合理的,不能直接把USD和CNY相加

// 使用structre来模拟nonimal可以解决这种问题 export declare class TaggedProtector小于Tag extends string> { protected __tag: Tag } export type Nonimal小于T,Tag extends string> = T & TaggedProtector小于Tag> // 交叉类型 // 用于在 TypeScript 中创建一个名义类型系统,允许基于类型的名称而不是其结构进行类型检查。

export type CNY = Nonimal小于number, 'CNY'> export type USD = Nonimal小于number, 'USD'> const addCNY = (source:CNY,target:CNY) => { return source+target } const CNYCount = 100 as CNY const USDCount = 100 as USD console.log(addCNY(CNYCount,CNYCount)) console.log(addCNY(CNYCount,USDCount))`

any,unknown,never

any vs unknown

any >>> 具有传染性

unknown >>> 类型安全

在js->ts重构时,推荐全部使用unknown,通过增添开发者的心智负担确保类型安全

never

never 小于 字面量类型 小于 原始类型 小于 any/unknown

也就是说,允许下层对上层进行赋值,例如将原始类型赋值给any/unknown,而不允许上层对下层进行赋值,例如将原始类型赋值给never类型

never的妙用

在开发中,常常会遇到需要对不同数据类型的变量,做出不同的处理这种情况;此时,可以利用never处于类型系统底层的特性,在判断的结尾(也就是else处),试图将变量赋值给never类型,如果之前已经对变量的所有类型都做了处理,判断就不会走到else处,也就不会报错,反之就会报错。

通过这种特性,可以增强代码的规范性,通过增添开发者的心智负担保证了系统的稳定性。

理解extends链

`// extends意味着什么,底层类型逐级extends上层类型,进而形成链条

// 类型系统的层级关系:构造一条extends链

// 从层级关系看类型断言的具体原理 class Base { name!: string } class DerivedBar extends Base { bar!: string } class DerivedBaz extends Base { baz!: string }

// 父类 >>> 子类的类型断言 类型系统向下转型 const bar = new Base() as DerivedBar bar.name = '23' bar.bar = '45'

// 子类 >>> 父类 // as 只用于转换存在子类型关系的两个类型 // extends通过结构化类型系统判断得到的兼容关系 const b = new DerivedBaz() as DerivedBar const b = new DerivedBaz() as Base as DerivedBar // 先向上转换,再向下转型

// 此外,还有一些其他类型系统知识,与类型编程相关性小,但同样重要 // - 协变和逆变 // - 类型控制流分析 // - 上下文类型`

基本数据类型

在Typescript中,基本数据类型有以下几种:

  1. 布尔值(Boolean):布尔值表示真或假,只有两个值:true和false。
  2. 数字(Number):数字包括整数和浮点数,例如:10,10.5等。
  3. 字符串(String):字符串表示文本数据,可以使用单引号、双引号或反引号来表示。
  4. 数组(Array):数组表示一组同类型的数据集合,可以使用[]或Array小于elementType>来定义。
  5. 元组(Tuple):元组表示已知元素数量和类型的数组,例如:[string, number]。
  6. 枚举(Enum):枚举表示具有命名的常量集合,例如:enum Color {Red, Green, Blue}。
  7. 任意值(Any):任意值表示可以被赋值为任意类型的数据,例如:let a: any = 'hello'。
  8. 空值(Void):空值表示没有任何返回值的函数,例如:function test(): void {console.log('hello') }。
  9. Null和Undefined:Null和Undefined表示没有值的数据类型,它们是所有类型的子类型。

交叉类型

交叉类型和联合类型实际上就是指类型操作符。

交叉类型可以看作是将多个类型合并为一个类型的操作。使用“&”符号来表示交叉类型。例如:

`type Person = { name: string; age: number; };

type Employee = { company: string; workId: string; };

type PersonEmployee = Person & Employee;`

在上面的例子中,定义了两个类型:Person和Employee。然后使用“&”符号将它们合并为一个新的类型:PersonEmployee。这个新的类型同时具有Person和Employee类型的所有属性。

交叉类型的使用场景包括:

  • 合并多个对象的属性⭐️
  • 合并多个函数的参数和返回值

联合类型

联合类型可以看作是将多个类型中的一个类型赋值给一个变量的操作。使用“|”符号来表示联合类型。例如:

`type Status = "success" | "error" | "loading";

function getStatusMessage(status: Status) { switch (status) { case "success": return "Operation successful"; case "error": return "Operation failed"; case "loading": return "Operation in progress"; } }`

在上面的例子中,我们定义了一个Status类型,它只能取“success”、“error”或“loading”这三个值中的一个。然后我们定义了一个函数getStatusMessage,它接受一个Status类型的参数,并根据参数的值返回不同的字符串。

联合类型的使用场景包括:

  • 定义一个变量可以是多个类型中的一种
  • 定义一个函数可以接受多个类型中的一种作为参数⭐️

泛型

本质:接受类型作为参数,并且返回一个类型

常用的泛型工具类基本上都是这种实现

为什么要有泛型?

泛型的本质:类型参数化,让我们可以通过参数来控制类型,让代码具有更强的可拓展性。

具体体现有泛型函数、泛型类以及泛型约束

常用泛型工具类

`// 1.Partial:将传入的属性变为可选项 interface IPeople { title: string; name: string; }

type MyPartial小于T> = { [P in keyof T]?:T[P] }

const people: MyPartial小于IPeople> = { title: 'Delete inactive users' };

// 2.Readonly interface Person { name: string; age: number; }

type MyReadonly小于T> = { readonly [P in keyof T]: T[P] }

const p: MyReadonly小于Person> = { name: '张三', age: 22 }

// p.name = '李四'; // 无法分配到 "name" ,因为它是只读属性

// 3.Required interface ICar { weight?:number, height?:number } type MyRequired小于T> = { [P in keyof T]-?:T[P] } const car:Required小于ICar> = { weight: 30, height: 20 }

// 4.Pick interface IPerson { name: string; age: number; salary: number }

type MyPick小于T,K extends keyof T> = { // 使用extends进行泛型约束,保证第二个参数(联合类型)肯定包含在T的keys里 [P in K]:T[P] // 通过映射对象类型,约束每一个属性成员 }

type TP = MyPick小于IPerson, 'name'|'salary'>;

const tp: TP = { // age: 22, // 对象文字可以只指定已知属性,并且“age”不在类型“TP”中 name: '张三', salary:10000 }`

',71),s=[r];function p(l,i,u,d,g,c){return t(),n("div",null,s)}const y=e(o,[["render",p]]);export{m as __pageData,y as default}; diff --git "a/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.14c655f8.lean.js" "b/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.6c1d1441.lean.js" similarity index 58% rename from "assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.14c655f8.lean.js" rename to "assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.6c1d1441.lean.js" index b25399f..b360e86 100644 --- "a/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.14c655f8.lean.js" +++ "b/assets/TS_\347\261\273\345\236\213\347\263\273\347\273\237.md.6c1d1441.lean.js" @@ -1 +1 @@ -import{_ as e,o as t,c as n,U as a}from"./chunks/framework.a7041386.js";const m=JSON.parse('{"title":"类型系统","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/类型系统.md","filePath":"TS/类型系统.md","lastUpdated":null}'),o={name:"TS/类型系统.md"},r=a("",71),s=[r];function p(l,i,u,d,g,c){return t(),n("div",null,s)}const y=e(o,[["render",p]]);export{m as __pageData,y as default}; +import{_ as e,o as t,c as n,U as a}from"./chunks/framework.a7041386.js";const m=JSON.parse('{"title":"类型系统","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/类型系统.md","filePath":"TS/类型系统.md","lastUpdated":1690701222000}'),o={name:"TS/类型系统.md"},r=a("",71),s=[r];function p(l,i,u,d,g,c){return t(),n("div",null,s)}const y=e(o,[["render",p]]);export{m as __pageData,y as default}; diff --git "a/assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.d7fb6669.js" "b/assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.428a8416.js" similarity index 90% rename from "assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.d7fb6669.js" rename to "assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.428a8416.js" index e9f7591..0150b63 100644 --- "a/assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.d7fb6669.js" +++ "b/assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.428a8416.js" @@ -1,4 +1,4 @@ -import{_ as s,o as n,c as a,U as e}from"./chunks/framework.a7041386.js";const u=JSON.parse('{"title":"面向对象","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/面向对象特性.md","filePath":"TS/面向对象特性.md","lastUpdated":null}'),p={name:"TS/面向对象特性.md"},l=e(`

面向对象

在 TypeScript 中,面向对象编程有三大特性:封装、继承和多态。

封装

封装是指将数据和方法相结合,形成一个有机的整体,同时隐藏了对象的内部细节,只向外部暴露必要的接口。这样可以保护对象的数据不被随意修改,同时也提高了代码的可维护性。

在 TypeScript 中,可以使用 publicprivateprotected 访问修饰符来实现封装。其中,public 表示公有,可以在任何地方访问;private 表示私有,只能在类内部访问;protected 表示受保护的,可以在类内及其子类中访问。

下面是一个使用封装的例子:

class Person {
+import{_ as s,o as n,c as a,U as e}from"./chunks/framework.a7041386.js";const u=JSON.parse('{"title":"面向对象","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/面向对象特性.md","filePath":"TS/面向对象特性.md","lastUpdated":1690701222000}'),p={name:"TS/面向对象特性.md"},l=e(`

面向对象

在 TypeScript 中,面向对象编程有三大特性:封装、继承和多态。

封装

封装是指将数据和方法相结合,形成一个有机的整体,同时隐藏了对象的内部细节,只向外部暴露必要的接口。这样可以保护对象的数据不被随意修改,同时也提高了代码的可维护性。

在 TypeScript 中,可以使用 publicprivateprotected 访问修饰符来实现封装。其中,public 表示公有,可以在任何地方访问;private 表示私有,只能在类内部访问;protected 表示受保护的,可以在类内及其子类中访问。

下面是一个使用封装的例子:

class Person {
   private name: string;
 
   public setName(name: string): void {
diff --git "a/assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.d7fb6669.lean.js" "b/assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.428a8416.lean.js"
similarity index 56%
rename from "assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.d7fb6669.lean.js"
rename to "assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.428a8416.lean.js"
index 0a79886..90e415e 100644
--- "a/assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.d7fb6669.lean.js"
+++ "b/assets/TS_\351\235\242\345\220\221\345\257\271\350\261\241\347\211\271\346\200\247.md.428a8416.lean.js"
@@ -1 +1 @@
-import{_ as s,o as n,c as a,U as e}from"./chunks/framework.a7041386.js";const u=JSON.parse('{"title":"面向对象","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/面向对象特性.md","filePath":"TS/面向对象特性.md","lastUpdated":null}'),p={name:"TS/面向对象特性.md"},l=e("",20),r=[l];function c(o,i,t,b,A,C){return n(),a("div",null,r)}const d=s(p,[["render",c]]);export{u as __pageData,d as default};
+import{_ as s,o as n,c as a,U as e}from"./chunks/framework.a7041386.js";const u=JSON.parse('{"title":"面向对象","description":"","frontmatter":{"tag":["TS"]},"headers":[],"relativePath":"TS/面向对象特性.md","filePath":"TS/面向对象特性.md","lastUpdated":1690701222000}'),p={name:"TS/面向对象特性.md"},l=e("",20),r=[l];function c(o,i,t,b,A,C){return n(),a("div",null,r)}const d=s(p,[["render",c]]);export{u as __pageData,d as default};
diff --git "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.4db9e488.js" "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.dc85089f.js"
similarity index 89%
rename from "assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.4db9e488.js"
rename to "assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.dc85089f.js"
index ad07e1b..2abfd44 100644
--- "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.4db9e488.js"	
+++ "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.dc85089f.js"	
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"前置","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/Vite 初探.md","filePath":"前端工程化/Vite 初探.md","lastUpdated":null}'),p={name:"前端工程化/Vite 初探.md"},o=l(`
  1. vite 的前世今生
  2. vite的编译结果及其分析
  3. vite的配置文件
  4. vite中处理CSS,静态资源,TS
  5. vite的插件以及常用用插件的使用
  6. vite构建原理

前置

什么是构建工具

早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协助开发者开发。

在 CSS 的基础上,衍生出了 Less,Sass,PostCss 这样的处理器,POSTCSS 由于其衍生原因,在现在的工程化项目中往往被作为后处理器使用,用于将兼容新旧 CSS的特性,实际上 PostCss 是可以替代 Less,Sass 这种前处理器,成为 CSS 的最终解决方案的,不过由于他内部需要不断开发插件,兼容 Less,Sass 的新版本迭代,后期社区就放弃了这种一体化解决方案,着重处理新旧 CSS 的特性了。

最终的处理逻辑可以理解为如下的链路:

Less/Sass文件 -> Less/Sass 处理器 -> 具有新特性的 CSS文件 -> PostCss 处理器 -> 兼容老特性的 CSS

在 JS 的基础上,衍生出了 TS 这种具有类型系统的编程语言,可以简单理解为 JS 的超集(数学概念)。同时,JS 这门语言自身的特性也在不断发展,也需要做新特性的兼容,由此一个新项目还需要考虑 JS 的处理逻辑,最终处理链路如下:

TS 文件 -> 具有新特性的 JS 文件 -> 兼容老特性的 JS

除了 CSS 和 JS 这两种文件的转换处理,在实际项目的开发中,还需要考虑诸如:打包优化,分包策略,静态资源处理等等许多问题,这些问题如果由我们人为的去配置,会大大减缓我们的开发效率。

因此,就诞生了诸如 ESBuild,Webpack,Rollup,Vite 这样的打包构建工具,专门为我们负责项目的打包构建流程,对于新手小白,可以不考虑其中的特性,直接使用即可。对于有一定基础的开发者,这些打包工具也各自都提供了自定义的配置项,我们可以使用这些配置项对项目进行更加专门化的打包构建。

Vite 解决的痛点

作为老版本的构建工具,Webpack 的特点是大而全,缺点是配置起来十分麻烦。

针对此,Vite 团队集成了大多数的常见配置,例如原生支持 CSS 和 TS 的编译,真正做到 out of box 开箱即用!这也是 Vue 团队一贯的作风——减少开发者的心智负担。对于这种作风的优劣,大家就见仁见智,这里就不过多评价了。

Vite 的构成

Vite 在开发阶段使用 ESbuild,生产阶段使用 Rollup;

NodeJS 学习

了解 fs 和 path 这两个 Node 库,是用来处理文件的

获取路径:

  • path.resolve(__dirname, '')
  • process.cwd():获取当前的node执行目录

依赖预构建

起因

开发阶段 Vite 会对项目中使用的第三方依赖如 reactreact-domlodash-es 等做预构建操作。

之所以要做预构建,是因为 Vite 是基于浏览器原生的 ESM 规范来实现 dev server 的,这就要求整个项目中涉及的所有源代码必须符合 ESM 规范。

而在实际开发过程中,业务代码我们可以严格按照 ESM 规范来编写,但第三方依赖就无法保证了,比如 react。这就需要我们通过 Vite预构建功能将非 ESM 规范的代码转换为符合 ESM 规范的代码。

另外,尽管有些第三方依赖已经符合 ESM 规范,但它是由多个子文件组成的,如 lodash-es。如果不做处理就直接使用,那么就会引发请求瀑布流,这对页面性能来说,简直就是一场灾难。同样的,我们可以通过 Vite预构建功能,将第三方依赖内部的多个文件合并为一个,减少 http 请求数量,优化页面加载性能。

综上,预构建,主要做了两件事情:

  • 将非 ESM 规范的代码转换为符合 ESM 规范的代码;
  • 将第三方依赖内部的多个文件合并为一个,减少 http 请求数量;

做法

  1. 自动依赖搜寻

    • 如果没有找到现有的缓存,Vite 会扫描您的源代码,并自动寻找引入的依赖项(即 "bare import",表示期望从 node_modules 中解析),并将这些依赖项作为预构建的入口点。预打包使用 esbuild 执行,因此通常速度非常快。
    • 在服务器已经启动后,如果遇到尚未在缓存中的新依赖项导入,则 Vite 将重新运行依赖项构建过程,并在需要时重新加载页面。
  2. Monorepo 和 链接依赖

  3. 自定义行为

    • optimizeDeps.include
    • optimizeDeps.exclude
  4. 缓存

    • 文件系统缓存
    • 浏览器缓存

二次预构建

同样的,Vite预构建的时候也是基于类似的机制去找到项目中所有的第三方依赖的。和 Webpack 不同, Vite 另辟蹊径,借助了 EsbuildWebpack 更快的打包能力,对整个项目做一个全量打包。打包的时候,通过分析依赖关系,得到项目中所有的源文件的 url,然后分离出第三方依赖。

这样,Vite 就可以对找到的第三方依赖做转化、合并操作了。

预构建功能非常棒,但在实际的项目中,并不能保证所有的第三方依赖都可以被找到。如果出现下面的这两种情况, Esbuild 也无能为力:

  • plugin 在运行过程中,动态给源码注入了新的第三方依赖;
  • 动态依赖在代码运行时,才可以确定最终的 url

Vite3 的优化

Vite3.0 版本对第三方依赖的请求和业务代码的请求有不同的处理逻辑。当浏览器请求业务代码时,dev server 只要完成源代码转换并收集到依赖模块的 url,就会给浏览器发送 response。而第三方依赖请求则不同,dev server 会等首屏期间涉及的所有模块的依赖关系全部解析完毕以后,才会给浏览器发送 response。这就导致了,如果发现有未预构建的第三方依赖,第三方依赖的请求会一直被阻塞,直到二次预构建完成为止。

总结:

Vite3.0二次预构建的优化,其实是以消耗首屏性能来优化 reload 交互体验

遗憾的是,Vite 3.0 并没有解决懒加载二次预构建导致的 reload 的问题。这个问题,目前只能通过社区提供的 vite-plugin-optimize-persistvite-plugin-package-config 来解决了。

可以理解为vite-plugin-package-config vite-plugin-optimize-persist其实是相当于在vite.config.js配置中配置了includes选项。

dev server 启动以后,内部有一个缓存来存取预构建的第三方依赖。而vite-plugin-optimize-persist 会监听这个缓存。当有新的第三方依赖需要预构建,并且修改缓存以后,vite-plugin-optimize-persist 会跟新 package.json 的 vite 字段。vite-plugin-package-config 负责在 dev server 启动时,初始化 vite.config,会把 package.json 中的 vite 配置项合并到 vite.config 中

对不同资源的处理

CSS

js
// vite.config.js
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"前置","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/Vite 初探.md","filePath":"前端工程化/Vite 初探.md","lastUpdated":1690701222000}'),p={name:"前端工程化/Vite 初探.md"},o=l(`
  1. vite 的前世今生
  2. vite的编译结果及其分析
  3. vite的配置文件
  4. vite中处理CSS,静态资源,TS
  5. vite的插件以及常用用插件的使用
  6. vite构建原理

前置

什么是构建工具

早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协助开发者开发。

在 CSS 的基础上,衍生出了 Less,Sass,PostCss 这样的处理器,POSTCSS 由于其衍生原因,在现在的工程化项目中往往被作为后处理器使用,用于将兼容新旧 CSS的特性,实际上 PostCss 是可以替代 Less,Sass 这种前处理器,成为 CSS 的最终解决方案的,不过由于他内部需要不断开发插件,兼容 Less,Sass 的新版本迭代,后期社区就放弃了这种一体化解决方案,着重处理新旧 CSS 的特性了。

最终的处理逻辑可以理解为如下的链路:

Less/Sass文件 -> Less/Sass 处理器 -> 具有新特性的 CSS文件 -> PostCss 处理器 -> 兼容老特性的 CSS

在 JS 的基础上,衍生出了 TS 这种具有类型系统的编程语言,可以简单理解为 JS 的超集(数学概念)。同时,JS 这门语言自身的特性也在不断发展,也需要做新特性的兼容,由此一个新项目还需要考虑 JS 的处理逻辑,最终处理链路如下:

TS 文件 -> 具有新特性的 JS 文件 -> 兼容老特性的 JS

除了 CSS 和 JS 这两种文件的转换处理,在实际项目的开发中,还需要考虑诸如:打包优化,分包策略,静态资源处理等等许多问题,这些问题如果由我们人为的去配置,会大大减缓我们的开发效率。

因此,就诞生了诸如 ESBuild,Webpack,Rollup,Vite 这样的打包构建工具,专门为我们负责项目的打包构建流程,对于新手小白,可以不考虑其中的特性,直接使用即可。对于有一定基础的开发者,这些打包工具也各自都提供了自定义的配置项,我们可以使用这些配置项对项目进行更加专门化的打包构建。

Vite 解决的痛点

作为老版本的构建工具,Webpack 的特点是大而全,缺点是配置起来十分麻烦。

针对此,Vite 团队集成了大多数的常见配置,例如原生支持 CSS 和 TS 的编译,真正做到 out of box 开箱即用!这也是 Vue 团队一贯的作风——减少开发者的心智负担。对于这种作风的优劣,大家就见仁见智,这里就不过多评价了。

Vite 的构成

Vite 在开发阶段使用 ESbuild,生产阶段使用 Rollup;

NodeJS 学习

了解 fs 和 path 这两个 Node 库,是用来处理文件的

获取路径:

  • path.resolve(__dirname, '')
  • process.cwd():获取当前的node执行目录

依赖预构建

起因

开发阶段 Vite 会对项目中使用的第三方依赖如 reactreact-domlodash-es 等做预构建操作。

之所以要做预构建,是因为 Vite 是基于浏览器原生的 ESM 规范来实现 dev server 的,这就要求整个项目中涉及的所有源代码必须符合 ESM 规范。

而在实际开发过程中,业务代码我们可以严格按照 ESM 规范来编写,但第三方依赖就无法保证了,比如 react。这就需要我们通过 Vite预构建功能将非 ESM 规范的代码转换为符合 ESM 规范的代码。

另外,尽管有些第三方依赖已经符合 ESM 规范,但它是由多个子文件组成的,如 lodash-es。如果不做处理就直接使用,那么就会引发请求瀑布流,这对页面性能来说,简直就是一场灾难。同样的,我们可以通过 Vite预构建功能,将第三方依赖内部的多个文件合并为一个,减少 http 请求数量,优化页面加载性能。

综上,预构建,主要做了两件事情:

  • 将非 ESM 规范的代码转换为符合 ESM 规范的代码;
  • 将第三方依赖内部的多个文件合并为一个,减少 http 请求数量;

做法

  1. 自动依赖搜寻

    • 如果没有找到现有的缓存,Vite 会扫描您的源代码,并自动寻找引入的依赖项(即 "bare import",表示期望从 node_modules 中解析),并将这些依赖项作为预构建的入口点。预打包使用 esbuild 执行,因此通常速度非常快。
    • 在服务器已经启动后,如果遇到尚未在缓存中的新依赖项导入,则 Vite 将重新运行依赖项构建过程,并在需要时重新加载页面。
  2. Monorepo 和 链接依赖

  3. 自定义行为

    • optimizeDeps.include
    • optimizeDeps.exclude
  4. 缓存

    • 文件系统缓存
    • 浏览器缓存

二次预构建

同样的,Vite预构建的时候也是基于类似的机制去找到项目中所有的第三方依赖的。和 Webpack 不同, Vite 另辟蹊径,借助了 EsbuildWebpack 更快的打包能力,对整个项目做一个全量打包。打包的时候,通过分析依赖关系,得到项目中所有的源文件的 url,然后分离出第三方依赖。

这样,Vite 就可以对找到的第三方依赖做转化、合并操作了。

预构建功能非常棒,但在实际的项目中,并不能保证所有的第三方依赖都可以被找到。如果出现下面的这两种情况, Esbuild 也无能为力:

  • plugin 在运行过程中,动态给源码注入了新的第三方依赖;
  • 动态依赖在代码运行时,才可以确定最终的 url

Vite3 的优化

Vite3.0 版本对第三方依赖的请求和业务代码的请求有不同的处理逻辑。当浏览器请求业务代码时,dev server 只要完成源代码转换并收集到依赖模块的 url,就会给浏览器发送 response。而第三方依赖请求则不同,dev server 会等首屏期间涉及的所有模块的依赖关系全部解析完毕以后,才会给浏览器发送 response。这就导致了,如果发现有未预构建的第三方依赖,第三方依赖的请求会一直被阻塞,直到二次预构建完成为止。

总结:

Vite3.0二次预构建的优化,其实是以消耗首屏性能来优化 reload 交互体验

遗憾的是,Vite 3.0 并没有解决懒加载二次预构建导致的 reload 的问题。这个问题,目前只能通过社区提供的 vite-plugin-optimize-persistvite-plugin-package-config 来解决了。

可以理解为vite-plugin-package-config vite-plugin-optimize-persist其实是相当于在vite.config.js配置中配置了includes选项。

dev server 启动以后,内部有一个缓存来存取预构建的第三方依赖。而vite-plugin-optimize-persist 会监听这个缓存。当有新的第三方依赖需要预构建,并且修改缓存以后,vite-plugin-optimize-persist 会跟新 package.json 的 vite 字段。vite-plugin-package-config 负责在 dev server 启动时,初始化 vite.config,会把 package.json 中的 vite 配置项合并到 vite.config 中

对不同资源的处理

CSS

js
// vite.config.js
 
 export default {
   // ...
diff --git "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.4db9e488.lean.js" "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.dc85089f.lean.js"
similarity index 53%
rename from "assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.4db9e488.lean.js"
rename to "assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.dc85089f.lean.js"
index 7adceab..dd8b2a0 100644
--- "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.4db9e488.lean.js"	
+++ "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Vite \345\210\235\346\216\242.md.dc85089f.lean.js"	
@@ -1 +1 @@
-import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"前置","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/Vite 初探.md","filePath":"前端工程化/Vite 初探.md","lastUpdated":null}'),p={name:"前端工程化/Vite 初探.md"},o=l("",149),e=[o];function t(r,c,i,y,D,F){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
+import{_ as s,o as n,c as a,U as l}from"./chunks/framework.a7041386.js";const A=JSON.parse('{"title":"前置","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/Vite 初探.md","filePath":"前端工程化/Vite 初探.md","lastUpdated":1690701222000}'),p={name:"前端工程化/Vite 初探.md"},o=l("",149),e=[o];function t(r,c,i,y,D,F){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{A as __pageData,b as default};
diff --git "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.a75fcff0.js" "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.a75fcff0.js"
new file mode 100644
index 0000000..926cb16
--- /dev/null
+++ "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.a75fcff0.js"
@@ -0,0 +1 @@
+import{_ as e,o as a,c as n,U as t}from"./chunks/framework.a7041386.js";const g=JSON.parse('{"title":"Webpack 初探","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/Webpack初探.md","filePath":"前端工程化/Webpack初探.md","lastUpdated":1690701222000}'),r={name:"前端工程化/Webpack初探.md"},s=t('

Webpack 初探

核心概念

Entry:入口

Entry 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始

`/** @type {import('webpack').Configuration} */

module.exports = { mode: 'development', entry: { main: { // 配置 chunk 名 filename: 'target_index.js', // 输出filename名 import: './src/index.js', // 指定入口文件 runtime: 'runTimeOne', // 配置当前chunk的运行时环境,默认情况下不同chunk的缓存是隔离开的 }, test: { filename: 'target_b.js', import: './src/b.js', // runtime: 'runTimeOne', // 和 main chunk 共享一个运行时环境 dependOn: 'main', // 指定要共享的运行时环境所在的chunk名,如果没有会报错 } }, output: { clean: true // 每次构建前清空输出文件夹下的内容 }, }`

Output:出口

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

为啥要有hash??【出于复用缓存的考虑】

浏览器有一个缓存机制-—->如果浏览器刷新时请求的当前js的文件名没有产生变化,则浏览器不会请求该资源而是直接使用缓存

有一个插件 plugins 是用来帮我们组装 index.html 页面的,配置hash是为了让我们的文件变化能够实时的被浏览器感知到

`/** @type {import('webpack').Configuration} */

module.exports = { entry: { index: './src/index.js', search: './src/search.js', }, output: { filename: '[name].[contenthash:6].js', // [name]模板字符串 path: __dirname + '/dist', clean: true }, };`

Loader:代码转换

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

使用 loader 可以把 css 之类的

loader 帮 webpack 做了一部分的代码转换工作

之前我们在用脚手架vue-cli, create-react-app的时候不需要关注这些东西是因为这些脚手架在底层帮我们都将webpack配置好了

css-loader 模块化

webpack.config.js

`/** @type {import('webpack').Configuration} */

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = { mode: "development", entry: { index: "./src/index.js", search: "./src/search.js", }, output: { filename: "[name].[contenthash:6].js", // [name]模板字符串 path: dirname + "/dist", clean: true, }, plugins: [ // 添加插件实例 new MiniCssExtractPlugin({ filename: '[name].[contenthash].css', }), ], module: { rules: [ { test: /.css$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { modules: { localIdentName: '[path][name][local]--[hash:base64:5]', }, }, }, ], }, ], }, };`

App.js

`import appStyle from './App.css'

function App() { const element = document.createElement('div') element.innerHTML = 'This is App' element.className = appStyle.wrapper return element } document.body.appendChild(App())`

Header.js

import headerStyle from './Header.css' function Header() {   const element = document.createElement('div')   element.innerHTML = 'This is Header'   element.className = headerStyle.wrapper   return element } document.body.appendChild(Header())

Plugin:插件机制

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

和 loader 的比较

webpack 的 loader 和 plugin 都是用于扩展 webpack 功能的工具,但它们在使用方式和处理过程中有一些区别。

Loader(加载器)【模块转换阶段】

  1. loader 主要用于对模块源代码进行转换。它们在构建过程中将文件从输入(例如 .css、.scss、.jsx 等)转换为输出(通常是 JavaScript 模块)。
  2. loader 是在 module.rules 配置中定义的,并且可以链式调用。链中的每个 loader 都会对资源进行转换,然后将结果传递给下一个 loader。
  3. loader 可以同步或异步执行,但通常是同步的。

Plugin(插件)【不仅仅是模块转换阶段】

  1. plugin 是用于执行更广泛的任务,如优化、代码分割、资源管理等。它们可以在整个构建过程中的不同阶段执行操作,而不仅仅是在模块转换阶段
  2. plugin 是在 plugins 配置中定义的,通过实例化插件并将其添加到 plugins 数组中来使用。
  3. plugin 通过订阅 webpack 的事件钩子(hooks)来工作,这使得它们可以在构建过程的特定时刻执行操作。

区别和联系

  1. 区别:loader 主要用于转换模块源代码,而 plugin 用于执行更广泛的任务。loader 在 module.rules 中定义,plugin 在 plugins 中定义。
  2. 联系:loader 和 plugin 都是用于扩展 webpack 功能的工具,它们可以相互配合使用。例如,css-loader 和 style-loader 可以将 CSS 转换为 JavaScript 模块,然后 MiniCssExtractPlugin 插件可以将这些模块提取到单独的 CSS 文件中。

总之,loader 和 plugin 是 webpack 的两种扩展方式,它们在不同的场景和处理过程中发挥作用,但都是为了实现更丰富的构建功能。

深入devDepend && dependencies

IIFE【立即执行函数】

只要加上运算符就能够进行隐式转换,将函数转换成函数表达式,成为立即执行函数

当一个函数变成函数表达式以后 丢掉自己原来的名字

+function () { 	console.log('12') }()

正文

只要不发包,这二者就随便放,大多数情况下确实如此。【合法但不合理】

我们还需要考虑SSR 服务端渲染【服务端渲染必须要时刻关注package. json即使你不发包】

很多人他会觉得如果将dependencies里的东西移到devDependencies里去会造成性能问题 除非是ssr才会造成这种情况

你在生产会用的依赖 比如react Lodash 你全部放到dependencies里去

在生产不会用到的依赖,比如ts webpack

构建生产代码 全部放到devDependencies里去

这是国内约定俗成的用法

这样的话你在ssr里也不会出错即使你会多安装一些依赖在ssr中【ssr里是会用到package.json在服务端装依赖的】,但是也比出错要好

在常规客户端渲染的情况下,二者无差异

package.json不会参与到webpack的构建工作中

',55),p=[s];function o(l,i,d,c,u,h){return a(),n("div",null,p)}const b=e(r,[["render",o]]);export{g as __pageData,b as default}; diff --git "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.e2f29a2d.lean.js" "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.a75fcff0.lean.js" similarity index 51% rename from "assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.e2f29a2d.lean.js" rename to "assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.a75fcff0.lean.js" index 2ee13ad..a866904 100644 --- "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.e2f29a2d.lean.js" +++ "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.a75fcff0.lean.js" @@ -1 +1 @@ -import{_ as e,o as a,c as n,U as t}from"./chunks/framework.a7041386.js";const g=JSON.parse('{"title":"Webpack 初探","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/Webpack初探.md","filePath":"前端工程化/Webpack初探.md","lastUpdated":null}'),r={name:"前端工程化/Webpack初探.md"},s=t("",55),p=[s];function o(l,i,d,c,u,h){return a(),n("div",null,p)}const b=e(r,[["render",o]]);export{g as __pageData,b as default}; +import{_ as e,o as a,c as n,U as t}from"./chunks/framework.a7041386.js";const g=JSON.parse('{"title":"Webpack 初探","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/Webpack初探.md","filePath":"前端工程化/Webpack初探.md","lastUpdated":1690701222000}'),r={name:"前端工程化/Webpack初探.md"},s=t("",55),p=[s];function o(l,i,d,c,u,h){return a(),n("div",null,p)}const b=e(r,[["render",o]]);export{g as __pageData,b as default}; diff --git "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.e2f29a2d.js" "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.e2f29a2d.js" deleted file mode 100644 index 8f58101..0000000 --- "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_Webpack\345\210\235\346\216\242.md.e2f29a2d.js" +++ /dev/null @@ -1 +0,0 @@ -import{_ as e,o as a,c as n,U as t}from"./chunks/framework.a7041386.js";const g=JSON.parse('{"title":"Webpack 初探","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/Webpack初探.md","filePath":"前端工程化/Webpack初探.md","lastUpdated":null}'),r={name:"前端工程化/Webpack初探.md"},s=t('

Webpack 初探

核心概念

Entry:入口

Entry 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始

`/** @type {import('webpack').Configuration} */

module.exports = { mode: 'development', entry: { main: { // 配置 chunk 名 filename: 'target_index.js', // 输出filename名 import: './src/index.js', // 指定入口文件 runtime: 'runTimeOne', // 配置当前chunk的运行时环境,默认情况下不同chunk的缓存是隔离开的 }, test: { filename: 'target_b.js', import: './src/b.js', // runtime: 'runTimeOne', // 和 main chunk 共享一个运行时环境 dependOn: 'main', // 指定要共享的运行时环境所在的chunk名,如果没有会报错 } }, output: { clean: true // 每次构建前清空输出文件夹下的内容 }, }`

Output:出口

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

为啥要有hash??【出于复用缓存的考虑】

浏览器有一个缓存机制-—->如果浏览器刷新时请求的当前js的文件名没有产生变化,则浏览器不会请求该资源而是直接使用缓存

有一个插件 plugins 是用来帮我们组装 index.html 页面的,配置hash是为了让我们的文件变化能够实时的被浏览器感知到

`/** @type {import('webpack').Configuration} */

module.exports = { entry: { index: './src/index.js', search: './src/search.js', }, output: { filename: '[name].[contenthash:6].js', // [name]模板字符串 path: __dirname + '/dist', clean: true }, };`

Loader:代码转换

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

使用 loader 可以把 css 之类的

loader 帮 webpack 做了一部分的代码转换工作

之前我们在用脚手架vue-cli, create-react-app的时候不需要关注这些东西是因为这些脚手架在底层帮我们都将webpack配置好了

css-loader 模块化

webpack.config.js

`/** @type {import('webpack').Configuration} */

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = { mode: "development", entry: { index: "./src/index.js", search: "./src/search.js", }, output: { filename: "[name].[contenthash:6].js", // [name]模板字符串 path: dirname + "/dist", clean: true, }, plugins: [ // 添加插件实例 new MiniCssExtractPlugin({ filename: '[name].[contenthash].css', }), ], module: { rules: [ { test: /.css$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { modules: { localIdentName: '[path][name][local]--[hash:base64:5]', }, }, }, ], }, ], }, };`

App.js

`import appStyle from './App.css'

function App() { const element = document.createElement('div') element.innerHTML = 'This is App' element.className = appStyle.wrapper return element } document.body.appendChild(App())`

Header.js

import headerStyle from './Header.css' function Header() {   const element = document.createElement('div')   element.innerHTML = 'This is Header'   element.className = headerStyle.wrapper   return element } document.body.appendChild(Header())

Plugin:插件机制

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

和 loader 的比较

webpack 的 loader 和 plugin 都是用于扩展 webpack 功能的工具,但它们在使用方式和处理过程中有一些区别。

Loader(加载器)【模块转换阶段】

  1. loader 主要用于对模块源代码进行转换。它们在构建过程中将文件从输入(例如 .css、.scss、.jsx 等)转换为输出(通常是 JavaScript 模块)。
  2. loader 是在 module.rules 配置中定义的,并且可以链式调用。链中的每个 loader 都会对资源进行转换,然后将结果传递给下一个 loader。
  3. loader 可以同步或异步执行,但通常是同步的。

Plugin(插件)【不仅仅是模块转换阶段】

  1. plugin 是用于执行更广泛的任务,如优化、代码分割、资源管理等。它们可以在整个构建过程中的不同阶段执行操作,而不仅仅是在模块转换阶段
  2. plugin 是在 plugins 配置中定义的,通过实例化插件并将其添加到 plugins 数组中来使用。
  3. plugin 通过订阅 webpack 的事件钩子(hooks)来工作,这使得它们可以在构建过程的特定时刻执行操作。

区别和联系

  1. 区别:loader 主要用于转换模块源代码,而 plugin 用于执行更广泛的任务。loader 在 module.rules 中定义,plugin 在 plugins 中定义。
  2. 联系:loader 和 plugin 都是用于扩展 webpack 功能的工具,它们可以相互配合使用。例如,css-loader 和 style-loader 可以将 CSS 转换为 JavaScript 模块,然后 MiniCssExtractPlugin 插件可以将这些模块提取到单独的 CSS 文件中。

总之,loader 和 plugin 是 webpack 的两种扩展方式,它们在不同的场景和处理过程中发挥作用,但都是为了实现更丰富的构建功能。

深入devDepend && dependencies

IIFE【立即执行函数】

只要加上运算符就能够进行隐式转换,将函数转换成函数表达式,成为立即执行函数

当一个函数变成函数表达式以后 丢掉自己原来的名字

+function () { 	console.log('12') }()

正文

只要不发包,这二者就随便放,大多数情况下确实如此。【合法但不合理】

我们还需要考虑SSR 服务端渲染【服务端渲染必须要时刻关注package. json即使你不发包】

很多人他会觉得如果将dependencies里的东西移到devDependencies里去会造成性能问题 除非是ssr才会造成这种情况

你在生产会用的依赖 比如react Lodash 你全部放到dependencies里去

在生产不会用到的依赖,比如ts webpack

构建生产代码 全部放到devDependencies里去

这是国内约定俗成的用法

这样的话你在ssr里也不会出错即使你会多安装一些依赖在ssr中【ssr里是会用到package.json在服务端装依赖的】,但是也比出错要好

在常规客户端渲染的情况下,二者无差异

package.json不会参与到webpack的构建工作中

',55),p=[s];function o(l,i,d,c,u,h){return a(),n("div",null,p)}const b=e(r,[["render",o]]);export{g as __pageData,b as default}; diff --git "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.41c48223.js" "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.41c48223.js" new file mode 100644 index 0000000..8938084 --- /dev/null +++ "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.41c48223.js" @@ -0,0 +1 @@ +import{_ as s,o as t,c as e,U as a}from"./chunks/framework.a7041386.js";const h=JSON.parse('{"title":"起源","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/起源.md","filePath":"前端工程化/起源.md","lastUpdated":1690701222000}'),S={name:"前端工程化/起源.md"},o=a('

起源

早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协助开发者开发。

在 CSS 的基础上,衍生出了 Less,Sass,PostCss 这样的处理器,POSTCSS 由于其衍生原因,在现在的工程化项目中往往被作为后处理器使用,用于将兼容新旧 CSS的特性,实际上 PostCss 是可以替代 Less,Sass 这种前处理器,成为 CSS 的最终解决方案的,不过由于他内部需要不断开发插件,兼容 Less,Sass 的新版本迭代,后期社区就放弃了这种一体化解决方案,着重处理新旧 CSS 的特性了。

最终的处理逻辑可以理解为如下的链路:

Less/Sass文件 -> Less/Sass 处理器 -> 具有新特性的 CSS文件 -> PostCss 处理器 -> 兼容老特性的 CSS

在 JS 的基础上,衍生出了 TS 这种具有类型系统的编程语言,可以简单理解为 JS 的超集(数学概念)。同时,JS 这门语言自身的特性也在不断发展,也需要做新特性的兼容,由此一个新项目还需要考虑 JS 的处理逻辑,最终处理链路如下:

TS 文件 -> 具有新特性的 JS 文件 -> 兼容老特性的 JS

除了 CSS 和 JS 这两种文件的转换处理,在实际项目的开发中,还需要考虑诸如:打包优化,分包策略,静态资源处理等等许多问题,这些问题如果由我们人为的去配置,会大大减缓我们的开发效率。

因此,就诞生了诸如 ESBuild,Webpack,Rollup,Vite 这样的打包构建工具,专门为我们负责项目的打包构建流程,对于新手小白,可以不考虑其中的特性,直接使用即可。对于有一定基础的开发者,这些打包工具也各自都提供了自定义的配置项,我们可以使用这些配置项对项目进行更加专门化的打包构建。

',9),p=[o];function _(r,c,n,d,i,l){return t(),e("div",null,p)}const m=s(S,[["render",_]]);export{h as __pageData,m as default}; diff --git "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.b85fc121.lean.js" "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.41c48223.lean.js" similarity index 55% rename from "assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.b85fc121.lean.js" rename to "assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.41c48223.lean.js" index c65dbee..56339e0 100644 --- "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.b85fc121.lean.js" +++ "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.41c48223.lean.js" @@ -1 +1 @@ -import{_ as s,o as t,c as e,U as a}from"./chunks/framework.a7041386.js";const h=JSON.parse('{"title":"起源","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/起源.md","filePath":"前端工程化/起源.md","lastUpdated":null}'),S={name:"前端工程化/起源.md"},o=a("",9),p=[o];function _(r,c,n,d,i,l){return t(),e("div",null,p)}const m=s(S,[["render",_]]);export{h as __pageData,m as default}; +import{_ as s,o as t,c as e,U as a}from"./chunks/framework.a7041386.js";const h=JSON.parse('{"title":"起源","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/起源.md","filePath":"前端工程化/起源.md","lastUpdated":1690701222000}'),S={name:"前端工程化/起源.md"},o=a("",9),p=[o];function _(r,c,n,d,i,l){return t(),e("div",null,p)}const m=s(S,[["render",_]]);export{h as __pageData,m as default}; diff --git "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.b85fc121.js" "b/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.b85fc121.js" deleted file mode 100644 index b80c353..0000000 --- "a/assets/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226_\350\265\267\346\272\220.md.b85fc121.js" +++ /dev/null @@ -1 +0,0 @@ -import{_ as s,o as t,c as e,U as a}from"./chunks/framework.a7041386.js";const h=JSON.parse('{"title":"起源","description":"","frontmatter":{"tag":["前端工程化"]},"headers":[],"relativePath":"前端工程化/起源.md","filePath":"前端工程化/起源.md","lastUpdated":null}'),S={name:"前端工程化/起源.md"},o=a('

起源

早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协助开发者开发。

在 CSS 的基础上,衍生出了 Less,Sass,PostCss 这样的处理器,POSTCSS 由于其衍生原因,在现在的工程化项目中往往被作为后处理器使用,用于将兼容新旧 CSS的特性,实际上 PostCss 是可以替代 Less,Sass 这种前处理器,成为 CSS 的最终解决方案的,不过由于他内部需要不断开发插件,兼容 Less,Sass 的新版本迭代,后期社区就放弃了这种一体化解决方案,着重处理新旧 CSS 的特性了。

最终的处理逻辑可以理解为如下的链路:

Less/Sass文件 -> Less/Sass 处理器 -> 具有新特性的 CSS文件 -> PostCss 处理器 -> 兼容老特性的 CSS

在 JS 的基础上,衍生出了 TS 这种具有类型系统的编程语言,可以简单理解为 JS 的超集(数学概念)。同时,JS 这门语言自身的特性也在不断发展,也需要做新特性的兼容,由此一个新项目还需要考虑 JS 的处理逻辑,最终处理链路如下:

TS 文件 -> 具有新特性的 JS 文件 -> 兼容老特性的 JS

除了 CSS 和 JS 这两种文件的转换处理,在实际项目的开发中,还需要考虑诸如:打包优化,分包策略,静态资源处理等等许多问题,这些问题如果由我们人为的去配置,会大大减缓我们的开发效率。

因此,就诞生了诸如 ESBuild,Webpack,Rollup,Vite 这样的打包构建工具,专门为我们负责项目的打包构建流程,对于新手小白,可以不考虑其中的特性,直接使用即可。对于有一定基础的开发者,这些打包工具也各自都提供了自定义的配置项,我们可以使用这些配置项对项目进行更加专门化的打包构建。

',9),p=[o];function _(r,c,n,d,i,l){return t(),e("div",null,p)}const m=s(S,[["render",_]]);export{h as __pageData,m as default}; diff --git a/hashmap.json b/hashmap.json index a0a396d..d837584 100644 --- a/hashmap.json +++ b/hashmap.json @@ -1 +1 @@ -{"algo_02.双指针.md":"32f9e142","js_js04.md":"ff91d80d","network_04.网络层.md":"adc07418","algo_刷题笔记4.md":"96af88dd","react_react hooks.md":"8d73e4ec","js_js03.md":"c7d70c08","network_02.应用层协议种类.md":"da532813","css_css入门.md":"186d1e5c","js_js01.md":"802b0284","network_06.计网快问快答.md":"7ac4ec10","js_js手写.md":"82c34a7c","network_03.传输层.md":"b7412285","react_react 事件机制.md":"5bb91d09","network_02.http基础.md":"ea049179","network_05.网络安全.md":"bc141604","js_js02.md":"6346f4ef","network_02.http优化.md":"ab3954cf","wechatminiprogram_01.小程序起步.md":"626b77b9","algo_刷题笔记3.md":"400a90c9","algo_刷题笔记2.md":"fce6ed95","ts_类型系统.md":"14c655f8","designpattern_发布订阅模式.md":"970ca931","projects_wecqu.md":"2d501a8b","ts_quick start.md":"07656ad5","network_01.计网起步.md":"362de3b5","projects_ecosystem.md":"1526ac70","react_react 基础学习.md":"8e95a85a","ts_面向对象特性.md":"d7fb6669","react_框架抽象.md":"62788972","react_react router v6.md":"49c0c8c5","react_quick start.md":"839528c9","vue_vue进阶.md":"04d4207b","wechatminiprogram_02.获取用户头像.md":"41f4f647","index.md":"f8789e92","ts_函数式编程.md":"609ef64b","wechatminiprogram_03.变量加锁与请求封装.md":"76d6f2a4","algo_01.汇总.md":"6fd127cc","wechatminiprogram_04.unionid与其近亲.md":"f8809690","vue_vuerouter.md":"edd3ed78","uniapp_uniapp基础.md":"613f7ea7","sop_config_in_coding.md":"065307d5","ts_泛型工具类.md":"5db5adca","前端工程化_webpack初探.md":"e2f29a2d","wechatminiprogram_05.微信云开发.md":"cd24c3a4","前端工程化_起源.md":"b85fc121","vue_手写系列.md":"809c6705","sop_project.md":"8c5fe64f","algo_刷题笔记1.md":"9afad74a","sop_introduce.md":"0a5bc9ff","前端工程化_vite 初探.md":"4db9e488"} +{"algo_02.双指针.md":"7f81d5b0","algo_刷题笔记4.md":"65097491","designpattern_发布订阅模式.md":"970ca931","algo_刷题笔记3.md":"7fcd0a6a","css_css入门.md":"186d1e5c","js_js03.md":"c7d70c08","network_01.计网起步.md":"362de3b5","js_js04.md":"ff91d80d","js_js手写.md":"82c34a7c","algo_刷题笔记2.md":"37b13e79","algo_刷题笔记1.md":"601bbf48","js_js02.md":"6346f4ef","algo_01.汇总.md":"ddcd8b4a","network_02.http优化.md":"ab3954cf","js_js01.md":"802b0284","react_quick start.md":"2fc9061e","projects_ecosystem.md":"1526ac70","projects_wecqu.md":"2d501a8b","network_05.网络安全.md":"bc141604","network_02.http基础.md":"ea049179","network_06.计网快问快答.md":"7ac4ec10","network_03.传输层.md":"b7412285","network_04.网络层.md":"adc07418","network_02.应用层协议种类.md":"da532813","ts_quick start.md":"0be8b76a","react_框架抽象.md":"4578d7b6","react_react 基础学习.md":"ddfa0dea","react_react hooks.md":"3560e5e9","react_react 事件机制.md":"1787ad45","uniapp_uniapp基础.md":"613f7ea7","ts_类型系统.md":"6c1d1441","ts_函数式编程.md":"9add7ee4","ts_面向对象特性.md":"428a8416","ts_泛型工具类.md":"a3ff378a","vue_vuerouter.md":"edd3ed78","wechatminiprogram_01.小程序起步.md":"626b77b9","vue_vue进阶.md":"04d4207b","react_react router v6.md":"760af9af","vue_手写系列.md":"809c6705","wechatminiprogram_04.unionid与其近亲.md":"f8809690","index.md":"f8789e92","wechatminiprogram_05.微信云开发.md":"cd24c3a4","sop_config_in_coding.md":"065307d5","sop_introduce.md":"0a5bc9ff","sop_project.md":"8c5fe64f","wechatminiprogram_02.获取用户头像.md":"41f4f647","wechatminiprogram_03.变量加锁与请求封装.md":"76d6f2a4","前端工程化_起源.md":"41c48223","前端工程化_webpack初探.md":"a75fcff0","前端工程化_vite 初探.md":"dc85089f"} diff --git a/index.html b/index.html index 0df9e6b..b03bcee 100644 --- a/index.html +++ b/index.html @@ -14,11 +14,40 @@ -
Skip to content

Luminous Docs

Sow nothing, reap nothing.

  • 前置

    什么是构建工具 -早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Fr

    Luminous10秒前前端工程化
  • Webpack 初探

    核心概念 - Entry:入口 -Entry 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始 -`/** @type {import('web

    Luminous10秒前前端工程化
  • 起源

    早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协

    Luminous10秒前前端工程化
  • Quick Start

    +

    Skip to content

    Luminous Docs

    Sow nothing, reap nothing.

    48博客文章
    +48本月更新
    +14本周更新
    🔥 精选文章
    1. 1
    2. 2
      JS手写
      2023-07-17
    3. 3
    4. 4
      项目汇总
      2023-07-17
    🏷 标签
    • DS
    • CSS
    • DesignPattern
    • JS
    • 计算机网络
    • Project
    • React
    • TS
    • Uniapp
    • Vue3
    • 微信小程序
    • SOP
    • 前端工程化
    - +在使用React Hooks时,有

    Luminous2023-07-30React
48博客文章
+6本月更新
+6本周更新
🔥 精选文章
  1. 1
  2. 2
    JS手写
    2023-07-17
  3. 3
  4. 4
    项目汇总
    2023-07-17
🏷 标签
  • DataStructure
  • CSS
  • DesignPattern
  • JS
  • 计算机网络
  • Project
  • React
  • TS
  • Uniapp
  • Vue3
  • 微信小程序
  • SOP
  • 前端工程化
+ \ No newline at end of file diff --git a/sop/config_in_coding.html b/sop/config_in_coding.html index 6a940d7..85bbd08 100644 --- a/sop/config_in_coding.html +++ b/sop/config_in_coding.html @@ -15,8 +15,8 @@
Skip to content
On this page
  • 配置 oh-my-zsh
  • 配置 vscode主题及图标

上次更新于:

- + \ No newline at end of file diff --git a/sop/introduce.html b/sop/introduce.html index 5c56f51..39f7cc4 100644 --- a/sop/introduce.html +++ b/sop/introduce.html @@ -15,8 +15,8 @@
Skip to content
On this page

关于我

介绍

欢迎来到我的个人博客!我是嘉诚,一名热爱生活的前端开发工程师。

在这里,我会和大家分享我的学习心得和实践经验,涉及数据结构、计算机网络、JavaScript、Vue.js、微信小程序等多方面的知识点。同时,我也会记录我的学习和生活中的有趣故事和感想,希望能够给大家带来一些启发和乐趣。

在实践过程中,我参与了多个前端项目的开发和维护,积累了一定的经验和技巧。在这篇博客中,我会把这些经验和技巧分享给大家,并且欢迎大家给我提出意见和建议。

当然,我的博客并不是完美无缺的。也欢迎大家随时指正。我会持续更新我的博客内容,并且努力改进我的表达方式和逻辑结构。

技术栈

  • Vue3 + Tailwind Css + DaisyUI + HeadlessUI/Vue
  • Wechat Miniprograme + CloudFunctions + GraceUI 3
  • Uniapp Miniprograme + GraceUI 5
  • React@18 + Reack Hooks + RooUI

上次更新于:

- + \ No newline at end of file diff --git a/sop/project.html b/sop/project.html index aaddc39..8915c60 100644 --- a/sop/project.html +++ b/sop/project.html @@ -15,8 +15,8 @@
Skip to content
On this page

项目汇总

仿掘金官网

  • Vue.js: 渐进式Javascript框架
  • Apifox: 充当后端, 提供mock数据

Wecqu微信小程序

  • 原生微信小程序
  • ColorUI: 基于原生微信小程序的样式库
  • GraceUI3: 基于原生微信小程序的样式库与组件库

生态圈论坛h5应用

  • Uniapp开发的h5应用
  • GraceUI5: 基于uniapp的样式库与组件库

博客管理系统

  • React18
  • React hooks
  • React Router
  • Mobx
  • Ant Design
  • Apache ECharts: 一个基于Javascript的开源可视化图表库

上次更新于:

- + \ No newline at end of file diff --git "a/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/Vite \345\210\235\346\216\242.html" "b/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/Vite \345\210\235\346\216\242.html" index 9baa411..d191f03 100644 --- "a/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/Vite \345\210\235\346\216\242.html" +++ "b/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/Vite \345\210\235\346\216\242.html" @@ -10,11 +10,11 @@ - + -
Skip to content
On this page
  1. vite 的前世今生
  2. vite的编译结果及其分析
  3. vite的配置文件
  4. vite中处理CSS,静态资源,TS
  5. vite的插件以及常用用插件的使用
  6. vite构建原理

前置

什么是构建工具

早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协助开发者开发。

在 CSS 的基础上,衍生出了 Less,Sass,PostCss 这样的处理器,POSTCSS 由于其衍生原因,在现在的工程化项目中往往被作为后处理器使用,用于将兼容新旧 CSS的特性,实际上 PostCss 是可以替代 Less,Sass 这种前处理器,成为 CSS 的最终解决方案的,不过由于他内部需要不断开发插件,兼容 Less,Sass 的新版本迭代,后期社区就放弃了这种一体化解决方案,着重处理新旧 CSS 的特性了。

最终的处理逻辑可以理解为如下的链路:

Less/Sass文件 -> Less/Sass 处理器 -> 具有新特性的 CSS文件 -> PostCss 处理器 -> 兼容老特性的 CSS

在 JS 的基础上,衍生出了 TS 这种具有类型系统的编程语言,可以简单理解为 JS 的超集(数学概念)。同时,JS 这门语言自身的特性也在不断发展,也需要做新特性的兼容,由此一个新项目还需要考虑 JS 的处理逻辑,最终处理链路如下:

TS 文件 -> 具有新特性的 JS 文件 -> 兼容老特性的 JS

除了 CSS 和 JS 这两种文件的转换处理,在实际项目的开发中,还需要考虑诸如:打包优化,分包策略,静态资源处理等等许多问题,这些问题如果由我们人为的去配置,会大大减缓我们的开发效率。

因此,就诞生了诸如 ESBuild,Webpack,Rollup,Vite 这样的打包构建工具,专门为我们负责项目的打包构建流程,对于新手小白,可以不考虑其中的特性,直接使用即可。对于有一定基础的开发者,这些打包工具也各自都提供了自定义的配置项,我们可以使用这些配置项对项目进行更加专门化的打包构建。

Vite 解决的痛点

作为老版本的构建工具,Webpack 的特点是大而全,缺点是配置起来十分麻烦。

针对此,Vite 团队集成了大多数的常见配置,例如原生支持 CSS 和 TS 的编译,真正做到 out of box 开箱即用!这也是 Vue 团队一贯的作风——减少开发者的心智负担。对于这种作风的优劣,大家就见仁见智,这里就不过多评价了。

Vite 的构成

Vite 在开发阶段使用 ESbuild,生产阶段使用 Rollup;

NodeJS 学习

了解 fs 和 path 这两个 Node 库,是用来处理文件的

获取路径:

  • path.resolve(__dirname, '')
  • process.cwd():获取当前的node执行目录

依赖预构建

起因

开发阶段 Vite 会对项目中使用的第三方依赖如 reactreact-domlodash-es 等做预构建操作。

之所以要做预构建,是因为 Vite 是基于浏览器原生的 ESM 规范来实现 dev server 的,这就要求整个项目中涉及的所有源代码必须符合 ESM 规范。

而在实际开发过程中,业务代码我们可以严格按照 ESM 规范来编写,但第三方依赖就无法保证了,比如 react。这就需要我们通过 Vite预构建功能将非 ESM 规范的代码转换为符合 ESM 规范的代码。

另外,尽管有些第三方依赖已经符合 ESM 规范,但它是由多个子文件组成的,如 lodash-es。如果不做处理就直接使用,那么就会引发请求瀑布流,这对页面性能来说,简直就是一场灾难。同样的,我们可以通过 Vite预构建功能,将第三方依赖内部的多个文件合并为一个,减少 http 请求数量,优化页面加载性能。

综上,预构建,主要做了两件事情:

  • 将非 ESM 规范的代码转换为符合 ESM 规范的代码;
  • 将第三方依赖内部的多个文件合并为一个,减少 http 请求数量;

做法

  1. 自动依赖搜寻

    • 如果没有找到现有的缓存,Vite 会扫描您的源代码,并自动寻找引入的依赖项(即 "bare import",表示期望从 node_modules 中解析),并将这些依赖项作为预构建的入口点。预打包使用 esbuild 执行,因此通常速度非常快。
    • 在服务器已经启动后,如果遇到尚未在缓存中的新依赖项导入,则 Vite 将重新运行依赖项构建过程,并在需要时重新加载页面。
  2. Monorepo 和 链接依赖

  3. 自定义行为

    • optimizeDeps.include
    • optimizeDeps.exclude
  4. 缓存

    • 文件系统缓存
    • 浏览器缓存

二次预构建

同样的,Vite预构建的时候也是基于类似的机制去找到项目中所有的第三方依赖的。和 Webpack 不同, Vite 另辟蹊径,借助了 EsbuildWebpack 更快的打包能力,对整个项目做一个全量打包。打包的时候,通过分析依赖关系,得到项目中所有的源文件的 url,然后分离出第三方依赖。

这样,Vite 就可以对找到的第三方依赖做转化、合并操作了。

预构建功能非常棒,但在实际的项目中,并不能保证所有的第三方依赖都可以被找到。如果出现下面的这两种情况, Esbuild 也无能为力:

  • plugin 在运行过程中,动态给源码注入了新的第三方依赖;
  • 动态依赖在代码运行时,才可以确定最终的 url

Vite3 的优化

Vite3.0 版本对第三方依赖的请求和业务代码的请求有不同的处理逻辑。当浏览器请求业务代码时,dev server 只要完成源代码转换并收集到依赖模块的 url,就会给浏览器发送 response。而第三方依赖请求则不同,dev server 会等首屏期间涉及的所有模块的依赖关系全部解析完毕以后,才会给浏览器发送 response。这就导致了,如果发现有未预构建的第三方依赖,第三方依赖的请求会一直被阻塞,直到二次预构建完成为止。

总结:

Vite3.0二次预构建的优化,其实是以消耗首屏性能来优化 reload 交互体验

遗憾的是,Vite 3.0 并没有解决懒加载二次预构建导致的 reload 的问题。这个问题,目前只能通过社区提供的 vite-plugin-optimize-persistvite-plugin-package-config 来解决了。

可以理解为vite-plugin-package-config vite-plugin-optimize-persist其实是相当于在vite.config.js配置中配置了includes选项。

dev server 启动以后,内部有一个缓存来存取预构建的第三方依赖。而vite-plugin-optimize-persist 会监听这个缓存。当有新的第三方依赖需要预构建,并且修改缓存以后,vite-plugin-optimize-persist 会跟新 package.json 的 vite 字段。vite-plugin-package-config 负责在 dev server 启动时,初始化 vite.config,会把 package.json 中的 vite 配置项合并到 vite.config 中

对不同资源的处理

CSS

js
// vite.config.js
+    
Skip to content
On this page
  1. vite 的前世今生
  2. vite的编译结果及其分析
  3. vite的配置文件
  4. vite中处理CSS,静态资源,TS
  5. vite的插件以及常用用插件的使用
  6. vite构建原理

前置

什么是构建工具

早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协助开发者开发。

在 CSS 的基础上,衍生出了 Less,Sass,PostCss 这样的处理器,POSTCSS 由于其衍生原因,在现在的工程化项目中往往被作为后处理器使用,用于将兼容新旧 CSS的特性,实际上 PostCss 是可以替代 Less,Sass 这种前处理器,成为 CSS 的最终解决方案的,不过由于他内部需要不断开发插件,兼容 Less,Sass 的新版本迭代,后期社区就放弃了这种一体化解决方案,着重处理新旧 CSS 的特性了。

最终的处理逻辑可以理解为如下的链路:

Less/Sass文件 -> Less/Sass 处理器 -> 具有新特性的 CSS文件 -> PostCss 处理器 -> 兼容老特性的 CSS

在 JS 的基础上,衍生出了 TS 这种具有类型系统的编程语言,可以简单理解为 JS 的超集(数学概念)。同时,JS 这门语言自身的特性也在不断发展,也需要做新特性的兼容,由此一个新项目还需要考虑 JS 的处理逻辑,最终处理链路如下:

TS 文件 -> 具有新特性的 JS 文件 -> 兼容老特性的 JS

除了 CSS 和 JS 这两种文件的转换处理,在实际项目的开发中,还需要考虑诸如:打包优化,分包策略,静态资源处理等等许多问题,这些问题如果由我们人为的去配置,会大大减缓我们的开发效率。

因此,就诞生了诸如 ESBuild,Webpack,Rollup,Vite 这样的打包构建工具,专门为我们负责项目的打包构建流程,对于新手小白,可以不考虑其中的特性,直接使用即可。对于有一定基础的开发者,这些打包工具也各自都提供了自定义的配置项,我们可以使用这些配置项对项目进行更加专门化的打包构建。

Vite 解决的痛点

作为老版本的构建工具,Webpack 的特点是大而全,缺点是配置起来十分麻烦。

针对此,Vite 团队集成了大多数的常见配置,例如原生支持 CSS 和 TS 的编译,真正做到 out of box 开箱即用!这也是 Vue 团队一贯的作风——减少开发者的心智负担。对于这种作风的优劣,大家就见仁见智,这里就不过多评价了。

Vite 的构成

Vite 在开发阶段使用 ESbuild,生产阶段使用 Rollup;

NodeJS 学习

了解 fs 和 path 这两个 Node 库,是用来处理文件的

获取路径:

  • path.resolve(__dirname, '')
  • process.cwd():获取当前的node执行目录

依赖预构建

起因

开发阶段 Vite 会对项目中使用的第三方依赖如 reactreact-domlodash-es 等做预构建操作。

之所以要做预构建,是因为 Vite 是基于浏览器原生的 ESM 规范来实现 dev server 的,这就要求整个项目中涉及的所有源代码必须符合 ESM 规范。

而在实际开发过程中,业务代码我们可以严格按照 ESM 规范来编写,但第三方依赖就无法保证了,比如 react。这就需要我们通过 Vite预构建功能将非 ESM 规范的代码转换为符合 ESM 规范的代码。

另外,尽管有些第三方依赖已经符合 ESM 规范,但它是由多个子文件组成的,如 lodash-es。如果不做处理就直接使用,那么就会引发请求瀑布流,这对页面性能来说,简直就是一场灾难。同样的,我们可以通过 Vite预构建功能,将第三方依赖内部的多个文件合并为一个,减少 http 请求数量,优化页面加载性能。

综上,预构建,主要做了两件事情:

  • 将非 ESM 规范的代码转换为符合 ESM 规范的代码;
  • 将第三方依赖内部的多个文件合并为一个,减少 http 请求数量;

做法

  1. 自动依赖搜寻

    • 如果没有找到现有的缓存,Vite 会扫描您的源代码,并自动寻找引入的依赖项(即 "bare import",表示期望从 node_modules 中解析),并将这些依赖项作为预构建的入口点。预打包使用 esbuild 执行,因此通常速度非常快。
    • 在服务器已经启动后,如果遇到尚未在缓存中的新依赖项导入,则 Vite 将重新运行依赖项构建过程,并在需要时重新加载页面。
  2. Monorepo 和 链接依赖

  3. 自定义行为

    • optimizeDeps.include
    • optimizeDeps.exclude
  4. 缓存

    • 文件系统缓存
    • 浏览器缓存

二次预构建

同样的,Vite预构建的时候也是基于类似的机制去找到项目中所有的第三方依赖的。和 Webpack 不同, Vite 另辟蹊径,借助了 EsbuildWebpack 更快的打包能力,对整个项目做一个全量打包。打包的时候,通过分析依赖关系,得到项目中所有的源文件的 url,然后分离出第三方依赖。

这样,Vite 就可以对找到的第三方依赖做转化、合并操作了。

预构建功能非常棒,但在实际的项目中,并不能保证所有的第三方依赖都可以被找到。如果出现下面的这两种情况, Esbuild 也无能为力:

  • plugin 在运行过程中,动态给源码注入了新的第三方依赖;
  • 动态依赖在代码运行时,才可以确定最终的 url

Vite3 的优化

Vite3.0 版本对第三方依赖的请求和业务代码的请求有不同的处理逻辑。当浏览器请求业务代码时,dev server 只要完成源代码转换并收集到依赖模块的 url,就会给浏览器发送 response。而第三方依赖请求则不同,dev server 会等首屏期间涉及的所有模块的依赖关系全部解析完毕以后,才会给浏览器发送 response。这就导致了,如果发现有未预构建的第三方依赖,第三方依赖的请求会一直被阻塞,直到二次预构建完成为止。

总结:

Vite3.0二次预构建的优化,其实是以消耗首屏性能来优化 reload 交互体验

遗憾的是,Vite 3.0 并没有解决懒加载二次预构建导致的 reload 的问题。这个问题,目前只能通过社区提供的 vite-plugin-optimize-persistvite-plugin-package-config 来解决了。

可以理解为vite-plugin-package-config vite-plugin-optimize-persist其实是相当于在vite.config.js配置中配置了includes选项。

dev server 启动以后,内部有一个缓存来存取预构建的第三方依赖。而vite-plugin-optimize-persist 会监听这个缓存。当有新的第三方依赖需要预构建,并且修改缓存以后,vite-plugin-optimize-persist 会跟新 package.json 的 vite 字段。vite-plugin-package-config 负责在 dev server 启动时,初始化 vite.config,会把 package.json 中的 vite 配置项合并到 vite.config 中

对不同资源的处理

CSS

js
// vite.config.js
 
 export default {
   // ...
@@ -248,9 +248,9 @@
     },
   },
   ...
-})

总结

  • 开发时
    • 利用构建工具或者脚手架或者第三方库的 proxy 代理配置
    • 我们自己搭建一个开发服务器
  • 生产时
    • 后端/运维通过ngnix代理服务,和我们自己搭建一个开发服务器的原理类似
    • 配置身份标记Access-Control-Allow-Origin,相当于一个白名单,会出现在服务端的响应头中
- +})

总结

  • 开发时
    • 利用构建工具或者脚手架或者第三方库的 proxy 代理配置
    • 我们自己搭建一个开发服务器
  • 生产时
    • 后端/运维通过ngnix代理服务,和我们自己搭建一个开发服务器的原理类似
    • 配置身份标记Access-Control-Allow-Origin,相当于一个白名单,会出现在服务端的响应头中

上次更新于:

+ \ No newline at end of file diff --git "a/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/Webpack\345\210\235\346\216\242.html" "b/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/Webpack\345\210\235\346\216\242.html" index cf65eaa..4234c3e 100644 --- "a/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/Webpack\345\210\235\346\216\242.html" +++ "b/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/Webpack\345\210\235\346\216\242.html" @@ -10,13 +10,13 @@ - + -
Skip to content
On this page

Webpack 初探

核心概念

Entry:入口

Entry 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始

`/** @type {import('webpack').Configuration} */

module.exports = { mode: 'development', entry: { main: { // 配置 chunk 名 filename: 'target_index.js', // 输出filename名 import: './src/index.js', // 指定入口文件 runtime: 'runTimeOne', // 配置当前chunk的运行时环境,默认情况下不同chunk的缓存是隔离开的 }, test: { filename: 'target_b.js', import: './src/b.js', // runtime: 'runTimeOne', // 和 main chunk 共享一个运行时环境 dependOn: 'main', // 指定要共享的运行时环境所在的chunk名,如果没有会报错 } }, output: { clean: true // 每次构建前清空输出文件夹下的内容 }, }`

Output:出口

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

为啥要有hash??【出于复用缓存的考虑】

浏览器有一个缓存机制-—->如果浏览器刷新时请求的当前js的文件名没有产生变化,则浏览器不会请求该资源而是直接使用缓存

有一个插件 plugins 是用来帮我们组装 index.html 页面的,配置hash是为了让我们的文件变化能够实时的被浏览器感知到

`/** @type {import('webpack').Configuration} */

module.exports = { entry: { index: './src/index.js', search: './src/search.js', }, output: { filename: '[name].[contenthash:6].js', // [name]模板字符串 path: __dirname + '/dist', clean: true }, };`

Loader:代码转换

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

使用 loader 可以把 css 之类的

loader 帮 webpack 做了一部分的代码转换工作

之前我们在用脚手架vue-cli, create-react-app的时候不需要关注这些东西是因为这些脚手架在底层帮我们都将webpack配置好了

css-loader 模块化

webpack.config.js

`/** @type {import('webpack').Configuration} */

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = { mode: "development", entry: { index: "./src/index.js", search: "./src/search.js", }, output: { filename: "[name].[contenthash:6].js", // [name]模板字符串 path: dirname + "/dist", clean: true, }, plugins: [ // 添加插件实例 new MiniCssExtractPlugin({ filename: '[name].[contenthash].css', }), ], module: { rules: [ { test: /.css$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { modules: { localIdentName: '[path][name][local]--[hash:base64:5]', }, }, }, ], }, ], }, };`

App.js

`import appStyle from './App.css'

function App() { const element = document.createElement('div') element.innerHTML = 'This is App' element.className = appStyle.wrapper return element } document.body.appendChild(App())`

Header.js

import headerStyle from './Header.css' function Header() {   const element = document.createElement('div')   element.innerHTML = 'This is Header'   element.className = headerStyle.wrapper   return element } document.body.appendChild(Header())

Plugin:插件机制

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

和 loader 的比较

webpack 的 loader 和 plugin 都是用于扩展 webpack 功能的工具,但它们在使用方式和处理过程中有一些区别。

Loader(加载器)【模块转换阶段】

  1. loader 主要用于对模块源代码进行转换。它们在构建过程中将文件从输入(例如 .css、.scss、.jsx 等)转换为输出(通常是 JavaScript 模块)。
  2. loader 是在 module.rules 配置中定义的,并且可以链式调用。链中的每个 loader 都会对资源进行转换,然后将结果传递给下一个 loader。
  3. loader 可以同步或异步执行,但通常是同步的。

Plugin(插件)【不仅仅是模块转换阶段】

  1. plugin 是用于执行更广泛的任务,如优化、代码分割、资源管理等。它们可以在整个构建过程中的不同阶段执行操作,而不仅仅是在模块转换阶段
  2. plugin 是在 plugins 配置中定义的,通过实例化插件并将其添加到 plugins 数组中来使用。
  3. plugin 通过订阅 webpack 的事件钩子(hooks)来工作,这使得它们可以在构建过程的特定时刻执行操作。

区别和联系

  1. 区别:loader 主要用于转换模块源代码,而 plugin 用于执行更广泛的任务。loader 在 module.rules 中定义,plugin 在 plugins 中定义。
  2. 联系:loader 和 plugin 都是用于扩展 webpack 功能的工具,它们可以相互配合使用。例如,css-loader 和 style-loader 可以将 CSS 转换为 JavaScript 模块,然后 MiniCssExtractPlugin 插件可以将这些模块提取到单独的 CSS 文件中。

总之,loader 和 plugin 是 webpack 的两种扩展方式,它们在不同的场景和处理过程中发挥作用,但都是为了实现更丰富的构建功能。

深入devDepend && dependencies

IIFE【立即执行函数】

只要加上运算符就能够进行隐式转换,将函数转换成函数表达式,成为立即执行函数

当一个函数变成函数表达式以后 丢掉自己原来的名字

+function () { 	console.log('12') }()

正文

只要不发包,这二者就随便放,大多数情况下确实如此。【合法但不合理】

我们还需要考虑SSR 服务端渲染【服务端渲染必须要时刻关注package. json即使你不发包】

很多人他会觉得如果将dependencies里的东西移到devDependencies里去会造成性能问题 除非是ssr才会造成这种情况

你在生产会用的依赖 比如react Lodash 你全部放到dependencies里去

在生产不会用到的依赖,比如ts webpack

构建生产代码 全部放到devDependencies里去

这是国内约定俗成的用法

这样的话你在ssr里也不会出错即使你会多安装一些依赖在ssr中【ssr里是会用到package.json在服务端装依赖的】,但是也比出错要好

在常规客户端渲染的情况下,二者无差异

package.json不会参与到webpack的构建工作中

- +
Skip to content
On this page

Webpack 初探

核心概念

Entry:入口

Entry 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始

`/** @type {import('webpack').Configuration} */

module.exports = { mode: 'development', entry: { main: { // 配置 chunk 名 filename: 'target_index.js', // 输出filename名 import: './src/index.js', // 指定入口文件 runtime: 'runTimeOne', // 配置当前chunk的运行时环境,默认情况下不同chunk的缓存是隔离开的 }, test: { filename: 'target_b.js', import: './src/b.js', // runtime: 'runTimeOne', // 和 main chunk 共享一个运行时环境 dependOn: 'main', // 指定要共享的运行时环境所在的chunk名,如果没有会报错 } }, output: { clean: true // 每次构建前清空输出文件夹下的内容 }, }`

Output:出口

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

为啥要有hash??【出于复用缓存的考虑】

浏览器有一个缓存机制-—->如果浏览器刷新时请求的当前js的文件名没有产生变化,则浏览器不会请求该资源而是直接使用缓存

有一个插件 plugins 是用来帮我们组装 index.html 页面的,配置hash是为了让我们的文件变化能够实时的被浏览器感知到

`/** @type {import('webpack').Configuration} */

module.exports = { entry: { index: './src/index.js', search: './src/search.js', }, output: { filename: '[name].[contenthash:6].js', // [name]模板字符串 path: __dirname + '/dist', clean: true }, };`

Loader:代码转换

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

使用 loader 可以把 css 之类的

loader 帮 webpack 做了一部分的代码转换工作

之前我们在用脚手架vue-cli, create-react-app的时候不需要关注这些东西是因为这些脚手架在底层帮我们都将webpack配置好了

css-loader 模块化

webpack.config.js

`/** @type {import('webpack').Configuration} */

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = { mode: "development", entry: { index: "./src/index.js", search: "./src/search.js", }, output: { filename: "[name].[contenthash:6].js", // [name]模板字符串 path: dirname + "/dist", clean: true, }, plugins: [ // 添加插件实例 new MiniCssExtractPlugin({ filename: '[name].[contenthash].css', }), ], module: { rules: [ { test: /.css$/i, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { modules: { localIdentName: '[path][name][local]--[hash:base64:5]', }, }, }, ], }, ], }, };`

App.js

`import appStyle from './App.css'

function App() { const element = document.createElement('div') element.innerHTML = 'This is App' element.className = appStyle.wrapper return element } document.body.appendChild(App())`

Header.js

import headerStyle from './Header.css' function Header() {   const element = document.createElement('div')   element.innerHTML = 'This is Header'   element.className = headerStyle.wrapper   return element } document.body.appendChild(Header())

Plugin:插件机制

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

和 loader 的比较

webpack 的 loader 和 plugin 都是用于扩展 webpack 功能的工具,但它们在使用方式和处理过程中有一些区别。

Loader(加载器)【模块转换阶段】

  1. loader 主要用于对模块源代码进行转换。它们在构建过程中将文件从输入(例如 .css、.scss、.jsx 等)转换为输出(通常是 JavaScript 模块)。
  2. loader 是在 module.rules 配置中定义的,并且可以链式调用。链中的每个 loader 都会对资源进行转换,然后将结果传递给下一个 loader。
  3. loader 可以同步或异步执行,但通常是同步的。

Plugin(插件)【不仅仅是模块转换阶段】

  1. plugin 是用于执行更广泛的任务,如优化、代码分割、资源管理等。它们可以在整个构建过程中的不同阶段执行操作,而不仅仅是在模块转换阶段
  2. plugin 是在 plugins 配置中定义的,通过实例化插件并将其添加到 plugins 数组中来使用。
  3. plugin 通过订阅 webpack 的事件钩子(hooks)来工作,这使得它们可以在构建过程的特定时刻执行操作。

区别和联系

  1. 区别:loader 主要用于转换模块源代码,而 plugin 用于执行更广泛的任务。loader 在 module.rules 中定义,plugin 在 plugins 中定义。
  2. 联系:loader 和 plugin 都是用于扩展 webpack 功能的工具,它们可以相互配合使用。例如,css-loader 和 style-loader 可以将 CSS 转换为 JavaScript 模块,然后 MiniCssExtractPlugin 插件可以将这些模块提取到单独的 CSS 文件中。

总之,loader 和 plugin 是 webpack 的两种扩展方式,它们在不同的场景和处理过程中发挥作用,但都是为了实现更丰富的构建功能。

深入devDepend && dependencies

IIFE【立即执行函数】

只要加上运算符就能够进行隐式转换,将函数转换成函数表达式,成为立即执行函数

当一个函数变成函数表达式以后 丢掉自己原来的名字

+function () { 	console.log('12') }()

正文

只要不发包,这二者就随便放,大多数情况下确实如此。【合法但不合理】

我们还需要考虑SSR 服务端渲染【服务端渲染必须要时刻关注package. json即使你不发包】

很多人他会觉得如果将dependencies里的东西移到devDependencies里去会造成性能问题 除非是ssr才会造成这种情况

你在生产会用的依赖 比如react Lodash 你全部放到dependencies里去

在生产不会用到的依赖,比如ts webpack

构建生产代码 全部放到devDependencies里去

这是国内约定俗成的用法

这样的话你在ssr里也不会出错即使你会多安装一些依赖在ssr中【ssr里是会用到package.json在服务端装依赖的】,但是也比出错要好

在常规客户端渲染的情况下,二者无差异

package.json不会参与到webpack的构建工作中

上次更新于:

+ \ No newline at end of file diff --git "a/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/\350\265\267\346\272\220.html" "b/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/\350\265\267\346\272\220.html" index 6daaeec..4802080 100644 --- "a/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/\350\265\267\346\272\220.html" +++ "b/\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226/\350\265\267\346\272\220.html" @@ -10,13 +10,13 @@ - + -
Skip to content
On this page

起源

早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协助开发者开发。

在 CSS 的基础上,衍生出了 Less,Sass,PostCss 这样的处理器,POSTCSS 由于其衍生原因,在现在的工程化项目中往往被作为后处理器使用,用于将兼容新旧 CSS的特性,实际上 PostCss 是可以替代 Less,Sass 这种前处理器,成为 CSS 的最终解决方案的,不过由于他内部需要不断开发插件,兼容 Less,Sass 的新版本迭代,后期社区就放弃了这种一体化解决方案,着重处理新旧 CSS 的特性了。

最终的处理逻辑可以理解为如下的链路:

Less/Sass文件 -> Less/Sass 处理器 -> 具有新特性的 CSS文件 -> PostCss 处理器 -> 兼容老特性的 CSS

在 JS 的基础上,衍生出了 TS 这种具有类型系统的编程语言,可以简单理解为 JS 的超集(数学概念)。同时,JS 这门语言自身的特性也在不断发展,也需要做新特性的兼容,由此一个新项目还需要考虑 JS 的处理逻辑,最终处理链路如下:

TS 文件 -> 具有新特性的 JS 文件 -> 兼容老特性的 JS

除了 CSS 和 JS 这两种文件的转换处理,在实际项目的开发中,还需要考虑诸如:打包优化,分包策略,静态资源处理等等许多问题,这些问题如果由我们人为的去配置,会大大减缓我们的开发效率。

因此,就诞生了诸如 ESBuild,Webpack,Rollup,Vite 这样的打包构建工具,专门为我们负责项目的打包构建流程,对于新手小白,可以不考虑其中的特性,直接使用即可。对于有一定基础的开发者,这些打包工具也各自都提供了自定义的配置项,我们可以使用这些配置项对项目进行更加专门化的打包构建。

- +
Skip to content
On this page

起源

早期的开发者们,使用 HTML,CSS,JS 来进行网页的开发,当网站体量大了之后,这种传统的方式却带来了难以维护,样式不统一等等问题,由此产生了像 React,Vue 这样的 Framework,协助开发者开发。

在 CSS 的基础上,衍生出了 Less,Sass,PostCss 这样的处理器,POSTCSS 由于其衍生原因,在现在的工程化项目中往往被作为后处理器使用,用于将兼容新旧 CSS的特性,实际上 PostCss 是可以替代 Less,Sass 这种前处理器,成为 CSS 的最终解决方案的,不过由于他内部需要不断开发插件,兼容 Less,Sass 的新版本迭代,后期社区就放弃了这种一体化解决方案,着重处理新旧 CSS 的特性了。

最终的处理逻辑可以理解为如下的链路:

Less/Sass文件 -> Less/Sass 处理器 -> 具有新特性的 CSS文件 -> PostCss 处理器 -> 兼容老特性的 CSS

在 JS 的基础上,衍生出了 TS 这种具有类型系统的编程语言,可以简单理解为 JS 的超集(数学概念)。同时,JS 这门语言自身的特性也在不断发展,也需要做新特性的兼容,由此一个新项目还需要考虑 JS 的处理逻辑,最终处理链路如下:

TS 文件 -> 具有新特性的 JS 文件 -> 兼容老特性的 JS

除了 CSS 和 JS 这两种文件的转换处理,在实际项目的开发中,还需要考虑诸如:打包优化,分包策略,静态资源处理等等许多问题,这些问题如果由我们人为的去配置,会大大减缓我们的开发效率。

因此,就诞生了诸如 ESBuild,Webpack,Rollup,Vite 这样的打包构建工具,专门为我们负责项目的打包构建流程,对于新手小白,可以不考虑其中的特性,直接使用即可。对于有一定基础的开发者,这些打包工具也各自都提供了自定义的配置项,我们可以使用这些配置项对项目进行更加专门化的打包构建。

上次更新于:

+ \ No newline at end of file