This commit is contained in:
zxfjd3g
2020-11-12 21:07:26 +08:00
parent 981d157ac7
commit 832b961d34
34 changed files with 1647 additions and 18 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,464 +0,0 @@
# 2. Composition API(其它部分)
## 1) shallowReactive 与 shallowRef
- shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
- shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
- 什么时候用浅响应式呢?
- 一般情况下使用ref和reactive即可
- 如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
- 如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
```vue
<template>
<h2>App</h2>
<h3>m1: {{m1}}</h3>
<h3>m2: {{m2}}</h3>
<h3>m3: {{m3}}</h3>
<h3>m4: {{m4}}</h3>
<button @click="update">更新</button>
</template>
<script lang="ts">
import { reactive, ref, shallowReactive, shallowRef } from 'vue'
/*
shallowReactive与shallowRef
shallowReactive: 只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
总结:
reactive与ref实现的是深度响应式, 而shallowReactive与shallowRef是浅响应式
什么时候用浅响应式呢?
一般情况下使用ref和reactive即可,
如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
*/
export default {
setup () {
const m1 = reactive({a: 1, b: {c: 2}})
const m2 = shallowReactive({a: 1, b: {c: 2}})
const m3 = ref({a: 1, b: {c: 2}})
const m4 = shallowRef({a: 1, b: {c: 2}})
const update = () => {
// m1.b.c += 1
// m2.b.c += 1
// m3.value.a += 1
m4.value.a += 1
}
return {
m1,
m2,
m3,
m4,
update,
}
}
}
</script>
```
## 2) readonly 与 shallowReadonly
- readonly:
- 深度只读数据
- 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
- 只读代理是深层的:访问的任何嵌套 property 也是只读的。
- shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
- 应用场景:
- 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
```vue
<template>
<h2>App</h2>
<h3>{{state}}</h3>
<button @click="update">更新</button>
</template>
<script lang="ts">
import { reactive, readonly, shallowReadonly } from 'vue'
/*
readonly: 深度只读数据
获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly: 浅只读数据
创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
应用场景:
在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
*/
export default {
setup () {
const state = reactive({
a: 1,
b: {
c: 2
}
})
// const rState1 = readonly(state)
const rState2 = shallowReadonly(state)
const update = () => {
// rState1.a++ // error
// rState1.b.c++ // error
// rState2.a++ // error
rState2.b.c++
}
return {
state,
update
}
}
}
</script>
```
## 3) toRaw 与 markRaw
- toRaw
- 返回由 `reactive``readonly` 方法转换成响应式代理的普通对象。
- 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
- markRaw
- 标记一个对象,使其永远不会转换为代理。返回对象本身
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
```vue
<template>
<h2>{{state}}</h2>
<button @click="testToRaw">测试toRaw</button>
<button @click="testMarkRaw">测试markRaw</button>
</template>
<script lang="ts">
/*
toRaw: 得到reactive代理对象的目标数据对象
*/
import {
markRaw,
reactive, toRaw,
} from 'vue'
export default {
setup () {
const state = reactive<any>({
name: 'tom',
age: 25,
})
const testToRaw = () => {
const user = toRaw(state)
user.age++ // 界面不会更新
}
const testMarkRaw = () => {
const likes = ['a', 'b']
// state.likes = likes
state.likes = markRaw(likes) // likes数组就不再是响应式的了
setTimeout(() => {
state.likes[0] += '--'
}, 1000)
}
return {
state,
testToRaw,
testMarkRaw,
}
}
}
</script>
```
## 4) toRef
- 为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
- 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
- 应用: 当要将 某个prop 的 ref 传递给复合函数时toRef 很有用
```vue
<template>
<h2>App</h2>
<p>{{state}}</p>
<p>{{foo}}</p>
<p>{{foo2}}</p>
<button @click="update">更新</button>
<Child :foo="foo"/>
</template>
<script lang="ts">
/*
toRef:
为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
应用: 当要将某个 prop 的 ref 传递给复合函数时toRef 很有用
*/
import {
reactive,
toRef,
ref,
} from 'vue'
import Child from './Child.vue'
export default {
setup () {
const state = reactive({
foo: 1,
bar: 2
})
const foo = toRef(state, 'foo')
const foo2 = ref(state.foo)
const update = () => {
state.foo++
// foo.value++
// foo2.value++ // foo和state中的数据不会更新
}
return {
state,
foo,
foo2,
update,
}
},
components: {
Child
}
}
</script>
```
```vue
<template>
<h2>Child</h2>
<h3>{{foo}}</h3>
<h3>{{length}}</h3>
</template>
<script lang="ts">
import { computed, defineComponent, Ref, toRef } from 'vue'
const component = defineComponent({
props: {
foo: {
type: Number,
require: true
}
},
setup (props, context) {
const length = useFeatureX(toRef(props, 'foo'))
return {
length
}
}
})
function useFeatureX(foo: Ref) {
const lenth = computed(() => foo.value.length)
return lenth
}
export default component
</script>
```
## 5) customRef
- 创建一个自定义的 ref并对其依赖项跟踪和更新触发进行显式控制
- 需求: 使用 customRef 实现 debounce 的示例
```vue
<template>
<h2>App</h2>
<input v-model="keyword" placeholder="搜索关键字"/>
<p>{{keyword}}</p>
</template>
<script lang="ts">
/*
customRef:
创建一个自定义的 ref并对其依赖项跟踪和更新触发进行显式控制
需求:
使用 customRef 实现 debounce 的示例
*/
import {
ref,
customRef
} from 'vue'
export default {
setup () {
const keyword = useDebouncedRef('', 500)
console.log(keyword)
return {
keyword
}
},
}
/*
实现函数防抖的自定义ref
*/
function useDebouncedRef<T>(value: T, delay = 200) {
let timeout: number
return customRef((track, trigger) => {
return {
get() {
// 告诉Vue追踪数据
track()
return value
},
set(newValue: T) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
// 告诉Vue去触发界面更新
trigger()
}, delay)
}
}
})
}
</script>
```
## 6) provide 与 inject
- provide` 和 `inject` 提供依赖注入,功能类似 2.x 的 `provide/inject
- 实现跨层级组件(祖孙)间通信
```vue
<template>
<h1>父组件</h1>
<p>当前颜色: {{color}}</p>
<button @click="color='red'"></button>
<button @click="color='yellow'"></button>
<button @click="color='blue'"></button>
<hr>
<Son />
</template>
<script lang="ts">
import { provide, ref } from 'vue'
/*
- provide` 和 `inject` 提供依赖注入,功能类似 2.x 的 `provide/inject
- 实现跨层级组件(祖孙)间通信
*/
import Son from './Son.vue'
export default {
name: 'ProvideInject',
components: {
Son
},
setup() {
const color = ref('red')
provide('color', color)
return {
color
}
}
}
</script>
```
```vue
<template>
<div>
<h2>子组件</h2>
<hr>
<GrandSon />
</div>
</template>
<script lang="ts">
import GrandSon from './GrandSon.vue'
export default {
components: {
GrandSon
},
}
</script>
```
```vue
<template>
<h3 :style="{color}">孙子组件: {{color}}</h3>
</template>
<script lang="ts">
import { inject } from 'vue'
export default {
setup() {
const color = inject('color')
return {
color
}
}
}
</script>
```
## 7) 响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由 `reactive` 创建的响应式代理
- isReadonly: 检查一个对象是否是由 `readonly` 创建的只读代理
- isProxy: 检查一个对象是否是由 `reactive` 或者 `readonly` 方法创建的代理

