语法拾贝 (JavaScript)

ES6

1. 解构与改名

1
const { a: a1 } = obj;

2. 数组去重

1
const c = [...new Set([...a, ...b])];

3. 数组扁平化

1
arr.flat(Infinity);

4. ?? or ||

?? 仅在左侧为 null, undefined 时跳到右侧
||0, '', NaN, null, undefined(这五个称为假值)

* [] {} 的值为 true

* ?? 推荐和 ?. 共用

typescript

一、操作符

1. void

作为函数返回值类型,表示不关注返回值类型,可以是任意值

2. 非空断言 !

1
2
3
// 表达式后缀
obj!.a;
func!();

使用场景:ref

3. 键值获取 keyof

1
2
3
4
5
type Person = {
name: string;
age: number;
}
type PersonKey = keyof Person; // 'name' | 'age'

使用场景:遍历一个对象的所有 key 时(拿不到类型时可以用 keyof typeof)

1
2
3
(Object.keys(params) as (keyof feedbackParams)[]).forEach((key) => {
formData.append(key, params[key]);
});

4. 联合类型 | 交叉类型 &

不是数学上的交集并集!

& 交叉类型:产生的新集合包含原各集合的所有属性(语义上的“且”)
| 联合类型:产生的新集合是一个 select,可以是 A,也可以是 B,但不能同时拥有 A 和 B (语义上的“或”)

使用场景:继承

二、泛型工具

Partial

将泛型中全部属性变为可选的。

Required

将泛型中全部属性变为必选的。

Record<K, T>

常用于定义对象。 Record<string, unknown>

Pick<T, K>

取键值对。
Pick<Animal, "name" | "age">

Omit<T, K>

去键值对。
Omit<Animal, 'name'|'age'>

三、Work with React

1. 声明函数式组件

这种方式会在 props 里显式声明 children

1
2
3
4
5
6
const App: React.FC<AppProps> = ({ message, children }) => (
<div>
{message}
{children}
</div>
)

2. 使用 typeof 减少冗余的 props 类型导出

1
2
import { Recent } from '@mercury/component'
type RecentProps = React.ComponentProps<typeof Recent>

3. React 事件类型定义

1
2
3
4
5
6
7
8
9
type Props = {
onClick: (event: React.MouseEvent<HTMLInputElement>) => void
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
onkeypress: (event: React.KeyboardEvent<HTMLInputElement>) => void
onBlur: (event: React.FocusEvent<HTMLInputElement>) => void
onFocus: (event: React.FocusEvent<HTMLInputElement>) => void
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
onClickDiv: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
}

对应的 handler 类型

1
2
3
type ChangeEventHandler<T = Element> = EventHandler<React.ChangeEvent<T>>
type KeyboardEventHandler<T = Element> = EventHandler<React.KeyboardEvent<T>>
type MouseEventHandler<T = Element> = EventHandler<React.MouseEvent<T>>

4. 类型断言/类型守卫

  1. 类型断言
1
2
值 as 类型
<类型>值
  • 联合类型→单一类型
  • 父类→子类
  • any→类型
  1. 类型守卫:通过 if 自动推断类型。
  • 类型判断:typeof 基本类型
  • 实例判断:instanceof 类(非接口)
  • 属性判断:字段 in 接口(所实现的实例) in 其实是 js 自带语法,应用在实例上
  • 字面量相等判断:==, ===, !=, !==,适用于枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const input1: string | number;
if (typeof input1 == 'string') {
// 这里 input1 的类型「收紧」为 string
}

class A {};
class B {};
const input2: A | B;
if (input2 instanceof A) {
// 这里 input2 的类型「收紧」为 A
}

interface Foo {
foo: string;
}
interface Bar {
bar: string;
}
const input3: Foo | Bar;
if ('foo' in input3) {
// 这里 input3 的类型「收紧」为 Foo
}

自定义类型守卫函数:代码随便写,返回值保证是参数 is 类型

1
2
3
4
function isBatman (man: any): man is Batman {
// 写各种判断
return man && man.helmet && man.cloak;
}

5. useEffect的時機

每次组件渲染后都执行:

1
useEffect(() => {});

只会在组件首次渲染后执行一次:

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

6. 我就是不想單獨聲明類型!

