Vue3源码之shared

远子 •  2021年01月21日

packages/shared 是工具包, 有很多工具方法, 这些方法大部分和 Vue 无耦合, 我们平时的项目里可以直接拿来用

一、概览

大概分成以下几类:

  • 枚举值:

    • PatchFlagNames
    • slotFlagsText
    • propsToAttrMap
  • 快捷:

    • EMPTY_ARR
    • EMPTY_OBJ
    • NO
    • NOOP
    • def
    • extend
    • generateCodeFrame
    • getGlobalThis
    • makeMap
  • 转换:

    • camelize
    • capitalize
    • escapeHtml
    • escapeHtmlComment
    • hyphenate
    • invokeArrayFns
    • looseEqual
    • looseIndexOf
    • normalizeClass
    • normalizeStyle
    • objectToString
    • parseStringStyle
    • remove
    • stringifyStyle
    • toDisplayString
    • toHandlerKey
    • toNumber
    • toRawType
    • toTypeString
  • 检测:

    • hasChanged
    • hasOwn
    • isArray
    • isBooleanAttr
    • isDate
    • isFunction
    • isGloballyWhitelisted
    • isHTMLTag
    • isIntegerKey
    • isKnownAttr
    • isMap
    • isModelListener
    • isNoUnitNumericStyleProp
    • isObject
    • isOn
    • isPlainObject
    • isPromise
    • isReservedProp
    • isSSRSafeAttrName
    • isSVGTag
    • isSet
    • isSpecialBooleanAttr
    • isString
    • isSymbol
    • isVoidTag
  • 其他:

    • babelParserDefaultPlugins

位运算实现枚举值

x << y 意思是 x 乘以 2 的 y 次方

// 方法1
export const enum PatchFlags {
  TEXT                = 1,
  CLASS               = 2,
  STYLE               = 3,
  PROPS               = 4,
  FULL_PROPS          = 5,
  HYDRATE_EVENTS      = 6,
  STABLE_FRAGMENT     = 7,
  KEYED_FRAGMENT      = 8,
  UNKEYED_FRAGMENT    = 9,
  NEED_PATCH          = 10,
  DYNAMIC_SLOTS       = 11,
  DEV_ROOT_FRAGMENT   = 12,
  HOISTED             = -1,
  BAIL                = -2
}

// 方法2
export const enum PatchFlags {
  TEXT                = 1,        // 1
  CLASS               = 1 << 1,   // 2
  STYLE               = 1 << 2,   // 4
  PROPS               = 1 << 3,   // 8
  FULL_PROPS          = 1 << 4,   // 16
  HYDRATE_EVENTS      = 1 << 5,   // 32
  STABLE_FRAGMENT     = 1 << 6,   // 64
  KEYED_FRAGMENT      = 1 << 7,   // 128
  UNKEYED_FRAGMENT    = 1 << 8,   // 256
  NEED_PATCH          = 1 << 9,   // 512
  DYNAMIC_SLOTS       = 1 << 10,  // 1024
  DEV_ROOT_FRAGMENT   = 1 << 11,  // 2048
  HOISTED             = -1,       // -1
  BAIL                = -2        // -2
}

思考一下, 为什么 Vue 用方法2实现?

答案是: 方法2可以做计算, 举个例子:

export const enum Action {
  Read(可读)          = 1,          // 1 : 001
  Write(可写)         = 1 << 1,     // 2 : 010
  Execute(可执行)     = 1 << 2      // 4 : 100
}

如果知道权限值, 就能反推出具体的权限:
1 代表: 可读
2 代表: 可写
3 代表: 可读、可写
4 代表: 可执行
5 代表: 可读、可执行
6 代表: 可写、可执行
7 代表: 可读、可写、可执行

判断 Promise

如果一个变量是 Object, 有 then 和 catch 方法, 就认为是 Promise

const isPromise = (val) => {
    return isObject(val) && isFunction(val.then) && isFunction(val.catch);
};

二、使用闭包缓存处理结果

例1

const HTML_TAGS = 'html,body,base,head,link,meta,style,title,address,article,aside,footer,' +
    'header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,' +
    'figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,' +
    'data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,' +
    'time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,' +
    'canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,' +
    'th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,' +
    'option,output,progress,select,textarea,details,dialog,menu,' +
    'summary,template,blockquote,iframe,tfoot';

function makeMap(str, expectsLowerCase) {
    const map = Object.create(null);
    const list = str.split(',');
    for (let i = 0; i < list.length; i++) {
        map[list[i]] = true;
    }
    return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val];
}

const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS);
const isHTMLTag = (val) => HTML_TAGS.includes(val);

例2

调用 camelize, hyphenate, capitalize 时, 都会缓存一份结果

const cacheStringFunction = (fn) => {
    const cache = Object.create(null);
    return ((str) => {
        const hit = cache[str];
        return hit || (cache[str] = fn(str));
    });
};

const camelizeRE = /-(\w)/g;
const camelize = cacheStringFunction((str) => {
    return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
});

const hyphenateRE = /\B([A-Z])/g;
const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());

const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));

(完)