vue3.0

结合李江南老师的视频技术胖的视频和文档进行二次编辑的

Vue3.0亮点

  • Performance:性能比Vue 2.x快1.2~2倍
  • Tree shaking support:按需编译,体积比Vue2.x更小
  • Composition API: 组合API(类似React Hooks)
  • Better TypeScript support:更好的 Ts 支持
  • Custom Renderer API:暴露了自定义渲染API
  • Fragment, Teleport(Protal), Suspense:更先进的组件

Vue3.0是如何变快的

diff算法优化: https://vue-next-template-explorer.netlify.app/

  • Vue2中的虚拟dom是进行全量的对比
  • Vue3新增了非静态标记(PatchFlag)
    1. 在与上次虚拟节点进行对比时候,只对比带有patch flag的节点
    2. 并且可以通过flag的信息得知当前节点要对比的具体内容
1
2
3
4
// template内的内容
<div>Hello World!</div>
<h1 :title='haha' :value='hh'>{{msg}}</h1>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 源码
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", null, "Hello World!"),
_createVNode("h1", {
title: _ctx.haha,
value: _ctx.hh
}, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["title", "value"])
], 64 /* STABLE_FRAGMENT */))
}
/*
不需要遍历节点一一对比
对动态的组件进行标记(只比较标记的内容)
数字的不同表示了 比较的内容不同
不需要针对整个节点去改变,只需要针对特定的内容去重新渲染

9 ---> 代表他动态的内容为 text 和 props
改变的时候只需要改变文本内容和属性
*/

hoistStatic 静态提升

  • Vue2中无论元素是否参与更新, 每次都会重新创建, 然后再渲染
  • Vue3中对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
1
2
3
4
// template内的内容
<div>Hello World!</div>
<h1 :title='haha' :value='hh'>{{msg}}</h1>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 源码
// 静态提升之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_createVNode("div", null, "Hello World!"),
_createVNode("h1", {
title: _ctx.haha,
value: _ctx.hh
}, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["title", "value"])
], 64 /* STABLE_FRAGMENT */))
}

// 静态提升之后
const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, "Hello World!", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode("h1", {
title: _ctx.haha,
value: _ctx.hh
}, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["title", "value"])
], 64 /* STABLE_FRAGMENT */))
}

/*
每次render的时候全部内容会重新创建一次,会造成额外的内存消耗
通过把静态的节点提取为全局变量
所以就只需要创建一次
组件渲染的时候只需要调用就行了
*/

cacheHandlers 事件侦听器缓存

  • 默认情况下onClick会被视为动态绑定, 所以每次都会去追踪它的变化
    但是因为是同一个函数,所以没有追踪变化, 直接缓存起来复用即可
1
<button @click='handleClick'>按钮</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 开启事件侦听之前
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("button", { onClick: _ctx.handleClick }, "按钮", 8 /* PROPS */, ["onClick"]))
}

// 开启事件侦听之后
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.handleClick(...args)))
}, "按钮"))
}
/*
开启之前,通过静态标记的形式来实现 ==> 比较属性(但是方法是不变的)
开启之后,不使用静态标记,所以不会进行比较。通过一个变量对方法进行缓存,下次在调用的时候直接从缓存中获取,不去再次创建整个函数
*/

ssr渲染

  • 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dmo来渲染的快上很多很多。

  • 当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

组合API(composition api)

vue2.x实现列表的增删功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
实现一个功能就需要
在data内定义数据,
在methods / computed / watch 中实现业务逻辑来操作数据
造成 数据和业务逻辑 分散
*/
<template>
<div>
<form>
<input placeholder="id" type="text" v-model="info.id" />
<br>
<input placeholder="name" type="text" v-model="info.name" />
<br>
<input type="submit" @click="handleAdd" />
</form>
<ul>
<li
v-for="(item,index) in list"
:key="item.id"
@click="handleRemove(index)"
>{{item.name}} ----- {{ index }}</li>
</ul>
</div>
</template>

<script>
export default {
name: "App",
data() {
return {
info: {
id:'',
name:''
},
list: [
{ id: 0, name: "jjj" },
{ id: 1, name: "hhh" },
{ id: 2, name: "0hh" },
],
itemRefs: [],
};
},
methods: {
handleRemove(index) {
this.list.splice(index, 1);
},
handleAdd(e){
e.preventDefault();
console.log(this.info)
this.list.push(this.info)
this.info = {
id:'',
name:''
}
}
},
};
</script>

vue3组合API的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<template>
<div>
<h1>{{count}}</h1>
<button @click="handleAdd">添加</button>
</div>
</template>

<script>
// 只能监听简单类型的变化,不能监听复杂类型的变化
import { ref } from "vue";

// 监听复杂类型的变化
import { reactive } form "vue";

export default {
name: "App",

// setup为组合函数的入口函数
setup() {
// 定义变量和初始化变量 count是一个对象,value是他的值
let count = ref(0);

// 定义方法
function handleAdd(){
count.value ++
}

// 暴露变量和方法以供使用
return { count,handleAdd }
},
};

// 可以将setup里面的内容单独抽离出来

export default {
name: "App",
setup() {
let { obj, handleRemove } = useRemove();
return { obj, handleRemove };
}
}

function useRemove() {
let obj = reactive({
arr: [
{ id: "1", name: "dorren" },
{ id: "2", name: "jerry" },
],
});
function handleRemove(index) {
obj.arr.splice(index, 1);
}
return { obj, handleRemove };
}
// 使用的时候通过obj.arr使用


// 抽离方式2
import { time, handleChange } from "./hooks/useGetTime.ts";
export default {
setup() {
return { time, handleChange };
}
}

// ./hooks/useGetTime.ts
import { ref } from 'vue'
let time = ref("00:00:00");
let handleChange = () => {
let date = new Date();
const hour =
date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
const min =
date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
const sec =
date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
time.value = hour + ":" + min + ":" + sec;
setTimeout(() => {
handleChange()
}, 1000);
};
export { time, handleChange }

组合API完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

<div>
<form>
<input placeholder="id" type="text" v-model="info.info.id" />
<br />
<input placeholder="name" type="text" v-model="info.info.name" />
<br />
<button @click="handleAdd">添加</button>
</form>
<h1
v-for="(item,index) in obj.arr"
:key="item.id"
@click="handleRemove(index)"
>{{item.name}}---{{item.id}}</h1>
</div>

<script>
import { reactive } from "vue";

export default {
name: "App",
setup() {
let { obj, handleRemove } = useRemove();
// 参数传递
let { info, handleAdd } = useAdd(obj);

return { obj, handleRemove, info, handleAdd };
},
};

