Понимание Script Setup в Vue 3

Тип <script setup> — это предлагаемое изменение в RFC Vue Git . Чтобы внести ясность: это не предназначено для полной замены любого из существующих способов написания кода. Его цель — предоставить разработчикам более краткий синтаксис для написания однофайловых компонентов.

В этой статье мы рассмотрим, как именно это работает, и некоторые способы, которыми это может быть полезно.

Краткое описание script setup

В <script setup> нам не нужно объявлять метод export default и setup — вместо этого все привязки верхнего уровня предоставляются шаблону.

В Composition API мы привыкли создавать метод настройки, а затем возвращать все, что мы хотим предоставить. Что-то вроде этого:

<script>
  import { ref, computed } from 'vue'
  export default {
     setup () {
        const a = ref(3)
        const b = computed(() => a.value + 2)

        const changeA = () => { a.value = 4 }
        return { a, b, changeA } // have to return everything!
     }
  }
</script>

Но с помощью script setup мы можем переписать тот же код вот так:

<script setup>
  import { ref, computed } from 'vue'
  // all of these are automatically bound to the template
  const a = ref(3)
  const b = computed(() => a.value + 2)

  const changeA = () => { a.value = 4 }
</script>

И это не только данные, вычисляемые свойства и методы! Даже импортированные директивы и компоненты, находящиеся на верхнем уровне области установки, автоматически доступны в нашем шаблоне.

Посмотрите на этот пример импорта компонента.

<template>
  <component-b />
</template>
<script setup>
import ComponentB from './components/ComponentB.vue' // really that's it!
</script>

Смысл использования

Короче говоря, этот синтаксис упрощает создание отдельных файловых компонентов.

Точные слова RFC: «Основная цель предложения — снизить многословность использования Composition API внутри SFC за счет прямого предоставления контекста настройки сценария шаблону».

Основная цель предложения — снизить многословность использования Composition API внутри SFC за счет прямого предоставления контекста <script setup>шаблону.

И это именно то, что мы только что увидели: не беспокоясь о создании setup метода и возврате именно того, что мы хотим предоставить, мы можем упростить наш код.

Кроме того, не нужно беспокоиться о том, что вы забудете вернуть что-то из нашего метода установки.

Более продвинутое использование

Теперь, когда мы знаем, что такое <script setup> и почему это может быть полезно, давайте взглянем на некоторые из его более продвинутых функций.

Доступ к props, создание событий и т. д.

Прежде всего, вам может быть интересно, как выполнять стандартные операции Vue, такие как:

  • доступ к props;
  • emits событий;
  • доступ к нашему объекту контекста.

В Composition API это были просто аргументы нашего метода установки:

export default {
  setup(props, context) {
    // context has attrs, slots, and emit
  },
}

Однако в синтаксисе настройки скрипта мы можем получить доступ к этим же параметрам с помощью трех импортов из Vue.

  • defineProps – как следует из названия, позволяет нам определять props для нашего компонента
  • defineEmits– позволяет нам определить события, которые может генерировать наш компонент
  • useContext– дает нам доступ к слотам и атрибутам нашего компонента
<template>
  <button @click="$emit('change')">Click Me</button>
</template>
<script setup>
import { defineProps, defineEmit, useContext } from 'vue'

const props = defineProps({
  foo: String,
})
const emit = defineEmit(['change', 'delete'])

const { slots, attrs } = useContext()
</script>

С помощью этих трех импортов мы можем получить функциональность, к которой мы привыкли при нашем традиционном методе установки.

Создание функции асинхронной настройки

Еще одна интересная особенность настройки скрипта — простота создания функции асинхронной настройки.

Это полезно для загрузки в API при создании вашего компонента и даже для привязки вашего кода к экспериментальной функции ожидания.

Все, что нам нужно сделать, чтобы сделать нашу функцию установки асинхронной, — это использовать ожидание верхнего уровня внутри нашей настройки сценария.

Например, если мы используем Fetch API , мы можем просто использовать await вот так:

<script setup>
const post = await fetch(`/api/pics`).then((a) => a.json())
</script>

…и наша результирующая setup() функция будет именно такой асинхронной.

Использование нескольких тегов сценария

