Введение

Экосистема Vue JS развивается с каждым годом. На данный момент существует несколько разных синтаксисов:

Так же встречаются проекты с синтаксисом:

Многим из нас приходится работать в разных проектах с разным синтаксисом.
Хранить в голове все варианты написания достаточно сложно. А постоянно листать разные документации - долго.
Я уже молчу про новичков, которые не успели изучить все варианты документаций.
Лично у меня несколько проектов с разными синтаксисами, поэтому я решил написать шпаргалку по всем вариантам, чтобы всегда иметь под рукой нужные шаблоны.

В добавок новички в Vue смогут сравнивать разные варианты синтаксиса и выбрать для себя вариант по душе и лучше ориентироваться в разных проектах.

Используемая документация:

Перед изучением сравнений я рекомендую прочитать статью про отличия script setup.

Определение компонента страницы

Vue Options API

<template>
  <!-- ... -->
</template>

<script>
export default {
  name: "ParentComponent",
};
</script>

Vue Composition API

<template>
 <!-- ... -->
</template>

<script>
export default {
  // ...
}
</script>

Vue Composition API

<template>
 <!-- ... -->
</template>

<script setup>
// ...
</script>

Vue Class API

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({})
export default class ParentComponent extends Vue {
  // ...
}
</script>

Vue Class API + vue-property-decorator

Синтаксис аналогичен Vue Class API.

Регистрация дочернего компонента

Vue Options API

<template>
  <child-component />
</template>

<script>
import ChildComponent from "@/components/ChildComponent.vue";

export default {
  name: "ParentComponent",
  components: {
    ChildComponent,
  },
};
</script>

Vue Composition API

<template>
  <child-component />
</template>

<script setup>
import ChildComponent "@/components/ChildComponent.vue"

// ...

</script>

Vue Composition API

<template>
  <child-component />
</template>

<script>
import ChildComponent from "@/components/ChildComponent.vue";

export default {
  components: {
    ChildComponent,
  },
};
</script>

Vue Class API

<template>
  <child-component />
</template>

<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ChildComponent from "@/components/ChildComponent.vue";

@Options({
  components: {
    ChildComponent,
  },
})
export default class ParentComponent extends Vue {}
</script>

Vue Class API + vue-property-decorator

Синтаксис аналогичен Vue Class API.

Определение реактивных данных

Vue Options API

<script>
export default {
  data() {
    return {
      someObject: {}
    }
  }
}
</script>

Vue Composition API

<script>
import { ref } from 'vue'

export default {
  setup() {
    const someObject = ref({})
    
    return {
      someObject
    }
  }
}
</script>

Vue Composition API

<script setup>
import { ref } from 'vue'

const someObject = ref({})
</script>

Vue Class API

<script>
import { Options, Vue } from "vue-class-component";
import ChildComponent from "@/components/ChildComponent.vue";

@Options({})
export default class ParentComponent extends Vue {
  message = "Hello World!";
}
</script>

Vue Class API + vue-property-decorator

Синтаксис аналогичен Vue Class API.

Определение computed свойства

Vue Options API

<script>
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(newValue) {
        [this.firstName, this.lastName] = newValue.split(' ')
      }
    }
  }
}
</script>

Vue Composition API

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const author = reactive({
      name: 'John Doe',
      books: [
        'Vue 2 - Advanced Guide',
        'Vue 3 - Basic Guide',
        'Vue 4 - The Mystery'
      ]
    })
    
    const publishedBooksMessage = computed(() => {
      return author.books.length > 0 ? 'Yes' : 'No'
    })
    
    return {
      publishedBooksMessage
    }
  }
}
</script>
<script setup>
import { ref, computed } from 'vue'

export default {
  setup() {
    const firstName = ref('John')
    const lastName = ref('Doe')

    const fullName = computed({
      get() {
        return firstName.value + ' ' + lastName.value
      },
      set(newValue) {
        [firstName.value, lastName.value] = newValue.split(' ')
      }
    })
    
    return {
      fullName
    }
  }
}
</script>

