First, let’s talk about the premise. I mainly use VUE3 + TSX for development in business

Why not use templates?

  1. Context inconsistency
  2. Ref implicit value conversion
  3. The type hint in the template is weak, and I rely heavily on the IDE’s intelligent hint
  4. Template often updated grammar, once a period of time template writing changed, heart tired

Tsx user experience problems

  1. Unable to display declare component props, resulting in a lot of code to declare properties
  2. A generic component cannot be defined
  3. Ref always needs to write.value in the render function
  4. Slots has no place to declare a type
  5. The component expose data and methods also have no type

Here’s how to use classes in Vue to solve the problems mentioned above. The main dependencies are in vue3-OOP, for example stackblitz.com/edit/vite-y…

Vue class components

import { Autobind, ComponentProps, Computed, Hook, Link, Mut, VueComponent } from 'vue3-oop'
import { Directive, VNodeChild, watch } from 'vue'

const focusDirective: Directive = {
  mounted(el: HTMLInputElement) {
    el.focus()
  },
}

// Display the definition component properties directly
interface Foo_Props {
  size: 'small' | 'large'
  // Component slots defines the type
  slots: {
    item(name: string): VNodeChild
  }
}

class Foo extends VueComponent<Foo_Props> {
  // The runtime property check required by vue
  static defaultProps: ComponentProps<Foo_Props> = {
    size: String,}// Local directives required by the component
  static directives: Record<string, Directive> = {
    focus: focusDirective,
  }
  constructor() {
    super(a)// Watch is initialized in the constructor
    watch(
      () = > this.count,
      () = > {
        console.log(this.count)
      },
    )
  }

  // The state of the component itself, which can be interpreted as all the variables used in the UI, should have this decorator, indicating that I need a UI refresh
  @Mut() count = 1
  // Calculate attributes
  @Computed(a)get doubleCount() {
    return this.count * 2
  }
  add() {
    this.count++
  }
  // Automatically bind this
  @Autobind(a)remove() {
    this.count--
  }

  // Lifecycle
  @Hook('Mounted')
  mount() {
    console.log('mounted')}// A reference to an element or component
  @Link() element? : HTMLDivElement@Mut() list = [{a: 1}]
  
  @Link() child? : FooChild// Reference components are typed

  render() {
    return (
      <div ref="element">
        <span>{this.props.size}</span>
        <button onClick={()= > this.add()}>+</button>
        <span>{this.count}</span>
        <button onClick={this.remove}>-</button>
        <div>{this.context.slots.item? .('aaa')}</div>
        <input type="text" v-focus />
        <FooChild ref="child" data={list} v-slots={{item(k){ return <span>{k.a}</span>}}} ></FooChild>  
      </div>)}}// Generic components
interface FooChild_Porps<T> {
  data: T[]
  onClick: (item: T) = > T
  slots: {
    item(item: T): VNodeChild
  }
}
class FooChild<T> extends VueComponent<FooChild_Porps<T>> {
  add(){}render() {
      return <ul>
      {
    	this.props.data.map(k => <li onClick={()= >this.props.onClick? .(k)}>{this.context.slots.item? .(k)}</li>)}</ul>}}Copy the code

There are very few major apis

API type role
VueComponent class Components must integrate and implement render methods
VueService class Services need inheritance
Mut Attribute decorator Tag attributes are reactive
Computed Method decorator Tag attributes are computed attributes
Hook Method decorator Declare the functions that need to be executed for the cycle
Link Attribute decorator Reference child components or DOM elements
Autobind Method decorator Bind this to the function

We have solved all of the above problems perfectly with the class component, and the IDE has excellent intelligence hints.

Class service = Vue hooks

What is the nature of vue hooks, which encapsulate variables and the functions that change them

function useState() {
    const count = ref(0)
    function add() {
        count.value++
    }
    return { count, add }
}
Copy the code

What is the essential difference between this and classes? I think classes mainly expose scope as this, whereas hooks use closures to avoid this calls, but lose the type

class CountService extends VueService {
  @Mut() count = 1
  add() {
    this.count++
  }
  remove() {
    this.count--
  }
}

class Foo extends VueComponent {
  countService = new CountService() / / new manually
}
Copy the code

State management

Vue3 now has native provide/inject, there is no use for state management pack in vue ecology, let’s see how to use it

class CountService extends VueService {
  // The class has this key, which is provided directly in the constructor
  static ProviderKey: InjectionKey<CountService> = Symbol(a)@Mut() count = 1
  add() {
    this.count++
  }
  remove() {
    this.count--
  }
}

class Foo extends VueComponent {
  countService = new CountService() / / new manually
  render() {
      return}}class FooChild extends VueComponent {
    countService = inject(CountService.ProviderKey)
    render() {
        return <div>{this.countService.count}</div>}}Copy the code

Automated dependency injection

Previously we saw that services are manually new and injected services are also manually injected. To improve the user experience of development, how can programmers play manual? We use Angular injection-js to manage our service as an IOC container

import { VueComponent, VueService } from 'vue3-oop'
import { Injectable } from 'injection-js'

class CountService extends VueService { 
	@Mut() count = 1
}

@Injectable(a)// If there are services that need to be injected
class BarService extends VueService {
  constructor(private countService: CountService) {super()}}// Component injection service
@Component(a)class Bar extends VueComponent {
  // The constructor argument declares the required service
  constructor(
    private countService: CountService, 
    private barService: BarService
  ) { super()}render() {
    return <div>{this.countService.count}</div>}}Copy the code

This gives us an Angular-like development experience with class + TSX + DI, which is simpler than Angular. For those interested, check out Vue3-OOP

conclusion

As front-end projects grow in size, we need a clean architecture for front-end development. Domain-driven Design (DDD) is a long-established architecture for back-end development. DDD relies on OOP, and with OOP we can design domains. But the brilliance of Angular’s architectural ideas should not be forgotten, so an Angular-like architecture was implemented on VUE to expose more front-end people to OOP and write better code.