精华内容
下载资源
问答
  • vue 2项目升级 vue 4

    2020-03-27 11:56:21
    1、写在vue2 npm uninstall vue-cli -g 2、安装vue4 npm install -g @vue-cli 3、新建项目 npm creat xxx 4、 5、 Vue CLI v3.0.0-beta.6 ? Please pick a preset: (Use arrow keys) > xs-default (vue-router, ...

    1、写在vue2
    npm uninstall vue-cli -g
    2、安装vue4
    npm install -g @vue-cli
    3、新建项目
    npm creat xxx
    4、是否选择使用淘宝镜像
    5、

    Vue CLI v3.0.0-beta.6
    ? Please pick a preset: (Use arrow keys)
    > xs-default (vue-router, vuex, stylus, babel, pwa, eslint, unit-jest) // 这是我运行过之后的默认设置,第一次执行create是没有的
      default (babel, eslint)
      Manually select features
    

    按键盘上下键选择默认(default)还是手动(Manually),如果选择default,一路回车执行下去就行了(注:现在vue-cli3.0默认使用yarn下载),这里我选择手动,
    6、选择配置,这时你会看见一些选项,
    你要集成什么就选就行了,我这里选个我比较常用的(注:空格键是选中与取消,A键是全选)

    ? Please pick a preset: Manually select features
    ? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
    >( ) Babel         **Es6转Es5
    ( ) TypeScript    **js超集
     ( ) Progressive Web App (PWA) Support       **渐进式Web应用
     ( ) Router       **路由
     ( ) Vuex         **状态管理
     ( ) CSS Pre-processors       **CSS预处理
     ( ) Linter / Formatter          **规范类型
     ( ) Unit Testing          ***单元测试
     ( ) E2E Testing        **E2E测试,即端对端测试
    

    7、

    Use class-style component syntax? (Y/n):是否使用babel做转义 yes
    

    8、TypeScript

    Use Babel alongside TypeScript (required for modern mode,auto-detected polyfills, transpiling JSX)? (Y/n) :是否使用class风格的组件语法 yes
    

    9、Router 路由管理器

    ? Use history mode for router?       ***路由使用历史模式
    

    10、CSS Pre-processors css预处理

    选择CSS 预处理类型:Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default)  
    选项:
    	Less
    	Sass
    	Stylus
    
    

    11、 Linter / Formatter 代码风格、格式校验

    选择Linter / Formatter类型:Pick a linter / formatter config:
    选项:
    	TSLint
    	ESLint with error prevention only  仅错误预防
    	ESLint + Airbnb config  Airbnb配置
    	ESLint + Standard config 标准配置
    	ESLint + Prettier
    选择lint方式:Pick additional lint features
    选项:
    	Lint on save 保存时检查
        Lint and fix on commit 提交时检查
    
    

    12、Unit Testing 单元测试

    选择unit testing类型:Pick a unit testing solution: (Use arrow keys)
    选项:
    	Mocha + Chai  
        Jest 
    

    13、 E2E Testing E2E(End To End)即端对端测试

    选择unit testing类型:Pick a unit testing solution: (Use arrow keys)
    选项:
    	Mocha + Chai  
        Jest 
    

    14、选择 Babel, PostCSS, ESLint 等自定义配置的存放位置

    选择unit testing类型:Pick a unit testing solution: (Use arrow keys)
    选项:
    	Mocha + Chai  
        Jest 
    

    15、 将此作为将来项目的预置吗?

    Save this as a preset for future projects? 
    选项: In dedicated config files 在专用的配置文件中
     In package.json 在package.json
    

    16、保存预设为

    save preset as
    
    展开全文
  • 前言2020年9月18日,vue3正式版发布了,前几天把文档整体读了一遍,感触很深,可以解决我项目中的一些痛点,于是就决定重构之前那个vue2的开源项目。本篇文章就记录下重构vue2项目...

    前言

    2020年9月18日,vue3正式版发布了,前几天把文档整体读了一遍,感触很深,可以解决我项目中的一些痛点,于是就决定重构之前那个vue2的开源项目。

    本篇文章就记录下重构vue2项目的过程,欢迎各位感兴趣的开发者阅读本文。

    环境搭建

    本来打算使用vite + vue3 + VueRouter + vuex + typescript来构架项目的,但是经过一番折腾后发现vite目前只对vue支持,对于vue周边的一些库还没做到支持,没法在项目中使用。

    最后,还是决定使用Vue Cli 4.5来构建了。

    虽然vite目前还无法正常在项目中使用,但是我也折腾了一回,就记录下在折腾时的过程以及一些报错。

    使用vite构建项目

    本文采用的包管理工具为yarn,将其升级至最新版本就可以正常创建vite项目了。

    初始化项目

    接下来,我们来看看具体步骤。

    • 打开终端,进入你的项目目录,运行命令:yarn crete vite-app vite-project,该命令用于创建一个名为vite-project的项目。

    • 创建完成后,会得到如下所示的文件。

    • 进入创建好的项目,运行命令:yarn install,该命令会安装package.json中声明的依赖。

    • 我们使用IDE打开刚才创建的项目,整体项目如下所示,vite官方为我们提供了一个简单的demo。

    • 打开package.json查看启动命令在终端运行命令:yarn run dev或者点击ide的运行图标来启动项目。

    • 大功告成,浏览器访问 http://localhost:3000/,如下所示。

    集成Vue周边库

    我们将Vue CLI初始化的项目文件替换到用vite初始化的项目中去,然后修改packge.json中的相关依赖,然后重新安装依赖即可。

    具体过程如下:

    • 替换文件,替换后的项目目录如下所示。

    • package.json中提取我们需要的依赖,提取后的文件下。

    {
      "name": "vite-project",
      "version": "0.1.0",
      "scripts": {
        "dev": "vite",
        "build": "vite build"
      },
      "dependencies": {
        "core-js": "^3.6.5",
        "vue": "^3.0.0-0",
        "vue-class-component": "^8.0.0-0",
        "vue-router": "^4.0.0-0",
        "vuex": "^4.0.0-0"
      },
      "devDependencies": {
        "vite": "^1.0.0-rc.1",
        "@typescript-eslint/eslint-plugin": "^2.33.0",
        "@typescript-eslint/parser": "^2.33.0",
        "@vue/compiler-sfc": "^3.0.0-0",
        "@vue/eslint-config-prettier": "^6.0.0",
        "@vue/eslint-config-typescript": "^5.0.2",
        "eslint": "^6.7.2",
        "eslint-plugin-prettier": "^3.1.3",
        "eslint-plugin-vue": "^7.0.0-0",
        "node-sass": "^4.12.0",
        "prettier": "^1.19.1",
        "sass-loader": "^8.0.2",
        "typescript": "~3.9.3"
      },
      "license": "MIT"
    }
    
    
    8abcc9f5b934568e54c0229c6663866c
    • 启动项目,没报错,嘴角疯狂上扬。

    • 浏览器访问后,空白页面,打开console后,发现main.js 404

    难搞,找不到main.js,那我把main.ts后缀改一下试试。将后缀改成js后,文件是不报错404了,但是又有了新的错误。

    vite服务500和@别名无法识别,于是我打开ide的控制台看了错误,大概是scss的错,vite还没支持scss。

    scss不支持,别名不识别,网上找了一圈也没找到解决方案,这些最基础的东西都无法被vite支持,那它就不能用在项目中了,于是我放弃了。

    综合上述,vite要走的路还有很多,等它在社区成熟了,再将它应用到项目中吧。

    使用Vue Cli构建项目

    由于vite的不合适,我们还是继续选择用webpack,此处我们选择用Vue CLI 4.5来创建项目。

    初始化项目

    • 在终端进入项目目录,执行命令:vue create chat-system-vue3该命令用于创建一个名为chat-system-vue3的项目。

    • 创建完成后,如下所示。

    • IDE打开项目,打开package.json文件,查看项目启动命令或者直接点编译器的运行按钮。

    • OK,大功告成,打开浏览器,访问终端的内网地址。

    解决报错问题

    在浏览CLI默认创建的demo时,打开main.js文件发现其中App.vue文件报类型错误,无法推导出具体的类型。

    一开始,我也懵逼,想起了Vue文档所说的,启用TypeScript必须要让 TypeScript 正确推断 Vue 组件选项中的类型,需要使用 defineComponent

    App.vue文件代码如下:

    <template>
      <div id="nav">
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link>
      </div>
      <router-view />
    </template>
    
    <style lang="scss">
    #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;
    
      a {
        font-weight: bold;
        color: #2c3e50;
    
        &.router-link-exact-active {
          color: #42b983;
        }
      }
    }
    </style>
    
    

    观察代码后我们发现CLI生成的代码没有包含文档中所描述的代码,因此我们将其补充上,然后导出即可。

    import { defineComponent } from "vue";
    const Component = defineComponent({
      // 已启用类型推断
    });
    export default Component;
    

    加入上述代码后,我们的代码就不报错了。

    根据官网描述,我们可以在defineComponent的包裹中写组件的逻辑代码,但是我看了CIL提供的demo的Home组件后发现,他的写法如下。

    export default class Home extends Vue {}
    

    在项目的src目录下有一个名为shims-vue.d.ts的文件,它声明了所有vue文件的返回类型,因此我们可以按照上述方法来写。该声明文件代码如下。

    declare module "*.vue" {
      import { defineComponent } from "vue";
      const component: ReturnType<typeof defineComponent>;
      export default component;
    }
    
    

    这样的写法看起来更符合TypeScript,不过这种写法写法只支持部分属性,同样的我们组件的逻辑代码写在类内部即可,那么将刚才App.vue文件中做的更改也应用到此处,如下所示。

    <script lang="ts">
    import { Vue } from "vue-class-component";
    export default class App extends Vue {}
    </script>
    

    class写法支持的属性如下图所示:

    image-20201009210815033

    配置IDE

    此处内容仅适用于webstorm,如果编辑器是其他的可跳过本部分。

    我们在项目中集成了eslintprettier,默认情况下webstorm是没有启用这两个东西的,需要我们自己手动开启。

    • 打开webstorm的配置菜单,如下所示

      image-20201006153458084
    • 搜索eslint,按照下图所示进行配置,配置完成后点APPLYOK即可。

      image-20201006153031544
    • 搜索prettier,按照下图所示进行配置,配置完成后点APPLYOK即可。

      image-20201006153654226

    配置完上面的内容后,还有一个问题,在组件上用v-if v-for等vue指令时没有提示,这是因为webstorm没法正确读取node_modules包,按照下述操作即可解决这一问题。

    image-20201006154114315

    执行上述操作后,等待时间根据cpu性能而定,届时电脑会发热。这都是正常现象

    image-20201006154306682

    成功后,我们发现编辑器已经可以正常识别v-指令了,并且给了相应的提示。

    image-20201006154454592

    项目目录对比

    按照上述步骤,即可创建一个vue3的项目,接下来我们将需要重构的vue2项目的目录与上面创建的项目进行下目录对比。

    • 如下所示,为vue2.0项目的目录

      image-20201006162826706
    • 如下所示,为vue3.0项目的目录

      image-20201006162936370

    仔细观察后,我们发现在目录上并没有什么大的区别,只是多了typescript的配置文件和项目内使用ts的时辅助文件。

    项目重构

    接下来,我们来一步步把vue2项目的文件迁移到vue3项目中,修改不合适的地方,让其适配vue3.0。

    适配路由配置

    我们先从路由配置文件开始适配,打开vue3项目的router/index.ts文件,发现有一个报错,报错如下。

    image-20201006215331894

    错误信息是类型没被推导出来,我看了下面路由的写法后,盲猜它需要用函数返回,于是试了下,还真就是这样,正确的路由写法如下。

      {
        path: "/",
        name: "Home",
        component: () => Home
      }
    

    整体的路由配置文件代码如下:

    import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
    import Home from "../views/Home.vue";
    
    const routes: Array<RouteRecordRaw> = [
      {
        path: "/",
        name: "Home",
        component: () => Home
      },
      {
        path: "/about",
        name: "About",
        // route level code-splitting
        // this generates a separate chunk (about.[hash].js) for this route
        // which is lazy-loaded when the route is visited.
        component: () =>
          import(/* webpackChunkName: "about" */ "../views/About.vue")
      }
    ];
    
    const router = createRouter({
      history: createWebHashHistory(),
      routes
    });
    
    export default router;
    
    

    我们再来看看vue2项目中的路由配置,为了简单起见我摘抄了部分代码过来,如下所示。

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import MsgList from '../views/msg-list'
    import Login from "../views/login"
    import MainBody from '../components/main-body'
    Vue.use(VueRouter);
    
    const routes = [
        {
            path: '/',
            redirect: '/contents/message/message',
        },
        {
            name: 'contents',
            path: '/contents/:thisStatus',
            // 重定向到嵌套路由
            redirect: '/contents/:thisStatus/:thisStatus/',
            components: {
                mainArea: MainBody
            },
            props: {
                mainArea: true
            },
            children: [
                {
                    path: 'message',
                    components: {
                        msgList: MsgList
                    }
                }
            ],
        },
        {
            name: 'login',
            path: "/login",
            components: {
                login:Login
            }
        }
    ];
    
    const router = new VueRouter({
        // mode: 'history',
        routes,
    });
    
    export default router
    
    

    经过观察后,它们的不同点如下:

    • Vue.use(VueRouter)这种写法被移除

    • new VueRouter({})写法改为了createRouter({})

    • hash模式和history模式声明由原先的mode选项变更为了createWebHashHistory()createWebHistory()更加语义化了

    • 声明路由时多了ts的类型注解Array<RouteRecordRaw>

    知道它们的区别后,我们就可以对路由进行适配和迁移了,迁移完成的路由配置文件:router/index.ts

    这里有个小坑,路由懒加载的时候必须给他返回一个函数。例如:component: () => import("../views/msg-list.vue")。不然就会报黄色警告。

    image-20201015223425458
    image-20201015223525227

    适配Vuex配置

    接下来我们来看看两个版本在vuex使用上的区别,如下所示为vue3的vuex配置。

    import { createStore } from "vuex";
    
    export default createStore({
      state: {},
      mutations: {},
      actions: {},
      modules: {}
    });
    

    我们再来看看vue2项目中的vuex配置,为了简洁起见,我只列出了大体代码。

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex);
    
    export default new Vuex.Store({
      state: {
      },
      mutations: {
      },
      actions: {
      },
      modules: {
      }
    })
    
    

    经过对比后,我们发现的不同点如下所示:

    • 按需导入import { createStore } from "vuex",移除了之前的整个导入import Vuex from 'vuex'

    • 移除了Vue.use(Vuex)的写法

    • 导出时丢弃之前的new Vuex.Store写法,改用了createStore写法。

    知道上述不同点后,我们就可以对代码进行适配和迁移了,迁移完成的vuex配置文件:store/index.ts

    如果需要在vue的原型上挂载东西,就不能使用以前的原型挂载方法,需要使用新方法config.globalProperties,详细用法请查阅官方文档。

    我的项目中用到了一个websocket的插件,他需要在vuex中往Vue原型上挂载方法,下面是我的做法。

    • main.ts中的createApp方法导出。

      import { createApp } from "vue";
      
      const app = createApp(App);
      
      export default app;
      
      
    • store/index.ts中导入main.ts,然后调用方法挂载即可。

        mutations: {
          // 连接打开
          SOCKET_ONOPEN(state, event) {
            main.config.globalProperties.$socket = event.currentTarget;
            state.socket.isConnected = true;
            // 连接成功时启动定时发送心跳消息,避免被服务器断开连接
            state.socket.heartBeatTimer = setInterval(() => {
              const message = "心跳消息";
              state.socket.isConnected &&
                main.config.globalProperties.$socket.sendObj({
                  code: 200,
                  msg: message
                });
            }, state.socket.heartBeatInterval);
          }
        }
      

    适配axios

    axios在封装成插件时与之前的差别对比如下:

    • 暴露install方法由原来的Plugin.install改为了install

    • 增加了ts的类型声明

    • Object.defineProperties舍弃了,现在直接使用app.config.globalProperties挂载即可

    适配完成的代码如下:

    import { App } from "vue";
    import axiosObj, { AxiosInstance, AxiosRequestConfig } from "axios";
    import store from "../store/index";
    
    const defaultConfig = {
      // baseURL在此处省略配置,考虑到项目可能由多人协作完成开发,域名也各不相同,此处通过对api的抽离,域名单独配置在base.js中
    
      // 请求超时时间
      timeout: 60 * 1000,
      // 跨域请求时是否需要凭证
      // withCredentials: true, // Check cross-site Access-Control
      heards: {
        get: {
          "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
          // 将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
        },
        post: {
          "Content-Type": "application/json;charset=utf-8"
          // 将普适性的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
        }
      }
    };
    
    /**
     * 请求失败后的错误统一处理,当然还有更多状态码判断,根据自己业务需求去扩展即可
     * @param status 请求失败的状态码
     * @param msg 错误信息
     */
    const errorHandle = (status: number, msg: string) => {
      // 状态码判断
      switch (status) {
        // 401: 未登录状态,跳转登录页
        case 401:
          // 跳转登录页
          break;
        // 403 token过期
        case 403:
          // 如果不需要自动刷新token,可以在这里移除本地存储中的token,跳转登录页
    
          break;
        // 404请求不存在
        case 404:
          // 提示资源不存在
          break;
        default:
          console.log(msg);
      }
    };
    
    export default {
      // 暴露安装方法
      install(app: App, config: AxiosRequestConfig = defaultConfig) {
        let _axios: AxiosInstance;
    
        // 创建实例
        _axios = axiosObj.create(config);
        // 请求拦截器
        _axios.interceptors.request.use(
          function(config) {
            // 从vuex里获取token
            const token = store.state.token;
            // 如果token存在就在请求头里添加
            token && (config.headers.token = token);
            return config;
          },
          function(error) {
            // Do something with request error
            error.data = {};
            error.data.msg = "服务器异常";
            return Promise.reject(error);
          }
        );
        // 响应拦截器
        _axios.interceptors.response.use(
          function(response) {
            // 清除本地存储中的token,如果需要刷新token,在这里通过旧的token跟服务器换新token,将新的token设置的vuex中
            if (response.data.code === 401) {
              localStorage.removeItem("token");
              // 页面刷新
              parent.location.reload();
            }
            // 只返回response中的data数据
            return response.data;
          },
          function(error) {
            if (error) {
              // 请求已发出,但不在2xx范围内
              errorHandle(error.status, error.data.msg);
              return Promise.reject(error);
            } else {
              // 断网
              return Promise.reject(error);
            }
          }
        );
        // 将axios挂载到vue的全局属性中
        app.config.globalProperties.$axios = _axios;
      }
    };
    
    

    然后将其在main.js中use,就可以在代码中通过this.$axios.xx来使用了。

    不过上述将axios挂载到vue上是多此一举的,因为我已经将api进行了抽离,在每个单独的api文件中都是通过导入我们封装好的axios的配置文件,然后用导入进来的axios实例来进行的接口封装。(ps: 之前由于自己太菜没注意到这个,傻傻的将其封装成了插件????)

    那么,不需要将其封装成插件的话,那它就属于对axios进行配置封装了,我们将它放在config目录下,将上述代码稍作修改即可,修改好的代码地址:config/axios.ts。

    最后在main.ts中将api挂载到全局属性。

    import { createApp } from "vue";
    import api from "./api/index";
    const app = createApp(App);
    app.config.globalProperties.$api = api;
    

    随后就就可以在业务代码中通过this.$api.xx按模块来调用我们抛出来的接口了。

    shims-vue.d.ts类型声明文件

    shims-vue.d.ts是一个Typescript的声明文件,当项目启用ts后,有些文件是我们自己封装的,类型较为复杂,ts不能推导出其具体类型,此时就需要我们进行手动声明。

    例如上面我们挂载到原型上的$api,它导出了一个类文件,此时类型就较为复杂了,ts没法推导出其类型,我们在使用时就会报错。

    image-20201010100416381

    要解决这个错误,我们就需要在shims-vue.d.ts中声明api的的类型

    // 声明全局属性类型
    declare module "@vue/runtime-core" {
      interface ComponentCustomProperties<T> {
        $api: T;
      }
    }
    

    注意:在shims-vue.d.ts文件中,类型声明超过1个时,组件内需要import包就不能在其内部进行,需要将其写在最外层,否则会报错。

    image-20201010101906448

    适配入口文件

    由于启用了typescript,入口文件由main.js变成了main.ts,文件中的写法与之前相比其不同点如下:

    • 初始化挂载vue由原先的new Vue(App)改为了按需导入写法的createApp(App)

    • 使用插件时,也由原先的Vue.use()改成了,createApp(App).use()

    在我的项目中引用了几个插件,需要在入口文件中做一些初始化的操作,插件还是2.x版本,没有ts的类型声明文件,因此导入时ts没法推导出它的类型,就得用// @ts-ignore让ts忽略它。

    完整的入口文件地址:main.ts

    适配组件

    基础设施完善后,接下来我们来适配组件,我们先来试试把2.x项目的所有组件搬过来看看,能不能直接启动。

    结果可想而知,无法运行。因为我用了2.x的插件,vue3.0有关插件的封装,一些写法变了。我项目中总共引用了2个插件v-viewervue-native-websocketv-viewer这个插件无解,他底层使用用到的2.x语法太多了,所以我选择放弃这个插件。vue-native-websocket这个插件就是使用的Vue.prototype.xx写法被舍弃了,用新的写法Vue.config.globalProperties.xx将其替换即可。

    image-20201009174402912

    替换完成后,重新编译即可,随后启动项目,如下所示,错误解决,项目成功启动。

    image-20201009175415170

    正如上图中所看到的,控制台有黄色警告,因为我们组件的代码还是使用的vue2.x的语法,我们要重新整理组件中的方法从而适配vue3.0

    注意:组件script标签声明lang="ts"后,就必须按照Vue官方文档所说使用defineComponent全局方法来定义组件。

    组件优化

    接下来,我们从login.vue组件开始重构,看看都做了哪些优化。

    1. 创建type文件夹,文件夹内创建ComponentDataType.ts,将组件中用到的类型指定放在其中。

    2. 创建enum文件夹,将组件中用到的枚举放在其中。

    我们先来看看第一点,将组件内用到的类型进行统一管理,我们以登录组件为例,我们需要为data返回的对象指定其每个属性的类型,因此我们ComponentDataType.ts中创建一个名为loginDataType的类型,其代码如下。

    export type loginDataType<T> = {
      loginUndo: T; // 禁止登录时的图标
      loginBtnNormal: T; // 登录时的按钮图标
      loginBtnHover: T; // 鼠标悬浮时的登录图标
      loginBtnDown: T; // 鼠标按下时的登录图标
      userName: string; // 用户名
      password: string; // 密码
      confirmPassword: string; // 注册时的确认登录密码
      isLoginStatus: number; // 登录状态:0.未登录 1.登录中 2.注册
      loginStatusEnum: Object; // 登录状态枚举
      isDefaultAvatar: boolean; // 头像是否为默认头像
      avatarSrc: T; // 头像地址
      loadText: string; // 加载层的文字
    };
    

    声明好类型后,就可以在组件中使用了,代码如下:

    import { loginDataType } from "@/type/ComponentDataType";
    export default defineComponent({
      data<T>(): loginDataType<T> {
        return {
          loginUndo: require("../assets/img/login/icon-enter-undo@2x.png"),
          loginBtnNormal: require("../assets/img/login/icon-enter-undo@2x.png"),
          loginBtnHover: require("../assets/img/login/icon-enter-hover@2x.png"),
          loginBtnDown: require("../assets/img/login/icon-enter-down@2x.png"),
          userName: "",
          password: "",
          confirmPassword: "",
          isLoginStatus: 0,
          loginStatusEnum: loginStatusEnum,
          isDefaultAvatar: true,
          avatarSrc: require("../assets/img/login/LoginWindow_BigDefaultHeadImage@2x.png"),
          loadText: "上传中"
        };
      }
    })
    
    

    上述代码完整地址:

    • type/ComponentDataType.ts

    • login.vue

    再然后,我们看看第二点,使用enum来优化组件内部的条件判断,例如上面data中的isLoginStatus就有3种状态,我们要根据这三种状态来做不同的事情,如果直接用数字来代表三种状态直接赋值数字,后期维护时将是一件很痛苦的事情,如果用enum来定义的话,根据语意一眼就能看出它的状态是什么。

    我们在enum文件夹中创建ComponentEnum.ts文件,组件内用到的所有枚举都会在此文件内定义,接下来在组件内创建loginStatusEnum,代码如下:

    export enum loginStatusEnum {
      NOT_LOGGED_IN = 0, // 未登录
      LOGGING_IN = 1, // 登录中
      REGISTERED = 2 // 注册
    }
    

    声明好后,我们就可以在组件中使用了,代码如下:

    import { loginStatusEnum } from "@/enum/ComponentEnum";
    
    export default defineComponent({
      methods: {
        stateSwitching: function(status) {
          case "条件1":
           this.isLoginStatus = loginStatusEnum.LOGGING_IN;
           break;
          case "条件2":
           this.isLoginStatus = loginStatusEnum.NOT_LOGGED_IN;
           break;
        }
      }
    })
    
    

    上述代码完整地址:

    • enum/ComponentEnum.ts

    • login.vue

    this指向

    在适配组件过程中,方法内部的this不能很好的识别,无奈就用了很笨的方法解决。

    如下所示:

    const _img = new Image();
    _img.src = base64;
    _img.onload = function() {
        const _canvas = document.createElement("canvas");
        const w = this.width / scale;
        const h = this.height / scale;
        _canvas.setAttribute("width", w + "");
        _canvas.setAttribute("height", h + "");
        _canvas.getContext("2d")?.drawImage(this, 0, 0, w, h);
        const base64 = _canvas.toDataURL("image/jpeg");
    }
    

    onload方法内部的this应该是指向_img的,但是ts并不这么认为,报错如下所示。

    image-20201013171520088

    this对象中不包含width属性,解决方案就是讲this换成_img,问题解决。

    image-20201013171712449

    Dom对象类型定义

    当操作dom对象时,层级过时ts就无法推断出具体类型了,如下所示:

    sendMessage: function(event: KeyboardEvent) {
          if (event.key === "Enter") {
            // 阻止编辑框默认生成div事件
            event.preventDefault();
            let msgText = "";
            // 获取输入框下的所有子元素
            const allNodes = event.target.childNodes;
            for (const item of allNodes) {
              // 判断当前元素是否为img元素
              if (item.nodeName === "IMG") {
                if (item.alt === "") {
                  // 是图片
                  let base64Img = item.src;
                  // 删除base64图片的前缀
                  base64Img = base64Img.replace(/^data:image\/\w+;base64,/, "");
                  //随机文件名
                  const fileName = new Date().getTime() + "chatImg" + ".jpeg";
                  //将base64转换成file
                  const imgFile = this.convertBase64UrlToImgFile(
                    base64Img,
                    fileName,
                    "image/jpeg"
                  );
                }
              }
            }
          }
    }
    

    上面为一个发送消息的函数的部分代码,消息框中包含图片和文字,要对图片进行单独处理,我们需要要从target中拿到所有节点childNodes,然后遍历每个节点获取其类型,childNodes的类型为NodeList,那么他的每一个元素就是Node类型,如果当前遍历到的元素的nodeName属性是IMG时,它就是一个图片,我们就获取它的alt属性进一步判断,再获取src属性。

    然而,ts会报错altsrc属性不存在,报错如下:

    image-20201013172815950

    此时,我们就需要把item断言成HTMLImageElement类型。

    image-20201019110053258

    复杂类型定义

    在适配组件过程中,遇到一个比较复杂的数据类型定义,数据如下:

     data(){
        return {
          friendsList: [
            {
              groupName: "我",
              totalPeople: 2,
              onlineUsers: 2,
              friendsData: [
                {
                  username: "神奇的程序员",
                  avatarSrc:
                    "https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg",
                  signature: "今天的努力只为未来",
                  onlineStatus: true,
                  userId: "c04618bab36146e3a9d3b411e7f9eb8f"
                },
                {
                  username: "admin",
                  avatarSrc:
                    "https://www.kaisir.cn/uploads/40ba319f75964c25a7370e3909d347c5admin.jpg",
                  signature: "",
                  onlineStatus: true,
                  userId: "32ee06c8380e479b9cd4097e170a6193"
                }
              ]
            },
            {
              groupName: "我的朋友",
              totalPeople: 0,
              onlineUsers: 0,
              friendsData: []
            },
            {
              groupName: "我的家人",
              totalPeople: 0,
              onlineUsers: 0,
              friendsData: []
            },
            {
              groupName: "我的同事",
              totalPeople: 0,
              onlineUsers: 0,
              friendsData: []
            }
          ]
        };
      },
    

    一开始我是这样定义的。

    image-20201014214430066

    嵌套到一起,自认为没问题,放进代码后,报错长度不匹配,这样写知识给第一个对象定义了类型。

    image-20201014214529652

    经过一番求助后,他们说应该分开写,不能这样嵌套定义,正确写法如下:

    • 类型分开定义

      // 联系人面板Data属性定义
      export type contactListDataType<V> = {
        friendsList: Array<V>;
      };
      
      // 联系人列表类型定义
      export type friendsListType<V> = {
        groupName: string; // 分组名称
        totalPeople: number; // 总人数
        onlineUsers: number; // 在线人数
        friendsData: Array<V>; // 好友列表
      };
      
      // 联系人类型定义
      export type friendsDataType = {
        username: string; // 昵称
        avatarSrc: string; // 头像地址
        signature: string; // 个性签名
        onlineStatus: boolean; // 在线状态
        userId: string; // 用户id
      };
      
      
    • 组件中使用

      import {
        contactListDataType,
        friendsListType,
        friendsDataType
      } from "@/type/ComponentDataType";
      
      data(): contactListDataType<friendsListType<friendsDataType>> {
          return {
            friendsList: [
              {
                groupName: "我",
                totalPeople: 2,
                onlineUsers: 2,
                friendsData: [
                  {
                    username: "神奇的程序员",
                    avatarSrc:
                      "https://www.kaisir.cn/uploads/1ece3749801d4d45933ba8b31403c685touxiang.jpeg",
                    signature: "今天的努力只为未来",
                    onlineStatus: true,
                    userId: "c04618bab36146e3a9d3b411e7f9eb8f"
                  },
                  {
                    username: "admin",
                    avatarSrc:
                      "https://www.kaisir.cn/uploads/40ba319f75964c25a7370e3909d347c5admin.jpg",
                    signature: "",
                    onlineStatus: true,
                    userId: "32ee06c8380e479b9cd4097e170a6193"
                  }
                ]
              },
              {
                groupName: "我的朋友",
                totalPeople: 0,
                onlineUsers: 0,
                friendsData: []
              },
              {
                groupName: "我的家人",
                totalPeople: 0,
                onlineUsers: 0,
                friendsData: []
              },
              {
                groupName: "我的同事",
                totalPeople: 0,
                onlineUsers: 0,
                friendsData: []
              }
            ]
          };
        }
      

    深刻的理解到了typescript泛型的使用,经验++????

    tag属性被移除

    我们在使用router-link时,它默认会渲染成a标签,如果想让他渲染成其它自定义标签,可以通过tag属性来修改,如下所示:

    <router-link :to="{ name: 'list' }" tag="div">
    

    然而,在vue-router的新版本中,官方将event和tag属性移除了,因此我们就不能这么使用了,当然官方文档中也给了解决方案使用v-solt来作为替代方案,上述代码中我们希望将其渲染成div,用v-solt的写法如下所示:

    <router-link :to="{ name: 'list' }" custom v-slot="{ navigate }">
        <div
          @click="navigate"
          @keypress.enter="navigate"
          role="link"
        >
      </div>
    </router-link>
    

    有关这一块的更多讲解,请移步官方文档:removal-of-event-and-tag-props-in-router-link

    组件无法外链文件

    当我把页面当组件进行引入声明时,发现vue3不支持将逻辑代码外链,像下面这样,通过src外链。

    <script lang="ts" src="../assets/ts/message-display.ts"></script>
    

    在组件中引用。

    <template>
       <message-display message-status="0" list-id="1892144211" />
    </template>
    
    <script>
    import messageDisplay from "@/components/message-display.vue";
    export default defineComponent({
      name: "msg-list",
      components: {
        messageDisplay
      },
    })
    </script>
    

    然后,他就报错了,类型无法推断。

    image-20201018224619607

    尝试了很多方法,最后发现是不能通过src外链的问题,于是我把ts文件中的代码写在vue模版中报错就没了。

    必须使用as进行断言

    当我把代码搬到vue模版中后,它报了一些很奇怪的错误,如下所示imgContent变量可能存在多个类型,ts无法推断出具体类型,此时就需要我们自己进行断言给他指定类型,我用了尖括号的写法,他报错了,webstorm可能对vue3的适配不是很好,他的报错很奇怪,如下所示

    image-20201018225114933

    一开始,我看到这个错误我是一脸懵逼的,一个朋友告诉我用排除法,注释下距离它最近的代码,看看是否会报错,于是找到了问题根源,就是上面的类型断言的锅,将它修改后,问题解决。

    image-20201018225618020

    问题是解决了,但是我很是想不通为何一定要用as,尖括号跟他是同等的才对,于是我翻了官方文档。

    image-20201018225919664

    正如官方文档所说,启用jsx后就只能使用as语法了。可能vue3的模版语法默认是启用jsx的吧。

    ref数组不会自动创建数组

    在vue2中,在v-for里使用ref属性时会用ref数组填充相应的$refs属性,如下所示为好友列表的部分代码,它通过循环friendsList,将groupArrowbuddyList放进ref数组中。

    <template>
                <div class="group-panel">
                    <div class="title-panel">
                        <p class="title">好友</p>
                    </div>
                    <div class="row-panel" v-for="(item,index) in friendsList" :key="index">
                        <div class="main-content" @click="groupingStatus(index)">
                            <div class="icon-panel">
                                <img ref="groupArrow" src="../assets/img/list/tchat_his_arrow_right@2x.png" alt="左箭头"/>
                            </div>
                            <div class="name-panel">
                                <p>{{item.groupName}}</p>
                            </div>
                            <div class="quantity-panel">
                                <p>{{item.onlineUsers}}/{{item.totalPeople}}</p>
                            </div>
                        </div>
                        <!--好友列表-->
                        <div class="buddy-panel" ref="buddyList" style="display:none">
                            <div class="item-panel" v-for="(list,index) in item.friendsData" :key="index" tabindex="0">
                                <div class="main-panel" @click="getBuddyInfo(list.userId)">
                                    <div class="head-img-panel">
                                        <img :src="list.avatarSrc" alt="用户头像">
                                    </div>
                                    <div class="nickname-panel">
                                        <!--昵称-->
                                        <div class="name-panel">
                                            {{list.username}}
                                        </div>
                                        <!--签名-->
                                        <div class="signature-panel">
                                            [{{list.onlineStatus?"在线":"离线"}}]{{list.signature}}
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
    </template>
    

    我们通过$refs可以访问到相应的节点,如下所示。

    import lodash from 'lodash';
    export default {
       name: "contact-list",
       methods:{
            // 分组状态切换
            groupingStatus:function (index) {
                if(lodash.isEmpty(this.$route.params.userId)===false){
                    this.$router.push({name: "list"}).then();
                }
                // 获取transform的值
                let transformVal = this.$refs.groupArrow[index].style.transform;
                if(lodash.isEmpty(transformVal)===false){
                    // 截取rotate的值
                    transformVal = transformVal.substring(7,9);
                    // 判断是否展开
                    if (parseInt(transformVal)===90){
                        this.$refs.groupArrow[index].style.transform = "rotate(0deg)";
                        this.$refs.buddyList[index].style.display = "none";
                    }else{
                        this.$refs.groupArrow[index].style.transform = "rotate(90deg)";
                        this.$refs.buddyList[index].style.display = "block";
                    }
                }else{
                    // 第一次点击添加transform属性,旋转90度
                    this.$refs.groupArrow[index].style.transform = "rotate(90deg)";
                    this.$refs.buddyList[index].style.display = "block";
                }
            },
            // 获取列表好友信息
            getBuddyInfo:function (userId) {
                // 判断当前路由params与当前点击项的userId是否相等
                if(!lodash.isEqual(this.$route.params.userId,userId)){
                    this.$router.push({name: "dataPanel", params: {userId: userId}}).then();
                }
            }
        }
    }
    

    上述写法在vue2没问题,但是在vue3中你得到的结果是报错,官方认为这种行为会变得不明确且效率低下,采用了新的语法来解决这个问题,通过ref来绑定一个函数去处理,如下所示。

    <template>
    		<!---其它代码省略--->
        <img :ref="setGroupArrow" src="../assets/img/list/tchat_his_arrow_right@2x.png" alt="左箭头" />
        <!---其它代码省略--->
              <div class="buddy-panel" :ref="setGroupList" style="display:none">
    
            </div>
    </template>
    
    <script lang="ts">
    import _ from "lodash";
    import { defineComponent } from "vue";
    import {
      contactListDataType,
      friendsListType,
      friendsDataType
    } from "@/type/ComponentDataType";
    
    export default defineComponent({
      name: "contact-list",
      data(): contactListDataType<friendsListType<friendsDataType>> {
        return {  
            groupArrow: [],
            groupList: []
        }
      },
       // 设置分组箭头Dom
       setGroupArrow: function(el: Element) {
          this.groupArrow.push(el);
       },
       // 设置分组列表dom
       setGroupList: function(el: Element) {
          this.groupList.push(el);
       },
        // 列表状态切换
        groupingStatus: function(index: number) {
          if (!_.isEmpty(this.$route.params.userId)) {
            this.$router.push({ name: "list" }).then();
          }
          // 获取transform的值
          let transformVal = this.groupArrow[index].style.transform;
          if (!_.isEmpty(transformVal)) {
            // 截取rotate的值
            transformVal = transformVal.substring(7, 9);
            // 判断分组列表是否展开
            if (parseInt(transformVal) === 90) {
              this.groupArrow[index].style.transform = "rotate(0deg)";
              this.groupList[index].style.display = "none";
            } else {
              this.groupArrow[index].style.transform = "rotate(90deg)";
              this.groupList[index].style.display = "block";
            }
          } else {
            // 第一次点击添加transform属性,旋转90度
            this.groupArrow[index].style.transform = "rotate(90deg)";
            this.groupList[index].style.display = "block";
          }
        }
    )}
    

    完整代码请移步:contact-list.vue

    ref更多描述请移步官方文档: v-for 中的 Ref 数组

    项目地址

    至此,项目已经可以正常启动了,重构工作也结束了,接下来要解决的问题就是vue-native-websocket这个插件无法在vue3中工作的问题了。一开始我以为把它在原型行挂载的写法改动下就可以了,然而是我想的太简单了,改动后编辑器是不报错了,但是在运行时会报很多错。无奈只好先把与服务端交互这部分代码移除掉了。

    接下来我会尝试重构vue-native-websocket这个插件,让其支持vue3。

    最后放上本文重构好的项目代码地址:chat-system


    最后

    如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

    1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

    2. 欢迎加我微信「qianyu443033099」拉你进技术群,长期交流学习...

    3. 关注公众号「前端下午茶」,持续为你推送精选好文,也可以加我为好友,随时聊骚。

    点个在看支持我吧,转发就更好了

    展开全文
  • 一.vue cli2升级vue cli3 1.先升级npm的版本 npm install -g npm 2.再卸载之前的vue cli 2.9.6 npm uninstall -g @vue/cli 3.下载最新的vue cli版本 npm install -g @vue/cli 二.vue3,0版本创建项目 1、vue ...

    一.vue cli2升级到vue cli3

    1.先升级npm的版本

      npm install -g npm
    

    2.再卸载之前的vue cli 2.9.6

    npm uninstall -g @vue/cli
    

    3.下载最新的vue cli版本

    npm install -g @vue/cli
    

    二.vue3,0版本创建项目

    1、vue create myproject 
    2、默认选择default方式即可
    

    vue2.0和vue3.0的区别

    1.打包方式:
        2.0是通过:npm run dev
        3.0是:npm run serve
        至于为什么会变,来看一下package.json
    2.文件夹目录:
       3.0取消掉了config目录、build目录、static目录  ,还有最重要的一点,3.0的安装项目时自动下载node-model
       vue.config.js也没了,需要手动添加。
    3.创建项目方式:
        3.0的安装:vue create 3.0project;
        2.0的安装:vue init webpack(据我所知有五个,这个是我常用的)  2.0project(项目名)
    
    展开全文
  • 3.全局安装vue-cli npm install vue-cli -g 4.验证是否安装成功: vue --version 5.进入自己要创建项目的路径并创建项目(vue init webpack 项目名): 6.按照上一步中给出的黄色提示进入项目目录,安装...

    1.安装vs code

    2.安装node(参考 https://mp.csdn.net/console/editor/html/88321770)

    验证是否安装成功:

    3.全局安装vue-cli

    npm install vue-cli -g

    4.验证是否安装成功:

    vue --version

    5.进入自己要创建项目的路径并创建项目(vue init webpack 项目名):

    6.按照上一步中给出的黄色提示进入项目目录,安装依赖,运行项目:

    cd wyh-xinguan
    npm install
    npm run dev

     然后去浏览器中访问上述地址:

    这样一个vue项目就创建好了,我们可以把文件目录导入到vs code中进行开发。

    由于2.x版本中没有vue ui这个命令,所以卸载了重新安装vue3的版本:

    安装vue-cli3:

    npm install -g @vue/cli

    现在的vue已经变成4的版本了。

    展开全文
  • 升级背景公司项目是用 vue-cli 搭建的,至今已经有2年了热更新也越来越慢,正好 vue cli3 也出来,听说编译速度大大提升,就想把项目移植到 vue cli3 现状技术: "webpack": "^3.6.0", "vue": "^2.5.2" 1....
  • vue2升级vue3

    2020-12-18 18:30:12
    一.vue cli2升级vue cli3 因为安装包的不同,所以需要先删除vue-cli安装vue-cli3.0。 1.先升级npm的版本 npm install -g npm 2.再卸载之前的vue cli 2.9.6 npm uninstall -g @vue/cli 若是mac可能会报错,没有...
  • 由于 vue-cli 2 构建的项目是基于 webpack3,所以只能自己动手改动,至于升级 webpack4之后提升的编译速度以及各种插件自己去体验。
  • vue项目升级vue-cli3.0问题总结

    千次阅读 2019-07-22 15:45:21
    2、删除新项目中src下的内容,把原项目中src目录覆盖到新项目3、把router从目录文件夹改为文件,src/router/index.js提高一层变成src/router.js 4、我的项目中src已经分为了views和components所以无需修改,如果...
  • 首先我们从vue3文档开始 vue3的新特性 组合式 API Teleport 片段 触发组件选项 createRenderer API 来自 @vue/runtime-core 创建自定义渲染器 单文件组件组合式 API 语法糖 (1,什么是组合式API? 平时使用vue开发...
  • Vue2升级到Vue3

    2020-10-29 21:38:48
    如题,Vue3已经是beta版了,想玩玩Vue3,就对原来Vue2项目进行了升级。参考了慕课网Dell Lee的课程内容和Vue3官方文档。 一、main.js中的变化 从技术上讲,Vue 2 没有“app”的概念,我们定义的应用只是通过 new ...
  • 以下备忘升级Vue CLI 3.x 版本后,将项目目录改为新结构时所需做的一些改动。 1. 卸载与安装 npm uninstall vue-cli -g npm install -g @vue/cli 注:若要使用 Vue CLI 3,需将 Node 版本升级至 8.9 及以上。 ...
  • vuecli2升级vuecli3

    千次阅读 2019-07-09 21:19:24
    一. 卸载旧版本vuecli与安装新版本vuecli npm uninstall vue-cli -g npm install -g @vue/cli ...1. 直接复制替换src文件夹2. 安装项目需要的依赖(可以将原来package.json dependencies下需要的直接复制过来,然...
  • 层出不穷,公司原项目使用vuecil2版本开发,elementUI等一系列库版本太低,也脱离世界很久远了,新版本库需要webpack4等环境,而老版本配置起来bug很多,在使用过程中很多东西都不能实现,所以升级真的是迫在眉睫;...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 495
精华内容 198
关键字:

vue2项目升级3

vue 订阅