Vue Composition API

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

const publishedBooksMessage = computed(() => {
  return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  get() {
    return firstName.value + ' ' + lastName.value
  },
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

Vue Class API

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({})
export default class ParentComponent extends Vue {
  firstName = "John";
  lastName = "Doe";

  get name() {
    return this.firstName + " " + this.lastName;
  }

  set name(value) {
    const splitted = value.split(" ");
    this.firstName = splitted[0];
    this.lastName = splitted[1] || "";
  }
}
</script>

Vue Class API + vue-property-decorator

Синтаксис аналогичен Vue Class API

Определение и вызов emit

Vue Options API

<template>
  <button @click="$emit('enlarge-text', 0.1)">
    Enlarge text
  </button>
</template>

<script>
export default {
  methods: {
    myFunction(data) {
      this.$emit('emitName', data)
    }
  }
}
</script>

Vue Composition API

<script>
export default {
  emits: ['submit']
  setup() {
    const myFunction = (data) => {
      $emit('submit', data)
    }
  }
}
</script>
<script>
export default {
  emits: {
    submit(payload) {
      // ...
    }
  }
}
</script>
<script>
export default {
  emits: {
    // No validation
    click: null,

    // Validate submit event
    submit: ({ email, password }) => {
      if (email && password) {
        return true
      } else {
        console.warn('Invalid submit event payload!')
        return false
      }
    }
  },
  methods: {
    submitForm(email, password) {
      this.$emit('submit', { email, password })
    }
  }
}
</script>
<template>
  <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
}
</script>
<script>
export default {
  emits: ['inFocus', 'submit'],
  setup(props, ctx) {
    ctx.emit('submit')
  }
}
</script>

Vue Composition API

<script setup>
const emit = defineEmits(['inFocus', 'submit'])

function buttonClick() {
  emit('submit')
}
</script>
<script setup>
const emit = defineEmits({
  submit(payload) {
    // return `true` or `false` to indicate
    // validation pass / fail
  }
})
</script>
<template>
  <input
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
<template>
  <input v-model="value" />
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])

const value = computed({
  get() {
    return props.modelValue
  },
  set(value) {
    emit('update:modelValue', value)
  }
})
</script>

Vue Class API

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({
  emits: ["submit"],
})
export default class ChildComponent extends Vue {
  onClick() {
    this.$emit("submit");
  }
}
</script>

Vue Class API + vue-property-decorator

<script lang="ts">
import { Vue, Component, Emit } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  count = 0

  @Emit()
  addToCount(n: number) {
    this.count += n
  }

  @Emit('reset')
  resetCount() {
    this.count = 0
  }

  @Emit()
  returnValue() {
    return 10
  }

  @Emit()
  onInputChange(e) {
    return e.target.value
  }

  @Emit()
  promise() {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(20)
      }, 0)
    })
  }
}
</script>

Код выше эквивалентен:

<script>
export default {
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    addToCount(n) {
      this.count += n
      this.$emit('add-to-count', n)
    },
    resetCount() {
      this.count = 0
      this.$emit('reset')
    },
    returnValue() {
      this.$emit('return-value', 10)
    },
    onInputChange(e) {
      this.$emit('on-input-change', e.target.value, e)
    },
    promise() {
      const promise = new Promise((resolve) => {
        setTimeout(() => {
          resolve(20)
        }, 0)
      })

      promise.then((value) => {
        this.$emit('promise', value)
      })
    },
  },
}
</script>

Определение lifecycle hooks

Vue Options API

<script>
export default {
  mounted() {
    // ...
  }
}
</script>

Vue Composition API

<script>
import { onMounted } from 'vue'

export default {
  onMounted() {
    // ...
  }
}
</script>

Vue Composition API

<script setup>
import { onMounted } from 'vue'

const el = ref()

onMounted(() => {
  el.value // <div>
})
</script>

Vue Class API

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({})
export default class ChildComponent extends Vue {
  mounted() {
    console.log("mounted");
  }
}
</script>