function useRemove() {
let obj = reactive({
arr: [
{ id: "1", name: "dorren" },
{ id: "2", name: "jerry" },
],
});

function handleRemove(index) {
obj.arr.splice(index, 1);
}
return { obj, handleRemove };
}

function useAdd(obj) {
let info = reactive({
info: {
id: "",
name: "",
},
});

function handleAdd(e) {
e.preventDefault();
const item = Object.assign({}, info.info);
obj.arr.push(item);
info.info.id = "";
info.info.name = "";
}
return { info, handleAdd };
}
</script>

组合API内使用生命周期钩子

选项API Hook inside setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmountvue2的beforeDestroy
unmounted onUnmounted
errorCaptured onErrorCaptured捕获子组件异常
renderTracked onRenderTracked对变化数据进行跟踪
renderTriggered onRenderTriggered精确跟踪触发(有新旧值))

reactive

  • vue3提供的实现响应式数据的方法
  • 用es6的proxy取代了defineProperty来实现响应式

tips:

  • reactive参数必须是对象(json/arr)
  • 如果给reactive传递了其他类型
    • 默认情况下修改值,界面不会自动更新
    • 如果想更新,可以通过重新赋值的方式

ref

  • vue3提供的对简单值的监听
  • 本质是reactive ref(xx) —> reactive({ value: xx })

tips:

  • 在vue中使用ref的值不用通过value获取
  • 在js中使用ref的值必须通过value获取 msg.value = 111

isRef和isReactive

用于判断变量类型

1
2
3
4
5
6
7
import { isRef,isReactive } from 'vue'

setup() {
let num = ref(99)
console.log( isRef(num) ) // true
console.log( isReactive(num) ) // false
}

组件通信

父传子( props )

基本和vue2.x一样,父组件绑定属性,子组件通过props获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 父组件
<child-component :name="name" :age="count" />

// 子组件
export default{
props:{
name:{
type:String, // 类型 String、Number、Boolean、Array、Object、Date、Function、Symbol
required:true, // 是否必传 true、false
default:'jerry', // 默认值 对象或数组默认值必须从一个工厂函数获取 ()=>...

// 自定义验证函数
validator:(value:string)=> return ['sherry','jerry','doreen'].indexOf(value)!==-1
},
age:{
// ...
}
},

// 通过参数、在setup中使用
setup:(props)=>{
// props是通过ref包裹的 响应式引用
console.log(props.name)
}
}

子传父( emit )

有细微的差别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 子组件
<button @click='handleEmit'> 按钮 </button>

setup:(props,context)=>{
// context 不是响应式的 可写为 { attrs,slots,emit } Attribute,插槽,广播
function handleEmit(){
context.emit('toParentComponent','some info')
}
return { handleEmit }
}

// 父组件
<child-component @toParentComponent='hanldeFun' />

setup:()=>{
function handleFun(value:string){
console.log(value) // some info
}
return { handleFun }
}

父传孙子( provide inject )

父组件通过provide传递,孙子组件通过inject接收

1
2
3
4
5
6
7
8
9
 // 父组件 传递
setup: () => {
provide("name", "value");
}

// 子组件 接收
setup: (props, context) => {
const result = inject("name");
},

递归监听

无论是通过ref还是reactive创建的数据都是递归监听
能够监听每一层数据 并且把每一层数据都包装成proxy对象

非递归监听

shallowReactiveshallowRef

  • 只为某个对象的私有(第一层)属性创建浅层的响应式代理,不会对“属性的属性”做深层次、递归地响应式代理,而只是保留原样。
  • 只有第一层被封装成proxy对象,其余内容都是对象的形式存储,如果第一层内容没有被改变,就不会触发页面的更新

Tips:
如果是通过shallowRef创建的数据,vue监听的是.value的变化,并不是第一层的变化。

1
2
3
4
5
6
7
8
<template>
<div>
<h1>{{date.data}}</h1>
<h1>{{date.obj.name}}</h1>
<h1>{{date.obj.son.name}}</h1>
<button @click="handleAdd">添加</button>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { shallowReactive } from "vue";
export default {
name: "App",
data() {
return {};
},
setup() {
let date = shallowReactive({
date: 'hhh',
obj: {
name:'doreen',
son: {
name: 'jerry'
}
}
});
function handleAdd() {
// 第一层数据改变,点击事件后,页面数据更新
date.data = 'eee'
date.obj.name = 'sherry'
date.obj.son.name = 'kris'

// 第一层数据没有改变,点击事件后,数据改变页面没有更新
date.obj.name = 'sherry'
date.obj.son.name = 'kris'

}

return { date, handleAdd };
},
};

triggerRef

针对ref类型的数据,vue3提供了triggerRef的方法进行触发更新

(对于reactive类型的数据没有提供相应方法)

1
2
3
4
5
6
7
8
import { triggerRef } from 'vue'
...

date.value.son.name = 'kris'
triggerRef(date);

...

toRow

获取ref / reactive 的原始数据

ref / reactive 数据类型的特点:
每次修改都会被追踪,触发界面更新,但是会非常消耗性能
对于一些不需要追踪的操作,不需要更新界面,这个时候
就可以用toRow方法拿到他的原始数据,对原始数据进行修改
这样就不会被追踪并且不会更新界面,提高了性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { reactive,toRow } from "vue";
export default {
setup() {
let obj = { name: "doreen",sex: "女" };
let state = reactive(obj);
let obj2 = toRaw(state); // 获取ref / reactive 的原始数据
// let state = ref(obj);
// let obj2 = toRow(state.value) // ref需要通过.value获取原始数据


// console.log(obj === obj2); // true
// console.log( obj === state ) false
/*
state 和 obj :
引用关系,state本质是一个proxy对象,在这个proxy对象中引用了obj
*/
function handleChange() {
/*
直接修改 obj 无法触发更新
通过proxy对象包装后,才能触发页面更新
*/
obj.name = "sherry";
console.log(obj); // { name:'sherry',sex:'女' }
console.log(state); // { name:'sherry',sex:'女' }
}

return { obj, state, handleChange };
},
};

markRow

让数据永远不会被追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setup() {
let obj = markRaw({
name: "doreen",
sex: "女",
});
let state = ref(obj);

function handleChange() {
// 不触发页面更新
obj.name = "sherry";
console.log(obj); // { name:'sherry',sex:'女' }
console.log(state); // { name:'sherry',sex:'女' }
}

return { state, handleChange };
},

