参考

Vue3+TypeScript 完整项目上手教程 https://mp.weixin.qq.com/s/fo3_KwiKtRLVs6BUfe3iVg

项目介绍: 完整的使用vue3.0 和 Typescript 实现的一个项目, 实现了下拉加载更多的hook方法, 封装axios, 以及最新的vue-router4.0 和vuex4.0 的写法

codding 项目地址

项目部署后页面地址

  1. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1225858/1602663605564-6a8181f9-3a73-409d-a2f2-bf6b0262a6c8.png#align=left&display=inline&height=589&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1178&originWidth=664&size=194116&status=done&style=none&width=332)

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

  1. <template>
  2. <div id="nav">
  3. <router-view />
  4. <van-tabbar v-model="active" route>
  5. <van-tabbar-item to="/" icon="home-o">Home</van-tabbar-item>
  6. <van-tabbar-item to="/mine" icon="search">Mine</van-tabbar-item>
  7. <van-tabbar-item to="/profile" icon="friends-o">Profile</van-tabbar-item>
  8. </van-tabbar>
  9. </div>
  10. </template>
  11. <style>
  12. #app {
  13. font-family: Avenir, Helvetica, Arial, sans-serif;
  14. -webkit-font-smoothing: antialiased;
  15. -moz-osx-font-smoothing: grayscale;
  16. text-align: center;
  17. color: #2c3e50;
  18. }
  19. #nav {
  20. padding: 30px;
  21. }
  22. #nav a {
  23. font-weight: bold;
  24. color: #2c3e50;
  25. }
  26. #nav a.router-link-exact-active {
  27. color: #42b983;
  28. }
  29. </style>

main.ts

  1. import { createApp } from "vue";
  2. import App from "./App.vue";
  3. import router from "./router";
  4. import store from './store';
  5. import Vant from "vant"
  6. import 'vant/lib/index.css'
  7. createApp(App)
  8. .use(store)
  9. .use(Vant)
  10. .use(router)
  11. .mount("#app");

src/views/home/index.vue

  1. <template>
  2. <div class="home">
  3. <h1>This is an home page</h1>
  4. <home-header></home-header>
  5. <home-list></home-list>
  6. <home-swiper></home-swiper>
  7. </div>
  8. </template>
  9. <script lang="ts">
  10. import { defineComponent } from "vue";
  11. import { useStore } from "vuex";
  12. import HomeHeader from "./component/home-header.vue";
  13. import HomeList from "./component/home-list.vue";
  14. import HomeSwiper from "./component/home-swiper.vue";
  15. // import { IGlobalState } from "../../store/index";
  16. export default defineComponent({
  17. components: {
  18. HomeHeader,
  19. HomeList,
  20. HomeSwiper
  21. },
  22. setup() {
  23. const store = useStore();
  24. // store.state
  25. }
  26. });
  27. </script>

3、axios 的封装

简单封装

  1. import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
  2. axios.defaults.baseURL = `http://www.fullstackjavascript.cn:3000`;
  3. axios.interceptors.request.use((config: AxiosRequestConfig) => {
  4. return config
  5. })
  6. axios.interceptors.response.use((response: AxiosResponse) => {
  7. if (response.data.err == 1) {
  8. return Promise.reject(response.data.data)
  9. }
  10. return response.data.data
  11. }, err => {
  12. return Promise.reject(err)
  13. })
  14. export default axios;

