背景
网课结束了啊哈哈哈哈哈哈...咳咳咳...呜呜呜... 这段时间在积极备考呢,英国考试局把毕业前最后一次全球统考取消了,改为学校评估成绩并采用专业科学严谨的数据模型分析学校提交成绩的可信度做出调整并公布最终成绩,学校无法胡作非为。于是还是需要参加老师自行出题的模考和期末考试了...
虽然很忙,但是闲暇时间还是有在折腾,赶在期末考试尝试做了本博客的 TypeScript 支持(重写),并且网课期间摸鱼改了一些博客上 UX/UI 相关的体验,于是就再赶在期末考试之前再水一篇文章吧...
TypeScript 入门
与 Nuxt.js 结合
TypeScript (www.typescriptlang.org) 是 JavaScript 的超集,为了使用 JavaScript 开发大型项目而生。其规避或解决了 JavaScript 一些常见大量重复出现的错误源,比如 Uncaught TypeError,加入了如:强类型判断与其他有趣的特性...据说能稍微方便别人看懂你的代码(对于我来说不存在的:)) 增加了代码的可读性和可维护性... TypeScript 在编辑器中支持非常良好,有各种提示可以参考,并且使用 Visual Studio Code 更能享受完整的生态。在尝试改造 Antony-Nuxt 时也确实遇到了很多次 TypeScript 的类型判断帮助 debug 的情况呢。具体可以参考来自掘金的安利指南与入门小册:
- https://juejin.im/post/5d8efeace51d45782b0c1bd6
- https://juejin.im/book/5da08714518825520e6bb810
学到新技术当然是照例在博客上动土,Nuxt.js 可以借助官方 TypeScript Module 来实现支持。如下是粗略的入门实践,深入的探究和学习缓缓先哈哈哈哈哈咳咳(真不要脸啊)。
首先需要安装 Nuxt 提供的 TypeScript 编译模组 @nuxt/typescript-build 以实现在项目(.ts 文件、.vue 文件)中书写和解析 TypeScript,具体安装流程可见 → https://typescript.nuxtjs.org/guide/setup.html#configuratio 完成后于 nuxt.config.js 加入配置:
代码语言:javascript复制buildModules: [
['@nuxt/typescript-build', {
typeCheck: true, //在不同的程序中启用 TypeScript 的类型检查
ignoreNotFoundWarnings: true
}]
]
↑ nuxt.config.js
在这之后就可以开始设定 TypeScript 的编译选项了,根目录下创建 tsconfig.json 来设定选项。需要注意的是在使用 @nuxtjs/axios 模块时(参照以下文章以了解使用原因) 可以通过 @types 声明它的类型(第三方模块类型声明在后文提及)
博客 Nuxt.js 移植重构与服务端渲染入门实现
ID: 659 发布于: 2020-03-13 20:09:20
tsconfig.json 的样例配置如下:
代码语言:javascript复制{
"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 文件配置如下:
代码语言:javascript复制declare module "*.vue" {
import Vue from 'vue'
export default Vue
}
↑ vue-shim.d.ts
同时项目中还引用了其他来自第三方的依赖,也需要为它们声明类型,可以新建 types/global.d.ts 文件样例配置如下:
代码语言:javascript复制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),使用样例如下:
代码语言:javascript复制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) 时需要采用以下方式书写:
代码语言:javascript复制@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 构建插件:
代码语言:javascript复制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 类型,于是需要做以下改写样例:
代码语言:javascript复制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 来编译和启动服务:
代码语言:javascript复制"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,这样一来就可以获取到子页面文档高度了。需要注意的是跨域问题,解决办法是强制设定父子页面域名一致,代码如下:
代码语言:javascript复制// 与 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
}
}
}
})
↑ 高度获取实现
逻辑是在快要滑动至底部评论区时请求获取子页面高度并调整父页面评论区高度和大小。但是新评论提交后高度变化并不是即时的也存在数据传输延迟导致不能直接通过父页面的再次请求来获取高度,于是才增加了「开启滑动」按钮来变相解决这个问题哈哈哈哈