Vue + TypeScript + Element 搭建简洁时尚的博客网站及踩坑记

前言 本文讲解如何在 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 +TypeScript vue-class-component vue-class-component 对 Vue 组件进行了一层封装,让 Vue 组件语法在结合了 TypeScript 语法之后更加扁平化: 上面的代码跟下面的代码作用是一样的: 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); ti
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信