Vue Class API + vue-property-decorator

Синтаксис аналогичен Vue Class API

Определение методов и функций

Vue Options API

<script>
export default {
  data() {
    return {
      someObject: {
        one: "one",
        two: "two",
      },
    };
  },
  methods: {
    funcOne() {
      console.log(this.someObject.one);
    },
  },
}
</script>

Vue Composition API

<script>
export default {
  import { ref } from 'vue'
  
  setup() {
    const someObject = ref({ one: "one", two: "two" })

    const funcOne = () => {
      console.log(someObject.value.one)
    }
    
    return {
      someObject,
      funcOne
    }
  }
}
</script>

Vue Composition API

<script setup>
import { ref } from 'vue'

const someObject = ref({ one: "one", two: "two" })

const funcOne = () => {
  console.log(someObject.value.one)
}
</script>

Vue Class API

<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ChildComponent from "@/components/ChildComponent.vue";

@Options({
  components: {
    ChildComponent,
  },
})
export default class ParentComponent extends Vue {
  someObject = { one: "one", two: "two" };

  funcOne() {
    console.log(this.someObject.one);
  }
}
</script>

Vue Class API + vue-property-decorator

Синтаксис аналогичен Vue Class API

Определение props свойства

Vue Options API

Определение props без валидации

<script>
export default {
  props: ['foo'],
}
</script>

Валидация props:

