Vue + TypeScript + Element 项目实践(简洁时尚博客网站)及踩坑记

news/2024/5/9 13:29:12/文章来源:https://blog.csdn.net/weixin_34056162/article/details/91431877

前言

本文讲解如何在 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 端

  • 移动端

完整效果请看:biaochenxuying.cn

2. 功能

已经完成功能

  • 登录
  • 注册
  • 文章列表
  • 文章归档
  • 标签
  • 关于
  • 点赞与评论
  • 留言
  • 历程
  • 文章详情(支持代码语法高亮)
  • 文章详情目录
  • 移动端适配
  • 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<number> = [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 语法之后更加扁平化:
<template><div><input v-model="msg"><p>prop: {{propMessage}}</p><p>msg: {{msg}}</p><p>helloMsg: {{helloMsg}}</p><p>computed msg: {{computedMsg}}</p><button @click="greet">Greet</button></div>
</template><script>
import Vue from 'vue'
import Component from 'vue-class-component'@Component({props: {propMessage: String}
})
export default class App extends Vue {// initial datamsg = 123// use prop values for initial datahelloMsg = 'Hello, ' + this.propMessage// lifecycle hookmounted () {this.greet()}// computedget computedMsg () {return 'computed ' + this.msg}// methodgreet () {alert('greeting: ' + this.msg)}
}
</script>
复制代码

上面的代码跟下面的代码作用是一样的:

<template><div><input v-model="msg"><p>prop: {{propMessage}}</p><p>msg: {{msg}}</p><p>helloMsg: {{helloMsg}}</p><p>computed msg: {{computedMsg}}</p><button @click="greet">Greet</button></div>
</template><script>
export default {// 属性props: {propMessage: {type: String}},data () {return {msg: 123,helloMsg: 'Hello, ' + this.propMessage}},// 声明周期钩子mounted () {this.greet()},// 计算属性computed: {computedMsg () {return 'computed ' + this.msg}},// 方法methods: {greet () {alert('greeting: ' + this.msg)}},
}
</script>
复制代码
  • 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 quxcreated () {this.stateFoo // -> store.state.foothis.stateBar // -> store.state.barthis.getterFoo // -> store.getters.foothis.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.mdchainWebpack: () => {},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分离插件 ExtractTextPluginextract: 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 coresparallel: require("os").cpus().length > 1,// PWA 插件相关配置// see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwapwa: {},// 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_urltimeout: 50000 // 请求超时时间});// request拦截器 axios的一些配置
service.interceptors.request.use((config: any) => {return config;},(error: any) => {// Do something with request errorconsole.error("error:", error); // for debugPromise.reject(error);}
);// respone拦截器 axios的一些配置
service.interceptors.response.use((response: any) => {return response;},(error: any) => {console.error("error:" + error); // for debugreturn 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是最外层// <ul>//   <li></li>//   <ul>//     <li></li>//   </ul>//   <li></li>// </ul>toHTML: function() {let levelStack = [];let result = "";const addStartUL = () => {result += '<ul class="anchor-ul" id="anchor-fix">';};const addEndUL = () => {result += "</ul>\n";};const addLI = (anchor, text) => {result +='<li><a class="toc-link" href="#' + anchor + '">' + text + "<a></li>\n";};this.toc.forEach(function(item) {let levelIndex = levelStack.indexOf(item.level);// 没有找到相应level的ul标签,则将li放入新增的ul中if (levelIndex === -1) {levelStack.unshift(item.level);addStartUL();addLI(item.anchor, item.text);} // 找到了相应level的ul标签,并且在栈顶的位置则直接将li放在此ul下else if (levelIndex === 0) {addLI(item.anchor, item.text);} // 找到了相应level的ul标签,但是不在栈顶位置,需要将之前的所有level出栈并且打上闭合标签,最后新增lielse {while (levelIndex--) {levelStack.shift();addEndUL();}addLI(item.anchor, item.text);}});// 如果栈中还有level,全部出栈打上闭合标签while (levelStack.length) {levelStack.shift();addEndUL();}// 清理先前数据供下次使用this.toc = [];this.index = 0;return result;},toc: [],index: 0
};class MarkUtils {constructor() {this.rendererMD = new marked.Renderer();this.rendererMD.heading = function(text, level, raw) {var anchor = tocObj.add(text, level);return `<h${level} id=${anchor}>${text}</h${level}>\n`;};highlight.configure({ useBR: true });marked.setOptions({renderer: this.rendererMD,headerIds: false,gfm: true,tables: true,breaks: false,pedantic: false,sanitize: false,smartLists: true,smartypants: false,highlight: function(code) {return highlight.highlightAuto(code).value;}});}async marked(data) {if (data) {let content = await marked(data); // 文章内容let toc = tocObj.toHTML(); // 文章目录return { content: content, toc: toc };} else {return null;}}
}const markdown = new MarkUtils();export default markdown;
复制代码

第三步: 使用

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 {// ... }}// 渲染
<div id="content"class="article-detail"v-html="articleDetail.content">
</div>
复制代码

第四步:引入 monokai_sublime 的 css 样式

<link href="http://cdn.bootcss.com/highlight.js/8.0/styles/monokai_sublime.min.css" rel="stylesheet">
复制代码

第五步:对 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 识别全局方法/变量
  1. 我们经常在 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 实例中声明。

  1. 再比如使用 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 还是通过不了,所以只能这样了:

<script lang="ts">
// 在用到 document.XXX  的文件中声明一下即可
declare var document: any;
// 此处省略 XXXX 多的代码
</script>
复制代码
  • 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

报错。

<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
export default class LoadingCustom extends Vue {}
</script>
复制代码

以下才是正确,因为这里的 Vue 是从 vue-property-decorator import 来的。

<script lang="ts">
import { Vue, Component } from "vue-property-decorator";@Component
export default class LoadingCustom extends Vue {}
</script>
复制代码
  • 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 了,但是还没有时间做模拟数据,想看具体效果,请稳步到我的网站上查看 biaochenxuying.cn

11. 项目地址与系列相关文章

基于 Vue + TypeScript + Element 的 blog-vue-typescript 前台展示

基于 react + node + express + ant + mongodb 的博客前台,这个是笔者之前做的,效果和这个类似,地址如下: blog-react 前台展示

推荐阅读 :

本博客系统的系列文章:

    1. react + node + express + ant + mongodb 的简洁兼时尚的博客网站
    1. react + Ant Design + 支持 markdown 的 blog-react 项目文档说明
    1. 基于 node + express + mongodb 的 blog-node 项目文档说明
    1. 服务器小白的我,是如何将node+mongodb项目部署在服务器上并进行性能优化的
    1. github 授权登录教程与如何设计第三方授权登录的用户表
    1. 一次网站的性能优化之路 -- 天下武功,唯快不破
    1. Vue + TypeScript + Element 搭建简洁时尚的博客网站及踩坑记

12. 最后

笔者也是初学 TS ,如果文章有错的地方,请指出,感谢。

一开始用 Vue + TS 来搭建时,我也是挺抵触的,因为踩了好多坑,而且很多类型检查方面也挺烦人。后面解决了,明白原理之后,是越用越爽,哈哈。

权衡

如何更好的利用 JS 的动态性和 TS 的静态特质,我们需要结合项目的实际情况来进行综合判断。一些建议:

  • 如果是中小型项目,且生命周期不是很长,那就直接用 JS 吧,不要被 TS 束缚住了手脚。
  • 如果是大型应用,且生命周期比较长,那建议试试 TS。
  • 如果是框架、库之类的公共模块,那更建议用 TS 了。

至于到底用不用TS,还是要看实际项目规模、项目生命周期、团队规模、团队成员情况等实际情况综合考虑。

其实本项目也是小项目来的,其实并不太适合加入 TypeScript ,不过这个项目是个人的项目,是为了练手用的,所以就无伤大大雅。

未来,class-compoent 也将成为主流,现在写 TypeScript 以后进行 3.0 的迁移会更加方便。

每天下班后,用几个晚上的时间来写这篇文章,码字不易,如果您觉得这篇文章不错或者对你有所帮助,请给个赞或者星吧,你的点赞就是我继续创作的最大动力。

参考文章:

  1. vue + typescript 项目起手式

  2. TypeScript + 大型项目实战

全栈修炼 有兴趣的朋友可以扫下方二维码关注我的公众号

我会不定期更新有价值的内容,长期运营。

关注公众号并回复 福利 可领取免费学习资料,福利详情请猛戳: Python、Java、Linux、Go、node、vue、react、javaScript

转载于:https://juejin.im/post/5c9dc965e51d4512967dd74c

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_814310.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第2章 大型网站架构模式

2019独角兽企业重金招聘Python工程师标准>>> ##2.1 网站架构模式## 为了解决大型网站面临的高并发访问&#xff0c;海量数据处理&#xff0c;高可靠运行等一系列问题与挑战&#xff0c;大型互联网公司在实践中提出了许多解决方案&#xff0c;以实现网站高性能&#…

基于jQuery游戏网站焦点图轮播特效

基于jQuery的一款游戏网站焦点图轮播特效。这是一款带进度条定时切换&#xff0c;带缩略图切换的jQuery网站焦点图代码。效果图如下: 在线预览 源码下载 实现的代码。 html代码&#xff1a; <center><br><div id"chinaz"><div class"ba…

保护您的网站不受黑客攻击系列二

步骤#1&#xff1a;使用CSP 跨站点脚本&#xff08;XSS&#xff09;攻击与SQL注入类似&#xff0c;是网站所有者需注意的另一常见威胁。当黑客找到渠道把恶意JavaScript代码偷偷写入您的网页时&#xff0c;曝露在该代码下的您访客的页面也会随之受到影响&#xff0c;这就是所谓…

onmouseover和onmouseout在GridView中应用 Ver2

第一个版本&#xff0c;可以参考&#xff1a;http://www.cnblogs.com/insus/archive/2009/03/13/1411057.html以前的版本&#xff0c;是在Gridview的OnRowCreated事件进行写代码。现今此版本&#xff0c;是使用Override Render方法进行&#xff0c; 能写更少与简洁的代码&#…

IIS网站服务器性能优化指南(转载)

原文网址:http://www.phontol.com/20090507_419416_1.html Windows Server自带的互联网信息服务器&#xff08;Internet Information Server&#xff0c;IIS&#xff09;是架设网站服务器的常用工具&#xff0c;它是一个既简单而又麻烦的东西&#xff0c;新手都可以使用I…

如何在电脑上测试手机网站(全)

最近公司要开发网站的移动版&#xff0c;让我准备准备知识&#xff0c;话说本人开发移动网站的经验还真不多&#xff0c;最悲剧的事情就是我的手机是个经典的诺基亚&#xff0c;而且公司还不给配手机&#xff0c;这是有多讨厌&#xff0c;没办法&#xff0c;没有手机只能用电脑…

我给女朋讲编程网络系列(2)--IIS8 如何在本地发布网站

通过IIS8 在本地发布网站&#xff0c;一个截图&#xff0c;你就全明白了&#xff0c;越是简单&#xff0c;越是实用。 如果有现成的网站&#xff0c;就将你的网站放到一个文件夹中&#xff0c;比如WebTest2中。 如何没有网站&#xff0c;可以在WebTest2中新建一个index.html文…

网站运营,从一条日志说起

2019独角兽企业重金招聘Python工程师标准>>> 网站运营的过程中需要关注的点很多&#xff0c;包括业务层面的&#xff0c;比如活跃用户数&#xff0c;刷单用户&#xff0c;新增用户数&#xff0c;恶意注册用户等&#xff1b;也包括安全层面&#xff0c;包括恶意密码猜…

互联网安全和数据挖掘网站

检测、踩点网站&#xff1a;https://www.netcraft.com/ 转载于:https://www.cnblogs.com/ZQWelcomeIndex/p/11442145.html

大型网站架构系列:消息队列(二) (转)

本文是大型网站架构系列&#xff1a;消息队列&#xff08;二&#xff09;&#xff0c;主要分享JMS消息服务&#xff0c;常用消息中间件&#xff08;Active MQ&#xff0c;Rabbit MQ&#xff0c;Zero MQ&#xff0c;Kafka&#xff09;。【第二篇的内容大部分为网络资源的整理和汇…

教你如何快速下载网站?如何仿制网址,下载网站,获取网站html文件,图像文件

原文地址&#xff1a;https://www.yii666.com/share/92.html 如何仿制网址,下载网站,获取网站html文件,图像文件 1、本文通过 WinHTTrack Website Copier 软件讲解如何实现下载网站进行离线访问。 安装包&#xff1a;httrack_x64-3.49.2.zip,点击下载。 2、点开软件&#xff0…

减少HTTP请求之将图片转成二进制并生成Base64编码,可以在网页中通过url查看图片(大型网站优化技术)...

减少HTTP请求之将图片转成二进制并生成Base64编码,可以在网页中通过url查看图片(大型网站优化技术) 原文:减少HTTP请求之将图片转成二进制并生成Base64编码,可以在网页中通过url查看图片(大型网站优化技术)在网站开发过程中&#xff0c;对于页面的加载效率一般都想尽办法求快。…

苹果在线签名服务器搭建,苹果iOS企业签名 、apple 超级签名网站服务器选用

苹果iOS企业签名运行流程1.用户手机安装预留的描述文件&#xff0c;获取本机udid后&#xff0c;向服务器返回用户的udid2.服务器收到UDID后&#xff0c;将UDID添加到开发者账号下&#xff0c;下载此udid签名用的证书描述文件。3.然后用此udid签名用的证书描述文件&#xff0c;把…

网站新文章全自动监控工具 支持 『论坛、贴吧』等新帖提醒

来源于&#xff1a;http://www.52pojie.cn/thread-532237-1-1.html 前言每天打开电脑&#xff0c;就是逛论坛&#xff0c;看一些娱乐网&#xff0c;看看有没有啥新鲜干货&#xff0c;现在已经有强迫症&#xff0c;于是这款工具诞生了&#xff0c;可以一键获取所有想要关注的网站…

参加google.com的送钱广告计划---“全面发挥贵网站的创收潜力”

分享一下我老师大神的人工智能教程&#xff01;零基础&#xff0c;通俗易懂&#xff01;http://blog.csdn.net/jiangjunshow也欢迎大家转载本篇文章。分享知识&#xff0c;造福人民&#xff0c;实现我们中华民族伟大复兴&#xff01;Google.com的金字招牌作保证&#xff0c;放心…

日常实用网站推荐

经常会有朋友让我推荐一些好用的网站&#xff0c;因此今天就写一篇博客&#xff0c;为大家介绍一些我认为爱不释手的日常实用网站&#xff1a;01 | 搞学习CSDN哔哩哔哩TED网易公开课菜鸟教程02 | 写代码GitHub码云开源中国源码之家03 | 小工具果核剥壳字体天下矢量图标库表情符…

SEO深度解读之HITS链接分析算法

HITS(Hyperlink - Induced Topic Search)链接分析算法诞生在1997年&#xff0c;该算法是由康奈尔大学中的一位博士提出&#xff0c;并且该算法沿用于全球多个搜索引擎当中。当然&#xff0c;不同的搜索引擎针对于该算法的侧重点和内部公式都有不一的算法结构调整&#xff0c;并…

Linux三大主流网站构建平台,Linux快速构建LAMP网站平台

1.1 问题本例要求基于Linux主机快速构建LAMP动态网站平台&#xff0c;并确保可以支撑PHP应用及数据库&#xff0c;完成下列任务&#xff1a;1)安装LAMP平台各组件&#xff0c;启动LAMP平台软件包&#xff1a;httpd、mariadb-server、mariadb、php、php-mysql系统服务&#xff1…

推荐一个制作卡通头像的网站(超强)

推荐一个制作卡通头像的网站 http://faceyourmanga.it/faceyourmanga.php?langeng 非常方便 灵活。 看看图的选择项目&#xff0c;脸型、眼睛、鼻子、嘴巴、发型、甚至连各种装饰品、纹身都有 如图

java 责任链 开源代码,JavaThinker技术网站

责任链(Chain of Responsibility)模式的定义&#xff1a;为了避免请求发送者与多个请求处理者耦合在一起&#xff0c;将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链&#xff1b;当有请求发生时&#xff0c;可将请求沿着这条链传递&#xff0c;直到有对象处…