View File

@@ -1,259 +0,0 @@
# 3. 手写组合API
## 1) shallowReactive 与 reactive
```js
const reactiveHandler = {
get (target, key) {
if (key==='_is_reactive') return true
return Reflect.get(target, key)
},
set (target, key, value) {
const result = Reflect.set(target, key, value)
console.log('数据已更新, 去更新界面')
return result
},
deleteProperty (target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('数据已删除, 去更新界面')
return result
},
}
/*
自定义shallowReactive
*/
function shallowReactive(obj) {
return new Proxy(obj, reactiveHandler)
}
/*
自定义reactive
*/
function reactive (target) {
if (target && typeof target==='object') {
if (target instanceof Array) { // 数组
target.forEach((item, index) => {
target[index] = reactive(item)
})
} else { // 对象
Object.keys(target).forEach(key => {
target[key] = reactive(target[key])
})
}
const proxy = new Proxy(target, reactiveHandler)
return proxy
}
return target
}
/* 测试自定义shallowReactive */
const proxy = shallowReactive({
a: {
b: 3
}
})
proxy.a = {b: 4} // 劫持到了
proxy.a.b = 5 // 没有劫持到
/* 测试自定义reactive */
const obj = {
a: 'abc',
b: [{x: 1}],
c: {x: [11]},
}
const proxy = reactive(obj)
console.log(proxy)
proxy.b[0].x += 1
proxy.c.x[0] += 1
```
## 2) shallowRef 与 ref
```js
/*
自定义shallowRef
*/
function shallowRef(target) {
const result = {
_value: target, // 用来保存数据的内部属性
_is_ref: true, // 用来标识是ref对象
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
/*
自定义ref
*/
function ref(target) {
if (target && typeof target==='object') {
target = reactive(target)
}
const result = {
_value: target, // 用来保存数据的内部属性
_is_ref: true, // 用来标识是ref对象
get value () {
return this._value
},
set value (val) {
this._value = val
console.log('set value 数据已更新, 去更新界面')
}
}
return result
}
/* 测试自定义shallowRef */
const ref3 = shallowRef({
a: 'abc',
})
ref3.value = 'xxx'
ref3.value.a = 'yyy'
/* 测试自定义ref */
const ref1 = ref(0)
const ref2 = ref({
a: 'abc',
b: [{x: 1}],
c: {x: [11]},
})
ref1.value++
ref2.value.b[0].x++
console.log(ref1, ref2)
```
## 3) shallowReadonly 与 readonly
```js
const readonlyHandler = {
get (target, key) {
if (key==='_is_readonly') return true
return Reflect.get(target, key)
},
set () {
console.warn('只读的, 不能修改')
return true
},
deleteProperty () {
console.warn('只读的, 不能删除')
return true
},
}
/*
自定义shallowReadonly
*/
function shallowReadonly(obj) {
return new Proxy(obj, readonlyHandler)
}
/*
自定义readonly
*/
function readonly(target) {
if (target && typeof target==='object') {
if (target instanceof Array) { // 数组
target.forEach((item, index) => {
target[index] = readonly(item)
})
} else { // 对象
Object.keys(target).forEach(key => {
target[key] = readonly(target[key])
})
}
const proxy = new Proxy(target, readonlyHandler)
return proxy
}
return target
}
/* 测试自定义readonly */
/* 测试自定义shallowReadonly */
const objReadOnly = readonly({
a: {
b: 1
}
})
const objReadOnly2 = shallowReadonly({
a: {
b: 1
}
})
objReadOnly.a = 1
objReadOnly.a.b = 2
objReadOnly2.a = 1
objReadOnly2.a.b = 2
```
## 4) isRef, isReactive 与 isReadonly
```js
/*
判断是否是ref对象
*/
function isRef(obj) {
return obj && obj._is_ref
}
/*
判断是否是reactive对象
*/
function isReactive(obj) {
return obj && obj._is_reactive
}
/*
判断是否是readonly对象
*/
function isReadonly(obj) {
return obj && obj._is_readonly
}
/*
是否是reactive或readonly产生的代理对象
*/
function isProxy (obj) {
return isReactive(obj) || isReadonly(obj)
}
/* 测试判断函数 */
console.log(isReactive(reactive({})))
console.log(isRef(ref({})))
console.log(isReadonly(readonly({})))
console.log(isProxy(reactive({})))
console.log(isProxy(readonly({})))
```

