Vue3+TS, написать полноценный проект

Vue.js
Vue3+TS, написать полноценный проект

Я так много узнал о концепциях Vue3 и TS. Хотите попрактиковаться?

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

1. Обновление версии

Все, должно быть, разрабатывали Vue раньше с версией 2.x. Перед этим проектом проверьте, в какой версии находится Vue. Для этого примера требуется версия 4.5.3 и выше:

vue --version
//@vue/cli 4.5.3

Проект можно создать после обновления версии, используя ту же команду, что и раньше:

vue create project-name

Ниже показаны различные этапы создания примера проекта:

Vue CLI v4.5.8
? Please pick a preset: (Use arrow keys)
  Default ([Vue 2] babel, eslint)		//默认,vue2版本
  Default (Vue 3 Preview) ([Vue 3] babel, eslint)	//默认,vue3版本
> Manually select features  		//手动选择配置
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Choose Vue version		//选择vue版本
 (*) Babel
 (*) TypeScript
 ( ) Progressive Web App (PWA) Support
 (*) Router
 (*) Vuex
 (*) CSS Pre-processors
 ( ) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing    
? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
  2.x
> 3.x (Preview)  
? Use class-style component syntax? (y/N)  N
//是否使用类样式组件
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) N
//不需要babel去配合TS
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y 
//使用路由的历史模式
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
> Sass/SCSS (with dart-sass)
  Sass/SCSS (with node-sass)
  Less
  Stylus 
//选择CSS预处理器
? Where do you prefer placing config for Babel, ESLint, etc.?
> In dedicated config files   生成独立的配置文件
  In package.json 
? Save this as a preset for future projects? (y/N) N 
//保存这个配置以备将来使用

После создания проекта для совместной работы с использованием TS и асинхронных запросов необходимо самостоятельно добавить несколько файлов.Соответствующая структура каталогов показана следующим образом:

─public
├─server
└─src
    ├─api
    │  └─index.ts
    │  └─home.ts
    ├─assets
    ├─components
    ├─router
    │  └─index.ts
    ├─store
    │  └─modules
    │  	  └─home.ts
    │  └─action-types.ts
    │  └─index.ts
    ├─typings
    │  └─home.ts
    │  └─index.ts
    └─views
        ├─cart
        ├─home
        │  └─index.vue
        │  └─homeHeader.vue
        │  └─homeSwiper.vue
        └─mine

В каталоге src есть файл shims-vue.d.ts, который представляет собой файл-прокладку, в котором объявляется, что **файл .vue** является таким компонентом:

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

1.1 Знакомство с Вантом

import Vant from 'vant'
import 'vant/lib/index.css'

createApp(App).use(store).use(router).use(Vant).mount('#app')

Версия 3.x начала использовать функциональное программирование, поэтому можно использовать цепные вызовы.

1.2 Знакомство с аксиомами

Простая инкапсуляция запроса,axios/index.ts:

import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'

axios.defaults.baseURL = 'http://localhost:3001/'

axios.interceptors.request.use((config:AxiosRequestConfig) => {
    return config;
})

axios.interceptors.response.use((response:AxiosResponse) => {
    if(response.data.err == 1){
        return Promise.reject(response.data.data);
    }
    return response.data.data;
},err => {
    return Promise.reject(err);
})

export default axios

2. Определите маршрут

существуетrouter/index.tsсередина:

const routes: Array<RouteRecordRaw> = [];

Указывает, что тип элемента массиваRouteRecordRaw, который предоставляет удобные подсказки при определении маршрутов.

Обработка других маршрутов не сильно отличается от предыдущей:

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/home/index.vue')
  },
  {
    path: '/cart',
    name: 'Cart',
    component: () => import('../views/cart/index.vue')
  },{
    path: '/mine',
    name: 'Mine',
    component: () => import('../views/mine/index.vue')
  }
]
const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})
export default router

После определения маршрута вы можете настроить нижнюю навигацию в App.vue:

<van-tabbar route>
      <van-tabbar-item to="/" icon="home-o">首页</van-tabbar-item>
      <van-tabbar-item to="/cart" icon="shopping-cart-o">购物车</van-tabbar-item>
      <van-tabbar-item to="/mine" icon="friends-o">我的</van-tabbar-item>
</van-tabbar>

3. Определите структуру данных

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

3.1. Категории объявлений

Добавьте следующее в typings/home.ts:

CATEGORY_TYPESИмеет 5 значений: Все, Обувь, Носки, Рубашка, Брюки.

export enum CATEGORY_TYPES {
    ALL,
    SHOES,
    SOCKES,
    SHIRT,
    PANTS
}

Значение типа перечисления по умолчанию начинается с 0 и автоматически увеличивается, также можно вручную указать значение первого типа перечисления, а затем автоматически добавить его на этом основании.

Затем объявите интерфейс домашнего файлаIHomeState, который указан как содержащий текущий атрибут классификации и должен бытьCATEGORY_TYPESТипы:

export interface IHomeState {
    currentCategory: CATEGORY_TYPES
}

Объявите состояние дома в store/moudles/home.ts:

const state:IHomeState = {
    currentCategory: CATEGORY_TYPES.ALL,
};
//action-types.ts中添加状态名称
export const SET_CATEGORY = 'SET_CATEGORY'
const home:Module<IHomeState,IGlobalState> = {
    namespaced: true,
    state,
    mutations: {
        [Types.SET_CATEGORY](state,payload:CATEGORY_TYPES){
            state.currentCategory = payload;
        }
    },
    actions: {}
}

export default home;

Определенный здесь дом принадлежит типу модуля в Vuex, и необходимо передать два общих типа S и R, которые являются состоянием и корневым состоянием текущего модуля соответственно. Текущее состояние — IHomeState, а корневому состоянию необходимо объявить интерфейс глобального типа в index.ts:

export interface IGlobalState{
  home: IHomeState,
      //这里后续增加其他状态模块
}

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

3.2 Статус Операция

После добавления состояния перейдите в home/index.vue, чтобы выполнить операцию состояния:

<template>
<HomeHeader :category="category" @setCurrentCategory="setCurrentCategory"></HomeHeader>
</template>
<script lang="ts">
function useCategory(store: Store < IGlobalState > ) {
    let category = computed(() => {
        return store.state.home.currentCategory
    })   
    function setCurrentCategory(category: CATEGORY_TYPES) {
        store.commit(`home/${Types.SET_CATEGORY}`, category)
    }
    return {
        category,
        setCurrentCategory
    }
}
export default defineComponent({
    components: {
        HomeHeader,
        HomeSwiper,
    },
    setup() {
        let store = useStore < IGlobalState > ();
        let {
            category,
            setCurrentCategory
        } = useCategory(store);
        return {
          category,
          setCurrentCategory
        }
    }
})    
</script>    

Для того, чтобы пройти кsetup()Для вывода типа параметра используйте defineComponent.

здесь определяетuseCategoryметод, предназначенный для обработки состояния переключения. используемый методcomputedПолучатьcurrentCategoryзначение; если не используетсяcomputed, то это мертвое значение, просто получите значение и поместите его сюда; только с помощью вычисляемого свойства можно изменить состояние, вычисленное новое значение также измениться и реагировать на представление.

computedИмеет свойство кеша, только при изменении значения зависимости будет пересчитывать watcher.value один раз

setCurrentCategoryКак событие эмиссии дочернего компонента, оно используется для вызова управления состоянием для изменения currentCategory, и здесь оно возвращается к компоненту homeHeader:

<template>
<div class="header">
    //....
    <van-dropdown-menu class="menu">
        <van-dropdown-item :modelValue="category" :options="option" @change="change"/>
    </van-dropdown-menu>
</div>
</template>
<script lang="ts">
export default defineComponent({
    props: {
        category: {
            type: Number as PropType<CATEGORY_TYPES>
        }
    },
    emits: ['setCurrentCategory'],   
    setup(props,context){
        let state = reactive({
            option: [
                {text: '全部',value: CATEGORY_TYPES.ALL},
                {text: '鞋子',value: CATEGORY_TYPES.SHOES},
                {text: '袜子',value: CATEGORY_TYPES.SOCKES},
                {text: '衬衫',value: CATEGORY_TYPES.SHIRT},
                {text: '裤子',value: CATEGORY_TYPES.PANTS},
            ]
        })
        function change(value: CATEGORY_TYPES){
            context.emit('setCurrentCategory',value) 
        }
        return {
            //ref用来处理简单类型
            ...toRefs(state),
            change
        }
    }
})    
</script>    