toRef

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
setup() {
let obj = {
name: "doreen",
};
/**
* let state = ref(obj.name)
* => let state = ref('doreen')
* => let state = reactive({ value: 'doreen' })
*/
let state = ref(obj.name);

function handleChange() {
state.value = "sherry";
/**
* 如果利用ref将某一个对象中的属性变成响应式的数据
* 我们修改的响应式数据不会影响到原始数据
*/
console.log(obj); // { name:'doreen' }
console.log(state); // { value: 'sherry' }
}

return { state, handleChange };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
setup() {
let obj = {
name: "doreen",
};
/*
ref和toRef的区别:
ref -> 复制,修改响应式数据不会影响以前的数据
数据发生改变,界面就会自动更新

toRef -> 引用,修改响应式数据会影响以前的数据
数据发生改变,界面也不会自动更新
*/
let state = toRef(obj,'name');
console.log(state)
function handleChange() {
state.value = "sherry";
/**
* 如果利用toRef将某一个对象中的属性变成响应式的数据
* 我们修改的响应式数据会影响到原始数据
* 如果响应式的数据通过toRef创建的,修改数据并不会触发界面更新
*/
console.log(obj); // { name:'sherry' }
console.log(state); // { value: 'sherry' }
}

return { state, handleChange };
},

customRef

返回一个ref对象,可以显式的控制依赖追踪和触发响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { customRef } from "vue";

// 自定义 ref
function myRef(value) {
// track -> 追踪 trigger -> 触发
// 没有这两个参数,ui界面就不会被触发更新
return customRef((track,trigger) => {
return {
get() {
track() // 告诉vue 数据需要被追踪变化的
console.log("get:", value);
return value;
},
set(newV) {
console.log("set:", newV);
value = newV
trigger() // 触发更新
},
};
});
}
export default {
setup() {
let state = myRef(15);
function handleChange() {
state.value++;
}
return { state, handleChange };
},
};

应用场景:
setup中不能进行异步操作
如果需要通过网络请求对数据进行赋值
就可以使用customRef来获取请求数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 import { customRef } from "vue";
function myRef(value) {
// track -> 追踪 trigger -> 触发
return customRef((track, trigger) => {
// 在get之前对数据进行赋值 不会造成重复刷新
fetch(value)
.then((res) => {
return JSON.json();
})
.then((data) => {
console.log(data);
value = data;
trigger(); // 获取数据、刷新页面
})
.catch((err) => {
console.log(err);
});

return {
get() {
track(); // 数据需要被追踪变化的
console.log("get:", value);
return value;
},
set(newV) {
console.log("set:", newV);
value = newV;
trigger(); // 触发更新
},
};
});

}
export default {
setup() {
let state = myRef('../public/data.json');
return { state };
},
};

获取元素

vue2x中通过绑定ref属性,然后通过$refs.xxx来获取元素
vue3取消了$refs,但是也可以通过ref来获取元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

<h1 ref="box"></h1>


import { ref, onMounted } from "vue";
export default {
setup() {
let box = ref(null);

onMounted(() => {
console.log(box.value); // <h1></h1>
});
// setup的生命周期在beforeCreated之前
console.log(box.value) // null
return { box };
},
};

readonly

用于创建一个只读的数据,并且递归只读

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { readonly } from "vue";
export default {
setup() {
let box = readonly({
name: "doreen",
attr: {
age: 12,
},
});

function handleChange() {
box.name = "sherry";
box.attr.age = 22;

console.log(box);
/**
* {
name: "doreen",
attr: {
age: 12,
},
}
*/
}
return { box, handleChange };
},
};

shallowReadonly

创建一个第一层只读的数据 (非递归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { shallowReadonly } from "vue";
export default {
setup() {
let box = shallowReadonly({
name: "doreen",
attr: {
age: 12,
},
});

function handleChange() {
box.name = "sherry";
box.attr.age = 22;

console.log(box);
/**
* {
name: "doreen",
attr: {
age: 22,
},
}
*/
}
return { box, handleChange };
},
};

isReadonly

判断是否是readonly类型 返回布尔值

1
isReadonly(state) // true / false

computed

使用getter函数,从getter返回的值返回一个不变的响应式ref对象

1
2
3
4
5
6
7

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2
plusOne.value++ // error

通过getset函数对象创建ref对象

1
2
3
4
5
6
7
8
9
10
11
12

let msg = ref("title");
let str = computed({
get: () => msg.value + " get ",
set: (val) => {
msg.value = val + " set ";
},
});

console.log(str.value); // title get
str.value = "set value"; // set value

watch

1
2
3
4
5
6
7
8
9
10
import { watch } from 'vue'

// ...
// 多个观察对象 watch([val1,val2 ... ],([])=>{ ... })
watch(msg, (newV, oldV) => {
console.log(newV, oldV);
});

// ...

Teleport组件

vue项目在编译后所有的内容都是在 <div id='app'></div> 标签下进行的,样式什么的会受到全局样式的影响

Teleport是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术

属性

to

<teleport to="#some-id / .some-class / [data-teleport]" /> to属性后面必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用)。目的是将teleport内的元素移动到该容器中

disabled

<teleport to='...' :disabled='disabledFlag'>此可选属性可用于禁用 <teleport> 的功能,这意味着其插槽内容将不会移动到任何位置,而是在您在周围父组件中指定了 <teleport> 的位置渲染

使用

  1. public - index.html
    <div id="app"></div> 下方或者什么地方新增 <div id="example"></div>

  2. 新建一个 Example.vue 文件 并挂载到 App.vue 中进行展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- Example.vue -->
<template>
<teleport to='#example'>
<div> ... </div>
</teleport>
</template>
<style>
/* ... */
</style>


<!-- App.vue -->
<template>
<div>
....
<example/>
</div>
</template>

这样Example.vue所写的内容就存在于<div id="example"></div>标签中,而不是<div id="app"></div>中,样式不会受app的影响

Suspense异步组件

Suspense组件用于在等待某个异步组件解析时显示后备内容

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

<!-- Example.vue -->
<template>
<div> {{result}} </div>
</template>
<script lang='ts'>
...
export default defineComponent({
async setup(){
const rawData = await axios.get('...');
return { result:rowData.data }
}
})

</script>
<style>
/* ... */
</style>


<!-- App.vue -->
<template>
<Suspense>
<template #default>
<!-- 请求成功后展示的组件 -->
<example/>
</template>
<template #fallback>
<!-- 没请求的时候展示的组件 -->
<h1>Loading...</h1>
</template>
</Suspense>
</template>
<script lang='ts'>
// 捕获请求异常
import { onErrorCaptured } from 'vue'
export default{
...
setup(){
// 返回bool值
onErrorCaptured((error)=>{
console.log(error)
return true
})
}
}
</script>

vue3响应式的实现

非递归监听,只有第一层改变才会触发界面更新

shallowReactive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function shallowReactive(obj){
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
obj[key] = value
return true
}
})
}
var obj = {name:'doreen'}
var state = new shallowReactive(obj)
state.name = 'sherry' // { name: 'sherry' }

shallowRef