View File

@@ -1,23 +0,0 @@
# 4. Composition API VS Option API
## 1) Option API的问题
- 在传统的Vue OptionsAPI中新增或者修改一个需求就需要分别在datamethodscomputed里修改 ,滚动条反复上下移动
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f84e4e2c02424d9a99862ade0a2e4114~tplv-k3u1fbpfcp-watermark.image)
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e5ac7e20d1784887a826f6360768a368~tplv-k3u1fbpfcp-watermark.image)
## 2) 使用Compisition API
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc0be8211fc54b6c941c036791ba4efe~tplv-k3u1fbpfcp-watermark.image)
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6cc55165c0e34069a75fe36f8712eb80~tplv-k3u1fbpfcp-watermark.image)
![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2c421e5392504ecc94c222057dba338a~tplv-k3u1fbpfcp-watermark.image)

243
docs/chapter2/1_type.md Normal file
View File

@@ -0,0 +1,243 @@
# 1. 基础类型
TypeScript 支持与 JavaScript 几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。
## 布尔值
最基本的数据类型就是简单的 true/false 值在JavaScript 和 TypeScript 里叫做 `boolean`(其它语言中也一样)。
```typescript
let isDone: boolean = false;
isDone = true;
// isDone = 2 // error
```
## 数字
和 JavaScript 一样TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量TypeScript 还支持 ECMAScript 2015中引入的二进制和八进制字面量。
```typescript
let a1: number = 10 // 十进制
let a2: number = 0b1010 // 二进制
let a3: number = 0o12 // 八进制
let a4: number = 0xa // 十六进制
```
## 字符串
JavaScript 程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用 `string` 表示文本数据类型。 和 JavaScript 一样,可以使用双引号(`"`)或单引号(`'`)表示字符串。
```typescript
let name:string = 'tom'
name = 'jack'
// name = 12 // error
let age:number = 12
const info = `My name is ${name}, I am ${age} years old!`
```
## undefined 和 null
TypeScript 里,`undefined``null` 两者各自有自己的类型分别叫做 `undefined``null`。 它们的本身的类型用处不是很大:
```typescript
let u: undefined = undefined
let n: null = null
```
默认情况下 `null``undefined` 是所有类型的子类型。 就是说你可以把 `null``undefined` 赋值给 `number` 类型的变量。
## 数组
TypeScript 像 JavaScript 一样可以操作数组元素。 有两种方式可以定义数组。 第一种,可以在`元素类型后面接上[]`,表示由此类型元素组成的一个数组:
```typescript
let list1: number[] = [1, 2, 3]
```
第二种方式是使用数组泛型,`Array<元素类型>`
```typescript
let list2: Array<number> = [1, 2, 3]
```
## 元组 Tuple
元组类型允许表示一个已知元素数量和类型的数组,`各元素的类型不必相同`。 比如,你可以定义一对值分别为 `string``number` 类型的元组。
```typescript
let t1: [string, number]
t1 = ['hello', 10] // OK
t1 = [10, 'hello'] // Error
```
当访问一个已知索引的元素,会得到正确的类型:
```typescript
console.log(t1[0].substring(1)) // OK
console.log(t1[1].substring(1)) // Error, 'number' 不存在 'substring' 方法
```
## 枚举
`enum` 类型是对 JavaScript 标准数据类型的一个补充。 使用枚举类型可以`为一组数值赋予友好的名字`
```typescript
enum Color {
Red,
Green,
Blue
}
// 枚举数值默认从0开始依次递增
// 根据特定的名称得到对应的枚举数值
let myColor: Color = Color.Green // 0
console.log(myColor, Color.Red, Color.Blue)
```
默认情况下,从 `0` 开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 `1` 开始编号:
```typescript
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green
```
或者,全部都采用手动赋值:
```typescript
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green
```
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为 2但是不确定它映射到 Color 里的哪个名字,我们可以查找相应的名字:
```typescript
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]
console.log(colorName) // 'Green'
```
## any
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 `any` 类型来标记这些变量:
```typescript
let notSure: any = 4
notSure = 'maybe a string'
notSure = false // 也可以是个 boolean
```
在对现有代码进行改写的时候,`any` 类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。并且当你只知道一部分数据的类型时,`any` 类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:
```typescript
let list: any[] = [1, true, 'free']
list[1] = 100
```
## void
某种程度上来说,`void` 类型像是与 `any` 类型相反,它`表示没有任何类型`。 当一个函数没有返回值时,你通常会见到其返回值类型是 `void`
```typescript
/* 表示没有任何类型, 一般用来说明函数的返回值不能是undefined和null之外的值 */
function fn(): void {
console.log('fn()')
// return undefined
// return null
// return 1 // error
}
```
声明一个 `void` 类型的变量没有什么大用,因为你只能为它赋予 `undefined``null`
```typescript
let unusable: void = undefined
```
## object
`object` 表示非原始类型,也就是除 `number``string``boolean`之外的类型。
使用 `object` 类型,就可以更好的表示像 `Object.create` 这样的 `API`。例如:
```typescript
function fn2(obj:object):object {
console.log('fn2()', obj)
return {}
// return undefined
// return null
}
console.log(fn2(new String('abc')))
// console.log(fn2('abc') // error
console.log(fn2(String))
```
## 联合类型
联合类型Union Types表示取值可以为多种类型中的一种
需求1: 定义一个一个函数得到一个数字或字符串值的字符串形式值
```typescript
function toString2(x: number | string) : string {
return x.toString()
}
```
需求2: 定义一个一个函数得到一个数字或字符串值的长度
```typescript
function getLength(x: number | string) {
// return x.length // error
if (x.length) { // error
return x.length
} else {
return x.toString().length
}
}
```
## 类型断言
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号”语法, 另一个为 `as` 语法
```typescript
/*
类型断言(Type Assertion): 可以用来手动指定一个值的类型
语法:
方式一: <类型>值
方式二: 值 as 类型 tsx中只能用这种方式
*/
/* 需求: 定义一个函数得到一个字符串或者数值数据的长度 */
function getLength(x: number | string) {
if ((<string>x).length) {
return (x as string).length
} else {
return x.toString().length
}
}
console.log(getLength('abcd'), getLength(1234))
```
## 类型推断
类型推断: TS会在没有明确的指定类型的时候推测出一个类型
有下面2种情况: 1. 定义变量时赋值了, 推断为对应的类型. 2. 定义变量时没有赋值, 推断为any类型
```typescript
/* 定义变量时赋值了, 推断为对应的类型 */
let b9 = 123 // number
// b9 = 'abc' // error
/* 定义变量时没有赋值, 推断为any类型 */
let b10 // any类型
b10 = 123
b10 = 'abc'
```

