Главная » Статьи » Разработка приложения с использованием Vue 3 Composition API

Разработка приложения с использованием Vue 3 Composition API

Разработка приложения с использованием Vue 3 Composition API

От автора: хотя мне нравится Vue.js 2, есть некоторые ограничения на то, как можно применять повторно используемые компоненты и как должен быть написан код. Это было одновременно и благословением, и проклятием. С одной стороны, для простых компонентов это делает изучение Vue очень простым, с низким порогом для входа. С другой стороны, по мере того, как компоненты становятся более сложными, вы обнаруживаете, что код для различных функций в одном компоненте искажается.

Vuejs 3 преодолел это ограничение с помощью composition API. В этой статье я продемонстрирую эти ограничения Vuejs 2 и покажу вам необязательный новый подход к использованию composition API.

Как сказано выше, вы не обязаны использовать composition API, это необязательно. Для более мелких компонентов просто продолжайте делать то, что делали всегда. По мере того как компоненты становятся более сложными, возможно, содержащими более одной функции, composition API становится действительно полезным.

Весь код на GitHub

Я разработал простое приложение для блога с двумя ветвями. Одна ветвь использует Vue 2 options API, а другая — Vue 3 Composition API.

https://github.com/simonjcarr/vue3_composition_api

Кривая обучения

Composition API требует обучения, но оно того стоит. Чтобы дать вам представление о том, почему это так полезно, я расскажу о том, что мы создадим в этой статье. Я надеюсь, вы увидите, что это стоит выгоды, которые она приносит.

Вот два фрагмента кода, оба из одного компонента. Первый использует API Options Vue 2, с которым вы знакомы, второй — новый API Composition.

Без API Composition

<template> <div> <div class="text-4xl font-bold">{{ post.title }}</div> <div>{{ post.body }}</div> <div class="italic mt-4">Author: <span class="text-red-500">{{ user.name }}</span></div> </div>
</template> <script>
export default { data() { return { post: {}, user: {} }; }, methods: { fetchPost() { fetch( `https://jsonplaceholder.typicode.com/posts/${this.$route.params.id}` ) .then(response => response.json()) .then(data => (this.post = data)) .then(() => { fetch( `https://jsonplaceholder.typicode.com/users/${this.post.userId}` ) .then(response => response.json()) .then(data => (this.user = data)); }); } }, mounted() { this.fetchPost(); }
};
</script> <style></style>

Тот же компонент с Composition API

<template> <div> <div class="text-4xl font-bold">{{ post.title }}</div> <div>{{ post.body }}</div> <div class="italic mt-4"> Author: <span class="text-red-500">{{ user.name }}</span> </div> </div>
</template> <script>
import usePosts from "@/composables/blog/posts";
import router from "@/router";
export default { setup() { const { currentRoute } = router; const { fetchPost, post, user } = usePosts(); fetchPost(currentRoute.value.params.id); return { post, user }; }
};
</script> <style></style>

Composition API не только значительно упрощает код, но и, как вы увидите в этой статье, код, который импортируется @/composables/blog/posts, можно повторно использовать в других компонентах.

Обновите Vue CLI

Прежде чем мы создадим новое приложение, убедитесь, что у вас установлена последняя версия Vue CLI.

npm install -g @vue/cli

Создайте новое приложение

vue create vue3_composition_api

При появлении запроса выберите Manually select features, а затем используйте клавишу пробела для включения и выключения функций, пока вы не получите такой же набор, как я сделал ниже.

Разработка приложения с использованием Vue 3 Composition API

Когда закончите, нажмите Enter

Обязательно выберите версию Vue 3.x (Preview) и нажмите Enter.

Разработка приложения с использованием Vue 3 Composition API

Остальные настройки должны быть такими:

Use history mode for router? Yes

Choose the linting version you prefer, I use ESLint + Prettier

Lint on save

In Dedicated config files

Save this as a preset for future projects? No

После завершения установки перейдите в папку приложения с помощью

cd vue3_composition_api

Добавьте tailwind CSS

В своем коде я использовал tailwind CSS. С Vue его очень просто установить.

vue add tailwind

При появлении запроса выберите minimal.

Запустите приложение

В терминале в корне приложения:

npm run serve

Vue запустится и сообщит вам, на каком порту работает приложение, у меня это 8080. Откройте браузер и перейдите к http://localhost:8080

Вы можете заметить некоторые сложности с форматированием, это не проблема, потому что tailwind конфликтует с некоторыми шаблонными CSS, созданными базовым приложением Vue.

Удаление стандартного кода

