事情的起源是这样子的:
- 小生在学习 ts -> 想在 ts 的学习过程中把 ts 引入在 html 里面
- 小生使用 webpack 配置了 ts 的操作 -> 又想把在 ts 中使用 jquery 操作 dom
- 小生配好了jquery -> 又觉得 vue 似乎也可以使用同样的方法引入 总结一下做了什么:
实现了一个 webpack 项目,并可以通过 webpack 打包模块化的 ts 模块和其他 es6 模块,然后又把 vue 和 jquery 集成在了 webpack 项目中。
PS:本文中大多数内容并没有采用比较官方的语言,而是我个人的理解,不喜勿喷,多多指教。
1. ts 的学习
1.1 为什么要学习 ts
这个问题贯穿了小生整个学习过程。为什么学习ts,ts有哪些优越性,ts的设计初衷是什么?
首先抄一下别人的说法:TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
1.1.1. 为什么学习他?
原因很单纯:别人都会我不会,岂不是显得我很呆?
1.1.2 ts有哪些优越性?
这个问题让我想起来刚开始看 vue 的时候,脑子里在想一个问题,vue 有什么用?jquery + html 不香嘛?为什么要再引入一个包?这样效率岂不是更低了 . . .
其实实际上确实是这样的,但是,要知道 vue 的初衷是开发单页面应用,模块化+工程化+自动化 . . . ,而 jquery + html 充其量是个 demo。一旦复杂的页面结构和复杂的业务逻辑上来,就会出现一个 js 脚本好几万行,再优秀的代码如果变成这样也会被人诟病:变量的声明在第3行,赋值在 33333 行,而且 我要加个变量,起个啥名字咧? new_param_for_feat_1011_baseUserInfo(这样总不会再重复了吧 !-_· ) ,所以vue就是在解决这些问题。
扯远了,回过头来说,与其说ts的优越性,不如说它实现了什么,它在做什么,它解决了什么问题?
第一点:TypeScript 是一种给 JavaScript 添加特性的语言扩展,支持 ES6语法。语言的扩展和 vue 、react、jquery 等框架还不一样,这些框架是以便于开发业务为目的而针对js的二次封装形成的一个类
,并进一步通过 webpack 等一系列手段形成了伴生这些框架而形成的新的文件规范,但归根结底他的语法还是 js 的基础语法。但是 ts 是针对 js 这门语言进行的扩展,什么意思嘞?如果把汉字和汉语拼音拆开当做两门语言,就相当于原来只有文字,而ts为文字加上了拼音这样的功能类似。
第二点:ts是的设计目标是开发大型应用,什么是大型应用?小生我没有概念,总体感觉呢,代码量应该很大,然后模块很多,业务很复杂,迭代很紧凑,参与的人很多 . . .
这样一来,源代码的可读性维护性就要求要很高,而且是很高很高,如果做到可读性高呢?加注释!而我认为 TS 最核心的地方就是 注释,并且把注释做到了极致,把注释添加到代码里面,并在编译阶段进行校验,以至于把99%的开发阶段的bug都扼杀在摇篮里。可读性高了维护性就高了呀! 具体案例会在下面的部分结合两个案例简介一下。
当然还有第三点,写起来可能是麻烦,但用起来是真香,尤其是结合vscode的提示,每个变量都变得规规矩矩的,而且一目了然。
1.2 记录几个demo
下面这几个demo,算是几个杂糅性比较强的几个小demo,很简单,便于理解。
1.2.1 结合基础类型,接口,类相关内容的一个简单实现
这个demo主要是为了记录一下基础语法,中间结合了枚举、接口、基础类型声明、接口的继承、类实现接口这些方面的使用。
// 枚举 sex
enum Sex { Girl = 0, Boy }
// 接口 person
interface Person {
id: number
name: string
sex: Sex
age?: number
}
// 函数接口 - join
// 注意:这样的接口只能在 class 中使用,不能被对象接收
interface Join {
regist(): boolean
login(id: number): Person
}
// 函数接口 - exit
interface Exit {
logout(): boolean
}
// 函数接口 - user-operate - 继承
interface UserOperate extends Join, Exit {
play(): string
}
// 类接口
class UserPoc implements UserOperate, Person {
id: number
name: string
sex: Sex
age?: number = 16
constructor(user: Person) {
this.id = user.id
this.name = user.name
this.sex = user.sex
this.age = user.age
console.log('[PocPlus] init ...');
}
// 新的方法
init() {
this.regist()
this.login(1)
this.play()
this.logout()
}
regist() {
console.log('[PocPlus] regist ...');
console.table({
id: this.id,
name: this.name,
sex: this.sex,
age: this.age || 18,
})
return true
}
login(id: number) {
console.log('[PocPlus] login ...');
return {
id: this.id,
name: this.name,
sex: this.sex,
age: this.age,
}
}
play() {
console.log('[PocPlus] playing ...');
return this.name + ' is played.'
}
logout() {
console.log('[PocPlus] logout ...');
return true
}
}
// 使用
let Tom = new UserPoc({ id: 1, name: 'Bob', sex: 1 })
Tom.init()
1.2.2 类型断言的用法
// 类型断言
// 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
// 类型断言有两种形式。 其一是“尖括号”语法, 另一个为 as 语法
/* 需求: 定义一个函数得到一个字符串或者数值数据的长度 */
function getLen(param: number | string): number {
if ((<string>param).length) {
return (param as string).length
} else {
return XMLDocument.toString().length
}
}
1.2.3 函数类型的完整书写方式
// 函数类型
function add(x: number, y: number): number {
return x + y
}
// 完整的函数类型书写方式
const myAdd: (x: number, y: number) => number = (x: number, y: number): number => {
return x + y
}
const myAdd1: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y
}
1.2.4 泛型的两个尝试
/*
需求 1:
声明一个方法,传入任何数据,导出传入值,且类型一致
*/
// 不使用泛型的方法
function idenFoo(arg: string): string {
return arg
}
// ... 多个一样的方法,或者使用函数重载
// 使用any类型
function identFooAny(arg: any): any {
return arg
}
// 使用泛型方法
function identFoo<T>(arg: T): T {
return arg
}
// ---
/*
需求 2:
泛型类型为非基础类型
这个 demo 仅仅是因为是 demo 所以看起来有点恶心
*/
interface idenUserInfo<T> {
data: T[]
add: (t: T) => void
getById: (id: number) => T | undefined // getById 会返回 undefined
}
class idenUser {
id?: number
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
class UserCURD implements idenUserInfo<idenUser>{
data: idenUser[] = []
add(user: idenUser): void {
user = { ...user, id: Date.now() }
this.data.push(user)
console.log('保存user', user.id);
}
getById(id: number): idenUser | undefined {
return this.data.find(item => item.id === id)
}
}
const user1 = new UserCURD()
user1.add(new idenUser('Tom', 12))
user1.add(new idenUser('Bob', 16))
console.log(user1.data);
1.2.5 泛型约束
// 泛型约束
// 场景:如果我们直接怼一个泛型参数取 length 属性,会报错,因为这个泛型根本不知道它有这个属性
// 如:报错场景
function fn<T>(x: T): void {
// console.log(x.length); // Error x 上不存在 length
}
// 引入约束来实现这个需求
interface LengthWise {
length: number
}
function fn2<T extends LengthWise>(x: T): void {
console.log(x.length);
}
// fn2(11) // 报错
fn2([1, 2, 3])
fn('111111')
2. webpack + ts 进行项目打包
2.1 为什么要使用 webpack
ts需要编译以后才能被js引擎认识,所以要想验证你写的对不对,就必须要经过编译。而开发过程中最舒服的当然就是一边写一边检查了,而能实现这个需求的最常用的就是 webpack 了。
这里开始到最后都在使用webpack 代理着,但是只是在第一次进行了配置,并没有进行更改。
2.2 实现流程
实现流程主要分为几个部分:
-
新建并声明 webpack 入口文件: src/main.ts
import './ts/tsdemo' (() => { // setTimeout(() => { // document.open(); // document.write('hello webpack ts!') // document.write('<br/>') // document.close(); // }, 500) })()
-
新建并声明 index 页面:public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>webpack & TS</title>
</head>
<body>
<div id="app">
{{name}}
<div class="main"></div>
<div v-html="aTag"></div>
</div>
</body>
</html>
- webpack 配置文件 build/webpack.config.js
这里简述一下配置文件需要进行的一些配置吧:
1. 声明入口和导出文件
2. 加载ts-loader解析ts文件
3. 把打包的js文件写入html中
4. 配置一个 node 服务 webpack-dev-server
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
const isProd = process.env.NODE_ENV === 'production' // 是否生产环境
function resolve(dir) {
return path.resolve(__dirname, '..', dir)
}
module.exports = {
mode: isProd ? 'production' : 'development',
entry: {
app: './src/main.ts'
},
output: {
path: resolve('dist'),
filename: '[name].[contenthash:8].js'
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
include: [resolve('src')]
}
]
},
plugins: [
new CleanWebpackPlugin({}),
new HtmlWebpackPlugin({
template: './public/index.html'
})
],
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
devtool: isProd ? 'cheap-module-source-map' : 'eval-cheap-source-map',
devServer: {
host: 'localhost', // 主机名
stats: 'errors-only', // 打包日志输出输出错误信息
port: 8081,
open: true
}
}
-
配置 webpack 相关命令:启动 和 打包 (dev/build)
{ "scripts"{ "dev": "cross-env NODE_ENV=development webpack serve --config build/webpack.config.js", "build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js" } }
-
下载依赖
# ts yarn add -D typescript # webpack yarn add -D webpack webpack-cli yarn add -D webpack-dev-server # html plugin yarn add -D html-webpack-plugin clean-webpack-plugin # ts loader yarn add -D ts-loader # cross-env 修改环境 yarn add -D cross-env
-
执行
# 启动 npm run dev # 打包 npm run build
2.3 阶段性总结
- 传送门:深入浅出 Webpack
3. 加载 jquery 操作 dom
3.1 为什么选择 jquery?jquery 过气了吗?
jquery 到目前为止,可以作为最原始的一些针对于和原生html配合的一些封装的库。同类型的还有向 echarts、bootstrap之类的同类型的一些包。
与其说 jquery 过气了,不如说它完成了属于它的历史使命。在当今各种模块化开发的今天,jquery已经难以满足现在的开发节奏,所以现在用的较少了,但无论如何它都是一个非常优秀的js工具库。
3.2 实现流程
主流的实现流程分为两种:
第一种是在 index.html 中引入 jquery.min.js 文件,然后在webpack中通过 window 获取,这样去修改 index.html 而且还要附加下载 jquery 文件的方式固然不错,但是既然已经使用webpack了,当然希望所有的第三方包都通过模块化的思想音符进来。
第二种就是通过安装npm包,然后引入的方式,也是本文使用的方式:
-
安装使用的包
npm install jquery --save npm install --save-dev @types/jquery typings
-
引入并使用
为了验证 $ 符是否生效,这里修改了一下 index.html ,在里面加了个div。
// webpack.config.js module: { rules: [ //暴露$和jQuery到全局 { test: require.resolve('jquery'), //require.resolve 用来获取模块的绝对路径 use: [{ loader: 'expose-loader', options: 'jQuery' }, { loader: 'expose-loader', options: '$' }] } ] } // 使用 import $ from 'jquery' const a: string = '<a href="#">{{name}}</a>' $('.main').html(a) // index.html <body> <div id="app"> <div class="main"></div> </div> </body>
4. 加载 vue 实现数据双向绑定
4.1 为什么要在这样的项目中挂载 vue?vue-cli不香吗?
没有为什么,就是为了熟悉一下操作,证明一下可以实现。
4.2 实现流程
-
下载 vue
npm i vue -s
-
引入并使用
-
简单修改 html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>webpack & TS</title> </head> <body> <div id="app"> {{name}} </div> </body> </html>
-
编辑 ts 文件
import Vue from 'vue' const vm = new Vue({ el: '#app', data: { name: 'Jhon Ranber', }, })
-
修改webpack.config.js
module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', include: [resolve('src')] }, ] },
-
验证
重启服务,打开 localhost:8081,页面出现 Jhon Ranber。
-
5. 模块化实现 vue 的 methods,实现复用性
5.1 这是在做什么?
到这里是我真的想表达的一个思想,可能不实用,但是只是一个简单的解决方案,模块化 vue 的methods,实现methods 的可复用性。
场景是多个html及多个入口文件,每个html引入一个main.js。main.js 中使用 jquery 处理初始化的 html 加载,vue.js 实现数据双向绑定。
从而可以开发系列的工具函数,然后在多个页面中达到复用,进一步会使用webpack打包优化,将公共文件单独打包,优化打包体积,使首页展开比单页面应用从根本上变得更快,用户体验更优秀。
这个思路可能在现在微服务比较盛行的时代不太实用,但是可以用来活动活动脑子。
5.2 简单实现一个页面
-
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>webpack & TS</title> </head> <body> <div id="app"> {{name}} <div class="main"></div> <div v-html="aTag"></div> </div> </body> </html>
-
ts
// data-mtd.ts interface iFunc { [key: string]: (...arg: any) => any } let methodsList: iFunc = { click: () => { console.log('1'); }, hover: () => { console.log('2'); }, keyup: () => { console.log('3'); } } const mapMethods = (arr: Array<string>): object => { let obj: iFunc = {} arr.map(item => { obj[item] = methodsList[item] }) return obj } export default mapMethods // ---- // vue-00-main.ts import Vue from 'vue' import mapMethods from './data-mtd' const a: string = '<a href="#">{{name}}</a>' const vm = new Vue({ el: '#app', data: { name: 'Jhon Ranber', aTag: a }, mounted() { console.log('mounted::', this.name) }, methods: mapMethods(['clickHandler', 'keyup']) }) export default vm // ----- // vue-01-引入.ts import $ from 'jquery' const a: string = '<a href="#">{{name}}</a>' $('.main').html(a) import _Vm from './vue-00-main' _Vm.$data.name = '诸葛-孔明' setTimeout(() => { _Vm.$data.name = '诸葛-村夫' }, 1000); // ----- // main.ts import './vue/vue-01-引入' // -----
6. 收获与汇总
收获分两点:
- ts 这门”语言“有了初步的认知。
- 从以ts为基础编辑的项目中将ts单独摘出来,并进行简单的扩展。