View File

@@ -0,0 +1,187 @@
# 2. 接口
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。我们使用接口Interfaces来定义对象的类型。`接口是对象的状态(属性)和行为(方法)的抽象(描述)`
## 接口初探
需求: 创建人的对象, 需要对人的属性进行一定的约束
```
id是number类型, 必须有, 只读的
name是string类型, 必须有
age是number类型, 必须有
sex是string类型, 可以没有
```
下面通过一个简单示例来观察接口是如何工作的:
```typescript
/*
在 TypeScript 中我们使用接口Interfaces来定义对象的类型
接口: 是对象的状态(属性)和行为(方法)的抽象(描述)
接口类型的对象
多了或者少了属性是不允许的
可选属性: ?
只读属性: readonly
*/
/*
需求: 创建人的对象, 需要对人的属性进行一定的约束
id是number类型, 必须有, 只读的
name是string类型, 必须有
age是number类型, 必须有
sex是string类型, 可以没有
*/
// 定义人的接口
interface IPerson {
id: number
name: string
age: number
sex: string
}
const person1: IPerson = {
id: 1,
name: 'tom',
age: 20,
sex: '男'
}
```
类型检查器会查看对象内部的属性是否与IPerson接口描述一致, 如果不一致就会提示类型错误。
## 可选属性
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。
```typescript
interface IPerson {
id: number
name: string
age: number
sex?: string
}
```
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 `?` 符号。
可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。
```typescript
const person2: IPerson = {
id: 1,
name: 'tom',
age: 20,
// sex: '男' // 可以没有
}
```
## 只读属性
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 `readonly` 来指定只读属性:
```typescript
interface IPerson {
readonly id: number
name: string
age: number
sex?: string
}
```
一旦赋值后再也不能被改变了。
```typescript
const person2: IPerson = {
id: 2,
name: 'tom',
age: 20,
// sex: '男' // 可以没有
// xxx: 12 // error 没有在接口中定义, 不能有
}
person2.id = 2 // error
```
### readonly vs const
最简单判断该用 `readonly` 还是 `const` 的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 `const`,若做为属性则使用 `readonly`
## 函数类型
接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
```typescript
/*
接口可以描述函数类型(参数的类型与返回的类型)
*/
interface SearchFunc {
(source: string, subString: string): boolean
}
```
这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
```typescript
const mySearch: SearchFunc = function (source: string, sub: string): boolean {
return source.search(sub) > -1
}
console.log(mySearch('abcd', 'bc'))
```
## 类类型
### 类实现接口
与 C# 或 Java 里接口的基本作用一样TypeScript 也能够用它来明确的强制一个类去符合某种契约。
```typescript
/*
类类型: 实现接口
1. 一个类可以实现多个接口
2. 一个接口可以继承多个接口
*/
interface Alarm {
alert(): any;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
```
## 一个类可以实现多个接口
```typescript
class Car2 implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
```
## 接口继承接口
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
```typescript
interface LightableAlarm extends Alarm, Light {
}
```

