React 的理念

设计理念:UI = f(state)

React 把 UI 看成「由状态推导出来的纯函数」:每次状态变化时,重新计算出一份新的 UI 描述(虚拟 DOM)→ React 帮你高效算出差异 → 最后用最小代价更新到真实界面。也就是:UI = f(state)

这个理念可以拆成三点心智模型:

  • 响应式编程:只要状态(state)变了,React 会负责「重新算 UI + 更新到界面」,不需要手动操作 DOM。
  • 组件化:页面拆成一个个组件,每个组件内部有自己的状态和逻辑,对外只暴露 props,方便复用和维护。
  • 单向数据流:数据从父组件单向流向子组件;子组件要影响父组件,只能通过回调把「意图」往上抛,这让数据流向更可预测、方便调试。
阅读全文 »

process.env

processNode.js 中的 一个全局变量,提供来有关当前 Node.js 进程的信息并对其进行控制。而process 中的 env 则是返回包含用户环境的对象。可以通过 process.env 拿到当前项目运行环境的信息。

设置环境变量

通过cli的方式进行设置
1
2
// index.js中
console.log(process.env.PROT)

Windows (cmd.exe)

1
set PROT=10086 && node index.js

Linux, macOS (Bash)

1
PROT=10086 node index.js
阅读全文 »

代码自动格式化工具:prettier

配置过程:https://prettier.io/docs/en/precommit.html

yarn add --dev --exact prettier

新建.prettierrc.json,.prettierignore文件

设置git commit预提交钩子

npx mrm@2 lint-staged

规范git commit工具:commitlint

配置过程:https://github.com/conventional-changelog/commitlint

yarn add --save-dev @commitlint/config-conventional @commitlint/cli

新建 commitlint.config.js文件,引入规则module.exports = {extends: [‘@commitlint/config-conventional’]}

阅读全文 »

背景:

  1. 希望利用 css 变量实现 dark 和 light 模式的切换
  2. 原有的工程都是 less 形式定义的 css,并且还有 less 的函数,比如 fade 等,不想手动改 less 的函数,希望该插件能支持解析 less 函数
  3. 需要支持局部不切换模式,比如某个区域是固定的 light 模式

步骤

第一步:less 变量转换成 css 变量

这一步比较简单,less 已经提供了字段用于转换,只需要添加一个配置项就可以,就是globalVars属性。

可以查看example 代码参考

1
2
3
4
5
6
{
loader: 'less-loader',
options: {
globalVars: LessGlobalCSSVars,
}
}

LessGlobalCSSVars大概长这个样子

1
2
3
4
5
{
"bg-body": "var(--bg-body)",
"static-white": "var(--static-white)",
...
}
阅读全文 »

React 性能优化思路

我觉得React 性能优化的理念的主要方向就是这两个:

  1. 减少重新 render 的次数。因为在 React 里最重(花时间最长)的一块就是 reconciliation(简单的可以理解为 diff),如果不 render,就不会 reconciliation。
  2. 减少计算的量。主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用。

在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdatePureComponent,这两个 API 所提供的解决思路都是为了减少重新 render 的次数,主要是减少父组件更新而子组件也更新的情况。

但是在函数式组件里面没有声明周期也没有类,那如何来做性能优化呢?

先分个类,组件什么时候会重新执行?

  1. 组件自己的状态改变
  2. 父组件重新渲染,导致子组件重新渲染,但是父组件的 props 没有改变
  3. 父组件重新渲染,导致子组件重新渲染,但是父组件传递的 props 改变

针对第二点,在FC中,可以通过memo减少rerender

1
2
3
4
function Component(props) {
/* 使用 props 渲染 */
}
const MyComponent = React.memo(Component);

通过 React.memo 包裹的组件在 props 不变的情况下,这个被包裹的组件是不会重新渲染的(相当于PureComonent)