在shallowReactive的基础上,在外部封装一层value,所以他的第一层是value,只有value改变才会触发界面更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function shallowRef(val){
return shallowReactive({value: val})
}
function shallowReactive(obj){
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
obj[key] = value
return true
}
})
}

var obj = {name:'doreen'}
var state = new shallowRef(obj)
state.value.name = 'haha' // { value: { name: 'haha' } }

shallowReadonly

1
2
3
4
5
6
7
8
9
10
function shallowReadonly(obj) {
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
console.warn('只读 不能被赋值')
}
})
}

非递归监听,每一层改变都会触发更新。原理是把每一层进行proxy封装

reactive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

function reactive(obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
obj.forEach((item, index) => {
if (typeof item === 'object') {
obj[index] = reactive(item)
}

})
} else {
for (let key in obj) {
let item = obj[key];
if (typeof item === 'object') {
obj[key] = reactive(item)
}
}
}
} else {
console.warn(`${obj} is not object`)
}
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
obj[key] = value
return true
}
})
}

ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function ref(val) {
return reactive({ value: val })
}
function reactive(obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
obj.forEach((item, index) => {
if (typeof item === 'object') {
obj[index] = reactive(item)
}

})
} else {
for (let key in obj) {
let item = obj[key];
if (typeof item === 'object') {
obj[key] = reactive(item)
}
}
}
} else {
console.warn(`${obj} is not object`)
}
return new Proxy(obj, {
get(obj, key) {
console.log(obj, key)
return obj[key]
},
set(obj, key, value) {
obj[key] = value
console.log(obj)
return true
}
})
}

readonly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function readonly(obj) {
if (typeof obj === 'object') {
if (obj instanceof Array) {
obj.forEach((item, index) => {
if (typeof item === 'object') {
obj[index] = readonly(item)
}

})
} else {
for (let key in obj) {
let item = obj[key];
if (typeof item === 'object') {
obj[key] = readonly(item)
}
}
}
} else {
console.warn(`${obj} is not object`)
}
return new Proxy(obj, {
get(obj, key) {
return obj[key]
},
set(obj, key, value) {
console.warn('不能被赋值')
}
})
}

vue的key属性

前提

在form表单需要实现两个输入框,绑定对象的两个属性一个存储key,一个存储value

遇到的问题

绑定值(key)的输入框,在一次输入后锚点就消失了,绑定名称(name)的输入框可以正常的使用

错误代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
statusType = [{
key:"",
value:""
}]

<el-form-item
prop="valueRange"
v-for="item in statusType"
:key="item.key"
:value="item.key"
>
<el-col :span="10">
<el-input placeholder="值" v-model="item.key"></el-input>
</el-col>
<el-col :span="4" style="text-align:center">:</el-col>
<el-col :span="10">
<el-input placeholder="名称" v-model="item.value"></el-input>
</el-col>
</el-form-item>

原因

在列表循环的时候key绑定的是item.key  // el-form-item :key="item.key" 
但是 值 输入框中绑定的数据也是 item.key
因为Vue是基于虚拟dom对元素进行重新渲染
每次输入都会改变key值,key属性值的改变会导致dom的重新渲染
所以锚点会消失!

解决方案

将父级元素的key属性通过index来绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

<el-form-item
prop="valueRange"
v-for="(item,index) in statusType"
:key="index"
:value="item.key"
>
<el-col :span="10">
<el-input placeholder="值" v-model="item.key"></el-input>
</el-col>
<el-col :span="4" style="text-align:center">:</el-col>
<el-col :span="10">
<el-input placeholder="名称" v-model="item.value"></el-input>
</el-col>
</el-form-item>

扩展应用

需求

页面有上下两个表格,上 -> 订单表、下 -> 详细表
通过点击订单表的某一行订单,获取到他的inboundOrderSn字段,并通过该字段向接口发送请求,获取详细数据并在下方的表格显示

难点

子组件封装的是在刷新之后对接口进行请求等价于在mounted/created内才会请求数据,但是组件在页面一开始就已经创建完成了,所以在点击的时候并不会进行数据的请求,就得需要重新渲染该组件。

解决方法

给详细列表组件绑定key属性,在点击某一订单的时候,对key值进行改变,组件就会重新渲染一次。就可以发送请求并对数据进行展示

1
2
3
4
5
6
7
8
<detail-table :key="timer" :id="selectedId" @success="forceRefresh"></detail-table>

private timer: number = new Date().getTime();

private handleRowClick(row: any) {
this.timer = new Date().getTime();
this.selectedId = row.inboundOrderSn ? row.inboundOrderSn : '';
}

vuex简单使用

安装

npm i vuex -S

配置

在src下创建一个store文件夹文件夹内创建index.js文件

配置(index.js)文件

  1. 引入组件vuex

    1
    2
    3
    4
    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)

  2. 定义数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 数据存储中心 对应于vue中的data
    const state = {
    count: 0
    }

    // 数据操作中心 操作state内的数据的变化
    // 只提供同步操作
    const mutations ={
    addCount() {
    state.count += 1
    },
    deCount() {
    state.count -= 1
    }
    }

    // 类似于mutation,他提交的是一个mutation而不是直接改变state内数据的状态
    // 可以包含任意异步操作
    const actions = {
    changeCount(context){
    context.commit('addCount') // addCount是mutations的一个方法名
    }
    }

    // 对state进行计算操作,类似于vue中的计算属性
    // 适用于多个组件复用的计算属性
    const getters = {
    computedCount: (state)=> {
    return state.count*2
    }
    }
  3. 实例化Vuex并暴露出去

    1
    2
    3
    4
    5
    6
    7
    const store = new Vuex.Store({
    state,
    mutations,
    getters,
    actions
    })
    export default store

    在main.js中引入使用store

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 引入store
    import store from './store'

    new Vue({
    // 在vue实例化中使用store
    store,
    render: h => h(App),
    }).$mount('#app')

    基本使用

  • state 通过 this.$store.state.xxx 来使用
  • mutation 通过 this.$store.commit(‘xxx’) 来使用
  • getter 通过 this.$store.getters.xxx 来使用
  • action 通过 this.$store.dispatch(‘xxx’) 来使用

配合辅助函数的使用

MapState:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {mapState} from 'vuex'

computed: {
...mapState([
/**
* 当映射的计算属性的名称与state的子节点名称相同
* 我们可以直接给mapState传一个字符串数组
*/
'name', //映射 this.name 为 store.state.name
'age'
])

...mapState({
vName: state=>state.name, //映射 this.vName 为 store.state.name
vAge: state =>state.age
})

},

mapGetters:

1
2
3
4
5
6
7
8
computed: {
...mapGetters({
name: 'NAME' //异名映射
}),
...mapGetters([
'NAME' //同名映射
])
},

mapMutations:

1
2
3
4
5
6
7
8
methods: {
...mapMutations([
"increment", // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
]),
...mapMutations({
add: "increment" // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}

mapAction:

1
2
3
4
5
6
7
8
9
10
11
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}

vue-router的简单使用

安装

npm i vue-router -S

配置

在src下创建一个router文件夹文件夹内创建index.js文件

配置路由(index.js)文件

  1. 创建组件、引入组件

    1
    2
    3
    4
    import Vue from 'vue'
    import Router from 'vue-router'

    import Nav from '../components/Nav.vue'
  2. 配置路由

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const routes = [
    { path:'/nav',component:Nav },
    { path: '/user/:id', component: User } // 动态路由
    // ....
    { path:'*',redirect:'/nav' }, // 默认跳转路径

    // 嵌套路由
    { path: '/user/:id', component: User,
    children: [
    {
    // 当 /user/:id/profile 匹配成功,
    // UserProfile 会被渲染在 User 的 <router-view> 中
    path: 'profile',
    component: UserProfile
    },
    ]
    }
    ]
    ]
  3. 实例化VueRouter并暴露出去

    1
    2
    3
    4
    const router = new VueRouter({
    routes
    })
    export default router

    在main.js中引入使用router

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 引入router
    import router from './router'

    new Vue({
    // 在vue实例化中使用router
    router,
    render: h => h(App),
    }).$mount('#app')

    使用

  • 声明式方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 路由配置的区域
    <router-link to="/nav">nav</router-link>
    <router-link to="/user/123" >user</router-link>

    // 路由需要配置参数 可以通过以下两种方法
    <router-link
    :to="{
    name:'tab', // index配置的时候就得添加name
    query:{
    name:'doreen'
    }, // 不会刷新 /tab?name=doreen
    params:{
    id:'123' // 会刷新 /tab/123
    }
    }"
    >tab</router-link>

    <router-link :to="{ path:'/tab/'+id }" >user</router-link>

    // 组件内容的显示区域
    <router-view />
  • 编程式导航

    path和params一起写的时候params不生效!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 字符串
    router.push('home')

    // 对象
    router.push({ path: 'home' })

    // 命名的路由
    router.push({ name: 'user', params: { userId: '123' }})

    // 带查询参数,变成 /register?plan=private
    router.push({ path: 'register', query: { plan: 'private' }})

    // 在浏览器记录中前进一步,等同于 history.forward()
    router.go(1)

    // 后退一步记录,等同于 history.back()
    router.go(-1)

    // 前进 3 步记录
    router.go(3)

    // 如果 history 记录不够用,那就默默地失败呗
    router.go(-100)
    router.go(100)

vue-property-decorator的简单使用

vue-property-decorator

提供给vue内写typescript语法的时候引入的组件(基于vue-class-component)

安装

npm i -s vue-property-decorator

基本语法

@Props

props是子组件用来接收父组件传递过来的参数

在常规的vue语法环境下的书写方式:

1
2
3
4
5
6
7
8
9
10
11
export default {
props: {
propA: {
type: Number,
// type: [ String,Boolean ]
// default: 'value'
// default: function (){ return 'hello' }
// required: true
}
}
}


在vue-property-decorator组件中使用:

@Prop( options: (PropOptions | Constructor[] | Constructor )={} )

@Prop装饰器接收一个参数,这个参数的三种写法:

  • constructor String、Number、Boolean等,指定prop的类型
  • constructor[] 指定prop的可选类型
  • propOptions 对象参数可以是 type,default,required…
1
2
3
4
5
6
7
8
9
10
11
12
<script lang='ts'>
import { Vue,Component,Prop } from 'vue-property-decorator'

@Component({}) // 不管有没有组件都必须写
export default class 组件名 extends Vue{

@Props(Number) propA!: number;
@Props([String,Boolean]) propB: string | boolean;
@Props({default:'value'}) propC!: string;

}
</script>

! 表示一定有值(非null和非undefined), ? 表示可选

@Component

用来接收一个对象作为参数,在对象中可以声明components,filters,directives等,也可以声明computed,watch

在常规的vue语法环境下的书写方式:

1
2
3
4
5
6
7
import DetailTable from './components/detail-table.vue';

export default {
components: {
DetailTable
},
}


在vue-property-decorator组件中使用:

Component( options:ComponentOptions = {} )

1
2
3
4
5
6
7
8
9
10
11
12
13
import DetailTable from './components/detail-table.vue';
// ...
@Component({
components:{
DetailTable
},
filters: {
toFixed: ()=>{ ... }
}
})
export default class 组件名 extends Vue {
// ...
}

@Watch

侦听数据的变化

在常规的vue语法环境下的书写方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
watch: {
msg: [
{
handler: 'onMsgChange',
immediate: false,
deep: false
}
]
},
methods: {
onMsgChange(newValue,oldValue) { }
}

在vue-property-decorator组件中使用:

@Watch( path: string, options: WatchOptions={} )

@Watch装饰器接收两个参数:

  • path:string 被侦听的属性名
  • options?: WatchOptions={} options 可以包含两个属性:
    • immediate?: boolean 侦听开始之后是否立即调用该回调函数;
    • deep?: boolean 被侦听的对象的属性被改变时,是否调用该回调函数;
      1
      2
      3
      4
      5
      6
      export default class 组件名 extends Vue {
      @Watch('msg')
      public onMsgChange (newValue: string, oldValue: string) {
      // ...
      }
      }

@Emit

广播方法获取变量

在常规的vue语法环境下的书写方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
data (){
count: 0
},
methods: {
addToCount(n) {
this.count += n
this.$emit('add-to-count',n)
},
resetCount() {
this.count = 0
this.$emit('reset')
}
}
}

@Emit(event?:string)

@Emit装饰器接收一个可选参数,作为事件名
@Emit将回调函数的返回值作为第二个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
export default class 组件名 extends Vue{
count = 0

@Emit()
public addToCount(n:number){
this.count += n
}

@Emit('reset')
public resetCount() {
this.count = 0
}
}

vue-class-component的简单使用

vue-class-component

vue对typescript支持的库

安装

npm install –save vue vue-class-component

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<script lang='ts'>
import { Component,Provide,Vue } from 'vue-property-decorator'
import { BaseAxios } from '../api/axios'
import input from 'components/input.vue'

// 组件的引入
@Component({
components: { input }
})

export default class Hello extends Vue {
// data
obj: Object = {}
msg: string = 'hello world'
num: number = 0

// methods
addTimes (a: number) :number {
this.num += a
return this.num
}

// computed
get computedMsg() {
return 'computed' + msg
}

// 生命周期
mounted() {
BaseAxios.post('...').then( res=>{
console.log(res)
})
}

}

</script>

createDecorator