328
docs/chapter2/3_class.md Normal file
View File

@@ -0,0 +1,328 @@
# 3. 类
对于传统的 JavaScript 程序我们会使用`函数``基于原型的继承`来创建可重用的组件,但对于熟悉使用面向对象方式的程序员使用这些语法就有些棘手,因为他们用的是`基于类的继承`并且对象是由类构建出来的。 从 ECMAScript 2015也就是 ES6 开始, JavaScript 程序员将能够使用基于类的面向对象的方式。 使用 TypeScript我们允许开发者现在就使用这些特性并且编译后的 JavaScript 可以在所有主流浏览器和平台上运行,而不需要等到下个 JavaScript 版本。
## 基本示例
下面看一个使用类的例子:
```typescript
/*
类的基本定义与使用
*/
class Greeter {
// 声明属性
message: string
// 构造方法
constructor (message: string) {
this.message = message
}
// 一般方法
greet (): string {
return 'Hello ' + this.message
}
}
// 创建类的实例
const greeter = new Greeter('world')
// 调用实例的方法
console.log(greeter.greet())
```
如果你使用过 C# 或 Java你会对这种语法非常熟悉。 我们声明一个 `Greeter` 类。这个类有 3 个成员:一个叫做 `message` 的属性,一个构造函数和一个 `greet` 方法。
你会注意到,我们在引用任何一个类成员的时候都用了 `this`。 它表示我们访问的是类的成员。
后面一行,我们使用 `new` 构造了 `Greeter` 类的一个实例。它会调用之前定义的构造函数,创建一个 `Greeter` 类型的新对象,并执行构造函数初始化它。
最后一行通过 `greeter` 对象调用其 `greet` 方法
## 继承
在 TypeScript 里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。
看下面的例子:
```typescript
/*
类的继承
*/
class Animal {
run (distance: number) {
console.log(`Animal run ${distance}m`)
}
}
class Dog extends Animal {
cry () {
console.log('wang! wang!')
}
}
const dog = new Dog()
dog.cry()
dog.run(100) // 可以调用从父中继承得到的方法
```
这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里,`Dog` 是一个 派生类,它派生自 `Animal` 基类,通过 `extends` 关键字。 派生类通常被称作*子类*,基类通常被称作*超类*。
因为 `Dog` 继承了 `Animal` 的功能,因此我们可以创建一个 `Dog` 的实例,它能够 `cry()``run()`
下面我们来看个更加复杂的例子。
```typescript
class Animal {
name: string
constructor (name: string) {
this.name = name
}
run (distance: number=0) {
console.log(`${this.name} run ${distance}m`)
}
}
class Snake extends Animal {
constructor (name: string) {
// 调用父类型构造方法
super(name)
}
// 重写父类型的方法
run (distance: number=5) {
console.log('sliding...')
super.run(distance)
}
}
class Horse extends Animal {
constructor (name: string) {
// 调用父类型构造方法
super(name)
}
// 重写父类型的方法
run (distance: number=50) {
console.log('dashing...')
// 调用父类型的一般方法
super.run(distance)
}
xxx () {
console.log('xxx()')
}
}
const snake = new Snake('sn')
snake.run()
const horse = new Horse('ho')
horse.run()
// 父类型引用指向子类型的实例 ==> 多态
const tom: Animal = new Horse('ho22')
tom.run()
/* 如果子类型没有扩展的方法, 可以让子类型引用指向父类型的实例 */
const tom3: Snake = new Animal('tom3')
tom3.run()
/* 如果子类型有扩展的方法, 不能让子类型引用指向父类型的实例 */
// const tom2: Horse = new Animal('tom2')
// tom2.run()
```
这个例子展示了一些上面没有提到的特性。 这一次,我们使用 `extends` 关键字创建了 Animal的两个子类`Horse``Snake`
与前一个例子的不同点是,派生类包含了一个构造函数,它 必须调用 `super()`,它会执行基类的构造函数。 而且,在构造函数里访问 `this` 的属性之前,我们 一定要调用 `super()`。 这个是 TypeScript 强制执行的一条重要规则。
这个例子演示了如何在子类里可以重写父类的方法。`Snake`类和 `Horse` 类都创建了 `run` 方法,它们重写了从 `Animal` 继承来的 `run` 方法,使得 `run` 方法根据不同的类而具有不同的功能。注意,即使 `tom` 被声明为 `Animal` 类型,但因为它的值是 `Horse`,调用 `tom.run(34)` 时,它会调用 `Horse` 里重写的方法。
```
sliding...
sn run 5m
dashing...
ho run 50m
```
## 公共,私有与受保护的修饰符
### 默认为 public
在上面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用 `public` 来做修饰例如C# 要求必须明确地使用 `public` 指定成员是可见的。 在 TypeScript 里,成员都默认为 `public`
你也可以明确的将一个成员标记成 `public`。 我们可以用下面的方式来重写上面的 `Animal` 类:
### 理解 private
当成员被标记成 `private` 时,它就不能在声明它的类的外部访问。
### 理解 protected
`protected` 修饰符与 `private` 修饰符的行为很相似,但有一点不同,`protected`成员在派生类中仍然可以访问。例如:
```typescript
/*
访问修饰符: 用来描述类内部的属性/方法的可访问性
public: 默认值, 公开的外部也可以访问
private: 只能类内部可以访问
protected: 类内部和子类可以访问
*/
class Animal {
public name: string
public constructor (name: string) {
this.name = name
}
public run (distance: number=0) {
console.log(`${this.name} run ${distance}m`)
}
}
class Person extends Animal {
private age: number = 18
protected sex: string = '男'
run (distance: number=5) {
console.log('Person jumping...')
super.run(distance)
}
}
class Student extends Person {
run (distance: number=6) {
console.log('Student jumping...')
console.log(this.sex) // 子类能看到父类中受保护的成员
// console.log(this.age) // 子类看不到父类中私有的成员
super.run(distance)
}
}
console.log(new Person('abc').name) // 公开的可见
// console.log(new Person('abc').sex) // 受保护的不可见
// console.log(new Person('abc').age) // 私有的不可见
```
## readonly 修饰符
你可以使用 `readonly` 关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
```typescript
class Person {
readonly name: string = 'abc'
constructor(name: string) {
this.name = name
}
}
let john = new Person('John')
// john.name = 'peter' // error
```
### 参数属性
在上面的例子中,我们必须在 `Person` 类里定义一个只读成员 `name` 和一个参数为 `name` 的构造函数,并且立刻将 `name` 的值赋给 `this.name`,这种情况经常会遇到。 参数属性可以方便地让我们在一个地方定义并初始化一个成员。 下面的例子是对之前 `Person` 类的修改版,使用了参数属性:
```typescript
class Person2 {
constructor(readonly name: string) {
}
}
const p = new Person2('jack')
console.log(p.name)
```
注意看我们是如何舍弃参数 `name`,仅在构造函数里使用 `readonly name: string` 参数来创建和初始化 `name` 成员。 我们把声明和赋值合并至一处。
参数属性通过给构造函数参数前面添加一个访问限定符来声明。使用 `private` 限定一个参数属性会声明并初始化一个私有成员;对于 `public``protected` 来说也是一样。
## 存取器
`TypeScript` 支持通过 `getters/setters` 来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
下面来看如何把一个简单的类改写成使用 `get``set`。 首先,我们从一个没有使用存取器的例子开始。
```typescript
class Person {
firstName: string = 'A'
lastName: string = 'B'
get fullName () {
return this.firstName + '-' + this.lastName
}
set fullName (value) {
const names = value.split('-')
this.firstName = names[0]
this.lastName = names[1]
}
}
const p = new Person()
console.log(p.fullName)
p.firstName = 'C'
p.lastName = 'D'
console.log(p.fullName)
p.fullName = 'E-F'
console.log(p.firstName, p.lastName)
```
## 静态属性
到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上。 在这个例子里,我们使用 `static` 定义 `origin`,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在 `origin` 前面加上类名。 如同在实例属性上使用 `this.xxx` 来访问属性一样,这里我们使用 `Grid.xxx` 来访问静态属性。
```typescript
/*
静态属性, 是类对象的属性
非静态属性, 是类的实例对象的属性
*/
class Person {
name1: string = 'A'
static name2: string = 'B'
}
console.log(Person.name2)
console.log(new Person().name1)
```
## 抽象类
抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。 `abstract` 关键字是用于定义抽象类和在抽象类内部定义抽象方法。
```typescript
/*
抽象类
不能创建实例对象, 只有实现类才能创建实例
可以包含未实现的抽象方法
*/
abstract class Animal {
abstract cry ()
run () {
console.log('run()')
}
}
class Dog extends Animal {
cry () {
console.log(' Dog cry()')
}
}
const dog = new Dog()
dog.cry()
dog.run()
```

