Vue3写一个面向对象插件系列1(使用篇)

背景

创作背景纯粹是因为本人喜欢面向对象风格,并不是说其他风格不好,无意挑起风格流之争,不喜欢的道友求放过,我的目标是兼容现有的vue3的项目,也就是说可以在现有的项目中也可以使用。

使用

用面向对象风格实现vue官网的鼠标位置实时跟踪案例,代码如下:

@Component({
  template: `
    <div>{{ x }}</div>
    <div>{{ y }}</div>
  `
})
export default class AngularDemo implements LifecycleHook {
  x = ref(0);
  y = ref(0);

  private update = (event: MouseEvent) => {
    this.x.value = event.x;
    this.y.value = event.y;
  };

  onMounted(): void {
    window.addEventListener("mousemove", this.update);
  }

  onUnmounted(): void {
    window.removeEventListener("mousemove", this.update);
  }
}

上述代码中onMounted,onUnmounted是生命周期钩子,命名与vue3保持一致。
如果不想把方法属性暴露出去可以使用private修饰
LifecycleHook 完整签名如下:

export interface LifecycleHook {
  onMounted?(): void;

  onUpdated?(): void;

  onUnmounted?(): void;

  onBeforeMount?(): void;

  onBeforeUpdate?(): void;

  onBeforeUnmount?(): void;

  onErrorCaptured?(err: unknown, instance: ComponentPublicInstance | null, info: string): boolean | void;

  onRenderTracked?(e: DebuggerEvent): void;

  onRenderTriggered?(e: DebuggerRenderEvent): void;

  onActivated?(): void;

  onDeactivated?(): void;

  onServerPrefetch?(): Promise<any>;
}

依赖注入

依赖注入语法基本与Angular的语法一致,手写依赖注入实现,没有使用vue内置的依赖注入,每个组件都会绑定一个注入器跟Angular类似的层级注入,看如下示例:

@Injectable()
class RootClassK {

  public update() {

  }

  private pMethod() {
  }
}

@Injectable()
class RootClassK2 {

  public update() {
  }

  constructor(public root: RootClassK) {
    console.log(root);
  }

  private pMethod() {
  }
}

class ClassK {

  public update() {

  }

  private pMethod() {
  }
}
const demoToken = InjectionToken<ClassK>();

@Component({
  styleUrls: ["./demo.less"],
  template: `
    <div>{{ x }}</div>
    <div>{{ y }}</div>
    <CommunityIcon></CommunityIcon>
  `,
  providers: [
    // 注册提供商
    { provide: demoToken, useClass: ClassK }
  ]
})
export default class AngularDemo implements LifecycleHook {
  x = ref(0);
  y = ref(0);

  props = defineProps({
    k: {}
  });

  private update = (event: MouseEvent) => {
    this.x.value = event.x;
    this.y.value = event.y;
  };

  constructor(@Optional() @Inject(demoToken) public demoData: ClassK, @Optional() private root: RootClassK2) {
    console.log(demoData, root);
  }

  private pMethod() {
  }

  onMounted(): void {
    console.log(this.demoData);
    window.addEventListener("mousemove", this.update);
  }

  onUnmounted(): void {
    window.removeEventListener("mousemove", this.update);
  }
}
  1. 解析修饰符

    可以使用 @Optional()@Self()@SkipSelf() 和 @Host() 来修饰依赖解析行为
    解析修饰符分为三类:

    • 如果找不到你要的东西该怎么办,用 @Optional()
    • 从哪里开始寻找,用 @SkipSelf()
    • 到哪里停止寻找,用 @Host() 和 @Self()

    默认情况下,始终从当前的 Injector 开始,并一直向上搜索。修饰符使你可以更改开始(默认是自己)或结束位置。

    另外,你可以组合除 @Host() 和 @Self() 之外的所有修饰符,当然还有 @SkipSelf() 和 @Self()

    @Optional()

    @Optional() 允许 你注入的服务视为可选服务。这样,如果无法在运行时解析它, 只会将服务解析为 null,而不会抛出错误。

    export default class OptionalComponent {
      // 如果依赖无法解析 optional则为null
      constructor(@Optional() public optional?: OptionalService) {}
    }
    

    @Self()

    使用 @Self()  仅从宿主元素注册的提供商中查找依赖。

    @Self() 的一个好例子是要注入某个服务,但只有当该服务在当前宿主元素上可用时才行。为了避免这种情况下出错,请将 @Self() 与 @Optional() 结合使用。

    比如,在下面的 SelfComponent 中。请注意在构造函数中注入的 LeafService

    @Component({
      templateUrl: './self-no-data.component.html',
      styleUrls: ['./self-no-data.component.css']
    })
    export default class SelfNoDataComponent {
      constructor(@Self() @Optional() public leaf?: LeafService) { }
    }
    

    该例中leaf将被赋值为null,因为宿主元素并没有注册LeafService,不过使用了@Optional()修饰
    当依赖不存在时不会抛出异常,会被赋值为null

    @Component({
      templateUrl: './self-no-data.component.html',
      styleUrls: ['./self-no-data.component.css'],
      providers: [
        // 注册提供商
        { provide: LeafService, useClass: LeafService }
      ]
    })
    export default class SelfNoDataComponent {
        constructor(@Self() @Optional() public leaf?: LeafService) { }
    }
    

    该例中leaf会被正确解析出来,因为宿主元素注册相应的提供商

    @SkipSelf()

    @SkipSelf() 与 @Self() 相反。使用 @SkipSelf(),会从父Injector中往上查找

    @Host()

    当使用 @Host() 装饰器时,VuePlus将在当前组件所在的宿主元素以及其父元素的注入器中查找依赖项。如果找不到依赖项,VuePlus 将继续向上查找,直到找到或到达根元素。

    @Host()@Self()有相同之处,下面是不同之处总结:

    • @Host() 会沿着组件树向上查找依赖项,直到找到或到达根元素。
    • @Self() 仅在当前组件的注入器中查找依赖项。

Props使用@Input替代

@Component({
    templateUrl: './props.component.html',
    styleUrls: ['./props.component.css']
})
export default class PropsComponent {
    @Input() message: string // string会自动约束message为String
    @Input(true) name: string // true 必传
    @Input() defaultValue = 'default' // 默认值
}

Emit使用 @Output替代

@Component({
    templateUrl: './emit.component.html',
    styleUrls: ['./emit.component.css']
})
export default class EmitComponent {
    // 相当于 increase = defineEmits(['increase'])
    @Output() increase = new EventEmitter<number>();
    // 装饰器传参支持别名 相当于 increaseOther = defineEmits(['increase1'])
    @Output('increase1') increaseOther = new EventEmitter<number>();
    
    increaseHandler() {
        // 相当于 
        // const emit = defineEmits(['increase'])
        // emit(1)
        this.increase.emit(1)
    }
}

后续

后续会补充实现的过程 内容比较多 最后可能还会出适配这个插件的Webstrom插件的实现过程(视情况而定)

原文链接:https://juejin.cn/post/7242911173723553849 作者:晟东

(0)
上一篇 2023年6月11日 上午10:56
下一篇 2023年6月11日 上午11:07

相关推荐

发表回复

登录后才能评论