默认情况下其只会对 props 的复杂对象做浅层对比(浅层对比就是只会对比前后两次 props 对象引用是否相同,不会对比对象里面的内容是否相同),如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

阅读全文 »

面向对象

类的修饰符

  • public:无访问限制
  • protected:仅当前类中的方法或子类中的方法能访问
  • private: 仅当前类的方法能访问,子类和外部都不行

类的继承

可以使用 extends 复用父类的逻辑和属性

抽象类

  • abstract关键字修饰的类,不能直接实例化,只能被子类继承;
  • 抽象类中用abstract修饰的方法,只有方法签名,无函数体,子类必须实现;
  • 核心作用是统一子类的接口规范,同时复用一些通用逻辑

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 抽象类:用 abstract 修饰,不能直接实例化
abstract class Shape {
// 普通属性:可复用(有具体值)
public color: string;

constructor(color: string) {
this.color = color;
}

// 普通方法:有具体实现,子类可直接复用
public showColor() {
console.log(`这个图形的颜色是:${this.color}`);
}

// 抽象方法:只有签名,无函数体,子类必须实现
abstract getArea(): number; // 计算面积
abstract getPerimeter(): number; // 计算周长
}

// 子类:圆形(继承抽象类 Shape)
class Circle extends Shape {
// 子类独有的属性
private radius: number;

constructor(color: string, radius: number) {
// 调用父类构造函数(抽象类也有构造函数)
super(color);
this.radius = radius;
}

// 必须实现抽象方法1:计算圆的面积
getArea(): number {
return Math.PI * this.radius **2;
}

// 必须实现抽象方法2:计算圆的周长
getPerimeter(): number {
return 2 * Math.PI * this.radius;
}
}

类实现接口

关键字implements:遵守规范,约束类的实现

接口 VS 抽象类

  • 接口interface:仅定义规范,无任何逻辑,class可以implements多个接口
  • 抽象类:既定义规范(抽象方法),又提供可复用逻辑(普通方法)

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 接口:仅定义“契约”(属性/方法签名),无具体实现
interface Flyable {
speed: number;
fly(): void; // 只定义方法签名,无函数体
}

// 类:implements 实现 Flyable 接口
class Bird implements Flyable {
// 必须实现接口的所有属性
speed: number;

constructor(speed: number) {
this.speed = speed;
}

// 必须实现接口的所有方法(函数体自己写)
fly() {
console.log(`鸟以 ${this.speed} km/h 的速度飞行`);
}
}

// 使用
const sparrow = new Bird(30);
sparrow.fly(); // 鸟以 30 km/h 的速度飞行

泛型类

在类名后用 声明 “类型变量”,类的属性、方法、构造函数可使用这个类型变量,实例化时再传入具体类型

类型收窄

类型收窄:通过代码逻辑(如条件判断)让 TS 自动推导变量的更具体类型(比如从 unknown 收窄到 string,从 string | number 收窄到 string),本质是缩小类型范围

方法 适用场景 示例
typeof 原始类型(string/number/boolean…) if (typeof x === "string") { x.length }
instanceof 类/构造函数创建的对象(Array、Date、自定义类) if (x instanceof Array) { x.push(1) }
in 对象属性存在判断,区分不同对象类型 if ("name" in x) { console.log(x.name) }
等值判断 字面量、联合类型(=== / !==) if (x === "admin") { ... }
类型守卫函数 自定义接口/复杂对象 function isUser(x: unknown): x is User { ... }
真值判断 排除 null/undefined/假值 if (x) { x.doSomething() }

自定义类型守卫函数

类型守卫函数:自定义收窄逻辑
通过返回 value is Type 的自定义函数(类型守卫),TS 会按照函数逻辑收窄类型(最灵活的场景)。

1
2
3
4
5
6
7
8
9
10
11
12
// 定义类型守卫函数:判断是否为数字
function isNumber(value: unknown): value is number {
return typeof value === "number" && !isNaN(value);
}