119
docs/chapter2/4_function.md Normal file
View File

@@ -0,0 +1,119 @@
# 4. 函数
函数是 JavaScript 应用程序的基础,它帮助你实现抽象层,模拟类,信息隐藏和模块。在 TypeScript 里虽然已经支持类命名空间和模块但函数仍然是主要的定义行为的地方。TypeScript 为 JavaScript 函数添加了额外的功能,让我们可以更容易地使用。
## 基本示例
和 JavaScript 一样TypeScript 函数可以创建有名字的函数和匿名函数。你可以随意选择适合应用程序的方式,不论是定义一系列 API 函数还是只使用一次的函数。
通过下面的例子可以迅速回想起这两种 JavaScript 中的函数:
```javascript
// 命名函数
function add(x, y) {
return x + y
}
// 匿名函数
let myAdd = function(x, y) {
return x + y;
}
```
## 函数类型
### 为函数定义类型
让我们为上面那个函数添加类型:
```typescript
function add(x: number, y: number): number {
return x + y
}
let myAdd = function(x: number, y: number): number {
return x + y
}
```
我们可以给每个参数添加类型之后再为函数本身添加返回值类型。TypeScript 能够根据返回语句自动推断出返回值类型。
### 书写完整函数类型
现在我们已经为函数指定了类型,下面让我们写出函数的完整类型。
```typescript
let myAdd2: (x: number, y: number) => number =
function(x: number, y: number): number {
return x + y
}
```
## 可选参数和默认参数
TypeScript 里的每个函数参数都是必须的。 这不是指不能传递 `null``undefined` 作为参数,而是说编译器检查用户是否为每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。 简短地说,传递给一个函数的参数个数必须与函数期望的参数个数一致。
JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 `undefined`。 在TypeScript 里我们可以在参数名旁使用 `?` 实现可选参数的功能。 比如,我们想让 `lastName` 是可选的:
在 TypeScript 里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是 `undefined` 时。 它们叫做有默认初始化值的参数。 让我们修改上例,把`firstName` 的默认值设置为 `"A"`
```typescript
function buildName(firstName: string='A', lastName?: string): string {
if (lastName) {
return firstName + '-' + lastName
} else {
return firstName
}
}
console.log(buildName('C', 'D'))
console.log(buildName('C'))
console.log(buildName())
```
### 剩余参数
必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在 JavaScript 里,你可以使用 `arguments` 来访问所有传入的参数。
在 TypeScript 里,你可以把所有参数收集到一个变量里:
剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( `...`)后面给定的名字,你可以在函数体内使用这个数组。
```typescript
function info(x: string, ...args: string[]) {
console.log(x, args)
}
info('abc', 'c', 'b', 'a')
```
## 函数重载
函数重载: 函数名相同, 而形参不同的多个函数
在JS中, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的
但在TS中, 与其它面向对象的语言(如Java)就存在此语法
```typescript
/*
函数重载: 函数名相同, 而形参不同的多个函数
需求: 我们有一个add函数它可以接收2个string类型的参数进行拼接也可以接收2个number类型的参数进行相加
*/
// 重载函数声明
function add (x: string, y: string): string
function add (x: number, y: number): number
// 定义函数实现
function add(x: string | number, y: string | number): string | number {
// 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
if (typeof x === 'string' && typeof y === 'string') {
return x + y
} else if (typeof x === 'number' && typeof y === 'number') {
return x + y
}
}
console.log(add(1, 2))
console.log(add('a', 'b'))
// console.log(add(1, 'a')) // error
```