<script>
export default {
  props: {
    propA: Number,
    propB: [String, Number],
    propC: {
      type: String,
      required: true
    },
    propD: {
      type: Number,
      default: 100
    },
    propE: {
      type: Object,
      default(rawProps) {
        return { message: 'hello' }
      }
    },
    propF: {
      validator(value) {
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    propG: {
      type: Function,
      default() {
        return 'Default function'
      }
    }
  }
}
</script>

Vue Composition API

Определение props без валидации

Для того чтобы объявить props с поддержкой вывода полного типа, мы можем использовать
defineProps, которые автоматически доступен внутри <script setup>

<script setup>
const props = defineProps(['foo'])
</script>

Валидация props:

<script setup>
const props = defineProps({
  propA: Number,
  propB: [String, Number],
  propC: {
    type: String,
    required: true
  },
  propD: {
    type: Number,
    default: 100
  },
  propE: {
    type: Object,
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  propF: {
    validator(value) {
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  propG: {
    type: Function,
    default() {
      return 'Default function'
    }
  }
})
</script>

Vue Composition API

Определение props без валидации

<script>
export default {
  props: ['foo']
}
</script>

Валидация props: аналогично синтаксису Vue Options API.

Vue Class API

Определение props без валидации

<script lang="ts">
import { Options, Vue } from "vue-class-component";

@Options({
  props: {
    msg: String,
  },
})
export default class ChildComponent extends Vue {
  msg!: string;
}
</script>

Валидация props: аналогично другим синтаксисам

Vue Class API + vue-property-decorator

<script lang="ts">
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
}
</script>

Код выше эквивалентен:

<script>
export default {
  props: {
    propA: {
      type: Number,
    },
    propB: {
      default: 'default value',
    },
    propC: {
      type: [String, Boolean],
    },
  },
}
</script>

Определение watch свойства

Vue Options API

Базовое использование

<script>
export default {
  watch: {
    someObject(newValue, oldValue) {
      // ...
    }
  }
}
</script>

Глубокое слежение

<script>
export default {
  watch: {
    someObject: {
      handler(newValue, oldValue) {
        // ...
      },
      deep: true
    }
  }
}
</script>

Обязательный обратный вызов

<script>
export default {
  watch: {
    question: {
      handler(newValue, oldValue) {
        // ...
      },
      immediate: true
    }
  }
}
</script>

Vue Composition API

Базовое использование

<script setup>
import { watch } from 'vue'

watch(someObject, async (newValue, oldValue) => {
  // ...
})
</script>

Глубокое слежение

Когда вы вызываете watch() непосредственно реактивный объект, он неявно создает глубокий наблюдатель — обратный вызов
будет запускаться для всех вложенных мутаций:

Это следует отличать от геттера, который возвращает реактивный объект — в последнем случае обратный вызов
сработает только в том случае, если геттер вернет другой объект:

<script setup>
import { watch } from 'vue'

watch(
    () => state.someObject,
    () => {
      // ...
    }
)
</script>

Однако вы можете принудительно перевести второй случай в глубокий наблюдатель, явно используя deep параметр:

<script setup>
import { watch } from 'vue'

watch(
    () => state.someObject,
    (newValue, oldValue) => {
      // ...
    },
    { deep: true }
)
</script>

Vue 2.7 / 3. Composition API

<script setup>
export default {
  watch: {
    someObject(newValue, oldValue) {
      // ...
    },
  },
  setup() {
    // ...
  }
};
</script>

Vue Class API

<script lang="ts">
import { Options, Vue } from "vue-class-component";
import ChildComponent from "@/components/ChildComponent.vue";

@Options({
  watch: {
    someString(newValue) {
      console.log(newValue);
    },
  },
})
export default class ParentComponent extends Vue {
  someString = "old";

  mounted() {
    this.someString = "new";
  }
}
</script>

Vue Class API + vue-property-decorator

<script>
import { Vue, Component, Watch } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Watch('child')
  onChildChanged(val: string, oldVal: string) {}

  @Watch('person', { immediate: true, deep: true })
  onPersonChanged1(val: Person, oldVal: Person) {}

  @Watch('person')
  onPersonChanged2(val: Person, oldVal: Person) {}
}
</script>

Код выше эквивалентен:

<script>
export default {
  watch: {
    child: [
      {
        handler: 'onChildChanged',
        immediate: false,
        deep: false,
      },
    ],
    person: [
      {
        handler: 'onPersonChanged1',
        immediate: true,
        deep: true,
      },
      {
        handler: 'onPersonChanged2',
        immediate: false,
        deep: false,
      },
    ],
  },
  methods: {
    onChildChanged(val, oldVal) {},
    onPersonChanged1(val, oldVal) {},
    onPersonChanged2(val, oldVal) {},
  },
}
</script>

Работа с Vuex

Vue Options API

<script>
import { mapGetters, mapState } from "vuex";

export default {
  computed: {
    ...mapGetters(["userId"]),
    ...mapState({
      pageTitle: (state) => state.pageTitle.toUpperCase(),
    })
  },
  methods: {
    getUserId() {
      return this.$store.state.userId
    },
    deleteUser(id) {
      this.$store.commit("deleteUser", id);
    },
  }
}
</script>

Vue Composition API

<script>
import { mapGetters, mapState, useStore } from "vuex";

export default {
  computed: {
    ...mapGetters(["userId"]),
    ...mapState({
      pageTitle: (state) => state.pageTitle.toUpperCase(),
    })
  },
  setup() {
    const store = useStore()
    
    const deleteUser = (id) => {
      store.commit("deleteUser", id);
    }

    const getUserId = () => {
      return store.state.userId
    }

    const deleteUser = (id) => {
      store.commit('deleteUser', id)
    }
  }
}
</script>

Vue Composition API

<script>
const store = useStore()

const deleteUser = (id) => {
  store.commit("deleteUser", id);
}

const getUserId = () => {
  return store.state.userId
}

const deleteUser = (id) => {
  store.commit('deleteUser', id)
}

const userId = computed(() => {
  return store.getters.userId
})

const count = computed(() => store.getters.count)
</script>

Vue Class API

Vuex не предоставляет типы для свойства this.$store из коробки.
При использовании с TypeScript вы должны объявить расширение собственного модуля.
Подробности.

Vue Class API + vue-property-decorator

Аналогично Vue Class API.

Комментарии (1)


  1. gmtd
    28.09.2022 15:27
    +5

    Лучше разделить названия Vue Composition API и Vue Composition API (script setup)