复杂点封装

  1. /**
  2. * axios 封装
  3. * 请求拦截、响应拦截、错误统一处理
  4. */
  5. import axios, { AxiosRequestConfig, AxiosResponse } from "axios"
  6. import router from '@/router'
  7. import { useStore } from 'vuex'
  8. import { Toast } from "vant"
  9. const store = useStore<any>();
  10. /**
  11. * 提示函数
  12. */
  13. const tip = (msg: string) => {
  14. Toast({
  15. message: msg,
  16. duration: 1000,
  17. forbidClick: true
  18. })
  19. }
  20. /**
  21. * 跳转登录页
  22. * 携带当前路由,以及在登录页面后返回当前页面
  23. */
  24. const toLogin = () => {
  25. router.replace({
  26. path: '/login',
  27. query: {
  28. redirect: router.currentRoute.fullpath
  29. }
  30. })
  31. }
  32. /**
  33. * 请求失败后错误统一处理
  34. * @params { Number } status 请求失败的状态码
  35. */
  36. const errorHandle = (status: number, other: any) => {
  37. // 状态码判断
  38. switch (status) {
  39. // 401 未登录,跳转登录页
  40. case 401:
  41. toLogin();
  42. break;
  43. // 403 token过期 清楚token并跳转登录页
  44. case 403:
  45. tip('登录过期,请重新登录')
  46. localStorage.removeItem('token')
  47. store.commit('loginSuccess', null)
  48. setTimeout(() => {
  49. toLogin()
  50. }, 1000)
  51. break;
  52. // 404
  53. case 404:
  54. tip('请求资源不存在')
  55. break;
  56. default:
  57. console.log(other)
  58. }
  59. }
  60. // 创建axios实例
  61. const instance = axios.create({ timeout: 1000 * 12 });
  62. // 设置默认请求地址
  63. instance.defaults.baseURL = `http://www.fullstackjavascript.cn:3000`;
  64. // 设置post请求头
  65. instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
  66. /**
  67. * 请求拦截器
  68. * 每次请求,都携带token
  69. */
  70. instance.interceptors.request.use((config: AxiosRequestConfig) => {
  71. const token = store.state.token;
  72. token && (config.headers.Authorization = token);
  73. return config
  74. })
  75. // 响应拦截器
  76. instance.interceptors.response.use((res: AxiosResponse) =>
  77. // 请求成功
  78. res.status === 200 ? Promise.resolve(res) : Promise.reject(res),
  79. // 请求失败
  80. error => {
  81. const { response } = error;
  82. if (response) {
  83. // 请求发出不在2xx范围内
  84. errorHandle(response.status, response.data.message)
  85. return Promise.reject(response)
  86. } else {
  87. // 处理断网
  88. if (!window.navigator.onLine) {
  89. store.commit(`network/changeNetwork`, false);
  90. } else {
  91. return Promise.reject(error)
  92. }
  93. }
  94. }
  95. )
  96. export default instance;

4、 封装的下拉加载更多hook

  1. import { IGlobalState } from '@/store';
  2. import { Ref, onMounted, computed, onUnmounted } from "vue";
  3. import { Store } from "vuex";
  4. import _ from "lodash";
  5. export function useLoadMore(refreshElm: Ref<null | HTMLElement>, store: Store<IGlobalState>, type: string) {
  6. // 防抖
  7. let element: HTMLElement;
  8. let fn: () => void
  9. function _loadMore() {
  10. let containerHeight = element.clientHeight; // 可视高度
  11. let scrollTop = element.scrollTop; // 卷去的高度
  12. let scrollHeight = element.scrollHeight; // 整个高度
  13. if (containerHeight + scrollTop >= scrollHeight - 20) {
  14. store.dispatch(type)
  15. }
  16. }
  17. onMounted(() => {
  18. element = refreshElm.value as HTMLElement;
  19. element.addEventListener('scroll', fn = _.debounce(_loadMore, 200))
  20. })
  21. const isLoading = computed(() => {
  22. return store.state.home.lessons.loading
  23. })
  24. const hasMore = computed(() => {
  25. return store.state.home.lessons.hasMore
  26. })
  27. onUnmounted(() => {
  28. element.removeEventListener('scroll', fn)
  29. })
  30. return {
  31. isLoading,
  32. hasMore
  33. }
  34. }

5、使用Suspense优化组件

  1. <!-- 轮播图 , Suspense 组件异步 数据没回来 显示loading 成功了显示组件 -->
  2. <Suspense>
  3. <template #default>
  4. <HomeSwiper></HomeSwiper>
  5. </template>
  6. <template #fallback>
  7. <div class="loading">loading</div>
  8. </template>
  9. </Suspense>

6、导航模块, 通过keep-alive做组件的缓存处理, van-tabbar 做成底部导航

  1. <template>
  2. <div id="nav">
  3. <!-- 通过 keep-alive 在路由做一下缓存 -->
  4. <router-view v-slot="{Component}">
  5. <keep-alive>
  6. <component :is="Component"></component>
  7. </keep-alive>
  8. </router-view>
  9. <!-- <router-view/> -->
  10. <van-tabbar v-model="active" route>
  11. <van-tabbar-item to="/" icon="home-o">Home</van-tabbar-item>
  12. <van-tabbar-item to="/mine" icon="search">Mine</van-tabbar-item>
  13. <van-tabbar-item to="/profile" icon="friends-o">Profile</van-tabbar-item>
  14. </van-tabbar>
  15. </div>
  16. </template>

7、部署

通过修改vue.config.js 的
module.exports = {
publicPath: “/dist/“,
};