Мы начнем с удаления некоторых шаблонных файлов и кода, созданного Vue. Удалите HelloWorld.vueиз /src/components/HelloWorld.vue. Удалите About.vueиз/src/views/About.vue.

Отредактируйте, /src/App.vue, чтобы он содержал следующий код.

<template> <div id="app"> <router-view /> </div>
</template>

Отредактируйте, /src/views/Home.vue, чтобы он содержал приведенный ниже код.

<template> <div class="home"></div>
</template> <script>
export default { name: "Home"
};
</script>

Измените /src/router/index.js, чтобы он содержал следующий код.

import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue"; const routes = [ { path: "/", name: "Home", component: Home }
]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes
}); export default router;

После этих изменений у вас не должно быть ошибок в приложении, но браузер должен быть пустым белым холстом.

Добавьте заголовок

Создайте новый файл в /src/components/site/header/Header.vue (вам нужно будет создать папки site/header). В Header.vue добавьте следующий код.

<template> <div class="bg-black h-32 p-8 flex justify-around"> <div class="text-white text-4xl">Composition API Blog</div> <div class="text-white text-xl"><router-link to="/">Home</router-link></div> </div>
</template>

Теперь укажите Vue использовать компонент Header и отобразить его. Отредактируйте файл /src/App.vue, чтобы он содержал следующий код.

<template> <div class="bg-gray-500 flex flex-col min-h-screen"> <Header /> <div class="flex-1 flex w-2/3 mx-auto p-4 text-lg bg-white h-full shadow-lg"> <div class="w-3/5"> <router-view :key="$route.fullPath" /> </div> </div> </div>
</template> <script>
import Header from "@/components/site/header/Header.vue";
export default { components: { Header }
};
</script>

В приведенном выше коде я импортировал компонент Header.vue и добавил его в параметр компонентов. Я добавил в шаблон структуру HTML вместе с некоторыми классами CSS и, конечно же, добавил компонент <Header />.

Также обратите внимание, что я добавил :key=»$route.fullPath» к элементу <router-view>. Это исправляет некоторые проблемы с реактивностью, когда Vue не узнает, что маршрут изменился, если единственное, что нужно изменить в URL-адресе — это параметр. Мы будем использовать параметры URL-адреса для просмотра отдельных статей блога. Если вы проверите браузер, теперь у вас должен быть заголовок и центральный раздел сайта.

Разработка приложения с использованием Vue 3 Composition API

Компоненты записи в блоге

Последние 3 компонента, которые я создам, показаны ниже. Я перейду к контенту, как только мы создадим все файлы и приложение будет готово к использованию.

/src/components/blog/BlogRoll.vue
/src/components/blog/LatestPosts.vue
/src/components/blog/Post.vue

Код для каждого из них приведен ниже. Опять же, это обычные компоненты Vue, основанные на Options API.

/src/components/blog/BlogRoll.vue

<template> <div class="text-4xl font-bold mb-4">My Blog Roll</div> <div v-for="post in posts" :key="post.id"> <div class="bg-gray-200 mb-4 shadow p-2 rounded"> <div class="text-2xl font-semibold"><router-link :to="`/post/${post.id}`">{{ post.title }}</router-link></div> <div class="mt-2 bg-gray-400 p-2 text-gray-700 rounded">{{ post.body }}</div> </div> </div>
</template> <script>
export default { data() { return { posts: [] }; }, methods: { fetchPosts() { fetch("https://jsonplaceholder.typicode.com/posts") .then(response => response.json()) .then(data => (this.posts = data)); } }, mounted() { this.fetchPosts(); }
};
</script> <style></style>

/src/components/blog/LatestPosts.vue

<template> <div class="mx-4 rounded bg-gray-400 shadow"> <div class="p-2"> <div class="font-bold">Latest Posts</div> </div> <div class="bg-white p-2 border-2 border-gray-400 "> <div v-for="post in posts" :key="post.id"> <div class="mb-2">- <router-link :key="`/post/${post.id}`" :to="`/post/${post.id}`">{{ post.title }}</router-link></div> </div> </div> </div>
</template> <script>
export default { data() { return { posts: [] }; }, methods: { fetchPosts() { fetch("https://jsonplaceholder.typicode.com/posts") .then(response => response.json()) .then(data => (this.posts = data.slice(-10))); } }, mounted() { this.fetchPosts(); }
};
</script> <style></style>

/src/components/blog/Post.vue

