vue-compositionAPI + ts + vue-cli4 工程搭建

431 阅读3分钟

vue-compositionAPI + ts + vue-cli4 工程搭建

1. 更新安装最新vue-cli4

安装cli4.4 npm i -g @vue/cli

2.安装composition-api

1. 为什么不直接升级vue3

因为目前vue3生态还没完善,比如加入typescript,目前还有挺多模块没有支持typescript,旧的UI组件库还没能支持vue3,会出现报错情况,还不太清楚如何去配置。

2.安装composition-api

yarn add @vue/composition-api

3. 项目初始化配置

1 项目总体预览

  • 统一的模块化路由

  • 统一的Ajax模块化,fetchapienv

  • 统一的vuex模块化 --> 改变成vue3支持的provideinject

  • 统一的公共方法, ulits

  • 统一的第三方UI组件

2 项目统一配置

1 router配置

  1. vue-router版本是3.2,如何是最新版本会存在不同

  2. require.context,来全局导入文件

    // 由于webpack-env不支持ts导入,RequireContext在项目中暂时不做处理
    const requireFile: RequireContext = require.context(
    	'./', // path: string
        true, // deep?: boolean
        /\.ts/ // filter?: RegExp, mode?: "sync" | "eager" | "weak" | "lazy" | "lazy-once"
    )
    requireFile.keys().forEach((file) => {
        // 将index.ts return,我们需要的是当前文件下的其他文件夹下面的文件
        if(file === './index.ts') {
          return;
        }
        const config = requireFile(file);
        routes = [...routes, ...(config.default || config)]; // 合并routes
      })
    

2. axios配置

  1. 配置不同env环境下的baseUrl.ts

    const env = process.env.NODE_ENV
    interface Baseconfig = {
        [propName: string]: string
    }
    const api: Baseconfig = {
        development: 'http://localhost:8080/',
      	production: 'https://xxx/'
    }
    
    export const baseUrl: string = api[env]
    
  2. 配置ajax.ts,导入baseUrl.ts

    这里只是提供一种方案,没有很详细

    import axios from 'axios';
    import Vue from 'vue';
    import {baseUrl} from './baseURL'
    import {ResponseType} from './baseInterface'
    import qs from 'qs';
    import thisVue from '../main';
    // 设置统一的过期时间 20s
    axios.defaults.timeout = 20000;
    
    // 接口路径统一配置
    axios.defaults.baseURL = baseUrl;
    
    axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
    axios.defaults.withCredentials = true;
    axios.defaults.xsrfCookieName = "CSRF-TOKEN";
    axios.defaults.xsrfHeaderName = "X-CSRF-TOKEN";
    
    // http request 拦截器
    
    // 处理特殊错误errcode函数
    function _handleError(errorCode: number, errorMessage: any){
      switch (errorCode) {
          /* 用户未登录 */
          case 401:
            thisVue.$message.warning({
              msg: '您还未登录,即将跳转登录页面',
              });
              // 重新登录
              window.location.href = baseUrl + '/passport'
          break;
              //权限不足
          case 403:
            thisVue.$message.warning({
              msg: errorMessage,
              });
              break;
    
              //资源不存在
          case 404:
            thisVue.$message.error({
                  message: '资源不存在'
              });
              break;
    
          //     //参数格式不正确
          // case 409:
          //     Message.error({
          //         message: errorMessage
          //     });
          //     break;
    
          //     // 验证异常
          // case 406:
          //     Message.error({
          //         message: errorMessage
          //     });
          //     break;
    
          default:
            thisVue.$message.error({
                  message: errorMessage
              });
              break;
      }
    }
    
    // http response 拦截器
    axios.interceptors.response.use(
      (response): any => {
        if(response.data.errcode !== 0) {
          _handleError(response.data.errcode, response.data.description_cn || response.data.description)
          return false
        }
        return response
      },
      (error): void => {
        _handleError(error.response.data.errcode, error.response.data.description_cn || error.response.dara.description)
      }
    )
    
    const fetch = {
      get(url: string, params: object | Array<any> | null): Promise<ResponseType> {
        return new Promise((resolve) => {
          axios.get(url, {params}).then(res => {
            resolve(res.data)
          })
        })
      },
      // post(url, params={}) 默认POST的类型是json数据,但其实大部分是用from:data数据,只需要配置 axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
      // 其实是qs.stringify 将格式改变了
      post(url: string, params: object | Array<any> | null): Promise<ResponseType> {
        params = qs.stringify(params)
        return new Promise((resolve) => {
          axios.post(
            url,
            params,
            // config={
            //   headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}
            // }
            ).then(res => {
            resolve(res.data)
          })
        })
      }
    }
    Vue.prototype.$fetch = fetch;
    export {
      fetch
    }
    

    关于引入到的一些文件

    baseInterface.ts

    // response 返回体
    export interface ResponseType {
      errcode: number,
      data: object | null | Array<any>,
      code?: number,
      description_cn?: string,
      description?: string
    }
    
  3. api统一调用,导入ajax.ts

    某个子文件调用permission.ts

    import {fetch} from '../ulits/ajax'
    
    export const userList = {
      getList(params: ParmasType){
        return fetch.get('ajax/inner/permission/list', params)
      },
    }
    

    统一收集各个子文件的api, 与router类似

    // 来个类似单例模式吧
    const files = () => {
      let filesTypes: any;
      return () => {
        if(!filesTypes) {
          filesTypes = require.context(
            './',
            true,
            /\.ts$/
          )
        }
        return filesTypes
      }
    }
    const ff = files()
    const requireFile: any = ff()
    let apiList = {}
    
    requireFile.keys().forEach((fileName: string) => {
      if(fileName === './index.ts') {
        return;
      }
      const config = requireFile(fileName);
      apiList = {...apiList, ...(config.default || config)};
    })
    
    export default apiList;
    