// 初始类型:unknown
let val: unknown = 123;

// 使用类型守卫
if (isNumber(val)) {
console.log(val * 2); // ✅ 收窄为 number
}

更好的类型收窄

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
interface User {
name: string;
age: number;
occupation: string;
}

interface Admin {
name: string;
age: number;
role: string;
}

export type Person = User | Admin;

export const persons: Person[] = [
{
name: 'Max Mustermann',
age: 25,
occupation: 'Chimney sweep'
},
{
name: 'Jane Doe',
age: 32,
role: 'Administrator'
},
{
name: 'Kate Müller',
age: 23,
occupation: 'Astronaut'
},
{
name: 'Bruce Willis',
age: 64,
role: 'World saver'
}
];

export function logPerson(person: Person) {
let additionalInformation: string;
if (person.role) { // ❌ 应该改为 'role' in person来判断,这种方式下ts才会组做类型收窄
additionalInformation = person.role;
} else {
additionalInformation = person.occupation;
}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}

persons.forEach(logPerson);

特殊类型

交叉类型(&)

简单理解就是合并两个属性

  • 若属性是基本类型:结果为两个类型的交叉(通常是 never)
  • 若属性是对象类型:会递归合并该属性的类型

联合类型(|)

允许一个变量是多种类型中的一种

never类型

never 代表永远不会有返回值、永远不会被执行到 或 永远无法匹配的类型,是 TS 中 “空” 的极致表达。

  1. 场景:抛出错误的函数:永远不会正常返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function throwError(message: string): never {
    throw new Error(message);
    // 后面的代码永远执行不到,返回值类型是 never
    }

    // 2. 无限循环的函数:永远不会结束
    function infiniteLoop(): never {
    while (true) {}
    }
  2. 场景:联合类型的 “空子集”

    1
    2
    3
    4
    5
    // 1. 互斥类型交叉 → never(没有值能同时是 string 和 number)
    type Impossible = string & number; // never

    // 2. 空联合类型 → never
    type EmptyUnion = never | never; // never

unknown类型

unknown 类型可以理解成是一个更安全的 any 类型,任何类型可赋值给 unknown
与 any 不同的是,在将 unknown 类型赋值给其他类型之前,必须先进行类型检查或类型断言

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 模拟接口请求函数(返回值类型未知)
async function fetchData(url: string): Promise<unknown> {
const res = await fetch(url);
return res.json(); // 响应体可能是任意类型
}

// 安全处理返回值
async function handleData() {
const data = await fetchData("/api/user");

// 第一步:判断是否是对象(排除 null)
if (typeof data === "object" && data !== null) {
// 第二步:判断是否包含 name 属性
if ("name" in data && typeof (data as { name: unknown }).name === "string") {
// 安全访问 name 属性
console.log("用户名:", (data as { name: string }).name);
}

// 第三步:判断是否包含 age 属性
if ("age" in data && typeof (data as { age: unknown }).age === "number") {
console.log("年龄:", (data as { age: number }).age);
}
}
}

映射类型

基本语法:{ [K in Keys]: T }
其实就类似 js 中的 for in语句,用于遍历 Keys 中的所有类型

比较常见的语法

1
2
3
4
5
6
{ [ P in K ] : T }
{ [ P in K ] ?: T }
{ [ P in K ] -?: T }
{ readonly [ P in K ] : T }
{ readonly [ P in K ] ?: T }
{ -readonly [ P in K ] ?: T }

利用映射类型实现的utility type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};

interface Person {
   name: string;
   age: number;
   location: string;
}

type LazyPerson = Getters<Person>;
// {
//   getName: () => string;
//   getAge: () => number;
//   getLocation: () => string;
// }

条件类型

基本语法:T extends U ? X : Y

常见用法:

1
2
3
4
5
6
type IsString<T> = T extends string ? true : false;

type I0 = IsString<number>;  // false
type I1 = IsString<"abc">;  // true
type I2 = IsString<any>;  // boolean
type I3 = IsString<never>;  // never

