В октябре 2019 года наконец-то была выпущена пре-альфа Vue 3.0. Так что использовать Vue 3.0 в рабочей среде - вопрос времени.

Чтобы подготовиться к этому, я хотел бы рассказать, как перейти с Vue 2.0 на Vue 3.0. Поскольку в наши дни большое количество пользователей Vue уже представили TypeScript, в этой статье также используется TypeScript.

Есть 3 шага по миграции, как показано ниже.

  1. Внедрение Vue 3.0 в Vue 2.0
  2. Заменить коды Vue 2.0 на Vue 3.0
  3. Создайте глобальный магазин с API композиции Vue 3.0

Поскольку тема немного длинная, я разделил ее на 3 статьи. Эта статья является продолжением первого шага. Если вы хотите начать с конкретной части, не стесняйтесь переходить на страницу.

Заменить коды Vue 2.0 на Vue 3.0

Вы уже настраивали API композиции Vue 3.0 в предыдущей статье! Итак, давайте постепенно заменим код Vue 2.0 на Vue 3.0!

Используйте createComponent и setup () вместо Vue.extend

В Vue 2.0 «Vue.extend» используется, чтобы позволить TypeScript правильно определять типы внутри компонента Vue. Вместо «Vue.extend» вы должны использовать createComponent в Vue 3.0 для вывода типа. Кроме того, внутри createComponent вы должны использовать функцию настройки, которая является точкой входа, где вызываются функции композиции.

// src/List.vue
<script lang="ts">
import { createComponent } from "@vue/composition-api";
export default createComponent({
  setup(){
    // DO SOMETHING HERE
  }
})

Но когда вы заменяете Vue.extend на createComponent, в вашем редакторе возникает много ошибок, потому что createComponent не знает this в Vue 2.0. У вас много ошибок компиляции в терминале.

Но не волнуйтесь. Это ошибка от Vetur. Все нормально работает.

Замените «данные» Vue 2.0 на реактивный

Следующим шагом будет замена локального состояния Vue 2.0, data. Это очень просто, используйте реактивную функцию внутри функции настройки, как показано ниже.

// src/List.vue
<script lang="ts">
import { FilterByEnum, Data, FilterBy } from "./types/List";
import { createComponent, reactive } from "@vue/composition-api";
export default createComponent({
  setup(){
    const todoState = reactive<Data>({
      todos: [],
      newTodo: "",
      filterBy: FilterByEnum.ALL
    });
  }
})

reactive является эквивалентом текущего Vue.observable () в Vue 2.0. Возвращаемое значение из реактивного - это реактивный объект, который вам нравится.

На самом деле у вас есть другой API для создания реактивных переменных, называемый ref(). Но сама функция очень похожа на reactive(). Пожалуйста, проверьте официальную документацию.

Замените «методы» Vue 2.0!

Методы в Vue2.0 превращаются в простые функции внутри функции настройки. Коды легче увидеть. Давайте сначала займемся задачами.