154
docs/chapter2/5_generic.md Normal file
View File

@@ -0,0 +1,154 @@
# 5. 泛型
指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。
## 引入
下面创建一个函数, 实现功能: 根据指定的数量 `count` 和数据 `value` , 创建一个包含 `count``value` 的数组
不用泛型的话,这个函数可能是下面这样:
```typescript
function createArray(value: any, count: number): any[] {
const arr: any[] = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const arr1 = createArray(11, 3)
const arr2 = createArray('aa', 3)
console.log(arr1[0].toFixed(), arr2[0].split(''))
```
## 使用函数泛型
```typescript
function createArray2 <T> (value: T, count: number) {
const arr: Array<T> = []
for (let index = 0; index < count; index++) {
arr.push(value)
}
return arr
}
const arr3 = createArray2<number>(11, 3)
console.log(arr3[0].toFixed())
// console.log(arr3[0].split('')) // error
const arr4 = createArray2<string>('aa', 3)
console.log(arr4[0].split(''))
// console.log(arr4[0].toFixed()) // error
```
## 多个泛型参数的函数
一个函数可以定义多个泛型参数
```typescript
function swap <K, V> (a: K, b: V): [K, V] {
return [a, b]
}
const result = swap<string, number>('abc', 123)
console.log(result[0].length, result[1].toFixed())
```
## 泛型接口
在定义接口时, 为接口中的属性或方法定义泛型类型
在使用接口时, 再指定具体的泛型类型
```typescript
interface IbaseCRUD <T> {
data: T[]
add: (t: T) => void
getById: (id: number) => T
}
class User {
id?: number; //id主键自增
name: string; //姓名
age: number; //年龄
constructor (name, age) {
this.name = name
this.age = age
}
}
class UserCRUD implements IbaseCRUD <User> {
data: User[] = []
add(user: User): void {
user = {...user, id: Date.now()}
this.data.push(user)
console.log('保存user', user.id)
}
getById(id: number): User {
return this.data.find(item => item.id===id)
}
}
const userCRUD = new UserCRUD()
userCRUD.add(new User('tom', 12))
userCRUD.add(new User('tom2', 13))
console.log(userCRUD.data)
```
## 泛型类
在定义类时, 为类中的属性或方法定义泛型类型
在创建类的实例时, 再指定特定的泛型类型
```typescript
class GenericNumber<T> {
zeroValue: T
add: (x: T, y: T) => T
}
let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function(x, y) {
return x + y
}
let myGenericString = new GenericNumber<string>()
myGenericString.zeroValue = 'abc'
myGenericString.add = function(x, y) {
return x + y
}
console.log(myGenericString.add(myGenericString.zeroValue, 'test'))
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12))
```
## 泛型约束
如果我们直接对一个泛型参数取 `length` 属性, 会报错, 因为这个泛型根本就不知道它有这个属性
```typescript
// 没有泛型约束
function fn <T>(x: T): void {
// console.log(x.length) // error
}
```
我们可以使用泛型约束来实现
```typescript
interface Lengthwise {
length: number;
}
// 指定泛型约束
function fn2 <T extends Lengthwise>(x: T): void {
console.log(x.length)
}
```
我们需要传入符合约束类型的值,必须包含必须 `length` 属性:
```typescript
fn2('abc')
// fn2(123) // error number没有length属性
```