<script setup> создает собственную область сценария для своих привязок верхнего уровня. Но в некоторых случаях существует код, который необходимо выполнить в области модуля.

Два конкретных примера в этом RFC:

  • Объявление именованного экспорта
  • Создание глобальных побочных эффектов, которые выполняются только один раз.

Это можно сделать, добавив обычный <script> блок рядом с настройкой скрипта, как показано ниже.

<script>
performGlobalSideEffect()

// this can be imported as `import { named } from './*.vue'`
export const named = 1
</script>

<script setup>
// code here
</script>

Использование Vuex

В версии 2.0 мы можем использовать предоставленные vuex mapState напрямую mapMutation, а в версии 3.0 нам нужно обернуть их в наши собственные методы.

в 2.0:

<template>
  <div>
    {{ count }}
    {{ countIsOdd }}
    {{ countIsEven }}
  </div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
  computed: {
    ...mapGetters(['count', 'countIsOdd', 'countIsEven'])
  }
}
</script>

в 3.0 с использованием script setup:

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

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

чтобы избежать избыточности, мы также можем определить внешний файл, в этом случае мы создаем файл с именем map-state.js:

import { computed } from 'vue'
import { useStore } from 'vuex'

const mapGetters = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store.getters).map(
      getter => [getter, computed(() => store.getters[getter])]
    )
  )
}

export { mapGetters }

и его можно использовать следующим образом:

<template>
  <div>
    {{ count }}
    {{ countIsOdd }}
    {{ countIsEven }}
  </div>
</template>
<script setup>
import { mapGetters } from '../map-state'
const { count, countIsOdd, countIsEven } = mapGetters()
</script>

конечно, map-state.js файл можно расширить:

import { computed } from 'vue'
import { useStore } from 'vuex'
const mapState = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store.state).map(
      key => [key, computed(() => store.state[key])]
    )
  )
}
const mapGetters = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store.getters).map(
      getter => [getter, computed(() => store.getters[getter])]
    )
  )
}
const mapMutations = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store._mutations).map(
      mutation => [mutation, value => store.commit(mutation, value)]
    )
  )
}
const mapActions = () => {
  const store = useStore()
  return Object.fromEntries(
    Object.keys(store._actions).map(
      action => [action, value => store.dispatch(action, value)]
    )
  )
}
export { mapState, mapGetters, mapMutations, mapActions }

Глобальная конфигурация

Разница также возникает, когда вы пытаетесь использовать плагин или вешать глобальный компонент, например, с помощью $message или $dialog в js.

Создание плагина Amplitude (инструмент отслеживания данных с помощью js sdk):

/* Amplitude.js */
import amplitude from 'amplitude-js';

export default {
    install: (Vue, { apiKey, userId }) => {
        amplitude.getInstance().init(apiKey, userId, {
            includeUtm: true,
            includeReferrer: true,
            deviceIdFromUrlParam: true
        });

// in 2.0 it was Vue.prototype.$amplitude = amplitude;
Vue.config.globalProperties.$amplitude = amplitude;
    }
};

и используйте это в main.js:

/* main.js */
import AmplitudePlugin from './plugins/amplitude';
const app = createApp(App);
// in 2.0 it was Vue.use(......)
app.use(AmplitudePlugin, {
    apiKey: process.env.VUE_APP_AMPLITUDE_API_KEY,
    userId: userInfo?.id
});

для компонента сообщения после того, как вы его создали и зарегистрировали глобально, например:

// import all local fundamental components you build for your project, things like message, button, drawer, etc --> not business components
import * as components from './components';

export default {
    install: app => {
        Object.keys(components).forEach(key => {
            app.component(key, components[key]);
            if (key === 'DPMessage') {

// register your $message method globally
        app.config.globalProperties.$message = components[key];
            }
        });
    }
};

конечно, вам нужно его использовать:

<template>
  <div><button @click="showSuccessMessage">click me to show success message</button>
</template>
<script setup>
const { proxy } = getCurrentInstance();

const showErrorMessage = () => {
//better to have a '?' here, just in case Vue does not find the method
    proxy?.$message({ type: 'error', text: 'hey this is sam test' });
};
</script>

в то время как другие полезные плагины, например, axios могут работать глобально или нет, зависит от вас.