【笔记】Vue3和React18源码片段1——shared
Apr 16, 2022笔记reactvueVue3 和 React18 源码片段1——shared
最近在整合框架时,又一次涉及到了 Vue/React 相关源码,也包括tarjs
、raxjs
、uni-app
等跨端 DSL,优秀的框架通常都会注重性能和代码执行细节,因此借此文/系列开始进行整理。
代码来源
- Vue3.2:https://github.com/vuejs/core/tree/3.2
- React18.0:https://github.com/facebook/react/releases/tag/v18.0.0
一、工具函数部分shared
Vue:packages/shared
类型判断
1 | // 稳妥的类型判断方式 |
数据信息判断
正整数字符串的判断:1
2
3
4
5
6// 正整数字符串的判断,主要排除NaN、Infinity、负数等边界
export const isIntegerKey = (key: unknown) =>
isString(key) &&
key !== 'NaN' &&
key[0] !== '-' &&
'' + parseInt(key, 10) === key
判断属性:1
2
3
4
5
6// 判断属性key是否为对象数据val本身属性
const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (
val: object,
key: string | symbol
): key is keyof typeof val => hasOwnProperty.call(val, key)
数据处理
对象拓展/合并属性:1
2// 用于拓展/合并属性。*Object.assign兼容移动端还好、PC IE11起步,可用Polyfill
export const extend = Object.assign
删除数组元素:1
2
3
4
5
6export const remove = <T>(arr: T[], el: T) => {
const i = arr.indexOf(el)
if (i > -1) {
arr.splice(i, 1)
}
}
字符串转数字:1
2
3
4
5// 处理了NaN的边界
export const toNumber = (val: any): any => {
const n = parseFloat(val)
return isNaN(n) ? val : n
}
新旧值比较:1
2
3
4// oldValue === oldValue主要考虑了NaN
// compare whether a value has changed, accounting for NaN.
export const hasChanged = (value: any, oldValue: any): boolean =>
value !== oldValue && (value === value || oldValue === oldValue)
处理HTML转义: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
49
50
51
52
53
54
55
56/**
* @from escapeHtml.ts
*/
const escapeRE = /["'&<>]/
function escapeHtml(string) {
const str = '' + string
const match = escapeRE.exec(str) // 匹配值将进行转义替换
if (!match) {
return str
}
let html = ''
let escaped
let index
let lastIndex = 0
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) { // 根据 Unicode 编码进行判断
case 34: // "
escaped = '"'
break
case 38: // &
escaped = '&'
break
case 39: // '
escaped = '''
break
case 60: // <
escaped = '<'
break
case 62: // >
escaped = '>'
break
default:
continue
}
if (lastIndex !== index) {
html += str.substring(lastIndex, index)
}
lastIndex = index + 1
html += escaped
}
return lastIndex !== index ? html + str.substring(lastIndex, index) : html
}
// https://www.w3.org/TR/html52/syntax.html#comments
const commentStripRE = /^-?>|<!--|-->|--!>|<!-$/g
function escapeHtmlComment(src) {
return src.replace(commentStripRE, '') // 删除html注释
}
数据比较: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
49
50
51
52
53
54
55
56
57
58
59
60
61
62/**
* @from looseEqual.ts
*/
import { isArray, isDate, isObject } from './'
function looseCompareArrays(a: any[], b: any[]) {
if (a.length !== b.length) return false
let equal = true
for (let i = 0; equal && i < a.length; i++) {
equal = looseEqual(a[i], b[i])
}
return equal
}
// 比较严格的比较
export function looseEqual(a: any, b: any): boolean {
if (a === b) return true
let aValidType = isDate(a)
let bValidType = isDate(b)
// Date数据类型比较
if (aValidType || bValidType) {
return aValidType && bValidType ? a.getTime() === b.getTime() : false
}
aValidType = isArray(a)
bValidType = isArray(b)
// Array数据类型比较
if (aValidType || bValidType) {
return aValidType && bValidType ? looseCompareArrays(a, b) : false
}
aValidType = isObject(a)
bValidType = isObject(b)
// Object数据类型比较
if (aValidType || bValidType) {
/* istanbul ignore if: this if will probably never be called */
if (!aValidType || !bValidType) {
return false
}
const aKeysCount = Object.keys(a).length
const bKeysCount = Object.keys(b).length
if (aKeysCount !== bKeysCount) {
return false
}
for (const key in a) {
const aHasKey = a.hasOwnProperty(key)
const bHasKey = b.hasOwnProperty(key)
if (
(aHasKey && !bHasKey) ||
(!aHasKey && bHasKey) ||
!looseEqual(a[key], b[key])
) {
return false
}
}
}
// NaN
return String(a) === String(b)
}
export function looseIndexOf(arr: any[], val: any): number {
return arr.findIndex(item => looseEqual(item, val))
}
React:packages/shared
React 的 shared 模块更为松散。
类型判断
1 | /** |
1 | /** |
数据信息判断
属性判断:1
2
3
4
5
6
7/**
* @from hasOwnProperty.js
*/
const hasOwnProperty = Object.prototype.hasOwnProperty;
export default hasOwnProperty;
环境信息判断
是否为DOM环境:1
2
3
4
5
6
7
8
9/**
* @from ExecutionEnvironment.js
*/
export const canUseDOM: boolean = !!(
typeof window !== 'undefined' &&
typeof window.document !== 'undefined' &&
typeof window.document.createElement !== 'undefined'
);
数据处理
对象拓展/合并:1
2
3
4
5
6
7
8/**
* @from assign.js
*/
// 跟Vue中extend函数一样
const assign = Object.assign;
export default assign;
数据比较:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
* @from objectIs.js
*/
// 算是Object.is的polyfill
function is(x: any, y: any) {
return (
// 后面的比较主要区分+0和-0
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}
// Object.is iOS9/安卓5起支持,IE不兼容
const objectIs: (x: any, y: any) => boolean =
typeof Object.is === 'function' ? Object.is : is;
export default objectIs;
浅比较: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
49
50
51/**
* @from shallowEqual.js
*/
import is from './objectIs';
import hasOwnProperty from './hasOwnProperty';
// 浅比较
/**
* Performs equality by iterating through keys on an object and returning false
* when any key has values which are not strictly equal between the arguments.
* Returns true when the values of all keys are strictly equal.
*/
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
// 根据键数量先排除一波
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
const currentKey = keysA[i];
if (
!hasOwnProperty.call(objB, currentKey) ||
!is(objA[currentKey], objB[currentKey])
) {
return false;
}
}
return true;
}
export default shallowEqual;
二、标识处理
Vue:Patch flag
Vue3的静态标记是diff性能优化的一个手段、主要是在与上次 virtual DOM 比较时,只对比带有Patch Flag的节点
1 | // @from patchFlags.ts |
React:Symbol Flag
React16起,源码中的大多判断处理就依赖与Symbol常量
1 | // @from ReactSymbols.js |
1 | // @from ReactTypes.js |
从 shared
包中就不难发现,shared
承担高频工具函数、标识和通用处理,也是整个框架中至关重要的代码。
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com