特殊关键字

extends关键字

主要使用场景

  • 类继承
  • 接口继承
  • 泛型约束
  • 条件类型

泛型约束使用场景:

1
2
3
4
5
6
7
8
9
10
11
12
// 约束 K 必须是 T 的键(keyof T 是 T 所有键的联合类型)
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

const product = { name: "手机", price: 2999, stock: 100 };
// ✅ K 是 "price"(属于 keyof product)
getProperty(product, "price"); // 2999

// ❌ K 是 "color"(不属于 keyof product)
getProperty(product, "color");
// 报错:Argument of type '"color"' is not assignable to parameter of type '"name" | "price" | "stock"'

infer关键字

infer = 类型层面的 “变量”,仅能在 T extends U ? X : Y 这种条件类型中使用
作用是「捕获 / 提取」复杂类型的某一部分(比如函数返回值、数组元素类型),并给这部分类型起一个变量名供后续使用

提取数组元素类型
1
2
type ArrayItem<T> = T extends (infer U)[] ? U : never;
// 这里T extends (infer U)[] ? U : never;的意思是,如果T是某个待推断类型的数组,则返回推断的类型,否则返回never
提取函数返回值类型
1
2
3
4
5
type GetReturn<T> = T extends (...args: any[]) => infer R ? R : never;

// 使用
type Fn = () => { name: string; age: number };
type FnReturn = GetReturn<Fn>; // { name: string; age: number }
React 中infer的使用

useReducer 举例,如果我们这样使用 useReducer

1
2
3
const reducer = (x: number) => x + 1;
const [state, dispatch] = useReducer(reducer, '');
// Argument of type "" is not assignable to parameter of type 'number'.

这里useReducer会报一个类型错误,说””不能赋值给number类型

那React这里是如何通过reducer函数的类型来判断state的类型的?

直接看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
function useReducer<R extends Reducer<any, any>, I>(
 reducer: R,
 // ReducerState 推断类型
 initializerArg: I & ReducerState<R>,
 initializer: (arg: I & ReducerState<R>) => ReducerState<R>
): [ReducerState<R>, Dispatch<ReducerAction<R>>];

// infer推断
type ReducerState<R extends Reducer<any, any>> = R extends Reducer<infer S, any>
 ? S
: never;
// Reducer类型
type Reducer<S, A> = (prevState: S, action: A) => S;

in关键字

特殊操作符

! 非空断言操作符

+- 修饰符

Ts challenge

字段全改字符串

1
2
3
4
5
6
7
8
type Demo1 = {
a: number;
b: number;
}

type NewDemo1 = {
[P in keyof Demo1]: string;
}

字段全改函数,函数返回值是字段原本类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Demo1 = {
name: number;
age: string;
}

type NewDemo1 = {
[P in keyof Demo1]: () => Demo1[P];
}

// 改成 {getName: () => number, getAge: () => string} 这种形式
type NewDemo1 = {
[P in keyof Demo1 as `get${Capitalize<P>}`]: () => Demo1[P];
}

实现一个Optional(把一个类型里的部分字段变为可选)

1
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

实现一个GetOptional(得到某个类型里面所有可选字段)

1
type GetOptional<T> = [P in keyof T]

只保留对象类型中值为 string 类型的键值对

1
2
3
type PickStringValues<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K]
}

TypeScript简介

  1. TypeScript是JavaScript的超集。
  2. 它对JS进行了扩展,向JS中引入了类型的概念,并添加了许多新的特性。
  3. TS代码需要通过编译器编译为JS,然后再交由JS解析器执行。
  4. TS完全兼容JS,换言之,任何的JS代码都可以直接当成JS使用。
  5. 相较于JS而言,TS拥有了静态类型,更加严格的语法,更强大的功能;TS可以在代码执行前就完成代码的检查,减小了运行时异常的出现的几率;TS代码可以编译为任意版本的JS代码,可有效解决不同JS运行环境的兼容问题;同样的功能,TS的代码量要大于JS,但由于TS的代码结构更加清晰,变量类型更加明确,在后期代码的维护中TS却远远胜于JS。

