Skip to content

46.Vue 中使用了哪些设计模式?

1.单例模式

单例模式就是整个程序中有且仅有一个实例 (pinia 中的某一个 store)

js
import { useCounterStore } from "./counterStore.js";
// 组件 1
const counterStore1 = useCounterStore();
// 组件 2
const counterStore2 = useCounterStore();
// 在组件1和组件2中使用的是同一个存储实例
import { useCounterStore } from "./counterStore.js";
// 组件 1
const counterStore1 = useCounterStore();
// 组件 2
const counterStore2 = useCounterStore();
// 在组件1和组件2中使用的是同一个存储实例

store 实现原理

js
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
  // ...
  if (!pinia._s.has(id)) {
    if (isSetupStore) { // 缓存实例
      createSetupStore(id, setup, options, pinia)
    } else {
      createOptionsStore(id, options as any, pinia)
    }
  }
  const store: StoreGeneric = pinia._s.get(id)!
  return store as any
}
function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
  // ...
  if (!pinia._s.has(id)) {
    if (isSetupStore) { // 缓存实例
      createSetupStore(id, setup, options, pinia)
    } else {
      createOptionsStore(id, options as any, pinia)
    }
  }
  const store: StoreGeneric = pinia._s.get(id)!
  return store as any
}

2.工厂模式

传入参数即可创建实例 (createComponentInstance)

js
export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  const instance: ComponentInternalInstance = {
    vnode,
    parent,
    suspense,
    // ...
  };
  return instance;
}
export function createComponentInstance(
  vnode: VNode,
  parent: ComponentInternalInstance | null,
  suspense: SuspenseBoundary | null
) {
  const instance: ComponentInternalInstance = {
    vnode,
    parent,
    suspense,
    // ...
  };
  return instance;
}

3.发布订阅模式

订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码。(on、emit)

vue
<!-- 事件绑定 -->
<MyComponent @myFn="a" @myFn="b"></MyComponent>
<!-- 触发事件 emit("myFn") -->
<!-- 事件绑定 -->
<MyComponent @myFn="a" @myFn="b"></MyComponent>
<!-- 触发事件 emit("myFn") -->
js
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_MyComponent = _resolveComponent("MyComponent");
  return (
    _openBlock(),
    _createBlock(
      _component_MyComponent,
      {
        onMyFn: [_ctx.a, _ctx.b], // 订阅
      },
      null,
      8
    )
  );
}
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_MyComponent = _resolveComponent("MyComponent");
  return (
    _openBlock(),
    _createBlock(
      _component_MyComponent,
      {
        onMyFn: [_ctx.a, _ctx.b], // 订阅
      },
      null,
      8
    )
  );
}

4.代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。(toRef)

js
class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true

  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}
  // ref.value.key -> 访问原始 object.key
  get value() {
    const val = this._object[this._key]
    return val === undefined ? this._defaultValue! : val
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }

  get dep(): Dep | undefined {
    return getDepFromReactive(toRaw(this._object), this._key)
  }
}
class ObjectRefImpl<T extends object, K extends keyof T> {
  public readonly __v_isRef = true

  constructor(
    private readonly _object: T,
    private readonly _key: K,
    private readonly _defaultValue?: T[K]
  ) {}
  // ref.value.key -> 访问原始 object.key
  get value() {
    const val = this._object[this._key]
    return val === undefined ? this._defaultValue! : val
  }

  set value(newVal) {
    this._object[this._key] = newVal
  }

  get dep(): Dep | undefined {
    return getDepFromReactive(toRaw(this._object), this._key)
  }
}

5.中介者模式

中介者是一个行为设计模式,通过提供一个统一的接口让系统的不同部分进行通信。 (pinia)

6.外观模式

提供了统一的接口,用来访问子系统中的一群接口,以便隐藏底层复杂性。(baseCompile)

js
// 可以粗略理解成外观模式 (主要目标是编译而不是隐藏复杂性,但抽象了底层的编译细节)
export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  // ...
  // 1.编译
  const ast = isString(template) ? baseParse(template, options) : template;
  const [nodeTransforms, directiveTransforms] =
    getBaseTransformPreset(prefixIdentifiers);
  // 2.转化
  transform(
    ast,
    extend({}, options, {
      prefixIdentifiers,
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []), // user transforms
      ],
      directiveTransforms: extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {} // user transforms
      ),
    })
  );
  // 3.生成代码
  return generate(
    ast,
    extend({}, options, {
      prefixIdentifiers,
    })
  );
}
// 可以粗略理解成外观模式 (主要目标是编译而不是隐藏复杂性,但抽象了底层的编译细节)
export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  // ...
  // 1.编译
  const ast = isString(template) ? baseParse(template, options) : template;
  const [nodeTransforms, directiveTransforms] =
    getBaseTransformPreset(prefixIdentifiers);
  // 2.转化
  transform(
    ast,
    extend({}, options, {
      prefixIdentifiers,
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []), // user transforms
      ],
      directiveTransforms: extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {} // user transforms
      ),
    })
  );
  // 3.生成代码
  return generate(
    ast,
    extend({}, options, {
      prefixIdentifiers,
    })
  );
}

7.装饰模式

Vue2装饰器的用法 (对功能进行增强 @) 古老写法

js
import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA: number | undefined
  @Prop({ default: 'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC: string | boolean | undefined
}
import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA: number | undefined
  @Prop({ default: 'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC: string | boolean | undefined
}

8.策略模式

策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案。 (Vue2中mergeOptions)

js
function mergeField(key: any) {
  const strat = strats[key] || defaultStrat;
  options[key] = strat(parent[key], child[key], vm, key);
}
return options;

// ...
strats.data = fn1;
strats[hook] = fn2;
strats[type + "s"] = fn3;
strats.watch = fn4;
strats.props = strats.methods = strats.inject = strats.computed = fn5;
strats.provide = fn6;
function mergeField(key: any) {
  const strat = strats[key] || defaultStrat;
  options[key] = strat(parent[key], child[key], vm, key);
}
return options;

// ...
strats.data = fn1;
strats[hook] = fn2;
strats[type + "s"] = fn3;
strats.watch = fn4;
strats.props = strats.methods = strats.inject = strats.computed = fn5;
strats.provide = fn6;

9.观察者模式

Vue2 中watcher&dep的关系,无需用户主动触发更新,状态变化时会自动更新。

js
export default class Dep {
  addSub(sub: DepTarget) {  // 访问属性时, 订阅对应的watcher
    this.subs.push(sub)
  }
  notify(info?: DebuggerEventExtraInfo) { // 属性变化时,发布对应的watcher
    const subs = this.subs.filter(s => s) as DepTarget[]
    for (let i = 0, l = subs.length; i < l; i++) {
      const sub = subs[i]
      sub.update()
    }
  }
}
export default class Dep {
  addSub(sub: DepTarget) {  // 访问属性时, 订阅对应的watcher
    this.subs.push(sub)
  }
  notify(info?: DebuggerEventExtraInfo) { // 属性变化时,发布对应的watcher
    const subs = this.subs.filter(s => s) as DepTarget[]
    for (let i = 0, l = subs.length; i < l; i++) {
      const sub = subs[i]
      sub.update()
    }
  }
}
js
export default class Watcher implements DepTarget {
  //...更新逻辑
  update() {
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  }
}
export default class Watcher implements DepTarget {
  //...更新逻辑
  update() {
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  }
}

...

Released under the MIT License.