摘要:前言本文講解如何在項目中使用來搭建并開發項目,并在此過程中踩過的坑。具有類型系統,且是的超集,在年勢頭迅猛,可謂遍地開花。年將會更加普及,能夠熟練掌握,并使用開發過項目,將更加成為前端開發者的優勢。
前言
本文講解如何在 Vue 項目中使用 TypeScript 來搭建并開發項目,并在此過程中踩過的坑 。
TypeScript 具有類型系統,且是 JavaScript 的超集,TypeScript 在 2018年 勢頭迅猛,可謂遍地開花。
Vue3.0 將使用 TS 重寫,重寫后的 Vue3.0 將更好的支持 TS。2019 年 TypeScript 將會更加普及,能夠熟練掌握 TS,并使用 TS 開發過項目,將更加成為前端開發者的優勢。
所以筆者就當然也要學這個必備技能,就以 邊學邊實踐 的方式,做個博客項目來玩玩。
此項目是基于 Vue 全家桶 + TypeScript + Element-UI 的技術棧,且已經開源,github 地址 blog-vue-typescript 。
因為之前寫了篇純 Vue 項目搭建的相關文章 基于vue+mint-ui的mobile-h5的項目說明 ,有不少人加我微信,要源碼來學習,但是這個是我司的項目,不能提供原碼。
所以做一個不是我司的項目,且又是 vue 相關的項目來練手并開源吧。
1. 效果效果圖:
pc 端
移動端
完整效果請看:https://biaochenxuying.cn
2. 功能 已經完成功能[x] 登錄
[x] 注冊
[x] 文章列表
[x] 文章歸檔
[x] 標簽
[x] 關于
[x] 點贊與評論
[x] 留言
[x] 歷程
[x] 文章詳情(支持代碼語法高亮)
[x] 文章詳情目錄
[x] 移動端適配
[x] github 授權登錄
待優化或者實現[ ] 使用 vuex-class
[ ] 更多 TypeScript 的優化技巧
[ ] 服務器渲染 SSR
3. 前端主要技術所有技術都是當前最新的。
vue: ^2.6.6
typescript : ^3.2.1
element-ui: 2.6.3
vue-router : ^3.0.1
webpack: 4.28.4
vuex: ^3.0.1
axios:0.18.0
redux: 4.0.0
highlight.js: 9.15.6
marked:0.6.1
4. 5 分鐘上手 TypeScript如果沒有一點點基礎,可能沒學過 TypeScript 的讀者會看不懂往下的內容,所以先學點基礎。
TypeScript 的靜態類型檢查是個好東西,可以避免很多不必要的錯誤, 不用在調試或者項目上線的時候才發現問題 。
類型注解
TypeScript 里的類型注解是一種輕量級的為函數或變量添加約束的方式。變量定義時也要定義他的類型,比如常見的 :
// 布爾值 let isDone: boolean = false; // 相當于 js 的 let isDone = false; // 變量定義之后不可以隨便變更它的類型 isDone = true // 不報錯 isDone = "我要變為字符串" // 報錯
// 數字 let decLiteral: number = 6; // 相當于 js 的 let decLiteral = 6;
// 字符串 let name: string = "bob"; // 相當于 js 的 let name = "bob";
// 數組 // 第一種,可以在元素類型后面接上 [],表示由此類型元素組成的一個數組: let list: number[] = [1, 2, 3]; // 相當于 js 的let list = [1, 2, 3]; // 第二種方式是使用數組泛型,Array<元素類型>: let list: Array= [1, 2, 3]; // 相當于 js 的let list = [1, 2, 3];
// 在 TypeScript 中,我們使用接口(Interfaces)來定義 對象 的類型。 interface Person { name: string; age: number; } let tom: Person = { name: "Tom", age: 25 }; // 以上 對象 的代碼相當于 let tom = { name: "Tom", age: 25 };
// Any 可以隨便變更類型 (當這個值可能來自于動態的內容,比如來自用戶輸入或第三方代碼庫) let notSure: any = 4; notSure = "我可以隨便變更類型" // 不報錯 notSure = false; // 不報錯
// Void 當一個函數沒有返回值時,你通常會見到其返回值類型是 void function warnUser(): void { console.log("This is my warning message"); }
// 方法的參數也要定義類型,不知道就定義為 any function fetch(url: string, id : number, params: any): void { console.log("fetch"); }
以上是最簡單的一些知識點,更多知識請看 TypeScript 中文官網
5. 5 分鐘上手 Vue +TypeScriptvue-class-component?
vue-class-component 對?Vue?組件進行了一層封裝,讓?Vue?組件語法在結合了?TypeScript?語法之后更加扁平化:
prop: {{propMessage}}
msg: {{msg}}
helloMsg: {{helloMsg}}
computed msg: {{computedMsg}}
上面的代碼跟下面的代碼作用是一樣的:
prop: {{propMessage}}
msg: {{msg}}
helloMsg: {{helloMsg}}
computed msg: {{computedMsg}}
vue-property-decorator?
vue-property-decorator 是在?vue-class-component?上增強了更多的結合?Vue?特性的裝飾器,新增了這 7 個裝飾器:
@Emit
@Inject
@Model
@Prop
@Provide
@Watch
@Component?(從?vue-class-component?繼承)
在這里列舉幾個常用的@Prop/@Watch/@Component, 更多信息,詳見官方文檔
import { Component, Emit, Inject, Model, Prop, Provide, Vue, Watch } from "vue-property-decorator" @Component export class MyComponent extends Vue { @Prop() propA: number = 1 @Prop({ default: "default value" }) propB: string @Prop([String, Boolean]) propC: string | boolean @Prop({ type: null }) propD: any @Watch("child") onChildChanged(val: string, oldVal: string) { } }
上面的代碼相當于:
export default { props: { checked: Boolean, propA: Number, propB: { type: String, default: "default value" }, propC: [String, Boolean], propD: { type: null } } methods: { onChildChanged(val, oldVal) { } }, watch: { "child": { handler: "onChildChanged", immediate: false, deep: false } } }
vuex-class
vuex-class?:在?vue-class-component?寫法中 綁定?vuex 。
import Vue from "vue" import Component from "vue-class-component" import { State, Getter, Action, Mutation, namespace } from "vuex-class" const someModule = namespace("path/to/module") @Component export class MyComp extends Vue { @State("foo") stateFoo @State(state => state.bar) stateBar @Getter("foo") getterFoo @Action("foo") actionFoo @Mutation("foo") mutationFoo @someModule.Getter("foo") moduleGetterFoo // If the argument is omitted, use the property name // for each state/getter/action/mutation type @State foo @Getter bar @Action baz @Mutation qux created () { this.stateFoo // -> store.state.foo this.stateBar // -> store.state.bar this.getterFoo // -> store.getters.foo this.actionFoo({ value: true }) // -> store.dispatch("foo", { value: true }) this.mutationFoo({ value: true }) // -> store.commit("foo", { value: true }) this.moduleGetterFoo // -> store.getters["path/to/module/foo"] } }6. 用 vue-cli 搭建 項目
筆者使用最新的 vue-cli 3 搭建項目,詳細的教程,請看我之前寫的 vue-cli3.x 新特性及踩坑記,里面已經有詳細講解 ,但文章里面的配置和此項目不同的是,我加入了 TypeScript ,其他的配置都是 vue-cli 本來配好的了。詳情請看 vue-cli 官網 。
6.1 安裝及構建項目目錄安裝的依賴:
安裝過程選擇的一些配置:
搭建好之后,初始項目結構長這樣:
├── public // 靜態頁面 ├── src // 主目錄 ├── assets // 靜態資源 ├── components // 組件 ├── views // 頁面 ├── App.vue // 頁面主入口 ├── main.ts // 腳本主入口 ├── router.ts // 路由 ├── shims-tsx.d.ts // 相關 tsx 模塊注入 ├── shims-vue.d.ts // Vue 模塊注入 └── store.ts // vuex 配置 ├── tests // 測試用例 ├── .eslintrc.js // eslint 相關配置 ├── .gitignore // git 忽略文件配置 ├── babel.config.js // babel 配置 ├── postcss.config.js // postcss 配置 ├── package.json // 依賴 └── tsconfig.json // ts 配置
奔著 大型項目的結構 來改造項目結構,改造后 :
├── public // 靜態頁面 ├── src // 主目錄 ├── assets // 靜態資源 ├── filters // 過濾 ├── store // vuex 配置 ├── less // 樣式 ├── utils // 工具方法(axios封裝,全局方法等) ├── views // 頁面 ├── App.vue // 頁面主入口 ├── main.ts // 腳本主入口 ├── router.ts // 路由 ├── shime-global.d.ts // 相關 全局或者插件 模塊注入 ├── shims-tsx.d.ts // 相關 tsx 模塊注入 ├── shims-vue.d.ts // Vue 模塊注入, 使 TypeScript 支持 *.vue 后綴的文件 ├── tests // 測試用例 ├── .eslintrc.js // eslint 相關配置 ├── postcss.config.js // postcss 配置 ├── .gitignore // git 忽略文件配置 ├── babel.config.js // preset 記錄 ├── package.json // 依賴 ├── README.md // 項目 readme ├── tsconfig.json // ts 配置 └── vue.config.js // webpack 配置
tsconfig.json 文件中指定了用來編譯這個項目的根文件和編譯選項。
本項目的 tsconfig.json 配置如下 :
{ // 編譯選項 "compilerOptions": { // 編譯輸出目標 ES 版本 "target": "esnext", // 采用的模塊系統 "module": "esnext", // 以嚴格模式解析 "strict": true, "jsx": "preserve", // 從 tslib 導入外部幫助庫: 比如__extends,__rest等 "importHelpers": true, // 如何處理模塊 "moduleResolution": "node", // 啟用裝飾器 "experimentalDecorators": true, "esModuleInterop": true, // 允許從沒有設置默認導出的模塊中默認導入 "allowSyntheticDefaultImports": true, // 定義一個變量就必須給它一個初始值 "strictPropertyInitialization" : false, // 允許編譯javascript文件 "allowJs": true, // 是否包含可以用于 debug 的 sourceMap "sourceMap": true, // 忽略 this 的類型檢查, Raise error on this expressions with an implied any type. "noImplicitThis": false, // 解析非相對模塊名的基準目錄 "baseUrl": ".", // 給錯誤和消息設置樣式,使用顏色和上下文。 "pretty": true, // 設置引入的定義文件 "types": ["webpack-env", "mocha", "chai"], // 指定特殊模塊的路徑 "paths": { "@/*": ["src/*"] }, // 編譯過程中需要引入的庫文件的列表 "lib": ["esnext", "dom", "dom.iterable", "scripthost"] }, // ts 管理的文件 "include": [ "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx" ], // ts 排除的文件 "exclude": ["node_modules"] }
更多配置請看官網的 tsconfig.json 的 編譯選項
本項目的 vue.config.js:
const path = require("path"); const sourceMap = process.env.NODE_ENV === "development"; module.exports = { // 基本路徑 publicPath: "./", // 輸出文件目錄 outputDir: "dist", // eslint-loader 是否在保存的時候檢查 lintOnSave: false, // webpack配置 // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md chainWebpack: () => {}, configureWebpack: config => { if (process.env.NODE_ENV === "production") { // 為生產環境修改配置... config.mode = "production"; } else { // 為開發環境修改配置... config.mode = "development"; } Object.assign(config, { // 開發生產共同配置 resolve: { extensions: [".js", ".vue", ".json", ".ts", ".tsx"], alias: { vue$: "vue/dist/vue.js", "@": path.resolve(__dirname, "./src") } } }); }, // 生產環境是否生成 sourceMap 文件 productionSourceMap: sourceMap, // css相關配置 css: { // 是否使用css分離插件 ExtractTextPlugin extract: true, // 開啟 CSS source maps? sourceMap: false, // css預設器配置項 loaderOptions: {}, // 啟用 CSS modules for all css / pre-processor files. modules: false }, // use thread-loader for babel & TS in production build // enabled by default if the machine has more than 1 cores parallel: require("os").cpus().length > 1, // PWA 插件相關配置 // see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa pwa: {}, // webpack-dev-server 相關配置 devServer: { open: process.platform === "darwin", host: "localhost", port: 3001, //8080, https: false, hotOnly: false, proxy: { // 設置代理 // proxy all requests starting with /api to jsonplaceholder "/api": { // target: "https://emm.cmccbigdata.com:8443/", target: "http://localhost:3000/", // target: "http://47.106.136.114/", changeOrigin: true, ws: true, pathRewrite: { "^/api": "" } } }, before: app => {} }, // 第三方插件配置 pluginOptions: { // ... } };6.2 安裝 element-ui
本來想搭配 iview-ui 來用的,但后續還想把這個項目搞成 ssr 的,而 vue + typescript + iview + Nuxt.js 的服務端渲染還有不少坑, 而 vue + typescript + element + Nuxt.js 對 ssr 的支持已經不錯了,所以選擇了 element-ui 。
安裝:
npm i element-ui -S
按需引入, 借助?babel-plugin-component,我們可以只引入需要的組件,以達到減小項目體積的目的。
npm install babel-plugin-component -D
然后,將 babel.config.js 修改為:
module.exports = { presets: ["@vue/app"], plugins: [ [ "component", { libraryName: "element-ui", styleLibraryName: "theme-chalk" } ] ] };
接下來,如果你只希望引入部分組件,比如 Button 和 Select,那么需要在 main.js 中寫入以下內容:
import Vue from "vue"; import { Button, Select } from "element-ui"; import App from "./App.vue"; Vue.component(Button.name, Button); Vue.component(Select.name, Select); /* 或寫為 * Vue.use(Button) * Vue.use(Select) */ new Vue({ el: "#app", render: h => h(App) });6.3 完善項目目錄與文件 route
使用路由懶加載功能。
export default new Router({ mode: "history", routes: [ { path: "/", name: "home", component: () => import(/* webpackChunkName: "home" */ "./views/home.vue") }, { path: "/articles", name: "articles", // route level code-splitting // this generates a separate chunk (articles.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "articles" */ "./views/articles.vue") }, ] });utils
utils/utils.ts 常用函數的封裝, 比如 事件的節流(throttle)與防抖(debounce)方法:
// fn是我們需要包裝的事件回調, delay是時間間隔的閾值 export function throttle(fn: Function, delay: number) { // last為上一次觸發回調的時間, timer是定時器 let last = 0, timer: any = null; // 將throttle處理結果當作函數返回 return function() { // 保留調用時的this上下文 let context = this; // 保留調用時傳入的參數 let args = arguments; // 記錄本次觸發回調的時間 let now = +new Date(); // 判斷上次觸發的時間和本次觸發的時間差是否小于時間間隔的閾值 if (now - last < delay) { // 如果時間間隔小于我們設定的時間間隔閾值,則為本次觸發操作設立一個新的定時器 clearTimeout(timer); timer = setTimeout(function() { last = now; fn.apply(context, args); }, delay); } else { // 如果時間間隔超出了我們設定的時間間隔閾值,那就不等了,無論如何要反饋給用戶一次響應 last = now; fn.apply(context, args); } }; }
utils/config.ts 配置文件,比如 github 授權登錄的回調地址、client_id、client_secret 等。
const config = { "oauth_uri": "https://github.com/login/oauth/authorize", "redirect_uri": "https://biaochenxuying.cn/login", "client_id": "XXXXXXXXXX", "client_secret": "XXXXXXXXXX", }; // 本地開發環境下 if (process.env.NODE_ENV === "development") { config.redirect_uri = "http://localhost:3001/login" config.client_id = "502176cec65773057a9e" config.client_secret = "65d444de381a026301a2c7cffb6952b9a86ac235" } export default config;
如果你的生產環境也要 github 登錄授權的話,請在 github 上申請一個 Oauth App ,把你的 redirect_uri,client_id,client_secret 的信息填在 config 里面即可。具體詳情請看我寫的這篇文章 github 授權登錄教程與如何設計第三方授權登錄的用戶表
utils/urls.ts 請求接口地址,統一管理。
// url的鏈接 export const urls: object = { login: "login", register: "register", getArticleList: "getArticleList", }; export default urls;
utils/https.ts axios 請求的封裝。
import axios from "axios"; // 創建axios實例 let service: any = {}; service = axios.create({ baseURL: "/api", // api的base_url timeout: 50000 // 請求超時時間 }); // request攔截器 axios的一些配置 service.interceptors.request.use( (config: any) => { return config; }, (error: any) => { // Do something with request error console.error("error:", error); // for debug Promise.reject(error); } ); // respone攔截器 axios的一些配置 service.interceptors.response.use( (response: any) => { return response; }, (error: any) => { console.error("error:" + error); // for debug return Promise.reject(error); } ); export default service;
把 urls 和 https 掛載到 main.ts 里面的 Vue 的 prototype 上面。
import service from "./utils/https"; import urls from "./utils/urls"; Vue.prototype.$https = service; // 其他頁面在使用 axios 的時候直接 this.$http 就可以了 Vue.prototype.$urls = urls; // 其他頁面在使用 urls 的時候直接 this.$urls 就可以了
然后就可以統一管理接口,而且調用起來也很方便啦。比如下面 文章列表的請求。
async handleSearch() { this.isLoading = true; const res: any = await this.$https.get(this.$urls.getArticleList, { params: this.params }); this.isLoading = false; if (res.status === 200) { if (res.data.code === 0) { const data: any = res.data.data; this.articlesList = [...this.articlesList, ...data.list]; this.total = data.count; this.params.pageNum++; if (this.total === this.articlesList.length) { this.isLoadEnd = true; } } else { this.$message({ message: res.data.message, type: "error" }); } } else { this.$message({ message: "網絡錯誤!", type: "error" }); } }store ( Vuex )
一般大型的項目都有很多模塊的,比如本項目中有公共信息(比如 token )、 用戶模塊、文章模塊。
├── modules // 模塊 ├── user.ts // 用戶模塊 ├── article.ts // 文章模塊 ├── types.ts // 類型 └── index.ts // vuex 主入口
store/index.ts 存放公共的信息,并導入其他模塊
import Vue from "vue"; import Vuex from "vuex"; import * as types from "./types"; import user from "./modules/user"; import article from "./modules/article"; Vue.use(Vuex); const initPageState = () => { return { token: "" }; }; const store = new Vuex.Store({ strict: process.env.NODE_ENV !== "production", // 具體模塊 modules: { user, article }, state: initPageState(), mutations: { [types.SAVE_TOKEN](state: any, pageState: any) { for (const prop in pageState) { state[prop] = pageState[prop]; } } }, actions: {} }); export default store;
types.ts
// 公共 token export const SAVE_TOKEN = "SAVE_TOKEN"; // 用戶 export const SAVE_USER = "SAVE_USER";
user.ts
import * as types from "../types"; const initPageState = () => { return { userInfo: { _id: "", name: "", avator: "" } }; }; const user = { state: initPageState(), mutations: { [types.SAVE_USER](state: any, pageState: any) { for (const prop in pageState) { state[prop] = pageState[prop]; } } }, actions: {} }; export default user;7. markdown 渲染
markdown 渲染效果圖:
markdown 渲染 采用了開源的 marked, 代碼高亮用了 highlight.js 。
用法:
第一步:npm i marked highlight.js --save
npm i marked highlight.js --save
第二步: 導入封裝成 markdown.js,將文章詳情由字符串轉成 html, 并抽離出文章目錄。
marked 的封裝 得感謝這位老哥。
const highlight = require("highlight.js"); const marked = require("marked"); const tocObj = { add: function(text, level) { var anchor = `#toc${level}${++this.index}`; this.toc.push({ anchor: anchor, level: level, text: text }); return anchor; }, // 使用堆棧的方式處理嵌套的ul,li,level即ul的嵌套層次,1是最外層 //
第三步: 使用
import markdown from "@/utils/markdown"; // 獲取文章詳情 async handleSearch() { const res: any = await this.$https.post( this.$urls.getArticleDetail, this.params ); if (res.status === 200) { if (res.data.code === 0) { this.articleDetail = res.data.data; // 使用 marked 轉換 const article = markdown.marked(res.data.data.content); article.then((response: any) => { this.articleDetail.content = response.content; this.articleDetail.toc = response.toc; }); } else { // ... } else { // ... } } // 渲染
第四步:引入 monokai_sublime 的 css 樣式
第五步:對 markdown 樣式的補充
如果不補充樣式,是沒有黑色背景的,字體大小等也會比較小,圖片也不會居中顯示
/*對 markdown 樣式的補充*/ pre { display: block; padding: 10px; margin: 0 0 10px; font-size: 14px; line-height: 1.42857143; color: #abb2bf; background: #282c34; word-break: break-all; word-wrap: break-word; overflow: auto; } h1,h2,h3,h4,h5,h6{ margin-top: 1em; /* margin-bottom: 1em; */ } strong { font-weight: bold; } p > code:not([class]) { padding: 2px 4px; font-size: 90%; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; } p img{ /* 圖片居中 */ margin: 0 auto; display: flex; } #content { font-family: "Microsoft YaHei", "sans-serif"; font-size: 16px; line-height: 30px; } #content .desc ul,#content .desc ol { color: #333333; margin: 1.5em 0 0 25px; } #content .desc h1, #content .desc h2 { border-bottom: 1px solid #eee; padding-bottom: 10px; } #content .desc a { color: #009a61; }8. 注意點
關于 頁面
對于 關于 的頁面,其實是一篇文章來的,根據文章類型 type 來決定的,數據庫里面 type 為 3
的文章,只能有一篇就是 博主介紹 ;達到了想什么時候修改內容都可以。
所以當 當前路由 === "/about" 時就是請求類型為 博主介紹 的文章。
type: 3, // 文章類型: 1:普通文章;2:是博主簡歷;3 :是博主簡介;
移動端適配
移動端使用 rem 單位適配。
// 屏幕適配( window.screen.width / 移動端設計稿寬 * 100)也即是 (window.screen.width / 750 * 100) ——*100 為了方便計算。即 font-size 值是手機 deviceWidth 與設計稿比值的 100 倍 document.getElementsByTagName("html")[0].style.fontSize = window.screen.width / 7.5 + "px";
如上:通過查詢屏幕寬度,動態的設置 html 的 font-size 值,移動端的設計稿大多以寬為 750 px 來設置的。
比如在設計圖上一個 150 * 250 的盒子(單位 px):
原本在 css 中的寫法:
width: 150px; heigth: 250px;
通過上述換算后,在 css 中對應的 rem 值只需要寫:
width: 1.5rem; // 150 / 100 rem heigth: 2.5rem; // 250 / 100 rem
如果你的移動端的設計稿是以寬為 1080 px 來設置的話,就用 window.screen.width / 10.8 吧。
9. 踩坑記1. 讓 vue 識別全局方法/變量
我們經常在 main.ts 中給 vue.prototype 掛載實例或者內容,以方便在組件里面使用。
import service from "./utils/https"; import urls from "./utils/urls"; Vue.prototype.$https = service; // 其他頁面在使用 axios 的時候直接 this.$http 就可以了 Vue.prototype.$urls = urls; // 其他頁面在使用 urls 的時候直接 this.$urls 就可以了
然而當你在組件中直接 this.$http 或者 this.$urls 時會報錯的,那是因為 $http 和 $urls 屬性,并沒有在 vue 實例中聲明。
再比如使用 Element-uI 的 meesage。
import { Message } from "element-ui"; Vue.prototype.$message = Message;
之前用法如下圖:
this.$message({ message: "恭喜你,這是一條成功消息", type: "success" })
然而還是會報錯的。
再比如 監聽路由的變化:
import { Vue, Watch } from "vue-property-decorator"; import Component from "vue-class-component"; import { Route } from "vue-router"; @Component export default class App extends Vue { @Watch("$route") routeChange(val: Route, oldVal: Route) { // do something } }
只是這樣寫的話,監聽 $route 還是會報錯的。
想要以上三種做法都正常執行,就還要補充如下內容:
在 src 下的 shims-vue.d.ts 中加入要掛載的內容。 表示 vue 里面的 this 下有這些東西。
import VueRouter, { Route } from "vue-router"; declare module "vue/types/vue" { interface Vue { $router: VueRouter; // 這表示this下有這個東西 $route: Route; $https: any; // 不知道類型就定為 any 吧(偷懶) $urls: any; $Message: any; } }
2. 引入的模塊要聲明
比如 在組件里面使用 window.document 或者 document.querySelector 的時候會報錯的,npm run build 不給通過。
再比如:按需引用 element 的組件與動畫組件:
import { Button } from "element-ui"; import CollapseTransition from "element-ui/lib/transitions/collapse-transition";
npm run serve 時可以執行,但是在 npm run build 的時候,會直接報錯的,因為沒有聲明。
正確做法:
我在 src 下新建一個文件 shime-global.d.ts ,加入內容如下:
// 聲明全局的 window ,不然使用 window.XX 時會報錯 declare var window: Window; declare var document: Document; declare module "element-ui/lib/transitions/collapse-transition"; declare module "element-ui";
當然,這個文件你加在其他地方也可以,起其他名字都 OK。
但是即使配置了以上方法之后,有些地方使用 document.XXX ,比如 document.title 的時候,npm run build 還是通過不了,所以只能這樣了:
3. this 的類型檢查
比如之前的 事件的節流(throttle)與防抖(debounce)方法:
export function throttle(fn: Function, delay: number) { return function() { // 保留調用時的 this 上下文 let context = this; }
function 里面的 this 在 npm run serve 時會報錯的,因為 tyescript 檢測到它不是在類(class)里面。
正確做法:
在根目錄的 tsconfig.json 里面加上 "noImplicitThis": false ,忽略 this 的類型檢查。
// 忽略 this 的類型檢查, Raise error on this expressions with an implied any type. "noImplicitThis": false,
4. import 的 .vue 文件
import .vue 的文件的時候,要補全 .vue 的后綴,不然 npm run build 會報錯的。
比如:
import Nav from "@/components/nav"; // @ is an alias to /src import Footer from "@/components/footer"; // @ is an alias to /src
要修改為:
import Nav from "@/components/nav.vue"; // @ is an alias to /src import Footer from "@/components/footer.vue"; // @ is an alias to /src
5. 裝飾器 @Component
報錯。
以下才是正確,因為這里的 Vue 是從 vue-property-decorator import 來的。
6. 路由的組件導航守衛失效
vue-class-component 官網里面的路由的導航鉤子的用法是沒有效果的 Adding Custom Hooks
路由的導航鉤子不屬于 Vue 本身,這會導致 class 組件轉義到配置對象時導航鉤子無效,因此如果要使用導航鉤子需要在 router 的配置里聲明(網上別人說的,還沒實踐,不確定是否可行)。
7. tsconfig.json 的 strictPropertyInitialization 設為 false,不然你定義一個變量就必須給它一個初始值。
position: sticky;
本項目中的文章詳情的目錄就是用了 sticky。
.anchor { position: sticky; top: 213px; margin-top: 213px; }
position:sticky 是 css 定位新增屬性;可以說是相對定位 relative 和固定定位 fixed 的結合;它主要用在對 scroll 事件的監聽上;簡單來說,在滑動過程中,某個元素距離其父元素的距離達到 sticky 粘性定位的要求時(比如 top:100px );position:sticky 這時的效果相當于 fixed 定位,固定到適當位置。
用法像上面那樣用即可,但是有使用條件:
1、父元素不能 overflow:hidden 或者 overflow:auto 屬性。
2、必須指定 top、bottom、left、right 4 個值之一,否則只會處于相對定位
3、父元素的高度不能低于 sticky 元素的高度
4、sticky 元素僅在其父元素內生效
8. eslint 報找不到文件和裝飾器的錯
App.vue 中只是寫了引用文件而已,而且 webpack 和 tsconfig.josn 里面已經配置了別名了的。
import Nav from "@/components/nav.vue"; // @ is an alias to /src import Slider from "@/components/slider.vue"; // @ is an alias to /src import Footer from "@/components/footer.vue"; // @ is an alias to /src import ArrowUp from "@/components/arrowUp.vue"; // @ is an alias to /src import { isMobileOrPc } from "@/utils/utils";
但是,還是會報如下的錯:
只是代碼不影響文件的打包,而且本地與生產環境的代碼也正常,沒報錯而已。
這個 eslint 的檢測目前還沒找到相關的配置可以把這些錯誤去掉。
9. 路由模式修改為 history
因為文章詳情頁面有目錄,點擊目錄時定位定相應的內容,但是這個目錄定位內容是根據錨點來做的,如果路由模式為 hash 模式的話,本來文章詳情頁面的路由就是 #articleDetail 了,再點擊目錄的話(比如 #title2 ),會在 #articleDetail 后面再加上 #title2,一刷新會找不到這個頁面的。
10. Build Setup# clone git clone https://github.com/biaochenxuying/blog-vue-typescript.git
# cd cd blog-vue-typescript
# install dependencies npm install
# Compiles and hot-reloads for development npm run serve
# Compiles and minifies for production npm run build
### Run your tests npm run test
### Lints and fixes files npm run lint
### Run your unit tests npm run test:unit
Customize configuration
See Configuration Reference.
如果要看有后臺數據完整的效果,是要和后臺項目 blog-node 一起運行才行的,不然接口請求會失敗。
雖然引入了 mock 了,但是還沒有時間做模擬數據,想看具體效果,請穩步到我的網站上查看 https://biaochenxuying.cn
11. 項目地址與系列相關文章基于 Vue + TypeScript + Element 的 blog-vue-typescript 前臺展示: https://github.com/biaochenxuying/blog-vue-typescript
基于 react + node + express + ant + mongodb 的博客前臺,這個是筆者之前做的,效果和這個類似,地址如下:
blog-react 前臺展示: https://github.com/biaochenxuying/blog-react
推薦閱讀 :
本博客系統的系列文章:
react + node + express + ant + mongodb 的簡潔兼時尚的博客網站
react + Ant Design + 支持 markdown 的 blog-react 項目文檔說明
基于 node + express + mongodb 的 blog-node 項目文檔說明
服務器小白的我,是如何將node+mongodb項目部署在服務器上并進行性能優化的
github 授權登錄教程與如何設計第三方授權登錄的用戶表
一次網站的性能優化之路 -- 天下武功,唯快不破
Vue + TypeScript + Element 搭建簡潔時尚的博客網站及踩坑記
12. 最后筆者也是初學 TS ,如果文章有錯的地方,請指出,感謝。
一開始用 Vue + TS 來搭建時,我也是挺抵觸的,因為踩了好多坑,而且很多類型檢查方面也挺煩人。后面解決了,明白原理之后,是越用越爽,哈哈。
權衡如何更好的利用 JS 的動態性和 TS 的靜態特質,我們需要結合項目的實際情況來進行綜合判斷。一些建議:
如果是中小型項目,且生命周期不是很長,那就直接用 JS 吧,不要被 TS 束縛住了手腳。
如果是大型應用,且生命周期比較長,那建議試試 TS。
如果是框架、庫之類的公共模塊,那更建議用 TS 了。
至于到底用不用TS,還是要看實際項目規模、項目生命周期、團隊規模、團隊成員情況等實際情況綜合考慮。
其實本項目也是小項目來的,其實并不太適合加入 TypeScript ,不過這個項目是個人的項目,是為了練手用的,所以就無傷大大雅。
未來,class-compoent 也將成為主流,現在寫 TypeScript 以后進行 3.0 的遷移會更加方便。
每天下班后,用幾個晚上的時間來寫這篇文章,碼字不易,如果您覺得這篇文章不錯或者對你有所幫助,請給個贊或者星吧,你的點贊就是我繼續創作的最大動力。
參考文章:
vue + typescript 項目起手式
TypeScript + 大型項目實戰
對 全棧修煉 有興趣的朋友可以掃下方二維碼關注我的公眾號
我會不定期更新有價值的內容,長期運營。
關注公眾號并回復 福利 可領取免費學習資料,福利詳情請猛戳: Python、Java、Linux、Go、node、vue、react、javaScript
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109257.html
摘要:具體問題,就是中通過標簽引入一個第三方的圖片地址,報。解決方案如原網址顯示此圖片來自微信公眾平臺,未經允許不得應用方法在標簽里加這樣存在第三方網站上的圖片,在你的網站上就可以訪問了。 showImg(https://segmentfault.com/img/bVbtK8u?w=436&h=284); 問題 筆者網站的圖片都是上傳到第三方網站上的,比如 簡書、掘金、七牛云上的,但是最近簡...
摘要:前言都到了,所以是時候玩轉一下的新特性了。安裝的包名稱由改成了。方法一原因的配置改變了,導致正確的不能用。打開終端,切換到根路徑文件里面修改為方法二是默認路徑修改了路徑會出現錯誤。按上面的方法修改完,再全局卸載果然就成功了。 showImg(https://segmentfault.com/img/remote/1460000016423946); 前言 vue-cli 都到 3.0....
摘要:執行過程如下實現瀏覽器的前進后退第二個方法就是用兩個棧實現瀏覽器的前進后退功能。我們使用兩個棧,和,我們把首次瀏覽的頁面依次壓入棧,當點擊后退按鈕時,再依次從棧中出棧,并將出棧的數據依次放入棧。 showImg(https://segmentfault.com/img/bVbtK6U?w=1280&h=910); 如果要你實現一個前端路由,應該如何實現瀏覽器的前進與后退 ? 2. 問題...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
閱讀 1579·2021-09-26 09:46
閱讀 2664·2021-09-07 09:59
閱讀 2750·2021-09-07 09:59
閱讀 1855·2019-08-30 14:20
閱讀 922·2019-08-26 13:39
閱讀 3172·2019-08-26 12:24
閱讀 770·2019-08-26 11:55
閱讀 1211·2019-08-23 16:49