<template> <div> <div class="text-4xl font-bold">{{ post.title }}</div> <div>{{ post.body }}</div> <div class="italic mt-4">Author: <span class="text-red-500">{{ user.name }}</span></div> </div>
</template> <script>
export default { data() { return { post: {}, user: {} }; }, methods: { fetchPost() { fetch( `https://jsonplaceholder.typicode.com/posts/${this.$route.params.id}` ) .then(response => response.json()) .then(data => (this.post = data)) .then(() => { fetch( `https://jsonplaceholder.typicode.com/users/${this.post.userId}` ) .then(response => response.json()) .then(data => (this.user = data)); }); } }, mounted() { this.fetchPost(); }
};
</script> <style></style>

Теперь давайте подключим эти компоненты к приложению. Отредактируйте файл /src/views/Home.vue следующим образом.

<template> <div class="home"> <BlogRoll /> </div>
</template> <script>
import BlogRoll from '@/components/blog/BlogRoll.vue'
export default { name: "Home", components: { BlogRoll }
};
</script>

В приведенном выше файле я импортировал компонент BlogRoll и отобразил его в шаблоне. Отредактируйте /src/App.vue, чтобы он содержал следующий код.

<template> <div class="bg-gray-500 flex flex-col min-h-screen"> <Header /><!-- //height 32 --> <div class="flex-1 flex w-2/3 mx-auto p-4 text-lg bg-white h-full shadow-lg"> <div class="w-3/5"> <router-view :key="$route.fullPath" /> </div> <div class="w-2/5"> <LatestPosts /> </div> </div> </div>
</template> <script>
import Header from "@/components/site/header/Header.vue";
import LatestPosts from "@/components/blog/LatestPosts.vue"
export default { components: { Header, LatestPosts }
};
</script> <style></style>

Наконец, я добавлю новый маршрут, который позволит просматривать отдельные записи в блогах. Отредактируйте /src/router/index.js и обновите его, чтобы он содержал код, приведенный ниже.

import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import Post from "../components/blog/Post.vue";
const routes = [ { path: "/", name: "Home", component: Home }, { path: "/post/:id", name: "Post", component: Post }
]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes
}); export default router;

Если вы сейчас проверите браузер, все должно работать.

Разработка приложения с использованием Vue 3 Composition API

У нас есть список постов в основном содержимом страницы, если вы кликните по заголовку поста, вы перейдете к этому посту.

Компонент LatestPosts отображается в правой части страницы и показывает только последние 10 постов. Снова кликните один из этих элементов, чтобы просмотреть его полностью.

Резюме на данный момент

Приложение работает. Это очень просто, вы не можете создавать новые сообщения, вместо этого все сообщения берутся с https://jsonplaceholder.typicode.com/

Важно отметить, что каждый из трех компонентов блога извлекает контент из jsonplaceholder. В случае BlogRoll и LatestPosts они оба извлекают один и тот же контент. Лучше сделать это из одного места.

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

Обновление приложения для использования Composition API

На высоком уровне процесс использования Composition API выглядит следующим образом:

Создайте файл javascript, называемый компонуемым, который экспортирует данные и методы, к которым ваши компоненты должны иметь доступ.

Импортируйте компонуемый объект в компонент и добавьте метод setup(), с помощью которого вы можете импортировать данные и методы, экспортированные компонентом.

Давайте сначала преобразуем один из компонентов для использования Composition API, а затем рассмотрим, что мы сделали.

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

Создайте новый файл /src/composables/blog/posts.js и добавьте в него код, приведенный ниже.

