vue-typescript

LenGxin發表於2019-04-30

vue-typescript

教你搭建typescript的vue專案

自尤大神去年9月推出vue對typescript的支援後,一直想開箱嘗試vue+ts,最近一個新專案準備入手typescript,也遇到了很多坑,下面就一步步來吧!!!

1. 專案建立和初始化

1.1 安裝腳手架與建立專案

全域性安裝 vue-cli腳手架

$ npm install -g @vue/cli
複製程式碼

等待安裝完成後開始下一步,檢查是否安裝成功: Vue -v

1.2. 初始化專案

$ vue create vue-ts
複製程式碼
  1. 選擇預設的模板
    vue-typescript
    選擇更多功能 Manully select features
    回車後來到選擇外掛
  2. 選擇外掛
    這邊選擇了(Babel、Typescript、Router、Css前處理器、Linter / Formatter 格式檢查、Unit測試框架)
    使用鍵盤空格選擇外掛
    vue-typescript
  3. 自動檢測typescript(yes)
    vue-typescript
  4. 路由模式選擇
    是否使用 history模式的路由 (Yes)
    vue-typescript
  5. 選擇一個css前處理器 (Sass/SCSS)
    vue-typescript
  6. 選擇格式檢查規則(什麼時候進行 tslint 校驗: Line on save)
    vue-typescript
  7. 是否儲存這份預設配置?(yes)
    選是的話,下次建立一個vue專案,可以直接使用這個預設檔案,而無需再進行新的配置,直接使用選擇的模板建立專案

等待所有的依賴完成

vue-typescript

下面這些功能是基於lentoo大神的vuecli3-project的進行升級改造的

2. 通過node來生成元件

安裝chalk

$ npm install chalk --save-dev
複製程式碼

在根目錄中建立一個 scripts 資料夾,

2.1. 通過node來生成元件

新增一個generateComponent.js檔案,放置生成元件的程式碼、

新增一個template.js檔案,放置元件模板的程式碼 template.js檔案

    /**
 * 將駝峰命名轉為中橫槓例如:PlInputGroup --> pl-input-group
 * @param str 
 */
function toMiddleLine (str) {
  let temp = str.replace(/[A-Z]/g,
  function (match) {
    return "-" + match.toLowerCase()
  });
  if (temp.slice(0, 1) === '-') { //如果首字母是大寫,執行replace時會多一個-,這裡需要去掉
    temp = temp.slice(1)
  }
  return temp;
}

/**
 * 首字母大寫
 * @param {*} str 字串
 * @returns
 */
function initialToUp (str) {  
  return str.slice(0, 1).toUpperCase() + str.slice(1);  
}

module.exports = {
vueTemplate: componentName => {
  return `<template>
  <div class="${toMiddleLine(componentName)}">
    ${toMiddleLine(componentName)}
  </div>
</template>

<script lang="ts">
  import { Vue, Component, Prop, Watch, Emit, Provide, Inject } from 'vue-property-decorator'
  
  @Component({})
  export default class ${initialToUp(componentName)} extends Vue {
    
  }
</script>

<style lang="scss" scoped>
@import './style.scss';
.${toMiddleLine(componentName)} {}
</style>`
},
styleTemplate: componentName => {
  return `.${toMiddleLine(componentName)} {}`
},
entryTemplate: `import Main from './main.vue'
export default Main
`
}

複製程式碼

generateComponent.js檔案

const chalk = require('chalk')
const path = require('path')
const fs = require('fs')

const resolve = (...file) => path.resolve(__dirname, ...file)
const log = message => console.log(chalk.green(`${message}`))
const successLog = message => console.log(chalk.blue(`${message}`))
const errorLog = error => console.log(chalk.red(`${error}`))
const { vueTemplate, entryTemplate, styleTemplate } = require('./template')

