After learning so much about Vue3 and TS concepts, do you feel tempted to put them into practice?
This article will show you how to write a basic project using Vue3+TS. With the main framework concepts in mind, subsequent application development will be much easier.
1. Upgrade the version
Before doing Vue development, we must check which Vue is in version 2.x. For this example, Vue version 4.5.3 or above is required:
Vue - version / / @ vue/cli 4.5.3Copy the code
After the version upgrade, you can create projects using the same command as before:
vue create project-name
Copy the code
Here are the options for each step in creating the sample project:
Vue CLI v4.5.8? Please pick a preset: (Use arrow keys) Default ([Vue 2] Babel, esLint) // Default, vue2 version Default (Vue 3 Preview) ([Vue 3] Babel, eslint) // Default, Vue3 > Manually select features // Manually select configurationCopy the code
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, < I > to invert Selection) >(*) Choose Vue version (*) Babel (*) TypeScript () Progressive Web App (PWA) Support (*) Router (*) Vuex (*) CSS Pre-processors ( ) Linter / Formatter ( ) Unit Testing ( ) E2E TestingCopy the code
? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
2.x
> 3.x (Preview)
Copy the code
? Use class-style component syntax? (y/N) N // Whether to use class-style componentsCopy the code
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n) n // Don't need Babel to cooperate with TSCopy the code
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) Y/ / Requires proper server setup for index fallback in productionCopy the code
? 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 // Select a CSS preprocessorCopy the code
? Where do you prefer placing config for Babel, ESLint, etc.? > In dedicated config files to generate a separate configuration file In package.jsonCopy the code
? Save this as a preset for future projects? (y/N) N // Save this configuration for future useCopy the code
After the project is created, we need to add several files to support the use of TS and asynchronous requests. The corresponding directory structure is shown below:
├─ public ├─server ├─ SRC ├─ API │ ├─ ├. Ts │ ├─assets │ ├─router │ └ └ ─ home. Ts │ └ ─ action - types. Ts │ └ ─ but ts ├ ─ typings │ └ ─ home. Ts │ └ ─ but ts └ ─ views ├ ─ cart ├ ─ home │ └ ─ index. The vue │ └ ─ homeHeader. Vue │ └ ─ homeSwiper. Vue └ ─ mimeCopy the code
In the SRC directory there is a shims-vue.d.ts file, which is a shim file that declares that the **. Vue file ** is a component of this:
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
Copy the code
1.1 introduced Vant
import Vant from 'vant'
import 'vant/lib/index.css'
createApp(App).use(store).use(router).use(Vant).mount('#app')
Copy the code
3. The X version began to use functional programming, so chained calls could be used.
1.2 introduced axios
To simply wrap the request, 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
Copy the code
2. Define routes
Ts: in the router/index.
const routes: Array<RouteRecordRaw> = [];
Copy the code
Specifies that the array element type is RouteRecordRaw, which kindly prompts when defining the route.
The processing of other routes is not much different from before:
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
Copy the code
After defining the route, you can configure the bottom navigation in app.vue:
<van-tabbar route> <van-tabbar-item to="/" icon="home-o"> </van tabbar-item> <van tabbar-item to="/mine" icon=" friend-o "> </van tabbar-item> </van-tabbar>Copy the code
3. Define data structures
With the basic route defined, write the data structure. The general development idea is to define the required data structure, the use will be more convenient, the idea will be clear.
3.1. Declare categories
Add the following to typings/home.ts:
CATEGORY_TYPES has 5 values: all, shoes, socks, shirts, pants
export enum CATEGORY_TYPES {
ALL,
SHOES,
SOCKES,
SHIRT,
PANTS
}
Copy the code
Values of enumerated types start at 0 by default and are automatically ascending; You can also manually specify the value of the first enumerated type and then automatically add one to it.
Then declare the interface to the home file, IHomeState, to contain the current category attributes and must be CATEGORY_TYPES:
export interface IHomeState {
currentCategory: CATEGORY_TYPES
}
Copy the code
Declare the home state in store/moudles/home.ts:
const state:IHomeState = {
currentCategory: CATEGORY_TYPES.ALL,
};
Copy the code
// add the status name to action-types.ts
export const SET_CATEGORY = 'SET_CATEGORY'
Copy the code
const home:Module<IHomeState,IGlobalState> = {
namespaced: true,
state,
mutations: {
[Types.SET_CATEGORY](state,payload:CATEGORY_TYPES){ state.currentCategory = payload; }},actions: {}}export default home;
Copy the code
The home defined here is of the Module type in Vuex and requires passing two generics S and R, the current Module state and the root state. The current state is IHomeState, and the root state is index.ts.
export interface IGlobalState{
home: IHomeState,
// Other status modules will be added later
}
Copy the code
All state will be managed here later, both for use time code hints and for global state management.
3.2 Status Operations
After the state is added, go to home/index.vue for the state operation:
<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>
Copy the code
To get type inference passed to the setup() parameter, you need to use defineComponent.
A useCategory method is defined specifically to handle the switch state. Method uses computed to get the currentCategory value; If you don’t use computed, then this is a dead value, you just take the value and put it there; Only by using computed properties can we ensure that the state changes, the computed new value changes, and is reflected in the view.
Computed has a cache property, and wathcer.value is recalculated only when the value of the dependency changes
SetCurrentCategory is the emitted event of the child component, which is used to call state management to modify currentCategory, back in the homeHeader component:
<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: 'all ',value: Cati_types. ALL}, {text: 'SHOES ',value: cati_types. SHOES}, {text:' socks ',value: cati_types. SOCKES}, {text: 'cati_types. SHOES ', {text:' cati_types. SOCKES ', {text: 'SHIRT ',value: CATEGORY_TYPES.SHIRT}, {text:' CATEGORY_TYPES ',value: CATEGORY_TYPES.PANTS},]}) function change(value: CATEGORY_TYPES. CATEGORY_TYPES){context.emit('setCurrentCategory',value)} return {//ref uses CATEGORY_TYPES to handle simple types... toRefs(state), change } } }) </script>Copy the code
The setup method takes two parameters: props and context. The props object is responsive. Do not structure the props object because it is unresponsive. Context is a context object, similar to the this property in 2.x, with selectively exposed properties.
First, props receives a property from the parent, but it is important to use as to assert that PropType is CATEGORY_TYPES when declaring type.
Second, use Reactive to process data into reactive objects, and use toRefs to convert a reactive object into a normal object. Refs returned in setup are untangled in the template, without writing.value.
emits the name of the method used to register the array emission event, which can be submitted in the convenient form of a code hint when it is actually called;
ModelValue currently belongs to an internally compiled binding attribute method, which is used for value binding. See reference document [2] for detailed compilation.
At this point, you can communicate with parent components and change the page state value by selecting the current category.
3.3 Asynchronous Data Acquisition
Then strike while the iron is hot to achieve dynamic acquisition of round cast data:
Home register homeSwiper component, here no longer express, directly look at the component internal logic
<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>
Copy the code
Writing async to setup is not recommended here, but is put there for demonstration purposes.
UseStore uses vuex’s exposed methods directly, which is much more convenient than the previous binding to this.
Same as the previous component, where computed data is used to get image data to be rotated and asynchronous dispatch is initiated when the data is empty.
First define the data type typings/home.ts:
export interface ISlider{
url:string
}
export interface IHomeState {
currentCategory: CATEGORY_TYPES,
sliders: ISlider[]
}
Copy the code
Add status action name action-types.ts:
export const SET_SLIDER_LIST = 'SET_SLIDER_LIST'
Copy the code
Add slider and define the sliders in the home state as an array of type ISlider.
Then add the state action to 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)
}
}
}
Copy the code
The SLIders data is asynchronously fetched in the action and submitted to the types.set_slider_list to request the interface to fetch the data, then the dependent data is changed by submitting the data state, and computed values are retrieved again, and the rotation graph is displayed on the page.
Section 3.4
In the above process, there is no import code, if you use TS correctly, you will find that import automatically imports the required content through the code prompt function, which will be a feature of TS in the future. Not only have type verification, but also can improve the development process experience.
4. Component slot
<Suspense> <template #default> <HomeSwiper></HomeSwiper> </template> <template #fallback> <div>loading... </div> </template> </Suspense>Copy the code
In Suspense components, the default part is executed by default after fetching data, while the Fallback part is executed for fetching data, making asynchronous processing easier.
However, if you look carefully, you will notice that the console output reads:
is an experimental feature and its API will likely change.
This is an experimental syntax and it is not clear whether the API will be retained in the future, which adds to the anticipation for the release of Vue3.
5. Reference documents
1.Vue combined API
2.Vue 3 Template Exploer