TypeScript 开发环境搭建

下载Node.js
安装Node.js
使用npm全局安装typescript
  • 进入命令行
  • 输入:npm i -g typescript
创建一个ts文件
使用tsc对ts文件进行编译
  • 进入命令行
  • 进入ts文件所在目录
  • 执行命令:tsc xxx.ts
阅读全文 »

useEffect

执行副作用,一般情况下会在浏览器绘制页面后异步调用

useEffect() 的用途

只要是副效应,都可以使用useEffect()引入。它的常见用途有下面几种。

  • 获取数据(data fetching)
  • 事件监听或订阅(setting up a subscription)
  • 改变 DOM(changing the DOM)
  • 输出日志(logging)

useEffect返回值

副效应是随着组件加载而发生的,那么组件卸载时,可能需要清理这些副效应。

useEffect()允许返回一个函数,在组件卸载时,执行该函数,清理副效应。如果不需要清理副效应,useEffect()就不用返回任何值。

1
2
3
4
5
6
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
}, [props.source]);

上面例子中,useEffect()在组件加载时订阅了一个事件,并且返回一个清理函数,在组件卸载时取消订阅。

实际使用中,由于副效应函数默认是每次渲染都会执行,所以清理函数不仅会在组件卸载时执行一次,每次副效应函数重新执行之前,也会执行一次,用来清理上一次渲染的副效应。

阅读全文 »

Redux

设计思想

  • (1)Web 应用是一个状态机,视图与状态是一一对应的。
  • (2)所有的状态,保存在一个对象里面。

Action

  1. Action 就是 View 发出的通知,表示 State 应该要发生变化了。

  2. Action 是一个对象,其中的type属性是必须的,表示 Action 的名称,Action 描述当前发生的事情。

  3. 改变 State 的唯一办法,就是使用 Action。

可以定义一个函数来生成 Action,这个函数就叫 Action Creator,示例如下

1
2
3
4
5
6
7
8
9
10
const ADD_TODO = '添加 TODO';

function addTodo(text) {
return {
type: ADD_TODO,
text
}
}

const action = addTodo('Learn Redux');
阅读全文 »

Chrome浏览器架构

Chrome 采用多进程架构,其顶层存在一个 Browser process 用以协调浏览器的其它进程。
image-20210930012419754

Browser Process

  • 子进程的管理(网络、渲染、GPU)
  • 负责包括地址栏,书签栏,前进后退按钮等部分的工作;
  • 负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问;

Network Service

  • 负责加载网络资源。网络进程内部会启动多个线程来处理不同的网络任务

Renderer Process

  • 负责一个 tab 内关于网页呈现的所有事情,进程启动后,会开启一个渲染主线程,负责执行HTML、CSS、JavaScript代码

Plugin Process

  • 负责控制一个网页用到的所有插件,如 flash

GPU Process

  • 负责处理 GPU 相关的任务

由于一个tab标签页都有一个独立的渲染进程,所以一个tab异常崩溃后,其他tab不会受到影响。

一个渲染进程包括

  • 主线程
  • HTTP请求线程
  • 定时触发线程
  • 事件触发线程
  • GUI线程

渲染进程中的主线程是如何工作的

包括但不限于

  • 解析HTML
  • 解析CSS
  • 计算样式
  • 布局
  • 处理图层
  • 每秒把页面绘制60次
  • 执行全局js代码
  • 执行事件处理函数
  • 执行计时器回调函数

浏览器JS异步执行原理

  1. 所有任务都在主线程上执行,形成一个执行栈。
  2. 主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
  3. 一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
  4. 主线程不断重复上面的第三步。
阅读全文 »
0%