const generateFile = (path, data) => {
  if (fs.existsSync(path)) {
    errorLog(`${path}檔案已存在`)
    return
  }
  return new Promise((resolve, reject) => {
    fs.writeFile(path, data, 'utf8', err => {
      if (err) {
        errorLog(err.message)
        reject(err)
      } else {
        resolve(true)
      }
    })
  })
}
log('請輸入要生成的元件名稱、如需生成全域性元件,請加 global/ 字首')
let componentName = ''
process.stdin.on('data', async chunk => {
  const inputName = String(chunk).trim().toString()
  /**
   * 元件目錄路徑
   */
  const componentDirectory = resolve('../src/components', inputName)

  /**
   * vue元件路徑
   */
  const componentVueName = resolve(componentDirectory, 'main.vue')
  /**
   * 入口檔案路徑
   */
  const entryComponentName = resolve(componentDirectory, 'index.ts')
  /**
   * style樣式路徑
   */
  const styleComponentName = resolve(componentDirectory, 'style.less')

  const hasComponentDirectory = fs.existsSync(componentDirectory)
  if (hasComponentDirectory) {
    errorLog(`${inputName}元件目錄已存在,請重新輸入`)
    return
  } else {
    log(`正在生成 component 目錄 ${componentDirectory}`)
    await dotExistDirectoryCreate(componentDirectory)
    // fs.mkdirSync(componentDirectory);
  }
  try {
    if (inputName.includes('/')) {
      const inputArr = inputName.split('/')
      componentName = inputArr[inputArr.length - 1]
    } else {
      componentName = inputName
    }
    log(`正在生成 vue 檔案 ${componentVueName}`)
    await generateFile(componentVueName, vueTemplate(componentName))
    log(`正在生成 entry 檔案 ${entryComponentName}`)
    await generateFile(entryComponentName, entryTemplate)
    log(`正在生成 style 檔案 ${styleComponentName}`)
    await generateFile(styleComponentName, styleTemplate(componentName))
    successLog('生成成功')
  } catch (e) {
    errorLog(e.message)
  }

  process.stdin.emit('end')
})
process.stdin.on('end', () => {
  log('exit')
  process.exit()
})
function dotExistDirectoryCreate (directory) {
  return new Promise((resolve) => {
    mkdirs(directory, function () {
      resolve(true)
    })
  })
}

// 遞迴建立目錄
function mkdirs (directory, callback) {
  var exists = fs.existsSync(directory)
  if (exists) {
    callback()
  } else {
    mkdirs(path.dirname(directory), function () {
      fs.mkdirSync(directory)
      callback()
    })
  }
}

複製程式碼

配置package.json

"new:comp": "node ./scripts/generateComponent"
複製程式碼

執行 npm / cnpm / yarn run new:comp 生成元件

2.2. 通過node來生成頁面元件

在scripts目錄下新建一個generateView.js檔案

generateView.js檔案

const chalk = require('chalk')
const path = require('path')
const fs = require('fs')

const resolve = (...file) => path.resolve(__dirname, ...file)
const log = message => console.log(chalk.green(`${message}`))
const successLog = message => console.log(chalk.blue(`${message}`))
const errorLog = error => console.log(chalk.red(`${error}`))
const { vueTemplate } = require('./template')

const generateFile = (path, data) => {
  if (fs.existsSync(path)) {
    errorLog(`${path}檔案已存在`)
    return
  }
  return new Promise((resolve, reject) => {
    fs.writeFile(path, data, 'utf8', err => {
      if (err) {
        errorLog(err.message)
        reject(err)
      } else {
        resolve(true)
      }
    })
  })
}
log('請輸入要生成的頁面元件名稱、會生成在 views/目錄下')
let componentName = ''
process.stdin.on('data', async chunk => {
  const inputName = String(chunk).trim().toString()
  /**
   * Vue頁面元件路徑
   */
  let componentVueName = resolve('../src/views', inputName)
  // 如果不是以 .vue 結尾的話,自動加上
  if (!componentVueName.endsWith('.vue')) {
    componentVueName += '.vue'
  }
  /**
   * vue元件目錄路徑
   */
  const componentDirectory = path.dirname(componentVueName)

  const hasComponentExists = fs.existsSync(componentVueName)
  if (hasComponentExists) {
    errorLog(`${inputName}頁面元件已存在,請重新輸入`)
    return
  } else {
    log(`正在生成 component 目錄 ${componentDirectory}`)
    await dotExistDirectoryCreate(componentDirectory)
  }
  try {
    if (inputName.includes('/')) {
      const inputArr = inputName.split('/')
      componentName = inputArr[inputArr.length - 1]
    } else {
      componentName = inputName
    }
    log(`正在生成 vue 檔案 ${componentVueName}`)
    await generateFile(componentVueName, vueTemplate(componentName))
    successLog('生成成功')
  } catch (e) {
    errorLog(e.message)
  }

  process.stdin.emit('end')
})
process.stdin.on('end', () => {
  log('exit')
  process.exit()
})
function dotExistDirectoryCreate (directory) {
  return new Promise((resolve) => {
    mkdirs(directory, function () {
      resolve(true)
    })
  })
}

// 遞迴建立目錄
function mkdirs (directory, callback) {
  var exists = fs.existsSync(directory)
  if (exists) {
    callback()
  } else {
    mkdirs(path.dirname(directory), function () {
      fs.mkdirSync(directory)
      callback()
    })
  }
}

複製程式碼

配置package.json

"new:view": "node ./scripts/generateView"
複製程式碼

執行 npm / cnpm / yarn run new:view 生成頁面

3. vue與typescript結合

3.1. 首先元件宣告

若對vue-property-decorator庫不瞭解的,請點選vue-property-decorator的更多詳解

建立元件如下:

<script lang="ts">
  import { Component, Prop, Vue, Watch, Emit, Provide, Inject } from 'vue-property-decorator'
  @Component
  export default class Test extends Vue {}