3.provideinject代替vuex

  1. 其中某个模块文件
// 由于暂时不支持vuex,所以用@vue/comoisition中的provide做全局状态管理
// 返回一个provide 和 reject,使用时请使用当前文件为前缀,避免冲突
import { provide, ref, computed, inject, Ref, reactive} from '@vue/composition-api'

interface Types {
  count: Ref<number>;
  [key: string]: Ref<any>;
}

// 定义symbol()来放provide
const APPROVECOUNT = Symbol()


export const useApproveProvide  = () => {
  // 常量
  const count = ref<number>(12)
  // 改变
  const setCount = (value: number) => {
    return count.value = value
  }
  
  // compute可以实时返回
  const doubleCount = computed(() => {
    return count.value * 2
  })

  provide(APPROVECOUNT, {
    count,
    setCount,
    doubleCount
  })
}

// inject
export const useApproveInject = () => {
  const countContext = inject<Types>(APPROVECOUNT)
  // inject必须在provide后面
  if (!countContext) {
    throw new Error('inject必须在provide后面')
  }
  return countContext
}
  1. 将所有模块整合,最后引入在main.ts中的setup方法中

    // 所有全局状态的入口
    import {useApproveProvide, useApproveInject} from "./approve/approve";
    export { useApproveInject}
    // 将所有的模块函数都放在这里
    export const stateList = () => {
      useApproveProvide()
    }
    

4.第三方组件库

导入第三方组件库的时候,通常会显示找不到该模块,是因为没有支持ts,需要在shims-vue.d.ts中去掉

// 我们在 typescript 的项目中安装一些包的话,可能会报错 Could not find a declaration file for module 'xxx' ,这是因为这个包可能不是.ts文件而是.js文件
declare module 'xxx'

4.简单demo文件

<template>
  <div class="q">
    <h1>{{ count }}</h1>
    <h2>asd</h2>
    <v-button @click="setCount(22)">点击变成22</v-button>
  </div>
</template>

<script lang="ts">
import {defineComponent, ref, computed, inject} from '@vue/composition-api';
import {useApproveInject} from '@/provide/index';
export default defineComponent({
  setup(props, {root}) {
    // console.log(state)
    console.log(root)
    const {count, setCount} = useApproveInject();
    
    return {
      count,
      setCount
    }
  }
})
</script>

<style>

</style>

5 参考文章

1.juejin.cn/post/684490…