В это время метод setup получает два параметра: props и context, где объект props является отзывчивым, будьте осторожны, чтобы не структурировать объект props, что сделает его невосприимчивым; context — это объект контекста, аналогичный свойству this в 2. x и выборочно отображать некоторые свойства.

Прежде всего, props по-прежнему получает значение атрибута родительского компонента, но здесь следует отметить, что при объявлении типа используйтеasутверждениеPropTypeДля типа CATEGORY_TYPES;

Во-вторых, используйте reactive для обработки данных в реактивные объекты, используйте toRefs для преобразования реактивного объекта в обычный объект и используйте toRefs.setupСсылка, возвращаемая в шаблоне, будет автоматически развернута в шаблоне, писать не нужно.value;

emitsИмя метода, используемого для регистрации события эмиссии массива, может быть представлено в удобной форме подсказки кода, когда он действительно вызывается;

В настоящее время modelValue относится к внутренне скомпилированному методу свойства привязки, который используется здесь для привязки значения.Для конкретной компиляции см. справочный документ [2].

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

3.3 Асинхронный сбор данных

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

Зарегистрируйте на главной странице компонент homeSwiper, который не будет здесь описываться, а непосредственно посмотрите на внутреннюю логику компонента

<template>
    <van-swipe v-if="sliderList.length" class="my-swipe" :autoplay="3000" indicator-color="white">
        <van-swipe-item v-for="l in sliderList" :key="l.url">
            <img class="banner" :src="l.url" alt="">
        </van-swipe-item>
    </van-swipe>
</template>
<script lang="ts">
export default defineComponent({
    async setup(){
        let store = useStore<IGlobalState>();
        let sliderList = computed(() => store.state.home.sliders);
        if(sliderList.value.length == 0){     
            await store.dispatch(`home/${Types.SET_SLIDER_LIST}`);  
        }   
        return {
            sliderList
        }
    }
})
</script>

Не рекомендуется писать асинхронность для настройки здесь, а поставить сюда для демонстрации.

useStore напрямую использует методы, предоставляемые vuex, что более удобно, чем привязка к этому ранее.

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

Сначала определите тип данных typings/home.ts:

export interface ISlider{
    url:string
}
export interface IHomeState {
    currentCategory: CATEGORY_TYPES,
    sliders: ISlider[]
}

Добавьте имя действия состояния action-types.ts:

export const SET_SLIDER_LIST = 'SET_SLIDER_LIST'

Тип ползунка добавляется на исходной основе, а ползунки в исходном состоянии определяются как массив типа ISlider.

Затем добавьте операции с состоянием в modules/home.ts:

const state:IHomeState = {
    currentCategory: CATEGORY_TYPES.ALL,
    sliders: []
}
const home:Module<IHomeState,IGlobalState> = {
    namespaced: true,
    state,
    mutations: {
        [Types.SET_CATEGORY](state,payload:CATEGORY_TYPES){
            state.currentCategory = payload;
        },
        [Types.SET_SLIDER_LIST](state,payload:ISlider[]){
            state.sliders = payload;
        }
    },
    actions: {
        async [Types.SET_SLIDER_LIST]({commit}){
            let sliders = await getSliders<ISlider>();
            commit(Types.SET_SLIDER_LIST,sliders)
        }
    }
}

Получайте данные ползунков асинхронно в действии и отправляйтеTypes.SET_SLIDER_LISTДанные получаются путем запроса интерфейса в середине, а затем зависимые данные изменяются путем отправки статуса данных.Значение, полученное через вычисление, будет повторно получено, и карусель будет отображаться на странице.

Раздел 3.4

В приведенном выше процессе нет кода импортированного контента.Если вы правильно используете TS, вы можете обнаружить, что импорт может автоматически импортировать требуемый контент с помощью функции подсказки кода, что также станет важной функцией TS в будущем. Он не только имеет проверку типов, но и улучшает процесс разработки.

4. Слоты компонентов

<Suspense>
	<template #default>
		<HomeSwiper></HomeSwiper>
	</template>
	<template #fallback>
		<div>loading...</div>
	</template>
</Suspense>

Здесь показано использование слота асинхронного рендеринга компонентов в Vue3:SuspenseВ компоненте выполняется по умолчанию при получении данныхdefaultЧасть контента, которая выполняется при получении данныхfallbackНекоторое содержимое упрощается при асинхронной обработке.

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

is an experimental feature and its API will likely change.

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

5. Справочная документация

1.API композиции Vue

2.Vue 3 Template Exploer