看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type BasicInfo = {
accountMembers: Array<{
micComId: string;
companyNameCn: string;
establishYears: string;
capitalAmount: string;
mainCategory: string;
mainCategoryYearAvgInvest: string;
portraitUrl: string;
smtCaseUrl: string;
advancedMember: boolean;
}>;
};

// 這是一個數組
type accountMembers = BasicInfo["accountMembers"];

// 從數組裡抽出類型
type accountMember = BasicInfo["accountMembers"][number];

// 获取函数的第一个参数的类型
type UpdateArchiveDataType = Parameters<typeof updateArchive>[0];

Trick

if 条件太长时

1
2
3
4
5
6
7
8
if(
type == 1 ||
type == 2 ||
type == 3 ||
type == 4 ||
){
//...
}

1
2
3
4
const condition = [1, 2, 3, 4];
if (condition.includes(type)) {
//...
}

升降 CSS 优先级

内联 > ID > 类/伪类/属性 > 元素/伪元素

  • (升优先级)自我重复,提高选择器的优先级:.{className}.{className}
  • (降优先级)属性选择器 [id='{targetId}'] 替代 #{targetId} 以获得与 .{className} 相同的优先级
  • 优先级是权重相加制,更具体的选择器拥有更高的优先级

import type がウザい!

1
import { type RecentProps } from '@mercury/component'

表單提交數組/對象數組

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
const params = {
clusterId: currentCluster.clusterId,
instanceIdList: [currentInstance.instanceId],
mavenInfoList: [currentPlugin],
};
const urlSearchParams = new URLSearchParams();
Object.keys(params).forEach((key) => {
// instanceIdList 和 mavenInfoList 都是数组,表单提交时需要特殊处理
if (key === "mavenInfoList") {
// 对象数组
params[key].forEach((item, index) => {
Object.keys(item).forEach((itemKey) => {
if (item[itemKey] === null) {
item[itemKey] = "";
}
urlSearchParams.append(`${key}[${index}].${itemKey}`, item[itemKey]);
});
});
} else if (key === "instanceIdList") {
// 纯数组
params[key].forEach((item, index) => {
urlSearchParams.append(`${key}[${index}]`, item);
});
} else {
urlSearchParams.append(key, params[key]);
}
});

巧用對象來去重

  1. 對象數組,根據某字段來去重
1
2
3
4
5
6
7
8
9
10
11
const newNodes = [];
// 将 newNodes 根据各项的customId去重
const obj = {};
newNodes.forEach((item) => {
obj[item.customId] = item;
});
const newNodes2 = [];
Object.keys(obj).forEach((key) => {
newNodes2.push(obj[key]);
});
// 得到 newNodes2 已去重
  1. 對象數組,獲取某字段的枚舉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const clusterTypeEnum = {};
const allCluster = [];
allCluster.forEach((item) => {
clusterTypeEnum[item.parKey] = { text: item.parKey };
});

// 可以得到形如這種的枚舉
{
"user-service": {
"text": "user-service"
},
"api-service": {
"text": "api-service"
},
"route": {
"text": "route"
}
}

复制到剪贴板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export const copyToClipboard = async (text) => {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
message.success("复制成功");
} else {
const textarea = document.createElement("textarea");
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
message.success("复制成功");
document.body.removeChild(textarea);
}
} catch (err) {
message.error("复制失败", err);
}
};

下载

使用axios

1
2
3
4
5
6
7
8
9
axios.get("XXX", { responseType: "blob" }).then((res) => {
const blob = new Blob([res.data]);
const downloadADom = document.createElement("a");
downloadADom.href = URL.createObjectURL(blob);
downloadADom.download = fileName;
downloadADom.target = "_blank";
downloadADom.click();
URL.revokeObjectURL(downloadADom.href);
});

上傳

URL操作

1
2
3
4
5
6
7
8
9
10
function addParamToUrl(url, paramName, paramValue) {
let myUrl = new URL(url);
myUrl.searchParams.set(paramName, paramValue);
return myUrl.href;
}
// 示例使用
const productUrl = "http://example.com/product";
const newUrl = addParamToUrl(productUrl, "tradeFrom", "3_1");

console.log(newUrl);

git

慎用 –hard!

回溯到某次提交

1
git reset XXX

撤回上一次提交

已经提交到远程了:

1
git revert HEAD

还没提交到远程,只是本地暂存了:

1
git reset XXX # XXX 为最近一次提交,不要用 --hard