教你搭建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
復制代碼
- 選擇預設的模板
選擇更多功能Manully select features
回車后來到選擇插件 - 選擇插件
這邊選擇了(Babel、Typescript、Router、Css預處理器、Linter / Formatter 格式檢查、Unit測試框架)使用鍵盤空格選擇插件
- 自動檢測typescript(yes)
- 路由模式選擇
是否使用 history模式的路由 (Yes) - 選擇一個css預處理器 (Sass/SCSS)
- 選擇格式檢查規則(什么時候進行 tslint 校驗: Line on save)
- 是否保存這份預設配置?(yes)
選是的話,下次創建一個vue項目,可以直接使用這個預設文件,而無需再進行新的配置,直接使用選擇的模板創建項目
等待所有的依賴完成
通過node生產組件和頁面是基于
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'@Componentexport default class Test extends Vue {}
</script>
復制代碼
3.2. data定義
若對ts的基本類型不了解的, 請點擊 typescript中文文檔
private listTotal: number = 50private 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的時候,會報錯
解決方法:src目錄下面新建一個shims-tinymce.d.ts文件
declare module 'tinymce/tinymce'
復制代碼
重新啟動項目就ok了
5.2 主題、樣式、語言配置
- 主題
引入主題報錯import 'tinymce/themes/modern/theme'
可以使用sliver主題
import 'tinymce/themes/silver/theme'
復制代碼
- 樣式及語言漢化
在public目錄新建的static文件
2.1 將node_modules/tinymce/skins文件拷貝到static中
2.2 zh_CN.js 下載,拷貝到static文件中
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'}},// 覆蓋默認的字體單位為ptfontsize_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 = nulllet formData: any = nullxhr = new XMLHttpRequest()xhr.withCredentials = falsexhr.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)}}
復制代碼
附上效果圖:
小結
前端小菜雞,各位大神有發現不足之處請告知,謝謝!!, 項目地址后期更新
相關資源鏈接
- TypeScript 體系調研報告
- ts通俗易懂文檔
- ts中文文檔
- vue-cli3 項目從搭建優化到docker部署