Это статья по обмену практическим опытом.Некоторые специфические функциональные точки реализованы во фронтенде для упрощения.Бэкенда поддержки нет.Основная цель - изучитьVue3+Typescriptсочетаются в средеVuex@4.xтак же какVue-Router@4.xразработка. Общее требование состоит в том, чтобы смоделировать реализацию функции корзины.Функция относительно проста, но она все еще разрабатывается как упрощенная версия проекта.Если у вас есть какие-либо вопросы, пожалуйста, оставьте сообщение для обсуждения и исправления, спасибо .
Давайте сначала посмотрим на эффект:
Возможно~ Вы можете увидеть это в конце:
- Как создать среду проекта, как в названии
- Советы по быстрому созданию SFC путем установки шаблонов
- улучшить понимание
setup
И сценарий использования (надеюсь, не вызвал у всех непонимания) - о
vite
Некоторые базовые знания о настройке - как использовать в тс
vuex@4.x
,vue-router@4.x
- установить глобальный метод в vue3
- more???
если правильноVue3Для тех, кто еще не изучил это, вы можете сначала прочитать эту статью:
Давайте начнем~
Создать проект
первое использованиеVite
создание инструментаVue3+Typescriptсреда проекта
Уведомление:ViteНужна твойNodejsверсия >=12.0.0
// yarn 方式
yarn create @vitejs/app v3-ts --template vue-ts
cd v3-ts
yarn install
// yarn create vite-app v3-ts --template vue-ts 这个1.x的命令
// npm
npm init @vitejs/app v3-ts --template vue-ts
cd v3-ts
npm install
Vite
Также предоставляются некоторые другие шаблоны:
vanilla
vue
vue-ts
react
react-ts
preact
preact-ts
Сначала наведите порядок в проекте и удалите официальную демку (HelloWorld
удаления, связанные с компонентами).
добавить меньше
yarn add less less-loader --dev
// npm
npm install less less-loader --save-dev
Примечание: если не добавлено здесь
--dev
, пакет будет установлен наdependencies
, что приведет к сбою компиляции. нужно бытьless
а такжеless-loader
переехал вdevDependencies
повторно выполнитьyarn
Установить.
Улучшить структуру каталогов
Создайте в проекте несколько папок и соответствующие им шаблоны файлов vue.Структура папок примерно следующая (можно создатьpagesПапки, остальные будут создаваться последовательно):
Советы: Здесь, кстати, я представлю небольшой метод для vscode для быстрого создания страницы или шаблона vue (пожалуйста, игнорируйте, если вы уже это знаете)
Шаг 1: vscode> Настройки> Фрагменты пользователя
Шаг 2: Введите vue, найдите vue.json, чтобы открыть
Шаг 3: Установите шаблон кода (уже подготовлен для всех, скопируйте и вставьте!)
// vue.json
{
"Vue Template":{
"prefix":"vueTemplate",
"body":[
"<template>\n\t<div>\n\n\t</div>\n</template>",
"<script lang=\"ts\">\nimport{ defineComponent }from 'vue';\nexport default defineComponent({\n\tname: \"\",\n\tsetup: () => {\n\n\t}\n})\n</script>",
"<style lang=\"less\" scoped>\n\n</style>"
],
"description":"生成vue文件"
}
}
Шаг 4: ctrl (команда) + S (сохранить)
Шаг 5: Войдите в файл vue.vueTemplate
, vscode выдаст подсказку, смело нажимайте Enter!
step6 : good job ~
// 模版长这样
<template>
<div>
</div>
</template>
<script lang="ts">
import{ defineComponent }from 'vue';
export default defineComponent({
name: "",
setup: () => {
}
})
</script>
<style lang="less" scoped>
</style>
Вы можете установить шаблон в соответствии с вашей собственной ситуацией.
добавить vue-маршрутизатор
Приступаем к настройке маршрутизации.
yarn add vue-router@4.0.1
// 查看历史版本
// npm(yarn) info vue-router versions
Обратите внимание на молниезащиту: не рекомендуется использовать4.0.0Версия (товарищи из инженерной компании, пожалуйста, не стесняйтесь ~).
vue-router
из4.xВ версии представлен уже не класс, а набор функций. мы проходимcreateRouter
Создавайте маршруты.
1.существуетrouter/index.ts
в, создав маршрут и экспортировав его.
Уведомление:существуетv3Есть некоторые изменения в определении асинхронных компонентов, иvite
Заменятьwebpack
, так чтоrequire
а такжеrequire.ensure
Недопустимый способ реализации ленивой загрузки маршрутов.
-
ESсередина
import
// index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Home from "/@pages/Products/index.vue";
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'product',
component: Home,
},
{
path: '/shoppingCart',
name: 'shoppingCart',
component: () => import('/@pages/ShoppingCart/index.vue'),
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
- пройти через
defineAsyncComponent
Создайте
import { defineAsyncComponent } from 'vue'
...
{
path: '/shoppingCart',
name: 'shoppingCart',
component:defineAsyncComponent(() => new Promise((resolve,reject)=>{
...doSomething
resolve({
// 异步组件选项
...
})
})) ,
},
{
path: '/shoppingCart',
name: 'shoppingCart',
component:defineAsyncComponent(() => import('.....')) ,
},
Мы используем псевдонимы для введения компонентов, чтобы сделать путь болеелаконично, ясно. Это требует от нас некоторой настройки, сначала создайте в корневом каталоге проектаvite.config.js
(более высокая версияVite
автоматически создаст этот файл илиvite.config.ts
) и настройте следующим образом.
Уведомление:Возможны ошибки в плагине vetur с подсказкой импортировать некоторые пути через алиасы в vscode, но проблема не большая.Если путь правильный то на работу проекта это не повлияет.Причина не особо понятна мне.Если кто-нибудь знает, пожалуйста, оставьте сообщение и дайте мне знать На мгновение, большое спасибо!
// vite.config.js
const path = require('path');
module.exports = {
alias:{
// 注意这边一定要加双斜杠
'/@pages/':path.resolve(__dirname,'./src/pages'),
'/@components/':path.resolve(__dirname,'./src/components')
}
}
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
alias:{
'@src':'/src/',
}
})
Вот список для васvite.config.ts
Общая конфигурация ,vite.config.js
Конфигурация примерно такая же, детали можно опуститьОфициальный сайтЭталонная конфигурация:
// vite.config.ts
export default defineConfig({
plugins: [vue()],
// 项目根目录,可以是绝对路径也可以是相对配置文件所在的路径
root?: '',
// 运行编译模式 'development' || 'production'
mode?: 'development' ,
// 路径别名
alias?: {} ,
// 全局定义变量替换
define?:{
'':''
},
// build选项
build?:{
base:'/', // 基础路径
target:'modules', // 浏览器兼容模块
outDir:'dist', // 输出路径
assetsDir:'assets' // 静态资源路径
...
},
// 依赖优化项
optimizeDeps?:{
...
},
// 开发服务器
server?:{
host:'', // 主机
prot: 3000, // 端口
https: true, // 是否开启 https
open: false, // 是否在浏览器自动打开
proxy: {
'/api': {
target: 'http://jsonplaceholder.typicode.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
}
...
}
})
Уведомление: В процессе разработки мы обнаружим, что введение файла vue в файл ts вызовет ошибку, что соответствующий модуль не может быть найден. Это связано с тем, что файл ts не может распознать файл vue, нам нужнонаучить его делать вещи,существуетsrc
Создайте файл shims-vue.d.ts в каталоге и отредактируйте его.
// shims-vue.d.ts
declare module '*.vue' {
import { Component } from 'vue'
const component: Component
export default component
}
2.существуетmain.ts
введен вrouter
:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router';
const app = createApp(App);
app.use(router);
app.mount('#app')
3.Наконец вapp.vue
Установите окно просмотра в
// App.vue
<template>
<nav-bar :count="0" :active="'product'"></nav-bar>
<div class="body">
<router-view />
</div>
</template>
Здесь мы представляем наши пользовательские компонентыNavBar
:
<template>
<div class="nav-bar">
<router-link
to="/"
:class="{ active: active === 'product' }"
>商品列表</router-link>
<router-link
to="/shoppingCart"
:class="{ active: active === 'shoppingCart' }"
>购物车{{count?`(${count})`:''}}</router-link>
</div>
</template>
<script lang="ts">
import{ defineComponent }from 'vue';
export default defineComponent({
name: "NavBar",
props:{
count: Number,
active: String
}
})
</script>
<style lang="less" scoped>
...
</style>
Готово, приступаем к сборкеyarn dev
, работает отлично!
Начать заполнение страницы~
Добавить библиотеку компонентов Vant
Чтобы эта демонстрация выглядела более «прилично», давайте добавим еще одну библиотеку компонентов.vant.
npm i vant@next -S
затем следуйтеофициальный сайт ВантШаги установки просто прекрасны.
Стоит отметить, что в нашем проекте используетсяVite, поэтому вам не нужно использовать загрузку по требованию.
Официальное примечание Ванта: В Vite нет необходимости рассматривать проблему внедрения по требованию. Vite автоматически удаляет неиспользуемые модули ESM с помощью Tree Shaking при создании кода. Все модули в Vant 3.0 написаны на основе ESM и, естественно, могут быть введены по запросу. Проблема, оставшаяся на этом этапе, заключается в том, что неиспользуемые стили компонентов не могут быть распознаны и удалены Tree Shaking, и мы рассмотрим возможность их поддержки с помощью плагинов Vite в будущем.
Поскольку мы не используемbabel-plugin-import
Загружайте по требованию, это нужно помнитьимпортировать стиль css! ! ! ! !
// main.ts
import 'vant/lib/index.css';
Внезапно я вспомнил, как мой коллега говорил, как добавлять глобальные методы при отработке v3? Давайте используем тост в качестве примера, oh huo ~ успешно установил засаду (наземную) ручку (свет) позади, и предоставим вам несколько методов.
- существует
app.config.globalProperties
Добавьте свойства.
// main.ts
const app = createApp(...);
// 添加全局方法
app.config.globalProperties.$toast = (msg)=>{
return Toast(msg) // 根据需求自定义
};
// this.$toast('轻提示太舒服了');
- Добавить кminixs
// utility/minix.ts
const mixin = {
methods: {
fn(){
console.log('----doSomething-----');
}
}
}
export default mixin;
// 添加
import mixin from '/@src/utility/minix.ts';
export default defineComponent({
name: "Products",
mixins:[mixin],
...
})
- Извлечение функций и экспорт
// utility/index.ts
import { Toast } from "vant"
export const toast = (msg:string) => {
return Toast(msg);
}
// 调用
import { toast } from '/@src/utility/index.ts';
...
toast('轻提示');
Список продуктов
О рисовании страниц и говорить нечего.Восемь Бессмертных могут показать свои магические силы, когда пересекают море.Засучите рукава и сделайте это.
<template>
<div class="products">
<!-- 在v3里面,v-for,v-if已经可以这么干了,v-if总是优先于v-for -->
<div class="product-list"
v-for="(item,index) in products"
:key="index"
v-if="!loading"
>
<!-- 作者比较懒没有封装成组件,大家请无视 -->
<span class="name">{{item.title}}</span>
<span class="price">{{item.price}}元</span>
<van-button
type="primary"
size="small"
@click="addToCart(item)"
>加入购物车</van-button>
</div>
<van-loading v-else />
</div>
</template>
<script lang="ts">
import { defineComponent,ref }from 'vue';
import { Product } from '/@src/interface';
import { apiGetProducts } from '/@src/api/index';
export default defineComponent({
name: "Products",
setup(){
const products= ref<Product[]>([]);
const loading = ref(false);
// 获取产品列表
const getProducts = async () => {
loading.value = true;
products.value = await apiGetProducts();
loading.value = false;
}
getProducts();
return {
loading, // 加载状态
products // 商品列表
}
},
methods:{
addToCart(product:Product){
console.log('加入购物车');
}
}
})
</script>
<style lang="less" scoped>
...
</style>
Я создал два новых файла со следующими путями:
-
/interface/index.ts
определенная модель -
/api/index.ts
Список интерфейсов
// interface/index.ts
export interface Product {
id:number, // id
title:string, // 名称
price:number, // 价格
count:number // 购买数量
}
// api/index.ts
/**
* 获取产品列表
*/
export const apiGetProducts = ()=>{
return new Promise<Product[]>((resolve,reject)=>{
// 模拟接口请求
setTimeout(()=>{
resolve(data);
},1000)
})
}
данные находятся вapi/data.tsпостроен в,
добавить Vuex@следующий
npm install vuex@next --save
// yarn
yarn add vuex@next --save
1. Новыйstore/index.ts
import { InjectionKey } from 'vue';
import { createStore, useStore as baseUseStore, Store} from 'vuex';
import { Product } from 'src/interface';
export interface State{
shoppingCart: Product[]
}
export const key: InjectionKey<Store<State>> = Symbol();
export const store = createStore<State>({
state:{
shoppingCart:[] // 购物车列表
},
})
export function useStore(){
// 通过key给store提供类型
return baseUseStore(key)
}
2. Добавьте vuex в App.vue
import { store , key} from './store/index';
...
app.use(store,key);
...
мы здесьvuex
Также успешно добавлены в проект, большая частьvuex
Приложение такое же, как и в среде js, за исключением того, что в ts больше оценок типов и некоторые тонкие отличия, используемые в сочетании с v3, которые позже будут использоваться в реальных сценариях.
мы вvuex
Добавьте некоторые из тех, которые мы будем использовать в первую очередьmutations
,getters
.
// store/index.ts
...
export const store = createStore<State>({
state:{
shoppingCart:[]
},
getters:{
// 是否在购物车中已存在
isInCart(state){
return (data:any)=>{
return state.shoppingCart.findIndex(item=>item.id === data.id) > -1 ? true : false;
}
}
},
mutations:{
// 添加购物车
ADD_TO_CARD(state,data){
state.shoppingCart.push(data);
},
// 更新购物车数量
CHANGE_COUNT(state,{type,data}){
return state.shoppingCart.map(item=>{
if(data.id=== item.id){
item.count += type === 'add' ? 1 : -1;
}
return item;
})
},
// 删除购物车
REMOVE_BY_ID(state,id){
state.shoppingCart = state.shoppingCart.filter(item=>item.id!==id);
}
}
})
export function useStore(){
// 通过key给store提供类型
return baseUseStore(key)
}
Далее продолжаем улучшать страницу списка товаров и реализовывать функцию добавления в корзину.
<template>
...
</template>
<script lang="ts">
import { defineComponent ,ref }from 'vue';
import { mapMutations, mapGetters } from 'vuex'
import { Product } from '/@src/interface';
import { apiGetProducts } from '/@src/api/index';
export default defineComponent({
name: "Products",
setup(){
const products= ref<Product[]>([]);
const loading = ref(false);
// 获取产品列表
const getProducts = async () => {
loading.value = true;
products.value = await apiGetProducts();
loading.value = false;
}
getProducts();
return {
loading, // 加载状态
products // 商品列表
}
},
computed:{
...mapGetters(['isInCart'])
},
methods:{
...mapMutations(['ADD_TO_CARD']),
addToCart(product:Product){
// 如果已经存在
if(this.isInCart(product)) return this.$toast('已存在');
// 加入购物车
this.ADD_TO_CARD({
title:product.title,
count:1,
id:product.id
})
this.$toast('添加成功')
}
}
})
</script>
<style lang="less" scoped>
...
</style>
Сначала я написал код этой страницы так, но чем больше я его читаю, тем он становится более хаотичным и несуразным, почему я так говорю?
v3 дает нам новый хукsetup
,существуетsetup
также можно использовать вcomputed
,methods
, но я все же вывел функциональный пункт добавления в корзину, а бизнес-логику постарался сконцентрировать вsetup
в, ноsetup
Нет вthis, и моя логика будет использоватьthis(Подсказка всплывающего окна настроена как глобальный метод), у меня нет возможности сделать это. Я считаю, что такие сценарии слишком распространены, так как же решить эту проблему и сделать код более элегантным и разумным? Какое-то время я был беспомощен, я был правsetup
понимание дляЛогика агрегации, но я смутно чувствую, что яsetup
понимание слишком "поверхностно". Поэтому я пошел на официальный сайт, чтобы учиться снова.
Логика агрегации,чтополимеризация? в области информатикиСвязанныйДанные анализируются и классифицируются. Ключ в том,Связанный",не то чтобы естьsetup
больше ничего писать не надоcomoputed
,methods
охватывать, но мы должны по-настоящему понятьsetup
для чего это, что у наслогические проблемысобраться вместе. Так вот вопросthis.$toast()
Действительно ли это наш основной родственный бизнес?
Если мы это поймем, я думаю, наш код можно оптимизировать так:
<template>
<div class="products">
...
<van-button
type="primary"
size="small"
@click="addHandle(item)"
>加入购物车</van-button>
...
</div>
</template>
<script lang="ts">
...
export default defineComponent({
name: "Products",
setup(){
const products= ref<Product[]>([]);
const loading = ref(false);
const { commit, getters } = useStore();
// 获取产品列表
const getProducts = async () => {
loading.value = true;
products.value = await apiGetProducts();
loading.value = false;
}
// 加入购物车
const addToCart = (product:Product) => {
commit('ADD_TO_CARD',{
title:product.title,
count:1,
id:product.id
})
}
// 判断是否在购物车中已存在
const isInCart = (product:Product)=>{
return getters.isInCart(product);
}
getProducts();
return {
loading, // 加载状态
products, // 商品列表
addToCart, // 加入购物车
isInCart // 是否在购物车中已存在
}
},
methods:{
addHandle(product:Product){
// 如果已经存在
if(this.isInCart(product)) return this.$toast('已存在');
this.addToCart(product);
this.$toast('添加成功')
}
}
})
</script>
Мы агрегировали соответствующую бизнес-логику добавления в корзину, ОК, так удобнее ~
Логика этого примера относительно проста и не объясняет настройку.Типичная сцена, но описанная выше проблема действительно существует на данный момент, оsetup
Применение кода и организация кода могут привести к гибкому реагированию на реальный сценарий спроса.Я надеюсь, что вы оставите много комментариев для обсуждения.
Мы также можем заменить метод извлечения функций выше.this.$toast()
:
<template>
<div class="products">
...
<van-button
type="primary"
size="small"
@click="addHandle(item)"
>加入购物车</van-button>
...
</div>
</template>
<script lang="ts">
...
import { toast } from '/@src/utility/index.ts';
export default defineComponent({
name: "Products",
setup(){
...
// 处理函数
const addHandle = (product:Product) => {
// 如果已经存在
if(isInCart(product)) return toast('已存在');
addToCart(product);
toast('添加成功')
}
getProducts();
return {
loading, // 加载状态
products, // 商品列表
addHandle // 添加购物车
}
}
})
</script>
Выглядит ли этот стиль кода завершенным и красивым?
Обновить App.vue
Чтобы мы могли убедиться, что корзина успешно добавлена, давайте обновим пользовательскийNavBar
, который отображает количество списков корзины покупок в навигации.
// App.vue
<template>
<nav-bar :count="count" :active="activeRouteName"></nav-bar>
<div class="body">
<router-view/>
</div>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from '/@src/store/index'
import NavBar from "/@components/NavBar/index.vue"
export default defineComponent({
name: 'App',
components:{
NavBar
},
setup(props,context) {
const store = useStore();
const route = useRoute(); // this.$route
// 购物车中的商品种类
const count = computed(():number=>{
return store.state.shoppingCart.length;
})
// 当前路由的name
const activeRouteName = computed(():string =>{
return route.name?.toString() || '';
})
return {
count,
activeRouteName
}
}
})
</script>
страница корзины покупок
Говорите как можно меньше!
<template>
<div class="shopping-cart">
<h2>我的购物车</h2>
<div
class="product-info"
v-for="item in shoppingCart"
:key="item.id"
>
<span>{{item.title}}</span>
<div class="btn-box">
<button @click="changeCount('reduce',item)">-</button>
<span>{{item.count}}</span>
<button @click="changeCount('add',item)">+</button>
</div>
<van-button
type="danger"
size="small"
@click="removeHandle(item)"
>删除</van-button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue';
import { Product } from 'src/interface';
import { useStore } from '/@src/store/index';
import { toast } from '/@src/utility/index.ts';
export default defineComponent({
name: "ShoppingCart",
setup: () => {
const { state,commit } = useStore();
const shoppingCart = computed(()=>{
return state.shoppingCart
})
// 更新购物车数量
const changeCount = (type:string,data:Product) => {
// 保证购物车中最小数量为1
if(type === 'reduce' && data.count <= 1) return;
commit('CHANGE_COUNT',{type,data})
}
// 删除购物车
const removeCart = (data:Product) => {
commit('REMOVE_BY_ID',data.id);
}
// 处理函数
const removeHandle = (data:Product) => {
removeCart(data);
toast('删除成功')
}
return {
shoppingCart, // 购物车列表
changeCount, // 更新购物车数量
removeHandle // 删除购物车
}
},
})
</script>
<style lang="less" scoped>
@import './index.less';
</style>
ОК~ Все кончено.
конец
Спасибо за ваше терпение, вы усердно работали, и я надеюсь, что у вас что-то получится.
Дорога проста, легка в познании и трудна в исполнении, единство знания и действия, успех.