自定义装饰器,接收一个回调函数作为第一个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// decorator.js
import { createDecorator } from 'vue-class-component'

export const NoCache = createDecorator((options,key)=>{
options.computed[key].cache = false
})

// MyComp.js
import { NoCache } from './decorators'
@Component
class MyComp extends Vue {
// computed属性不会被缓存
@NoCache
get random() {
return Math.random()
}
}

Component.registerHooks

自定义钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// class-component-hooks.js
import Component from 'vue-class-component'

// 注册钩子name
Component.registerHooks([
'beforeRouterEnter',
'beforeRouterLeave',
'beforeRouterUpdate',
])

// MyComp.js
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
class MyComp extends Vue {
// 在组件内部为注册的钩子函数实现原型方法
beforeRouterEnter(to,from,next) {
console.log('before-router-enter')
next()
}
beforeRouterLeave(to,from,next) {
console.log('before-router-leave')
next()
}
}

typescript基础语法

1.1 简介

  • ts 是由微软开发的开源的编程语言

  • typescriptjavascript的超集

  • ts 是开发大型应用的基石

  • ts 提供了更丰富的语法提示

  • ts 在编写阶段能够检查错误

2.1 ts 是静态类型 , js 是动态类型

动态类型运行时,解析器基于变量的类型决定变量的类型 (可以不需要提前声明变量类型)

3.1 数据类型

  • 原始数据类型: boolean string number null undefined symbol

  • 引用数据类型: object

  • 基础类型:boolean string number null undefined symbol any never void

    • any 没办法有代码提示;
    • any 在代码测试的时候用;
    • 对象 interface
    • 数组 number[] string[] boolean[] 泛型的写法: Array<number>

    函数的注解:

    1
    2
    3
    let test = function (a: number, b: number): number {
    return a + b
    }

    新的语法特性:
    as 类型 ;或者 <类型 >值 // 断言
    class (OOP 面向对象的三大特性) : 封装 , 继承, 多态;

4.1 原始数据的注解

  • 布尔值的注解:

    1
    let isDone: boolean = false;
  • 数字的注解:

    1
    let decLiteral: number = 6;
  • 字符串的注解:

    1
    let name: string = "bob";

5.1 原始数据的注解

any 数据类型: 任意数据类型;

  1. 如果是不同变量的话,可以是任意的数据类型:
  2. 如果是对象的话, any 不能够提示原有的属性和方法;
  3. 未给初始值的变量类型 为 any;

6.1 原始数据的注解

  • void 当一个函数没有返回值时

  • null && undefined

    • nullundefined是所有类型的子类型
    • –strictNullChecks : let num: number = undefined;
  • never 类型表示的是那些永不存在的值的类型;

  • object 表示非原始类型

7.1 类型注解 && 类型推断

7.2 联合类型;

注意:

  1. 联合类型的共有属性是不会报错;

  2. 在赋值的时候确认类型;

    1
    let a: string | number;

8.1 接口 interface

  1. 对象的形状进行描述;

  2. 对类的一部分行为的抽象;

    interface 不可多,不可少;
    可选属性:age?: number;
    任意属性:[propName: string]: any
    只读属性:readonly

    1
    2
    3
    4
    5
    interface Person {
    name: string;
    age?: number; // 可选属性;
    [propName: string]: any
    }

9.1数组的注解

写法方式

  1. 类型[] // number[] (纯数字的数组, 没有长度限制) , push 非类型的数据,是进不去的;

  2. Array<类型> Array

  3. interface 方式;

    1
    2
    3
    interface List {
    [index: number]: number | string
    }
  4. 类数组

    1
    2
    3
    4
    5
    interface Args {
    [index: number]: any;
    length: number;
    callee: Function;
    }

    10.1 函数的注解方式

    1. 函数声明的注解方式;

      1
      2
      3
      4
      5
      function test(a: number, b: number): number {
      return a + b
      // console.log(1)
      // throw new Error()
      }
    2. 函数表达式的注解方式;

    1
    2
    3
    let test1: (a: number, b: number):{} = function (a, b) {
    return { a: 1 }
    }

    10.2 函数的重载

  • 相同的函数因为传入的参数类型不同 返回的值的类型也不同

  • 表意更清楚;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function reverse(x: string): string;
    function reverse(x: number): number;
    function reverse(x: string | number) {
    if (typeof x === 'string') {
    return x.split('').reverse().join('')
    }

    if (typeof x === 'number') {
    return Number(x.toString().split('').reverse().join(''))
    }
    }

11.1 类

"strictPropertyInitialization": false,

类的注解方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 父类 / 基类
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}

class Cat extends Animal {
constructor(name:string){super(name)} // 1
move(position:number){
super.move(position) // 2
}
}

  • 1->继承中的super指的是构造函数
  • 2->构造函数以外的super指的是父类 ; 可以调用父类的属性和方法

11.2 类的修饰符

  • public 公共的成员属性 (默认):

    1. 自身调用;
    2. 子类可以调用;
    3. 实例调用;
  • private 私有属性:

    1. 自身调用;
  • protected 受保护的成员属性:

    1. 自身调用;
    2. 子类可以调用;

