参考
Vue3+TypeScript 完整项目上手教程 https://mp.weixin.qq.com/s/fo3_KwiKtRLVs6BUfe3iVg
项目介绍: 完整的使用vue3.0 和 Typescript 实现的一个项目, 实现了下拉加载更多的hook方法, 封装axios, 以及最新的vue-router4.0 和vuex4.0 的写法

10月12
vue —version
@vue/cli4.5.3 以上
vue create vue3-study
scss ts vue3x
class-style No
babel for ts No
初始化项目
1、Vue2.0 和 Vue3.0 对比
- Vue2.0源码 采用flow编写,vue3.0源码全是采用TS
- Vue3 的源码体检优化,移除部分api,更好的支持tree-shaking,需要导入,不需要的不导入
- 数据劫持方面,vue3.0 采用Proxy,性能提升大, vue2.0 对对象递归,性能消耗大
- 编译优化,vue3 实现了静态模版分析,重写diff算法
- 复用方面: CompositionAPI: 整合业务代码的逻辑,提取公共逻辑 ( vue2.0 中 采用mixin- 命名冲突)
- 新增 Fragment、Teleport、Suspense组件
2、vue3 的基本使用
App.vue
<template><div id="nav"><router-view /><van-tabbar v-model="active" route><van-tabbar-item to="/" icon="home-o">Home</van-tabbar-item><van-tabbar-item to="/mine" icon="search">Mine</van-tabbar-item><van-tabbar-item to="/profile" icon="friends-o">Profile</van-tabbar-item></van-tabbar></div></template><style>#app {font-family: Avenir, Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;}#nav {padding: 30px;}#nav a {font-weight: bold;color: #2c3e50;}#nav a.router-link-exact-active {color: #42b983;}</style>
main.ts
import { createApp } from "vue";import App from "./App.vue";import router from "./router";import store from './store';import Vant from "vant"import 'vant/lib/index.css'createApp(App).use(store).use(Vant).use(router).mount("#app");
src/views/home/index.vue
<template><div class="home"><h1>This is an home page</h1><home-header></home-header><home-list></home-list><home-swiper></home-swiper></div></template><script lang="ts">import { defineComponent } from "vue";import { useStore } from "vuex";import HomeHeader from "./component/home-header.vue";import HomeList from "./component/home-list.vue";import HomeSwiper from "./component/home-swiper.vue";// import { IGlobalState } from "../../store/index";export default defineComponent({components: {HomeHeader,HomeList,HomeSwiper},setup() {const store = useStore();// store.state}});</script>
3、axios 的封装
简单封装
import axios, { AxiosRequestConfig, AxiosResponse } from "axios"axios.defaults.baseURL = `http://www.fullstackjavascript.cn:3000`;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;
复杂点封装
/*** axios 封装* 请求拦截、响应拦截、错误统一处理*/import axios, { AxiosRequestConfig, AxiosResponse } from "axios"import router from '@/router'import { useStore } from 'vuex'import { Toast } from "vant"const store = useStore<any>();/*** 提示函数*/const tip = (msg: string) => {Toast({message: msg,duration: 1000,forbidClick: true})}/*** 跳转登录页* 携带当前路由,以及在登录页面后返回当前页面*/const toLogin = () => {router.replace({path: '/login',query: {redirect: router.currentRoute.fullpath}})}/*** 请求失败后错误统一处理* @params { Number } status 请求失败的状态码*/const errorHandle = (status: number, other: any) => {// 状态码判断switch (status) {// 401 未登录,跳转登录页case 401:toLogin();break;// 403 token过期 清楚token并跳转登录页case 403:tip('登录过期,请重新登录')localStorage.removeItem('token')store.commit('loginSuccess', null)setTimeout(() => {toLogin()}, 1000)break;// 404case 404:tip('请求资源不存在')break;default:console.log(other)}}// 创建axios实例const instance = axios.create({ timeout: 1000 * 12 });// 设置默认请求地址instance.defaults.baseURL = `http://www.fullstackjavascript.cn:3000`;// 设置post请求头instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';/*** 请求拦截器* 每次请求,都携带token*/instance.interceptors.request.use((config: AxiosRequestConfig) => {const token = store.state.token;token && (config.headers.Authorization = token);return config})// 响应拦截器instance.interceptors.response.use((res: AxiosResponse) =>// 请求成功res.status === 200 ? Promise.resolve(res) : Promise.reject(res),// 请求失败error => {const { response } = error;if (response) {// 请求发出不在2xx范围内errorHandle(response.status, response.data.message)return Promise.reject(response)} else {// 处理断网if (!window.navigator.onLine) {store.commit(`network/changeNetwork`, false);} else {return Promise.reject(error)}}})export default instance;
4、 封装的下拉加载更多hook
import { IGlobalState } from '@/store';import { Ref, onMounted, computed, onUnmounted } from "vue";import { Store } from "vuex";import _ from "lodash";export function useLoadMore(refreshElm: Ref<null | HTMLElement>, store: Store<IGlobalState>, type: string) {// 防抖let element: HTMLElement;let fn: () => voidfunction _loadMore() {let containerHeight = element.clientHeight; // 可视高度let scrollTop = element.scrollTop; // 卷去的高度let scrollHeight = element.scrollHeight; // 整个高度if (containerHeight + scrollTop >= scrollHeight - 20) {store.dispatch(type)}}onMounted(() => {element = refreshElm.value as HTMLElement;element.addEventListener('scroll', fn = _.debounce(_loadMore, 200))})const isLoading = computed(() => {return store.state.home.lessons.loading})const hasMore = computed(() => {return store.state.home.lessons.hasMore})onUnmounted(() => {element.removeEventListener('scroll', fn)})return {isLoading,hasMore}}
5、使用Suspense优化组件
<!-- 轮播图 , Suspense 组件异步 数据没回来 显示loading 成功了显示组件 --><Suspense><template #default><HomeSwiper></HomeSwiper></template><template #fallback><div class="loading">loading</div></template></Suspense>
6、导航模块, 通过keep-alive做组件的缓存处理, van-tabbar 做成底部导航
<template><div id="nav"><!-- 通过 keep-alive 在路由做一下缓存 --><router-view v-slot="{Component}"><keep-alive><component :is="Component"></component></keep-alive></router-view><!-- <router-view/> --><van-tabbar v-model="active" route><van-tabbar-item to="/" icon="home-o">Home</van-tabbar-item><van-tabbar-item to="/mine" icon="search">Mine</van-tabbar-item><van-tabbar-item to="/profile" icon="friends-o">Profile</van-tabbar-item></van-tabbar></div></template>
7、部署
通过修改vue.config.js 的
module.exports = {
publicPath: “/dist/“,
};
