原有vue项目接入typescript

本文详述了如何在现有Vue项目中引入TypeScript,包括安装相关npm包、配置Webpack和TS,以及逐步改造项目代码的过程。重点介绍了基础库和.vue文件的改造技巧。

为什么要接入typescript

javascript由于自身的弱类型,使用起来非常灵活。

这也就为大型项目、多人协作开发埋下了很多隐患。如果是自己的私有业务倒无所谓,主要是对外接口和公共方法,对接起来非常头疼。主要表现在几方面:

  1. 参数类型没有校验,怎么传都有,有时会出现一些由于类型转换带来的未知问题。

  2. 接口文档不规范,每次都要通过读代码才能知道传什么,怎么传

  3. 接口编写符合规范,但是公共库中有大量的处理类型校验的代码

这就非常不利于工程标准化。于是我们决定引入typescript进行代码层面的强校验。

概览

原有vue项目接入ts主要包含下面几大步骤:

  1. 安装typescript相关npm包

  2. 修改webpack和ts配置文件

  3. 项目公共库和vue文件改造

ok,我们开始

1. 安装typescript相关npm包

这块有个非常重要的点需要注意:

就是要根据你本地的环境,去升级对应版本的typescript

这块是很多初次使用的同学都会遇到的问题。

因为只是看到了官网的教程,一步一步安装完发现各种报错。主要问题就是webpack版本不匹配,或者其他一些npm包版本不匹配

以我本地为例:

我本地环境是webpack3,所以直接安装最新版本的typescript,控制台会报错webpack版本过低的问题。

所以你要不把自己的webpack升级到webapck4.要不就采用与之相匹配的typescript版本。

我选择的是后者,因为直接给自己的项目升级到webapck4,会花费更长的时间。我们用的脚手架是公司内部统一的。里面集成了很多底层通用的基础服务。冒然升级webpack4会带来更大的麻烦,更何况项目时间比较紧迫,你懂得。

下面是我安装的包和对应的版本:

  • "typescript": "^3.1.4" (这个是必须的,ts库)

  • "ts-loader": "^3.5.0" (识别ts的laoder)

  • "tslint": "^5.11.0" (tslint校验库)

  • "tslint-loader": "^3.5.4" (tslint的loader)

  • "tslint-config-standard": "^8.0.1" (用于tslint默认校验规则)

  • "vue-property-decorator": "^7.2.0" (用于在.vue文件中使用ts语法)