</script>
複製程式碼

3.2. data定義

若對ts的基本型別不瞭解的, 請點選 typescript中文文件

  private listTotal: number = 50
  private form: any = {
    addText: [],
    addTextarea: [],
    text: '',
    textarea: '',
    imgUrl: ''
  }
複製程式碼

3.3 props宣告

  // align justify 彈性佈局對齊方式
  @Prop({default: 'center'})
  private align!: string
  @Prop({default: 'flex-start'})
  private justify!: string
  // 千萬不要這樣定義 @Prop private align: string = 'center' ---> 踩
</script>
複製程式碼

3.4 vue生命週期及自定義方法

methods不需要像vue裡面 methods: { text () {return console.log(222)} }

  public created (): void {}
  public mounted (): void {}
  
  public handleClick () {} // methods定義
複製程式碼

3.5 Watch

  // 監聽路由變化
  @Watch('$route')
  onRouteChanged(route: any, oldRoute: any):void {
    console.log(route, oldRoute)
  }
複製程式碼

3.6 computed

 public get msg () {
   return 'from typescript'
 }
複製程式碼

3.7 Emit

  @Emit('change')
  private methodName(x: number, y: string) {
    console.log('child to parent a value')
  }
複製程式碼

5. 踩坑

5.1 tinymac富文字編輯器的結合ts的使用,tiny中文文件

引入tinymac的時候,會報錯

vue-typescript

解決方法:src目錄下面新建一個shims-tinymce.d.ts檔案

declare module 'tinymce/tinymce'
複製程式碼

重新啟動專案就ok了

5.2 主題、樣式、語言配置

  1. 主題

引入主題報錯import 'tinymce/themes/modern/theme'

可以使用sliver主題

   import 'tinymce/themes/silver/theme'
複製程式碼
  1. 樣式及語言漢化

在public目錄新建的static檔案

2.1 將node_modules/tinymce/skins檔案拷貝到static中

2.2 zh_CN.js 下載,拷貝到static檔案中

vue-typescript

5.3 引入主題,樣式,語言包

配置如下

public editorInit: any = {
    language_url: '/static/zh_CN.js',
    language: 'zh_CN',
    selector: 'textarea',
    skin_url: '/static/skins/ui/oxide',
    height: 300,
    browser_spellcheck: true, // 拼寫檢查
    branding: false, // 去水印
    // elementpath: false,  //禁用編輯器底部的狀態列
    statusbar: false, // 隱藏編輯器底部的狀態列
    paste_data_images: true, // 允許貼上影像
    plugins: setPlugins,
    toolbar: setToolbar,
    // 啟用選單欄並顯示如下項 [檔案 編輯 插入 格式 表格]
    menubar: 'file edit insert view format table',
    // 配置每個選單欄的子選單項(如下是預設配置)
    menu: {
      file: {
        title: 'File',
        items: 'newdocument'
      },
      edit: {
        title: 'Edit',
        items: 'undo redo | cut copy paste pastetext | selectall'
      },
      insert: {
        title: 'Insert',
        items: 'link media | template hr'
      },
      view: {
        title: 'View',
        items: 'visualaid'
      },
      format: {
        title: 'Format',
        items: 'bold italic underline strikethrough superscript subscript | formats | removeformat'
      },
      table: {
        title: 'Table',
        items: 'inserttable tableprops deletetable | cell row column'
      }
    },
    // 覆蓋預設的字型單位為pt
    fontsize_formats: '8px 10px 12px 14px 16px 18px 20px 24px 36px',
    /**
     * 下面方法是為tinymce新增自定義插入圖片按鈕
     * 也可以藉助elementui的Upload元件,上傳圖片
     */
    images_upload_url: '/api/image', // 上傳圖片介面地址
    images_upload_handler: (blobInfo: any, success: any, failure: any) => {
      let xhr: any = null
      let formData: any = null
      xhr = new XMLHttpRequest()
      xhr.withCredentials = false
      xhr.open('POST', this.$store.state.imgUrl)
      xhr.onload = () => {
        if (xhr.status < 200 || xhr.status >= 300) {
          failure(xhr.status)
          return
        }
        let json = JSON.parse(xhr.responseText)
        if (json.code === 0) {
          success(json.data[0].newFileName)
        } else {
          failure('HTTP Error: ' + json.msg)
        }
      }
      formData = new FormData()
      formData.append('file', blobInfo.blob(), blobInfo.filename())
      xhr.send(formData)
    }
  }
複製程式碼

附上效果圖:

vue-typescript

小結

前端小菜雞,各位大神有發現不足之處請告知,謝謝!!, 專案地址後期更新

相關資源連結

  1. TypeScript 體系調研報告
  2. ts通俗易懂文件
  3. ts中文文件
  4. vue-cli3 專案從搭建優化到docker部署