背景
网课结束了啊哈哈哈哈哈哈...咳咳咳...呜呜呜... 这段时间在积极备考呢,英国考试局把毕业前最后一次全球统考取消了,改为学校评估成绩并采用专业科学严谨的数据模型分析学校提交成绩的可信度做出调整并公布最终成绩,学校无法胡作非为。于是还是需要参加老师自行出题的模考和期末考试了...
虽然很忙,但是闲暇时间还是有在折腾,赶在期末考试尝试做了本博客的 TypeScript 支持(重写),并且网课期间摸鱼改了一些博客上 UX/UI 相关的体验,于是就再赶在期末考试之前再水一篇文章吧...
TypeScript 入门
与 Nuxt.js 结合
TypeScript (www.typescriptlang.org) 是 JavaScript 的超集,为了使用 JavaScript 开发大型项目而生。其规避或解决了 JavaScript 一些常见大量重复出现的错误源,比如 Uncaught TypeError,加入了如:强类型判断与其他有趣的特性...据说能稍微方便别人看懂你的代码(对于我来说不存在的:)) 增加了代码的可读性和可维护性... TypeScript 在编辑器中支持非常良好,有各种提示可以参考,并且使用 Visual Studio Code 更能享受完整的生态。在尝试改造 Antony-Nuxt 时也确实遇到了很多次 TypeScript 的类型判断帮助 debug 的情况呢。具体可以参考来自掘金的安利指南与入门小册:
学到新技术当然是照例在博客上动土,Nuxt.js 可以借助官方 TypeScript Module 来实现支持。如下是粗略的入门实践,深入的探究和学习缓缓先哈哈哈哈哈咳咳(真不要脸啊)。
首先需要安装 Nuxt 提供的 TypeScript 编译模组 @nuxt/typescript-build 以实现在项目(.ts 文件、.vue 文件)中书写和解析 TypeScript,具体安装流程可见 → https://typescript.nuxtjs.org/guide/setup.html#configuratio 完成后于 nuxt.config.js 加入配置:
buildModules: [
['@nuxt/typescript-build', {
typeCheck: true, //在不同的程序中启用 TypeScript 的类型检查
ignoreNotFoundWarnings: true
}]
]
↑ nuxt.config.js
在这之后就可以开始设定 TypeScript 的编译选项了,根目录下创建 tsconfig.json 来设定选项。需要注意的是在使用 @nuxtjs/axios 模块时(参照以下文章以了解使用原因) 可以通过 @types 声明它的类型(第三方模块类型声明在后文提及)
tsconfig.json 的样例配置如下:
{
"compilerOptions": {
"target": "es2018",
"module": "esnext",
"moduleResolution": "node",
"experimentalDecorators": true,
"lib": [
"esnext",
"esnext.asynciterable",
"dom"
],
"esModuleInterop": true,
"allowJs": true,
"sourceMap": true,
"strict": true,
"noEmit": true,
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./*"
]
},
"types": [
"@types/node",
"@nuxt/types",
"@nuxtjs/axios"
]
},
"exclude": [
"node_modules"
]
}
↑ tsconfig.json
其次需要为 .vue 文件声明类型,可以于根目录新建 types 文件夹,其中包含 d.ts 文件来配置全局类型声明。新建 types/vue-shim.d.ts 文件配置如下:
declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
↑ vue-shim.d.ts
同时项目中还引用了其他来自第三方的依赖,也需要为它们声明类型,可以新建 types/global.d.ts 文件样例配置如下:
declare module 'vue-i18n'
declare module 'vue-mugen-scroll'
declare module 'jquery'
declare module 'highlight.js'
declare module 'vue-cookie'
declare module 'nprogress'
declare module 'node-fetch'
↑ global.d.ts
声明文件作用与其他声明语句可参考 → https://ts.xcatliu.com/basics/declaration-files#shen-me-shi-sheng-ming-wen-jian
在此之后项目中的 .js 文件就都可以支持编译了,Antony-Nuxt 中将 Plugins 都改写为了 .ts,并无需另外的配置。接下来便可以开始修改 .vue 文件中的 JavaScript 语句了。
改写开始
与 Scss 类似,在 Vue 文件的 <script> 标签中加入 lang="ts" 即可书写 TypeScript 了。TypeScript 与 Vue 结合时可以通过以下两种方式来改写,首先是叫 Options API (官方文档这么写的我也不知道啊):
import Vue from 'vue'
export default Vue.extend({
data(){},
methods:{}
...
})
↑ Options API
这种方法无法使用 TypeScript 装饰器 (https://www.tslang.cn/docs/handbook/decorators.html) 特征,不用不是亏了吗,于是可以使用 vue-property-decorator 依赖来实现。Nuxt 也提供了基于它的依赖 nuxt-property-decorator (https://github.com/nuxt-community/nuxt-property-decorator),使用样例如下:
import { Component, Vue, Inject } from 'nuxt-property-decorator'
import topInsideCate from '~/components/topInsideCate.vue'
@Component({
components: {
topInsideCate
}
})
export default class Friends extends Vue {}
↑ Class API
需要注意的是 mounted()、updated() 等函数没有装饰器提供,并且在使用 Vue 过滤器 Filters (https://cn.vuejs.org/v2/guide/filters.html) 时需要采用以下方式书写:
@Component({
filters: {
link_page: function(cate_id: number): string {
if (cate_id == 2) {
return '添加于 '
} else if (cate_id == 5) {
return '创造于 '
} else {
return ''
}
}
}
})
export default class Cates extends Vue {}
↑ Filters 书写方法
具体可以参照 nuxt-property-decorator 文档使用。样例中,在此之后可直接在 Cates 类里定义函数(即编译为 methods 里的函数)、成员变量(即编译为 data 里的变量)等。同时也需要在 nuxt.config.js 中配置 babel 构建插件:
build: {
babel: {
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }]
]
},
}
↑ nuxt.config.js
在 Antony-Nuxt 中做了 SSR 服务端渲染支持,由后端异步请求数据后再渲染页面,其需要用到 async 函数 (http://www.ruanyifeng.com/blog/2015/05/async.html) 在 TypeScript 中也规定必须返回 Promise 类型,于是需要做以下改写样例:
async asyncData(context: any): Promise<{ tags: any[] }> {
let res: any[] = await Promise.all([
context.$axios
.get(
'https://www.ouorz.com/wp-json/wp/v2/tags?orderby=count&order=desc&per_page=15'
)
.then((tag_posts: { data: any }) => {
return tag_posts.data
})
])
return {
tags: res[0]
}
}
↑ 返回类型限制为 Promise<{}>
顺便一提,TypeScript 中(不知道是不是我的配置问题)需要使用文件全名来引入其他 .vue 组件,比如:
//import topInsideCate from '~/components/topInsideCate'
import topInsideCate from '~/components/topInsideCate.vue'
↑ 真奇妙
编译打包
首先既然支持了 TypeScript 自然就都改成 .ts 文件的好,于是修改 server/index.js 文件为 server/index.ts 即可,语法兼容。之后需要在 package.json 中修改编译打包配置,如下为样例。需要注意的是在生产环境需要使用 ts-node 来编译和启动服务:
"scripts": {
"dev": "cross-env NODE_ENV=development nodemon server/index.ts --watch server",
"build": "nuxt build",
"start": "nuxt-ts start",
"generate": "nuxt generate",
}
↑ package.json
评论区优化
应该有注意到呢,博客页面和文章下方的评论区现在高度展示正常了。之前是因为 Artalk 与其他 js 兼容的问题所以使用了 iframe 方式嵌入评论区,但是由于加载博客页面时需要一段时间等待 iframe 网页端获取评论数据以展示,就会出现加载完成后高度无法被博客页面获取的问题。之前也是一直采用固定高度滚动的方式来实现,用户体验不好没有评论欲望。没有人评论就没有人吐槽,也就没法相互♂学习进步了啊,这个问题终于被解决。
小小的百度了一下,发现父页面其实可以通过 iframe 元素的 contentWindow 属性来获取到子页面的 document,这样一来就可以获取到子页面文档高度了。需要注意的是跨域问题,解决办法是强制设定父子页面域名一致,代码如下:
// 与 iframe 通信获取评论列表高度函数
function getCommentsHeight():void {
// 强制设置同源
document.domain = 'ouorz.com'
var iframe:any = document.getElementById('article-comments-iframe')
var iwindow:any = iframe.contentWindow
var idoc:any = iwindow.document
iframe.style.height = idoc.body.offsetHeight + 'px'
}
// 强制设置同源
document.domain = 'ouorz.com'
/*
评论区监听事件
mounted 中执行会被在文章目录组件中对于监听的重置污染
*/
// 监听滑动,接近底部触发高度获取请求
$(window).scroll(function() {
//仅在文章页监听
if (window.location.pathname.split('/')[1] == 'post') {
var scrollTop = $(window).scrollTop()
var scrollHeight = $('div.footer.reveal').offset().top - 1500
if (scrollTop >= scrollHeight) {
if (click == 0) {
getCommentsHeight()
click++
}
}
}
})
↑ 高度获取实现
逻辑是在快要滑动至底部评论区时请求获取子页面高度并调整父页面评论区高度和大小。但是新评论提交后高度变化并不是即时的也存在数据传输延迟导致不能直接通过父页面的再次请求来获取高度,于是才增加了「开启滑动」按钮来变相解决这个问题哈哈哈哈 🙂
CSS 的 Cursor 属性
最近才发现可以通过 CSS 自定义元素的鼠标悬停样式,应该很多 dalao 都知道了,这里只是做个记录吧哈哈哈哈 🙂 万一你不知道呢,万一你也和我以前一样遇到一个普通元素配置了 @click 函数但是不知道怎么出现「点击」样式时无奈地只能包上 <a href="#"> 标签呢。由于博客增加了路由特征,现在这种方法会导致路由变更重新渲染的不必要操作,于是才找到了 Cursor 属性 (https://www.w3school.com.cn/cssref/pr_class_cursor.asp)。例如直接使用:
cursor: pointer
即可使一个元素「可点击」了,真是神奇 🙂
X 轴长内容滚动
Tony 主题群里又有人反馈无触控板的电脑端标签段无法滑动,之前因为美观并没有默认展示滚动条也就是 overflow-x: auto 自动隐藏滚动条,但是终究还是不能这样敷衍问题(被逼无奈哈哈哈哈),还是通过点按滚动来实现了。实现逻辑就是获取到所有标签元素,然后通过 transform: translateX(Mpx) 来整体移动。效果见博客首页,之后会完善好加入 Tony 主题...
渐进式应用程式
Nuxt 提供了 @nuxtjs/pwa 模块,可以快速简单地增加 PWA (https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps) 支持,在现代浏览器上支持离线访问、本地 App 打开,并且可在手机与电脑全端全平台使用,岂不妙哉。安装流程可见 → https://pwa.nuxtjs.org/setup.html
完成依赖安装后修改 nuxt.config.js 文件,配置 manifest 选项 (https://developer.mozilla.org/zh-CN/docs/Web/Manifest) 与 nuxt 模块,样例如下:
manifest: {
name: "TonyHe's Blog",
short_name: "TonyHe",
description: "Just A Poor Lifesinger",
background_color: "#f6f7f8",
theme_color: "#f1f2f3",
lang: "zh",
start_url: "/"
},
render: {
resourceHints: false,
http2: {
push: true
},
static: {
maxAge: "1y",
setHeaders(res, path) {
if (path.includes("sw.js")) {
res.setHeader("Cache-Control", `public, max-age=${15 * 60}`);
}
}
}
},
modules:["@nuxtjs/pwa"]
↑ nuxt.config.js
本地开发环境如果没有配置 https 是不可以安装 PWA 应用的,重新部署至生产环境并刷新缓存即可安装了。效果如下:
根据官方文档还可以增加 OneSignal 提供的通知推送服务,后面可能有需要再折腾上或者到其他项目里 🙂
后记
水完了之后干啥呢,学习!