98
docs/chapter2/6_other.md Normal file
View File

@@ -0,0 +1,98 @@
# 6. 其它
## 声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能
什么是声明语句
假如我们想使用第三方库 jQuery一种常见的方式是在 html 中通过 `<script>` 标签引入 `jQuery`,然后就可以使用全局变量 `$``jQuery` 了。
但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西
```typescript
/*
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
声明语句: 如果需要ts对新的语法进行检查, 需要要加载了对应的类型说明代码
declare var jQuery: (selector: string) => any;
声明文件: 把声明语句放到一个单独的文件jQuery.d.ts中, ts会自动解析到项目中所有声明文件
下载声明文件: npm install @types/jquery --save-dev
*/
jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.
```
这时,我们需要使用 declare var 来定义它的类型
```typescript
declare var jQuery: (selector: string) => any;
jQuery('#foo');
```
declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
```typescript
jQuery('#foo');
```
一般声明文件都会单独写成一个 `xxx.d.ts` 文件
创建 `01_jQuery.d.ts`, 将声明语句定义其中, TS编译器会扫描并加载项目中所有的TS声明文件
```typescript
declare var jQuery: (selector: string) => any;
```
很多的第三方库都定义了对应的声明文件库, 库文件名一般为 `@types/xxx`, 可以在 `https://www.npmjs.com/package/package` 进行搜索
有的第三库在下载时就会自动下载对应的声明文件库(比如: webpack),有的可能需要单独下载(比如jQuery/react)
## 内置对象
JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
内置对象是指根据标准在全局作用域Global上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM的标准。
1. ECMAScript 的内置对象
> Boolean
> Number
> String
> Date
> RegExp
> Error
```typescript
/* 1. ECMAScript 的内置对象 */
let b: Boolean = new Boolean(1)
let n: Number = new Number(true)
let s: String = new String('abc')
let d: Date = new Date()
let r: RegExp = /^1/
let e: Error = new Error('error message')
b = true
// let bb: boolean = new Boolean(2) // error
```
2. BOM 和 DOM 的内置对象
> Window
> Document
> HTMLElement
> DocumentFragment
> Event
> NodeList
```typescript
const div: HTMLElement = document.getElementById('test')
const divs: NodeList = document.querySelectorAll('div')
document.addEventListener('click', (event: MouseEvent) => {
console.dir(event.target)
})
const fragment: DocumentFragment = document.createDocumentFragment()
```