11.3 类的修饰符 readonly;

  1. 顺序 -> public readonly

  2. 是否可写: readonly 只读, 不可写

  3. 不能修饰成员方法;

    参数属性: 简写的写法;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    class Animal1 {
    constructor(private name: string) { }
    move(distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`)
    }
    }
    let jake = new Animal1('jake');
    console.log(jake);
    class Animal2 {
    private name: string;
    constructor(name: string) { this.name = name }
    move(distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
    }

11.4 类的存取器;

  • getter 取值函数 obj.a

  • setter 存值函数 obj.a = ‘123’

    //同时出现, 就是一个属性;

    改变赋值和读取的行为;

11.5 类的静态属性 && 抽象类;

  • 抽象类: 能够提供其他类的基类;

  • 抽象类:

    1. 无法创建实例;
    2. 抽象方法一定要有实现;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
          
    abstract class Animal {
    abstract makeSound(): void;
    move(): void {
    console.log('roaming the earch...');
    }
    }
    class Snack extends Animal {
    makeSound() {
    console.log('zzzzzzz');
    }
    move(): void {
    console.log('roaming the earch...');
    }
    }

    class Rhino extends Animal {
    makeSound() {
    console.log('mmmmmmm');
    }
    move(): void {
    console.log('roaming the earch...');
    }
    }

    new Snack();

11.6 高阶技巧;

  1. 定义类的时候 , 定义了一个类型;
  2. 定义类的时候, 定义了一个构造函数;
  3. 接口可以继承类;

12.1 接口和 函数类型接口;

函数类型接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function (source: string, subString: string): boolean {
let result = source.search(subString);
return result > -1;
}

type SearchFunc = (source: string, subString: string) => boolean

// function mySearch(source: string, subString: string): boolean {
// let result = source.search(subString);
// return result > -1;
// }

12.2 接口和 可索引的类型接口;

  1. 共有支持两种索引签名:字符串和数字。

    1
    2
    3
    interface NumberArray {
    [index: number]: number
    }

    是因为当使用number来索引时,
    JavaScript会将它转换成string然后再去索引对象;

  2. 索引签名 有点像老大的意思;

    1
    2
    3
    4
    5
    6
    7


    interface NumberDictionary {
    [index: string]: number | string;
    length: number; // 可以,length是number类型
    name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
    }
  3. 索引签名可以设置为只读;

    1
    2
    3
    4
    5
    6

    interface ReadonlyStringArray {
    readonly [index: number]: string;
    }
    let myArray: ReadonlyStringArray = ["Alice", "Bob"];
    myArray[2] = "Mallory"; // error!

    12.3 类类型接口; – 类实现接口;

    对类的一部分行为的抽象;

    类实现所有接口中的属性和方法; && 对比 抽象类: 抽象方法需要实现;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
      
    interface ClockInterface {
    currentTime: Date;
    }

    class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number) { }
    }

    interface Alarm {
    alert(): void;
    }

    interface Light {
    color: string;
    lightOn(): void;
    lightOff(): void;
    }

    class Door { }

    class SecurityDoor extends Door implements Alarm {
    alert() {
    console.log('hei');
    }
    }

    class Car implements Alarm, Light {
    color = 'red';
    lightOn() {

    }
    lightOff() {

    }
    alert() {
    console.log('hello');
    }
    }

12.4 类类型接口 –类静态部分与实例部分的区别

constructor存在于类的静态部分,所以不在检查的范围内

类静态部分与实例部分需要单独做;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 实例部分
interface ClockInterface {
currentTime: Date;
getTime(h: number, m: number): any;
}
// 构造函数 --静态类型部分
interface ClockConstructor {
new(h: number, m: number): any
}

class Clock implements ClockInterface {
currentTime = new Date()
constructor(h: number, m: number) { }
getTime() {}
}

function createClock(C: ClockConstructor, h: number, m: number) :ClockInterface{
return new C(h, m);
}

let clock = createClock(Clock, 12, 12);

12.5 接口继承接口 | 类实现接口;

1
2
3
4
5
6
7
8
9
10
11
 interface Shape {
color: string;
}

interface PenStroke {
penWidth: number;
}

interface Square extends Shape, PenStroke {
sideLength: number;
}

12.6 混合类型;

函数类型的interface, 添加属性的方式来实现 对象的interface;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 函数类型 + 对象的类型
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function getCounter(): Counter {
// 断言 => (<any>result).id => (result as any).id
// 初始化值的时候使用

let counter = <Counter>function(start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;

12.7 混合类型的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface ClockInterface {
currentTime: Date;
getTime(h: number, m: number): any;
}

interface ClockConstructor {
new(h: number, m: number): any;
getTime1(): void;
}
class Clock implements ClockInterface {
currentTime = new Date()
constructor(h: number, m: number) { }
getTime() {}

static getTime1() {}
}

function createClock(C: ClockConstructor, h: number, m: number) {
return new C(h, m);
}

let clock = createClock(Clock, 12, 12);

12.8 接口继承类:

  1. 类可以实现接口;
  2. 接口可以继承接口;
  3. 接口可以继承类;

13.1 泛型

*T 泛型变量 | 类型变量 *

1
2
3
4
5
6
7
8
9
10
11
12
13
function identity1<T>(arg: T): T {
return arg;
}

identity1('123')
identity1<number>(123)

interface MyArray<T> {
[n: number]: T;
}

let a: number[] = [12, 3, 4];
let b: Array<number> = [1, 2, 3, 4]

13.2 泛型类型

1
2
3
4
5
6
7
8
9
10
function identity<U>(arg: U): U {
return arg;
}

interface IdentityInterface<T> {
(arg: T): T
}
interface IdentityInterface {
<T>(arg: T): T
}
  1. 函数泛型的注解方式;

    1
    2
    let a: <T>(arg: T) => T = identity;

  2. 对象字面量的方式来定义泛型类型;

    1
    2
    let a: { <T>(arg: T): T } = identity;

  3. 泛型接口的定义方式:

    1
    2
    let a: IdentityInterface = identity;

    13.3 泛型类 泛型约束;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class MinClass<T>{
    public list: T[] = [];
    add(num: T) {
    this.list.push(num);
    }

    min(): T {
    let minNum = this.list[0];
    for (var i = 0; i < this.list.length; i++) {
    if (minNum > this.list[i]) {
    minNum = this.list[i];
    }
    }
    return minNum;
    }
    }
    • 泛型约束:
      extends 接口的方式(不一定是接口);
    1
    2
    3
    4
    5
    6
    7
    8
    interface LenthWise1 {
    length: number;
    }
    type LenthWise = string | LenthWise1
    function loggingIdentity<T extends LenthWise>(arg: T): T {
    console.log(arg.length); // Error: T doesn't have .length
    return arg;
    }

    13.4 多重泛型约束; || && | &

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    interface Sentence {
    content: string;
    title: string;
    }

    interface Music {
    url: string;
    }

    class Test<T extends Sentence & Music>{
    props: T
    constructor(public arg: T) {
    this.props = arg
    }

    info() {
    return {
    url: this.props.url,
    content: this.props.content,
    title: this.props.title
    }
    }
    }

13.5 在泛型里使用类类型, 约束或是更好的推论;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function create<T>(c: { new(): T; }): T {
return new c();
}

class BeeKeeper {
hasMask: boolean;
}

class ZooKeeper {
nametag: string;
}

class Animal {
numLegs: number;
}

class Bee extends Animal {
keeper: BeeKeeper;
}

class Lion extends Animal {
keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}

createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask;

14 元组;

  1. 确定成员 类型长度;

  2. push 越界的时 , 类型为联合类型;

  3. 可选的元素类型:

    1
    let list: [number, string, boolean?];
  4. rest 拓展运算符;

    declare function test(...args: [number, string, boolean]): void;
    declare function test(arg1: number, arg2: string, arg3: boolean): void;
    
    let list1: [number, ...string[]] = [1, '2', '3', '4'];
    let list2: [string, ...number[]] = ['1', 2, 3, 4, 5, 6, 7];

Next.js

next中文文档

next.js简介

Next.js 是一个轻量级的 React 服务端渲染应用框架。

  • 完善React项目架构,搭建轻松。比如:Webpack配置,服务器启动,路由配置,缓存能力,这些在它内部已经完善的为我们搭建完成了。
  • 自带数据同步策略,解决服务端渲染最大难点。把服务端渲染好的数据,拿到客户端重用,这个在没有框架的时候,是非常复杂和困难的。有了Next.js,它为我们提供了非常好的解决方法,让我们轻松的就可以实现这些步骤。
  • 丰富的插件帮开发人员增加各种功能。每个项目的需求都是不一样的,包罗万象。无所不有,它为我们提供了插件机制,让我们可以在使用的时候按需使用。你也可以自己写一个插件,让别人来使用。
  • 灵活的配置,让开发变的更简单。它提供很多灵活的配置项,可以根据项目要求的不同快速灵活的进行配置。

创建next.js应用程序

1
2
3
4
5
6
// 1.安装next的项目的脚手架工具 create-next-app
npm install -g create-next-app

// 2.创建next项目
npx create-next-app xxx

项目结构

1
2
3
4
5
6
7
8
xxx
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public // 公共静态文件存放地
├── components // 组件存放地
└── page // 页面存放地,自动生成路由渲染在服务器

page的使用

需要新添加路由则直接在page文件夹下创建新文件
eg:

1
2
3
export default function Index(){
return (<h1>hello world</h1>)
}

打开http://localhost:3000/Index即可访问到相关内容

component的使用

  1. 以创建一个item组件为例

    1
    2
    3
    4
    export default ({children})=>{
    <button>{children}</button>
    }
    // {children} => props.children 也可以在里面直接写props 获取的时候 props.children.xx
  2. 在page文件夹下的页面中引入

    1
    import Item from '../components/item'
  3. 使用组件

    1
    <Item>按钮组件 / {xx}</Item>

路由相关

页面跳转一般有两种形式,第一种是利用标签,第二种是用js编程的方式进行跳转,也就是利用Router组件。

标签式导航

在主页面和可跳转页面都引入link

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// home-page
import Link from 'next/link'
export default Home=()=>{
<div>
<h1>首页</h1>
<Link href='/pageA'><a>A页面</a></Link>
{/*
带参数传递
<Link href='/pageA?id=123'><a>A页面</a></Link>
<Link href={{pathname:'/pageA',query:{id:'123'}}}><a>A页面</a></Link>
*/}
<Link href='/pageB'><a>B页面</a></Link>
</div>
}
// ----------
// pageA
import Link from 'next/link'
export default ({router})=>{
<div>
<h1>page A</h1>
{/*
query接收参数
<span>{router.query.id}</span>
*/}
<Link href='/'><a>返回首页</a></Link>
</div>
}
// -----------
// pageB
import Link from 'next/link'
export default ()=>{
<div>
<h1>page B</h1>
<Link href='/'><a>返回首页</a></Link>
</div>
}

Router模块进行跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Router from 'next/router'
const Home = ()=>{

function gotoB(){
Router.push('/pageB')
/*
带参数传递

Router.push('/pageB?id=123')

Router.push({
pathname: '/pageB',
query: {
id: '123'
}
})
*/
}

return (
...
<div>
<button onClick={()=>{Router.push('pageA')}}>A页面</button>
<button onClick={gotoB}>B页面</button>
</div>
...
)
}

钩子函数

routeChangeStart路由发生变化时

routerChangeComplete路由结束变化时

beforeHistoryChange浏览器history触发前

routeChangeError路由跳转发生错误时

hashChangeStart hash开始跳转时

hashChangeComplete hash完成跳转时

getInitialProps数据获取

配合axios

  1. 安装引入axios
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    yarn add axios

    // 在所需的页面引入axios
    import axios from 'axios'

    const Home = ({router,list})=>{
    return (
    <>
    <div>{list}</div>
    </>
    )
    }

    /*
    通过getInitialProps获取数据
    然后页面通过props.list获取数据,并进行显示
    */
    Home.getInitialProps = async () => {

    const res = await axios('http://rap2.taobao.org:38080/app/mock/250724/get/data').then((res) => {
    return res.data
    })
    //返回值只能是resolve对象
    return res
    }
    export default withRouter(Home)

    CSS编写

  • 使用style jsx语法
    1
    2
    3
    4
    5
    6
    // 在return内部编写
    <style jsx>
    {`
    div{ color:red }
    `}
    </style>
  • 引入CSS文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 安装配置相应内容
    yarn add @zeit/next=css

    // 在根目录建立next.config.js配置
    const withCss = require('@zeit/next-css')

    if(typeof require !== 'undefined'){
    require.extensions['.css']=file=>{}
    }

    module.exports = withCss({})
    重启项目之后就可以进行使用
    1
    2
    // 在所需页面引入样式文件
    import '../public/test.css'

react-redux的简单使用

简介

redux为react提供的一个专用库
react-redux组成:

  1. UI组件(presentational component)

    • 只负责ui的呈现,不带有任何业务逻辑
    • 没有状态(不使用this.state这个变量)
    • 所有数据都由参数(this.props)提供
    • 不使用任何Redux的API
  2. 容器组件(container component)

    • 负责管理数据和业务逻辑,不负责UI的呈现
    • 带有内部状态
    • 使用Redux的API

安装

npm i react-redux –save

配合redux使用

npm i redux –save

使用

全局配置

利用<Provider>组件来实现容器组件获取state对象
在src下的index.js全局进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {Provider} from 'react-redux'
import store from './store'
const app = (
<Provider store={store}> // 让App所有的子组件都默认获取到了state
<App/>
</Provider>
)
//ReactDOM.render(<App />,document.getElementById('root'));
ReactDOM.render(app,document.getElementById('root'));

组件配置

connect方法用于从UI组件生成容器组件
在组件内部引入connect方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Component } from 'react';
import { connect } from 'react-redux'

// 将app转化为UI组件的形式进行展示
const App = (props)=>{
let {inputValue,add,list,changeInput} = props // 从属性中获取到方法和对象
return (
<div>
<input type="text" placeholder={inputValue} onChange={changeInput} />
<button onClick={add}>提交</button>
<ul>
{
list.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
);
}

connect方法接收两个参数:

  1. mapStateToProps
  2. mapDispatchToProps
    它们定义了ui组件的业务逻辑
    前者负责输入逻辑 => state映射到ui组件的参数(props)
    后者负责输出逻辑 => 用户对ui组件的操作映射成Action
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 映射关系
const stateToProps = (state) => {
return {
inputValue: state.inputValue,
list: state.list
}
}
const dispatchToProps = (dispatch)=>{
return {
changeInput(e){
let action = {
type: 'changeInput',
value: e.target.value
}
dispatch(action)
},
add(){
let action = {
type: 'add'
}
dispatch(action)
}
}
}
export default connect(stateToProps, dispatchToProps)(App);
  • Copyrights © 2019-2023 John Doe
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信