2. 修改配置文件

  • 修改webpack配置文件(加入ts的相关配)

 
  1. base: {

  2.  entry: {

  3.    ...

  4.    app: resolve('src/main.ts') // 把main.js改为main.ts

  5.  }

  6. ...

  7. resolve: {

  8.  ...

  9.  extensions: ['vue', '.js', '.ts']

  10. }

  11. module: {

  12.  rules: [

  13.    ...,

  14.    {                           // 加入对文件的ts识别        

  15.      test: /\.ts$/,

  16.      exclude: /node_modules/,

  17.      enforce: 'pre',

  18.      loader: 'tslint-loader'

  19.    }, {

  20.      test: /\.tsx?$/,

  21.      loader: 'ts-loader',

  22.      exclude: /node_modules/,

  23.      options: {

  24.        appendTsSuffixTo: [/\.vue$/],

  25.      }

  26.    }

  27.  ]

  28. }

注意: main.js改成main.ts后,还要做一些改造,这个比较简单,按照tslint的错误提示改就可以了

  • 在根目录下创建tslint.json(类似eslint,这里设定一个校验标准)

 
  1. {

  2.  "extends": "tslint-config-standard",

  3.  "globals": {

  4.    "require": true

  5.  }

  6. }

  • 在根目录创建tsconfig.json(typescript配置文件)

 
  1. {

  2.  "compilerOptions": {

  3.    // 编译目标平台

  4.    "target": "es5",

  5.    // 输出目录

  6.    "outDir": "./dist/",

  7.    // 添加需要的解析的语法,否则TS会检测出错。

  8.    "lib": ["es2015", "es2016", "dom"],

  9.    // 模块的解析

  10.    "moduleResolution": "node",

  11.    // 指定生成哪个模块系统代码

  12.    "module": "esnext",

  13.    // 在表达式和声明上有隐含的any类型时报错

  14.    "noImplicitAny": false,

  15.    // 把 ts 文件编译成 js 文件的时候,同时生成对应的 map 文件

  16.    "sourceMap": true,

  17.    // 允许编译javascript文件

  18.    "allowJs": true,

  19.    // 指定基础目录

  20.    "baseUrl": "./",

  21.    // 启用装饰器

  22.    "experimentalDecorators": true,

  23.    // 移除注释

  24.    "removeComments": true,

  25.    "pretty": true,

  26.    // 是相对于"baseUrl"进行解析

  27.    "paths": {

  28.      "vue": ["node_modules/vue/types"],

  29.      "@/*": ["src/*"]

  30.    }

  31.  },

  32.  "include": [

  33.    "src/**/*"

  34.  ],

  35.  "exclude": [

  36.    "node_modules"

  37.  ]

  38. }

  • 在src目录下创建sfc.d.ts(用来声明全局变量、class、module、function、命名空间)

我们在这里主要是让ts识别.vue文件、window对象和一些module

具体declare的使用方式请看这里 (https://www.tslang.cn/docs/handbook/declaration-files/by-example.html)

 
  1. /**

  2. * 告诉 TypeScript *.vue 后缀的文件可以交给 vue 模块来处理

  3. * 而在代码中导入 *.vue 文件的时候,需要写上 .vue 后缀。

  4. * 原因还是因为 TypeScript 默认只识别 *.ts 文件,不识别 *.vue 文件

  5. */

  6. declare module "*.vue" {

  7.  import Vue from 'vue'

  8.  export default Vue

  9. }

  10. /**

  11. * 告诉 TypeScript window是个全局对象,直接可用,这样就不会在window.xx = 123时报错

  12. */

  13. declare var window: any

  14. /**

  15. * 引入部分第三方库/自己编写的模块的时候需要额外声明文件

  16. * 引入的时候,需要使用类似 import VueLazyLaod from 'vue-lazyload' 的写法

  17. */

  18. declare module 'vue-lazyload'

  19. declare module '@zz/perf/vue'

  20. declare module 'raven-js'

  21. declare module 'raven-js/plugins/vue'

 

将src/main.js改为main.ts

项目改造

这个部分是最麻烦的,主要有几大块

 

  • 基础库改造

如果你的基础库引用了大量的npm包,那么恭喜你,这部分你的改造成本会低很多。

如果你的lib库有相当一部分都是自己手写的,那么,我也得恭喜你。。。

我们自己的lib库里,有大量的自己维护的js文件。那么如果你要进行ts改造的话,通通都要改。

举个例子: lib/url.js 中的getParam (算法并不高级,就是易读、兼容性好)

 

 
  1. export default class URL{

  2.  /**

  3.   * @memberOf URL

  4.   * @summary 获取当前页面连接中指定参数

  5.   * @type {function}

  6.   * @param {string} param1                     - 如果param2为undefined,param1是指从当前页面url中获取指定参数的key, 如果param2不为空,param1为指定的url

  7.   * @param {string} param2                     - 可选参数,如果param2存在,则从指定的param1连接中获取对应参数的key

  8.   * @return {string|null}

  9.   */

  10.  static getParam (param1, param2) {

  11.    let url = ''

  12.    let param = null;

  13.    // 如果只有一个参数,默认从当前页面链接获取参数

  14.    if (typeof param2 === 'undefined') {

  15.      url = window && window.location.href || ''

  16.      param = param1

  17.    // 从指定url中获取参数

  18.    } else {

  19.      url = param1

  20.      param = param2

  21.    }

  22.    // 排除hash的影响

  23.    url = url.split('#')[0]

  24.    if (url.indexOf('?') > -1) {

  25.      url = url.split('?')[1]

  26.    }

  27.    const reg = new RegExp('(^|&)' + param + '=([^&]*)[&#$]*', 'i')

  28.    const rstArr = url.match(reg)

  29.    if (rstArr !== null) {

  30.      return decodeURIComponent(rstArr[2])

  31.    }

  32.    return null

  33.  }

  34.  ...

  35. }

改造后的文件为:lib/url.ts

 
  1. export default class URL {

  2.  /**

  3.   * @memberOf URL

  4.   * @summary 获取url中指定参数

  5.   * @type {function}

  6.   * @param {string} param1                     - 如果param2为undefined,param1是指从当前页面url中获取指定参数的key, 如果param2不为空,param1为指定的url

  7.   * @param {string} param2                     - 可选参数,如果param2存在,则从指定的param1连接中获取对应参数的key

  8.   * @return {string|null}

  9.   */

  10.  static getParam (param1: string, param2?: string): string {

  11.    let url: string = ''

  12.    let param = null

  13.    // 如果只有一个参数,默认从当前页面链接获取参数

  14.    if (typeof param2 === 'undefined') {

  15.      url = window && window.location.href || ''

  16.      param = param1

  17.    // 从指定url中获取参数

  18.    } else {

  19.      url = param1

  20.      param = param2

  21.    }

  22.    url = url.split('#')[0]

  23.    if (url.indexOf('?') > -1) {

  24.      url = url.split('?')[1]

  25.    }

  26.    const reg = new RegExp('(^|&)' + param + '=([^&]*)[&#$]*', 'i')

  27.    const rstArr = url.match(reg)

  28.    if (rstArr !== null) {

  29.      return decodeURIComponent(rstArr[2])

  30.    }

  31.    return null

  32.  }

  33.  ...

  34. }

对于一个方法多种调用方式,如果你想完全改成typescript推荐的方式,你可以用到方法重载。(https://www.tslang.cn/docs/handbook/functions.html)

我没有用是因为我不希望改变原有页面的使用方式。

注:对于一个大型项目来讲,我们并不建议上来就对全部的文件进行ts改造。

我们更建议采用渐进式改造方案,在不影响原有页面的情况下,逐一改造。具体方案后面会介绍

  • vue文件改造

src/components/helper/newUser/index.vue

 
  1. <template>...</template>

  2. <script>

  3. import { LEGO_ATTR, initLegoData, legic } from '@/lib/legic'

  4. import { getMyProfile } from '@/api/helper'

  5. import { toast } from '@/lib/ZZSDK'

  6. import myComponent from './myComponent.vue'

  7. let flag = false // 是否发送视频点击埋点

  8. export default {

  9.  components: {

  10.    // 自定义组件

  11.    myComponent

  12.  },

  13.  data () {

  14.    return {

  15.      // 用户头像

  16.      portrait: '',

  17.      // 用户名称

  18.      nickName: '',

  19.      // 是否点击播放

  20.      isPlay: false

  21.    }

  22.  },

  23.  mounted () {

  24.    this.initData()

  25.    initLegoData({

  26.      type: 'newUserGuide'

  27.    });

  28.    legic(LEGO_ATTR.newUserGuide.SHOW);

  29.  },

  30.  methods: {

  31.    initData () {

  32.      getMyProfile().then(data => {

  33.        console.log('data', data)

  34.        const { respData } = data

  35.        this.portrait = respData.portrait || ''

  36.        this.nickName = respData.nickname || ''

  37.      }).catch(err => {

  38.        toast({ msg: err })

  39.      })

  40.    },

  41.    goPageClick (type) {

  42.      switch (type) {

  43.        case 'SUN':

  44.          legic(LEGO_ATTR.newUserGuide.SUNVILLAGECLICK)

  45.          break

  46.        case 'FOOTBALL':

  47.          legic(LEGO_ATTR.newUserGuide.FOOTBALLCLICK)

  48.          break

  49.        case 'SIGN':

  50.          legic(LEGO_ATTR.newUserGuide.SIGNCLICK)

  51.          break

  52.        default:

  53.          return

  54.      }

  55.    },

  56.    videoClick () {

  57.      if (flag) {

  58.        return

  59.      } else {

  60.        flag = true

  61.        legic(LEGO_ATTR.newUserGuide.SIGNCLICK)

  62.        this.isPlay = true

  63.        this.$refs.video.play()

  64.      }

  65.    }

  66.  }

  67. }

  68. </script>

  69. <style lang="scss" scoped>...</style>

改造后

 
  1. <template>...</template>

  2. <script lang="ts">

  3. import { LEGO_ATTR, initLegoData, legic } from '@/lib/legic'

  4. import { getMyProfile } from '@/api/helper.ts'

  5. import { toast } from '@/lib/ZZSDK'

  6. import { Component, Vue } from 'vue-property-decorator'

  7. import test from './test.vue'

  8.  

  9. let flag: boolean = false // 是否发送视频点击埋点

  10. @Component({

  11.  components: {

  12.    test

  13.  }

  14. })

  15. export default class NewUser extends Vue {

  16.  // 用户头像

  17.  portrait = ''

  18.  // 用户名称

  19.  nickName = ''

  20.  // 是否点击播放

  21.  isPlay = false

  22.  

  23.  mounted (): void {

  24.    this.initData()

  25.    initLegoData({

  26.      type: 'newUserGuide'

  27.    });

  28.    legic(LEGO_ATTR.newUserGuide.SHOW)

  29.  }

  30.  

  31.  initData () {

  32.    // 获取profile信息

  33.    getMyProfile().then((data: any) => {

  34.      console.log('data', data)

  35.      const { respData } = data

  36.      this.portrait = respData.portrait || ''

  37.      this.nickName = respData.nickname || ''

  38.    }).catch((err: string) => {

  39.      toast({ msg: err })

  40.    })

  41.  }

  42.  

  43.  goPageClick (type: string) {

  44.    switch (type) {

  45.      case 'SUN':

  46.        legic(LEGO_ATTR.newUserGuide.SUNVILLAGECLICK)

  47.        break

  48.      case 'FOOTBALL':

  49.        legic(LEGO_ATTR.newUserGuide.FOOTBALLCLICK)

  50.        break

  51.      case 'SIGN':

  52.        legic(LEGO_ATTR.newUserGuide.SIGNCLICK)

  53.        break

  54.      default:

  55.        return

  56.    }

  57.  }

  58.  

  59.  videoClick () {

  60.    if (flag) {

  61.      return

  62.    } else {

  63.      flag = true

  64.      legic(LEGO_ATTR.newUserGuide.SIGNCLICK)

  65.      this.isPlay = true

  66.      this.$refs.video['play']()

  67.    }

  68.  }

  69. }

  70. </script>

  71. <style lang="scss" scoped>...</style>

myComponent.vue改造前略,这里只展示改造后的组件

 
  1. <template>

  2.  <div class="main">{{title}}{{name}}</div>

  3. </template>

  4. <script lang="ts">

  5. import { Component, Prop, Vue } from 'vue-property-decorator'

  6.  

  7. @Component

  8. export default class MyComponent extends Vue {

  9.  

  10.  @Prop({ type: String, default: '' })

  11.  name: string

  12.  

  13.  title: string = '您好'

  14. }

  15. </script>

  16. <style lang="scss" scoped>

  17.  .main{

  18.    display: none;

  19.  }

  20. </style>

这里需要注意的是:

  • ts默认不会识别.vue文件,所以需要在sfc.d.ts文件中声明,同时在引入vue组件时,要加.vue后缀

  • 引入vue-property-decorator插件。采用修饰符的方式进行组件注册,这样里面的data、prop和function都通过扁平化方式调用(这也是官方推荐的方式)

  • ts中import引入文件,如果不写后缀,默认是js文件。如果js文件没有,则才识别ts文件

现在说下前面提到的改造方案:

这里其实主要涉及.vue文件和lib库的改造,vue文件没啥可说的,一个个改就可以了。主要说lib里面的文件,这里我建议:

  • 一开始保留原来的js文件,并不删除。这样目前尚未改造的文件可以继续使用

  • 新建对应的ts文件,比如lib中有util.js,新创建util.ts

  • 新改造的vue文件通通引入lib库中xx.ts(要加.ts后缀),如import Util from '@/lib/util.ts' 这样可以一点点改造整个项目,同时未改造的页面照样可以运行。

原文https://mp.weixin.qq.com/s/a0p2-c9oLoOAL1mx80kqPw

<think>好的,我现在需要帮助用户在PyCharm的Django项目中整合Vue3,同时不破坏现有的库配置。让我先理清用户的问题。用户已经在Django中部署了HTML、CSSJS,但JS不够完善,想用Vue3增强功能。PyCharm已经安装了Vue插件,现在需要更新架构,同时保持原有依赖库不受影响。 首先,我需要确认用户的项目结构。通常Django项目结构是项目根目录下有manage.py,各个应用在单独的目录中,而静态文件可能在static文件夹里。用户可能已经将HTML放在templates中,CSSJS在static下。现在要加入Vue3,可能需要将Vue作为前端框架,与Django后端分离或结合使用。 接下来要考虑如何整合Vue3。有两种常见方式:一种是将Vue作为前端单独项目,通过API与Django交互;另一种是在Django模板中直接使用Vue。用户可能希望后者,因为已经部署了HTML三件套,可能不想完全重构为前后端分离。但需要确认用户的具体需求。 然后,需要确保Vue3的引入不会影响现有JS代码。可能需要将原有JS逐步迁移到Vue组件中,或者保留部分功能,同时新增Vue部分。用户提到JS不完善,可能希望用Vue来增强交互,而不是完全替换。 接下来是具体步骤。首先在Django项目中安装Vue3,可以通过npm或直接引入CDN。考虑到用户已经在PyCharm安装了Vue插件,可能更倾向于使用npm管理依赖,这样可以利用构建工具如Vite或Webpack。但用户可能不希望复杂化项目结构,所以需要简单的方式。 如果使用CDN引入Vue3,可以直接在HTML模板中添加script标签,这样最快捷,但可能无法利用Vue的单文件组件现代前端工具链。如果用户需要更复杂的Vue功能,建议使用npm安装Vue,并配置构建步骤,但这可能会增加项目复杂度,需要调整Django的静态文件处理方式。 另外需要考虑静态文件目录的设置。Django的静态文件通常在static文件夹,而Vue构建后的文件可能需要放在这里。如果使用Vite,构建后的dist目录需要被Django识别为静态文件。可能需要配置Django的STATICFILES_DIRS来包含Vue的输出目录。 还需要处理模板中的HTMLVue的整合。确保Django的模板标签(如{% static %})与Vue的语法不冲突。例如,Django的{{ variable }}Vue的{{ message }}可能会有冲突,需要使用Vue的delimiters选项修改插值符号。 接下来是步骤规划: 1. 初始化前端项目:在Django项目根目录下创建frontend目录,使用npm init初始化,安装Vue3。 2. 配置构建工具:使用Vite或Webpack,设置输入输出路径,确保构建后的文件输出到Django的static目录。 3. 调整Django设置:配置STATICFILES_DIRS,让Django识别Vue构建后的文件。 4. 修改HTML模板:引入构建后的JS文件,使用Vue组件,注意避免模板语法冲突。 5. 开发流程:在开发时同时运行Django服务器Vue的热重载服务,或者配置Django使用Vue的构建结果。 需要确保用户的现有库不受影响,即不升级或更改已有的Python依赖,只在前端部分添加Vue3相关npm包。同时,PyCharm的Vue插件可以帮助语法高亮代码提示,但需要正确配置项目识别Vue文件。 可能遇到的挑战包括:静态文件路径配置错误、DjangoVue的模板语法冲突、构建流程与Django的集成问题。需要逐步测试每个步骤,确保各部分协同工作。 最后,提供具体的操作步骤,包括命令配置示例,确保用户能跟随执行。同时提醒用户注意备份项目,避免操作失误导致现有配置损坏。</think>以下是分步整合Vue3到现有Django项目的技术方案,保持原有依赖库稳定: --- ### 一、架构升级策略(最小化改动) ```bash your_project/ # Django项目根目录 ├── frontend/ # 新增Vue3工程目录(与Django应用平级) │ ├── src/ │ ├── package.json │ └── vite.config.js # 构建工具配置 ├── myapp/ # Django应用 ├── static/ # 原有静态资源 ├── templates/ # 原有HTML模板 └── manage.py ``` --- ### 二、关键操作步骤 #### 步骤1:创建独立Vue3工程 ```bash # 在项目根目录执行 mkdir frontend && cd frontend npm create vue@latest # 按提示选择必要配置 npm install # 安装依赖 ``` #### 步骤2:配置构建输出路径(vite示例) ```javascript // frontend/vite.config.js import { defineConfig } from 'vite' export default defineConfig({ build: { outDir: '../static/dist', # 输出到Django静态目录 emptyOutDir: true, manifest: true } }) ``` #### 步骤3:修改Django设置 ```python # settings.py STATICFILES_DIRS = [ BASE_DIR / "static", # 保留原有静态目录 BASE_DIR / "static/dist" # 添加Vue构建输出目录 ] ``` #### 步骤4:模板接入Vue组件 ```html <!-- templates/index.html --> {% load static %} <div id="app"> <!-- 原有HTML内容 --> <vue-component></vue-component> </div> <!-- 原有JS脚本 --> <script src="{% static 'js/legacy.js' %}"></script> <!-- 插入Vue打包文件 --> <script type="module" src="{% static 'dist/assets/main.js' %}"></script> ``` --- ### 三、开发调试模式配置 #### 双服务并行方案 ```bash # Terminal 1: 启动Django python manage.py runserver # Terminal 2: 启动Vue热更新 cd frontend && npm run dev -- --port 3000 ``` #### 代理配置(可选) ```javascript // frontend/vite.config.js server: { proxy: { '/api': 'http://localhost:8000' # 解决跨域API请求 } } ``` --- ### 四、关键注意事项 1. **模板变量冲突解决** 修改Vue插值符避免与Django冲突: ```javascript // frontend/src/main.js createApp(App).config({ compilerOptions: { delimiters: ['[[', ']]'] // 改为[[替代{{ ]] } }) ``` 2. **静态资源引用规范** Vue组件内使用Django静态路径: ```html <img :src="'{% static 'images/logo.png' %}'"> ``` 3. **生产部署优化** ```bash cd frontend && npm run build # 生成优化包到static/dist python manage.py collectstatic # 集中收集静态文件 ``` --- ### 五、验证流程 1. 浏览器访问`http://localhost:8000` 2. Chrome开发者工具检查: - Network面板确认Vue组件加载无404错误 - Console面板无Vue初始化报错 3. 测试原有JS功能与Vue组件的交互 --- ### 六、扩展建议 - **渐进式迁移**:通过`<script>`标签局部引入Vue3,逐步替换旧组件 - **状态管理**:复杂交互推荐使用Pinia(Vue3官方状态库) - **TypeScript支持**:通过`npm install -D @vitejs/plugin-vue-jsx`增强类型安全 是否需要详解某个具体环节(如Webpack替代方案、Django-Vue深度整合技巧)?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值