// src/List.vue
...
import todos from "../utils/todos";
export default createComponent({
  setup(){
    const todoState = reactive<Data>({
      ...
    const init = function(): void {
      getTodos();
    };
    const getTodos = function(): void {
      setTimeout(() => {
        todoState.todos = [...todos];
    }, 1000);
  }
})

Поскольку вам не нужно использовать this для доступа к локальному штату, это проще.

Пока что у вас есть функция инициализации задач. Но когда вы это называете? Да, вы хотите сделать это, когда экземпляр Vue «установлен».
Конечно, у Vue 3.0 есть хуки жизненного цикла. Вы должны использовать его, как показано ниже.

// src/List.vue
...
export default createComponent({
  setup(){
    const todoState = reactive<Data>({
      ...
    onMounted(() => {
      init();
    });
    const init = function(): void {
      getTodos();
    };
    const getTodos = function(): void {
      setTimeout(() => {
        todoState.todos = [...todos];
    }, 1000);
  }
})

Остальные методы можно переместить во внутреннюю функцию настройки. После перемещения всего метода List.vue становится таким, как показано ниже.

// src/List.vue
<script lang="ts">
import Vue from "vue";
import todos from "../utils/todos";
import { FilterByEnum, Data, FilterBy } from "./types/List";
import { createComponent, reactive, onMounted } from "@vue/composition-api";
export default createComponent({
  setup(props, context) {
    const todoState = reactive<Data>({
      todos: [],
      newTodo: "",
      filterBy: FilterByEnum.ALL
    });
onMounted(() => {
      init();
    });
    
    const init = function(): void {
      getTodos();
    };
    const getTodos = function(): void {
      setTimeout(() => {
        todoState.todos = [...todos];
      }, 1000);
    };
    const addTodo = function(): void {
      const newTodo = { name: todoState.newTodo, completed: false };
      todoState.todos = [...todoState.todos, newTodo];
      todoState.newTodo = "";
    };
    const deleteTodo = function(index: number): void {
      todoState.todos = todoState.todos.filter((todo, i) => i !== index);
    };
    const completeTodo = function(index: number): void {
      todoState.todos[index].completed = true;
    };
    const handleClickFilterBy = function(filterBy: FilterBy): void {
      todoState.filterBy = filterBy;
    };
    const goEditTodo = function(index: number): void {
      context.root.$router.push(`/todos/${index}/edit`);
    };
},

Одна вещь, которую вы еще не знаете, - это два аргумента настройки, свойства и контекст. Реквизит понять несложно. Вы можете получить реквизит внутри функции настройки. В контексте вы можете проверить многие вещи, которые можно увидеть в this Vue 2.0, например, slot, parent и root.

Итак, если вы хотите использовать $router, вы можете получить доступ с context.root.

Заменить «вычисленный» в Vue 2.0

Вы уже заметили, что у вас есть коды Vue2.0 только в вычисленных. Но его также очень легко заменить.

// src/List.vue
...
import { createComponent, reactive, onMounted, computed } from "@vue/composition-api";
export default createComponent({
  setup(props, context) {
    const todoState = reactive<Data>({
      todos: [],
      newTodo: "",
      filterBy: FilterByEnum.ALL
    });
    const filteredTodos = computed(function() {
      return todoState.todos.filter(todo => {
        if (todoState.filterBy === FilterByEnum.WORKING) {
          return !todo.completed;
        }
        if (todoState.filterBy === FilterByEnum.DONE) {
          return todo.completed;
        }
        return todo;
    });
    const numOfTodos = computed(function(): number {
      return todoState.todos.filter(todo => !todo.completed).length;
    });
...

Вы можете импортировать вычисленное из @vue/composition-api. И так же, как методы, вы можете объявить имя вычисляемого и передать вычисляемую логику аргументу вычисляемого. Вот и все.

Если вы хотите использовать переменные, методы в шаблонах

Пока вы заменили все коды Vue 2.0 на код Vue 3.0. Это приложение todo использует множество переменных и методов внутри шаблона, как показано ниже.

  • Состояние
    - задачи
    - newTodo
    - filterBy
  • Вычислено
    - filterTodos
    - numOfTodos
  • методы
    - addTodo
    - deleteTodo
    - completeTodo
    - handleClickFilterBy
    - goEditTodo

Но если вы хотите использовать их внутри шаблона, что вам следует делать? Это действительно просто. Просто верните то, что вам нужно, внутри функции настройки.

// src/List.vue
// Inside of template, nothing changed.
...
import { createComponent, reactive, onMounted, computed, toRefs } from "@vue/composition-api";
export default createComponent({
  setup(props, context) {
    ...
    return {
      ...toRefs(todoState),
      filteredTodos,
      numOfTodos,
      addTodo,
      handleClickFilterBy,
      completeTodo,
      goEditTodo,
      deleteTodo
    };
  }
})

Вы не знаете, что toRefs. Это потому, что когда вы используете reactive без reRefs, вы должны использовать их внутри храма, например todoState.todos, todoState.filterBy. Но это не СУХОЕ! С toRefs() и деструктуризацией вы можете использовать их так же, как и раньше.

Итак, теперь все работает правильно!

Сделайте это компонуемым

Пока что Vue 3.0 отлично работает. Но вы можете провести рефакторинг. Основная цель API композиции Vue 3.0 - сделать код компонуемым. Это значит, что код нужно разделять на мелкие части и заставлять их использовать снова и снова.

Итак, давайте составим задачи.

  1. Сделайте функцию с именем useTodos за пределами экспорта по умолчанию.
// src/List.vue
...
import { createComponent, reactive, onMounted, computed, toRefs } from "@vue/composition-api";
export default createComponent({
  setup(props, context) {
    ...
  }
})
const useTodos = () => {}

2. Скопируйте и вставьте функции и состояние внутри настройки в useTodos

Поскольку goEditTodo немного отличается, вам не нужно его перемещать.

// src/List.vue
...
import { createComponent, reactive, onMounted, computed, toRefs } from "@vue/composition-api";
export default createComponent({
  setup(props, context) {
    const goEditTodo = function(index: number): void {
      context.root.$router.push(`/todos/${index}/edit`);
    };
    return {
      goEditTodo
    };
  }
})
const useTodos = () => {
  const todoState = reactive<Data>({
    todos: [],
    newTodo: "",
    filterBy: FilterByEnum.ALL
  });
  
  ...
  return {
    ...toRefs(todoState),
    filteredTodos,
    numOfTodos,
    addTodo,
    handleClickFilterBy,
    completeTodo,
    deleteTodo
  }
}

Эта useTodos функция называется функцией композиции.

3. Вызовите useTodos внутри программы установки и используйте ее.

// src/List.vue
...
import { createComponent, reactive, onMounted, computed, toRefs } from "@vue/composition-api";
export default createComponent({
  setup(props, context) {
    const goEditTodo = function(index: number): void {
      context.root.$router.push(`/todos/${index}/edit`);
    };
    
    return {
      ...useTodos(),
      goEditTodo
    };
  }
})
const useTodos = () => {
  ...
return {
    ...toRefs(todoState),
    filteredTodos,
    numOfTodos,
    addTodo,
    handleClickFilterBy,
    completeTodo,
    deleteTodo
  }
}

Теперь работает отлично!

4. Отделите функцию композиции от другого файла.

Поскольку функция композиции - это просто функция, вы можете легко разделить ее. Из демонстрационного репозитория Линуса Борга эти функции композиции сохраняются в src/composables/. И каждая функция композиции названа как useHoge, как показано ниже. Фактически, типовой файл также перемещается в раздел composables.

// composables/useTodos.ts
import todos from "../utils/todos";
import { FilterByEnum, Data, FilterBy } from "./types/UseTodos";
import { reactive, onMounted, computed, toRefs } from "@vue/composition-api";
export const useTodos = () => {
  ...
};

И импортируйте его в List.vue.

// src/List.vue
<script lang="ts">
import Vue from "vue";
import { createComponent } from "@vue/composition-api";
import { useTodos } from "../composables/useTodos";
export default createComponent({
  setup(props, context) {
    const goEditTodo = function(index: number): void {
      context.root.$router.push(`/todos/${index}/edit`);
    };
    return {
      ...useTodos(),
      goEditTodo
    };
  }
});
</script>

Теперь List.vue супер чистый. С помощью Compostion API вы можете отделить логику от компонента vue, а компонент vue может больше сосредоточиться на представлении. Это одна из главных ценностей Vue 3.0.

Следующий уровень

Пока что функция композиции вызывается в компоненте List.vue. Что, если вы вызовете ту же функцию композиции в Edit.vue? Распределены ли состояния и функции между компонентами?

Ответ - нет". Поскольку функция композиции - это просто функция, вы должны использовать что-то, чтобы поделиться ими во всем мире. Функция композиции - это не управление состоянием, как Vuex.

Но если вы используете Provide и Inject of Vue, вы можете создать глобальное хранилище. В следующей статье объясняется, как создать глобальное хранилище с помощью Vue 3.0.

В этой статье я объяснил, как заменить Vue 2.0 на Vue 3.0. Но в этой статье пока не говорится о global store Vue. Я хотел бы поделиться возможностью глобального магазина Vue 3.0 и попытаться сделать это.

  1. Внедрение Vue 3.0 в Vue 2.0
  2. Заменить коды Vue 2.0 на Vue 3.0 (Теперь здесь!)
  3. Создайте глобальный магазин с API композиции Vue 3.0