解鎖 Vue3 全家桶 - TS 的正確姿勢
創建項目
基礎語法
定義 data
-
script 標籤上 lang="ts"
-
定義一個類型
type
或者接口interface
來約束data
-
可以使用
ref
或者toRefs
來定義響應式數據 -
使用
ref
在setup
讀取的時候需要獲取xxx.value
, 但在template
中不需要 -
使用
reactive
時,可以用toRefs
解構導出,在template
就可以直接使用了
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue';
type Todo = {
id: number,
name: string,
completed: boolean
}
export default defineComponent({
const data = reactive({
todoList: [] as Todo[]
})
const count = ref(0);
console.log(count.value)
return {
...toRefs(data)
}
})
</script>
複製代碼
定義 props
props
需要使用PropType
泛型來約束。
<script lang="ts">
import { defineComponent, PropType} from 'vue';
interface UserInfo = {
id: number,
name: string,
age: number
}
export default defineComponent({
props: {
userInfo: {
type: Object as PropType<UserInfo>, // 泛型類型
required: true
}
},
})
</script>
複製代碼
定義 methods
<script lang="ts">
import { defineComponent, reactive, ref, toRefs } from 'vue';
type Todo = {
id: number,
name: string,
completed: boolean
}
export default defineComponent({
const data = reactive({
todoList: [] as Todo[]
})
// 約束輸入和輸出類型
const newTodo = (name: string):Todo => {
return {
id: this.items.length + 1,
name,
completed: false
};
}
const addTodo = (todo: Todo): void => {
data.todoList.push(todo)
}
return {
...toRefs(data),
newTodo,
addTodo
}
})
</script>
複製代碼
vue-router
-
createRouter
創建router
實例 -
router
的模式分爲: -
createWebHistory
-- history 模式 -
createWebHashHistory
-- hash 模式 -
routes
的約束類型是RouteRecordRaw
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import Home from '../views/Home.vue';
const routes: Array< RouteRecordRaw > = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});
export default router;
複製代碼
擴展路由額外屬性
在實際項目開發中,常常會遇到這麼一個場景,某一個路由是不需要渲染到側邊欄導航上的,此時我們可以給該路由添加一個 hidden 屬性來實現。
在 ts 的強類型約束下,添加額外屬性就會報錯,那麼我們就需要擴展RouteRecordRaw
類型。
// 聯合類型
type RouteConfig = RouteRecordRaw & {hidden?: boolean}; //hidden 是可選屬性
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: Home,
hidden: true,
meta: {
permission: true,
icon: ''
}
}
];
複製代碼
在 setup 中使用
需要導入useRouter
創建一個router
實例。
<script lang="ts">
import { useRouter } from 'vue-router';
import { defineComponent } from 'vue';
export default defineComponent({
setup () {
const router = useRouter();
goRoute(path) {
router.push({path})
}
}
})
</script>
複製代碼
vuex
使用 this.$store
import { createStore } from 'vuex';
export type State = {
count: number
}
export default createStore({
state: {
count: 0
}
});
複製代碼
需要創建一個聲明文件vuex.d.ts
// vuex.d.ts
import {ComponentCustomProperties} from 'vue';
import {Store} from 'vuex';
import {State} from './store'
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$store: Store<State>
}
}
複製代碼
在 setup 中使用
-
定義InjecktionKey
-
在安裝插件時傳入key
-
在使用useStore時傳入
import { InjectionKey } from 'vue';
import { createStore, Store } from 'vuex';
export type State = {
count: number
}
// 創建一個injectionKey
export const key: InjectionKey<Store<State>> = Symbol('key');
複製代碼
// main.ts
import store, { key } from './store';
app.use(store, key);
複製代碼
<script lang="ts">
import { useStore } from 'vuex';
import { key } from '@/store';
export default defineComponent({
setup () {
const store = useStore(key);
const count = computed(() => store.state.count);
return {
count
}
}
})
</script>
複製代碼
模塊
新增一個todo
模塊。導入的模塊,需要是一個vuex
中的 interface Module
的對象, 接收兩個泛型約束,第一個是該模塊類型,第二個是根模塊類型。
// modules/todo.ts
import { Module } from 'vuex';
import { State } from '../index.ts';
type Todo = {
id: number,
name: string,
completed: boolean
}
const initialState = {
todos: [] as Todo[]
};
export type TodoState = typeof initialState;
export default {
namespaced: true,
state: initialState,
mutations: {
addTodo (state, payload: Todo) {
state.todos.push(payload);
}
}
} as Module<TodoState, State>; //Module<S, R> S 該模塊類型 R根模塊類型
複製代碼
// index.ts
export type State = {
count: number,
todo?: TodoState // 這裏必須是可選,不然state會報錯
}
export default createStore({
state: {
count: 0
}
modules: {
todo
}
});
複製代碼
使用:
setup () {
console.log(store.state.todo?.todos);
}
複製代碼
elementPlus
yarn add element-plus
複製代碼
完整引入
import { createApp } from 'vue'
import ElementPlus from 'element-plus';import 'element-plus/lib/theme-chalk/index.css';import App from './App.vue';
import 'dayjs/locale/zh-cn'
import locale from 'element-plus/lib/locale/lang/zh-cn'
const app = createApp(App)
app.use(ElementPlus, { size: 'small', zIndex: 3000, locale })
app.mount('#app')
複製代碼
按需加載
需要安裝babel-plugin-component
插件:
yarn add babel-plugin-component -D
// babel.config.js
plugins: [
[
'component',
{
libraryName: 'element-plus',
styleLibraryName: 'theme-chalk'
}
]
]
複製代碼
import 'element-plus/lib/theme-chalk/index.css';
import 'dayjs/locale/zh-cn';
import locale from 'element-plus/lib/locale';
import lang from 'element-plus/lib/locale/lang/zh-cn';
import {
ElAside,
ElButton,
ElButtonGroup,
} from 'element-plus';
const components: any[] = [
ElAside,
ElButton,
ElButtonGroup,
];
const plugins:any[] = [
ElLoading,
ElMessage,
ElMessageBox,
ElNotification
];
const element = (app: any):any => {
// 國際化
locale.use(lang);
// 全局配置
app.config.globalProperties.$ELEMENT = { size: 'small' };
components.forEach(component => {
app.component(component.name, component);
});
plugins.forEach(plugin => {
app.use(plugin);
});
};
export default element;
複製代碼
// main.ts
import element from './plugin/elemment'
const app = createApp(App);
element(app);
複製代碼
axios
axios 的安裝使用和 vue2 上沒有什麼大的區別,如果需要做一些擴展屬性,還是需要聲明一個新的類型。
type Config = AxiosRequestConfig & {successNotice? : boolean, errorNotice? : boolean}
複製代碼
import axios, { AxiosResponse, AxiosRequestConfig } from 'axios';
import { ElMessage } from 'element-plus';
const instance = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL || '',
timeout: 120 * 1000,
withCredentials: true
});
// 錯誤處理
const err = (error) => {
if (error.message.includes('timeout')) {
ElMessage({
message: '請求超時,請刷新網頁重試',
type: 'error'
});
}
if (error.response) {
const data = error.response.data;
if (error.response.status === 403) {
ElMessage({
message: 'Forbidden',
type: 'error'
});
}
if (error.response.status === 401) {
ElMessage({
message: 'Unauthorized',
type: 'error'
});
}
}
return Promise.reject(error);
};
type Config = AxiosRequestConfig & {successNotice? : boolean, errorNotice? : boolean}
// 請求攔截
instance.interceptors.request.use((config: Config) => {
config.headers['Access-Token'] = localStorage.getItem('token') || '';
return config;
}, err);
// 響應攔截
instance.interceptors.response.use((response: AxiosResponse) => {
const config: Config = response.config;
const code = Number(response.data.status);
if (code === 200) {
if (config && config.successNotice) {
ElMessage({
message: response.data.msg,
type: 'success'
});
}
return response.data;
} else {
let errCode = [402, 403];
if (errCode.includes(response.data.code)) {
ElMessage({
message: response.data.msg,
type: 'warning'
});
}
}
}, err);
export default instance;
複製代碼
setup script
官方提供了一個實驗性的寫法,直接在script
裏面寫setup
的內容,即:setup script
。
之前我們寫組件是這樣的:
<template>
<div>
{{count}}
<ImgReview></ImgReview >
</div>
</template>
<script lang="ts">
import { ref, defineComponent } from "vue";
import ImgReview from "./components/ImgReview.vue";
export default defineComponent({
components: {
ImgReview,
},
setup() {
const count = ref(0);
return { count };
}
});
</script>
複製代碼
啓用setup script
後:在script
上加上setup
<template>
<div>
{{count}}
<ImgReview></ImgReview>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import ImgReview from "./components/ImgReview.vue";
const count = ref(0);
</script>
複製代碼
是不是看起來簡潔了很多,組件直接導入就行了,不用註冊組件,數據定義了就可以用。其實我們可以簡單的理解爲script
包括的內容就是setup
中的,並做了return
。
導出方法
<script lang="ts" setup>
const handleClick = (type: string) => {
console.log(type);
}
</script>
複製代碼
定義 props
使用props
需要用到defineProps
來定義,具體用法跟之前的props
寫法類似:
基礎用法
<script lang="ts" setup>
import { defineProps } from "vue";
const props = defineProps(['userInfo', 'gameId']);
</script>
複製代碼
構造函數進行檢查 給 props 定義類型:
const props = defineProps({
gameId: Number,
userInfo: {
type: Object,
required: true
}
});
複製代碼
使用類型註解進行檢查
defineProps<{
name: string
phoneNumber: number
userInfo: object
tags: string[]
}>()
複製代碼
可以先定義好類型:
interface UserInfo {
id: number,
name: string,
age: number
}
defineProps<{
name: string
userInfo: UserInfo
}>()
複製代碼
defineEmit
<script lang="ts" setup>
import { defineEmit } from 'vue';
// expects emits options
const emit = defineEmit(['kk', 'up']);
const handleClick = () => {
emit('kk', '點了我');
};
</script>
複製代碼
<Comp @kk="handleClick"/>
<script lang="ts" setup>
const handleClick = (data) => {
console.log(data)
}
</script>
複製代碼
獲取上下文
在標準組件寫法裏,setup 函數默認支持兩個入參:
在 setup script 中使用 useContext 獲取上下文:
<script lang="ts" setup>
import { useContext } from 'vue'
const { slots, attrs } = useContext();
</script>
複製代碼
獲取到的slots
,attrs
跟setup
裏面的是一樣的。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/rv1lXw4VwBAk9B8UUC34wA