import { ref } from "vue"; export default function usePosts() { let post = ref({}); let posts = ref([]); let user = ref({}); function fetchPosts() { fetch(`https://jsonplaceholder.typicode.com/posts`) .then(response => response.json()) .then(data => (posts.value = data)); } function fetchPost(postId) { fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`) .then(response => response.json()) .then(data => (post.value = data)) .then(data => fetchUser(data.userId)); } function fetchUser(userId) { fetch(`https://jsonplaceholder.typicode.com/users/${userId}`) .then(response => response.json()) .then(data => (user.value = data)); } return { fetchPosts, fetchPost, fetchUser, post, posts, user };
}

Давайте рассмотрим некоторые ключевые моменты.

ref

ref используется для того, чтобы сделать переменные в Composition API реактивными. После импорта ref из Vue вы увидите, что я объявил 3 переменные, используя ref():

let post = ref({});
let posts = ref([]);
let user = ref({});

Делая переменную реактивной, ref преобразует ее в объект. Вы больше не обращаетесь к его значению напрямую, вместо этого вы используете .value, например, доступ к можно получить posts array через posts.value[0] или вы можете вставить элемент в его массив с помощью posts.value.push({title: ‘Hello World’}).

Есть и другие методы использования реактивности в API композиции Vue, вы можете прочитать о них здесь.

Все в одном месте

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

Далее мы увидим, как мы можем использовать этот компонуемый файл путем рефакторинга компонента BlogRoll.

Рефакторинг BlogRoll.vue

Отредактируйте /src/components/blog/BlogRoll.vue и обновите его, как показано ниже.

<template> <div class="text-4xl font-bold mb-4">My Blog Roll</div> <div v-for="post in posts" :key="post.id"> <div class="bg-gray-200 mb-4 shadow p-2 rounded"> <div class="text-2xl font-semibold"> <router-link :to="`/post/${post.id}`">{{ post.title }}</router-link> </div> <div class="mt-2 bg-gray-400 p-2 text-gray-700 rounded"> {{ post.body }} </div> </div> </div>
</template> <script>
import usePosts from "@/composables/blog/posts";
import { onMounted } from "vue";
export default { setup() { const { posts, fetchPosts } = usePosts(); onMounted(() => fetchPosts()); return { posts }; }
};
</script> <style></style>

Раздел шаблона не изменился. Новый параметр setup() был добавлен, а предыдущие data, methods и mounted удаляются.

Основная концепция, которую следует отметить в отношении этого параметра setup(), заключается в том, что вы не можете использовать внутри него ключевое слово this, так как setup() запускается до того, как остальная часть компонента станет доступной. Однако вы можете использовать this для ссылки на переменные внутри setup из любых других параметров компонента, то есть изнутри methods или mounted.

Вы видите, что я запускаю onMounted внутри setup, это заменило параметр mounted при использовании composition API. Вы видите, что его тоже нужно импортировать import { onMounted } from «vue»;

Первая строка кода внутри параметра setup() импортирует данные и методы, необходимые для этого компонента, из компонуемого объекта.

const { posts, fetchPosts } = usePosts();

fetchPosts используется только в функции настройки, когда компонент установлен. Наконец, единственный объект, требуемый для template, становится доступным для компонента в последней строке.

return { posts }

Важное примечание: обращаясь к переменным, экспортированным из компонуемого объекта, вы получаете доступ к их значениям через .value. Вам не нужно делать это при интерполяции строк в шаблоне, Vue сделает это за вас. Например, чтобы использовать значение posts внутри параметра setup(), вам нужно будет использовать posts.value, но в шаблоне вы можете просто сослаться на ‘posts’.

Рефакторинг двух оставшихся компонентов

Отредактируйте /src/components/blog/LatestPosts.vue и добавьте в него следующий код:

<template> <div class="mx-4 rounded bg-gray-400 shadow"> <div class="p-2"> <div class="font-bold">Latest Posts</div> </div> <div class="bg-white p-2 border-2 border-gray-400 "> <div v-for="post in recentPosts" :key="post.id"> <div class="mb-2"> - <router-link :key="`/post/${post.id}`" :to="`/post/${post.id}`">{{ post.title }}</router-link> </div> </div> </div> </div>
</template> <script>
import usePosts from "@/composables/blog/posts.js";
import { computed, onMounted, ref } from "vue";
export default { setup() { let recentPosts = ref([]); const { posts, fetchPosts } = usePosts(); onMounted(() => fetchPosts()); recentPosts = computed(() => { return posts.value.slice(-10); }); return { recentPosts }; }
};
</script> <style></style>

Наконец, отредактируйте /src/components/blog/Post.vue и добавьте в него следующий код.

<template> <div> <div class="text-4xl font-bold">{{ post.title }}</div> <div>{{ post.body }}</div> <div class="italic mt-4"> Author: <span class="text-red-500">{{ user.name }}</span> </div> </div>
</template> <script>
import usePosts from "@/composables/blog/posts";
import router from "@/router";
export default { setup() { const { currentRoute } = router; const { fetchPost, post, user } = usePosts(); fetchPost(currentRoute.value.params.id); return { post, user }; }
};
</script> <style></style>

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

Заключение

Я объяснил некоторые преимущества нового Composition API, но вы можете узнать больше и быть в курсе любых обновлений на официальной странице документации Vue для Vue 3.

Мы создали полное приложение с использованием Vue 2 options API и преобразовали его для использования Vue 3 composition API.

Помните, что весь код этого приложения доступен на GitHub. Репо содержит две ветки, одна с приложением, разработанным с использованием Options API, а другая ветка — Composition API. Надеюсь, вы нашли эту статью полезной.

Автор: Simon Carr

Источник: https://levelup.gitconnected.com

Редакция: Команда webformyself.