创建 Class 组件

  1. ES6 方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import React from 'react'

    class B extends React.Component{
    constructor(props){
    super(props)
    }
    render(){
    return (<div>hi</div>)
    }
    }
    export default B
    Read more »

函数组件

1
2
3
const Hello = (props) =>{
return <div>{props.message)}</div>
}
1
const Hello = props => <div>{props.message}</div>
1
2
3
function Hello(props){
return <div>{props.message}</div>
}

Read more »

Element V.S. Component

元素与组件

  • const div = React.createElement('div',...)
  • 这是一个 React 元素(d 小写)
  • const Div = () => React.createElement('div',...)
  • 这是一个 React 组件(D 大写)

Read more »

模板写法

一、完整版,写在HTML里

1
2
3
4
5
6
7
8
9
10
11
12
<div id='xxx'>
{{ n }}
<button @click="add">+=1</button>
</div>

new Vue({
el: '#xxx',
data: { n: 5 }, // data 可以写成函数
methods: {
add(){this.n += 1}
}
})
Read more »

options

1
const vm = new Vue(option)

options 的五类属性

数据:data、props、propsData、computed、methods、watch

DOM:el、template、render、renderError

生命周期钩子 :beforeCreate、created、before Mount、mounted、beforeUpdate、updated、activated、deactivated、beforeDestroy、destroyed、errorCaptured

资源:directives、filters、components

组合:parent、mixins、extends、provide、inject

其他:暂且不表

入门属性

  • el - 挂载点:与 $mount 有替换关系
  • data - 内部数据:支持对象和函数,优先用函数
  • methods - 方法: 事件处理函数或者是普通函数
  • components : 使用 Vue 组件,注意大小写
  • 四个钩子
    • created - 实力出现在内存中
    • mounted - 实力出现在页面中
    • updated - 示例更新了
    • destroyed - 实例消亡了
  • props - 外部属性

components 三种引入方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1 优先使用
import Demo from "./Demo.vue"

new Vue({
components:{
Frank1: Demo
},
template:`
<div>
...
<Frank/>
...
</div>
`
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 2
Vue.component('Frank2',{
template:`
<div>demo022</div>
`
})

new Vue({
template:`
<div>
...
<Frank2/>
...
</div>
`
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 3
const x = {
template: `
<div>demo333</div>
`
}

new Vue({
components:{
Frank3: x
},
template:`
<div>
...
<Frank3/>
...
</div>
`
})

四个钩子

demo

created - 实例出现在内存中

mounted - 实例出现在页面中

updated - 实例更新了

destroyed - 实例从页面和内存中消亡了

props

props 可以是数组或对象,用于接收来自父组件的数据。props 可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义验证和设置默认值。

1
2
3
message="n"  // 传入字符串
:message="n" // 传入 this.n
:fn="add" // 传入 this.add 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Demo.vue
<template>
<div>
”这是 demo 的内部“,
n = {{ message }}
<button @click="fn">call back</button>
</div>
</template>

<script>
export default {
props: ['message', 'fn']
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// main.js
import Demo from './Demo.vue'
new Vue({
components: {Demo},
data() {
return {n: 5}
},
template: `
<div>
{{ n }}
<Demo :message="n" :fn="add"/>
</div>
`,
methods: {
add() {
this.n += 1
}
}
}).$mount('#app2')

进阶属性

computed - 计算属性

被计算出来的属性就是计算属性

  • 当其依赖的属性的值发生变化的时,计算属性会重新计算。
  • 如果依赖的属性没有变化,就不会重新计算,使用缓存中的属性值。

computed 比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化。

例1. getter / setter -demo

用户名展示

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
// 需引用完整版 vue
Vue.config.productionTip = false;

new Vue({
data: {
user: {
email: "fangyinghang@qq.com",
nickname: "方方",
phone: "13812312312"
}
},
computed: {
displayName: {
get() {
const user = this.user;
return user.nickname || user.email || user.phone;
},
set(value) {
console.log(value);
this.user.nickname = value;
}
}
},
template: `
<div>
{{displayName}}
<div>
{{displayName}}
<button @click="add">set</button>
</div>
</div>
`,
methods: {
add() {
console.log("add");
this.displayName = "圆圆";
}
}
}).$mount("#app");

例2 使用 computed - demo

用 computed 筛选男女

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
Vue.config.productionTip = false;

let id = 0;
const createUser = (name, gender) => {
id += 1;
return { id: id, name: name, gender: gender };
};
new Vue({
data() {
return {
users: [
createUser("方方", "男"),
createUser("圆圆", "女"),
createUser("小新", "男"),
createUser("小葵", "女")
],
displayUsers: []
};
},
created() {
this.displayUsers = this.users;
},
methods: {
showMale() {
this.displayUsers = this.users.filter(u => u.gender === "男");
},
showFemale() {
this.displayUsers = this.users.filter(u => u.gender === "女");
},
showAll() {
this.displayUsers = this.users;
}
},

template: `
<div>
<div>
<button @click="showAll">全部</button>
<button @click="showMale">男</button>
<button @click="showFemale">女</button></div>
<ul>
<li v-for="(u,index) in displayUsers" :key="index">{{u.name}} - {{u.gender}}</li>
</ul>

</div>
`
}).$mount("#app");

例2.1不使用 computed - demo

不用 computed 筛选男女

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
Vue.config.productionTip = false;
let id = 0;
const createUser = (name, gender) => {
id += 1;
return {
id: id,
name: name,
gender: gender,
};
};
new Vue({
data() {
return {
users: [
createUser("方方", "男"),
createUser("圆圆", "女"),
createUser("小新", "男"),
createUser("小葵", "女"),
],
gender: "",
};
},
computed: {
displayUsers() {
const hash = {
male: "男",
female: "女",
};
const { users, gender } = this;
if (gender === "") {
return users;
} else if (typeof gender === "string") {
return users.filter((u) => u.gender === hash[gender]);
} else {
throw new Error("gender 的值是意外的值");
}
},
},
methods: {
setGender(string) {
this.gender = string;
},
},

template: `
<div>
<div>
<button @click="setGender('') ">全部</button>
<button @click="setGender('male')">男</button>
<button @click="setGender('female')">女</button></div>
<ul>
<li v-for="(u,index) in displayUsers" :key="index">{{u.name}} - {{u.gender}}</li>
</ul>

</div>
`,
}).$mount("#app");

官方 计算属性和侦听器

watch - 侦听器

  • 使用watch来响应数据的变化,也就是当数据变化时,执行一个函数。
  • 一般用于异步或者开销较大的操作
  • watch 中的属性 一定是 data 中 已经存在的数据
  • 当需要监听一个对象的改变时,普通的 watch 方法无法监听到对象内部属性的改变,只有 data 中的数据才能够监听到变化,此时就需要 deep 属性对对象进行深度监听

选项:deep

为了发现对象内部值的变化,可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。

1
2
3
4
5
vm.$watch('someObject', callback, {
deep: true
})
vm.someObject.nestedValue = 123
// callback is fired

选项:immediate

在选项参数中指定 immediate: true 将立即以表达式的当前值触发回调:

1
2
3
4
vm.$watch('a', callback, {
immediate: true
})
// 立即以 `a` 的当前值触发回调

注意在带有 immediate 选项时,你不能在第一次回调时取消侦听给定的 property。

1
2
3
4
5
6
7
8
9
// 这会导致报错
var unwatch = vm.$watch(
'value',
function () {
doSomething()
unwatch()
},
{ immediate: true }
)

如果你仍然希望在回调内部调用一个取消侦听的函数,你应该先检查其函数的可用性:

1
2
3
4
5
6
7
8
9
10
var unwatch = vm.$watch(
'value',
function () {
doSomething()
if (unwatch) {
unwatch()
}
},
{ immediate: true }
)

vm.$watch

例1. 撤销-demo

https://codesandbox.io/s/lucid-shamir-cpcw3

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
Vue.config.productionTip = false;

new Vue({
data: {
n: 0,
history: [],
inUndoMode: false,
},
watch: {
n: function (newValue, oldValue) {
console.log(this.inUndoMode);
if (!this.inUndoMode) {
this.history.push({ from: oldValue, to: newValue });
}
},
},
// 不如用 computed 来计算 displayName
template: `
<div>
{{n}}
<hr />
<button @click="add1">+1</button>
<button @click="add2">+2</button>
<button @click="minus1">-1</button>
<button @click="minus2">-2</button>
<hr/>
<button @click="undo">撤销</button>
<hr/>

{{history}}
</div>
`,
methods: {
add1() {
this.n += 1;
},
add2() {
this.n += 2;
},
minus1() {
this.n -= 1;
},
minus2() {
this.n -= 2;
},
undo() {
const last = this.history.pop();
this.inUndoMode = true;
console.log("ha" + this.inUndoMode);
const old = last.from;
this.n = old; // watch n 的函数会异步调用
this.$nextTick(() => {
this.inUndoMode = false;
});
},
},
}).$mount("#app");

例2. 模拟 computed-demo

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
// 引用完整版 Vue,方便讲解
import Vue from "vue/dist/vue.js";

Vue.config.productionTip = false;

new Vue({
data: {
user: {
email: "fangfang@qq.com",
nickname: "方方",
phone: "13812312312"
},
displayName: ""
},
watch: {
"user.email": {
handler: "changed",
immediate: true // 第一次渲染是也触发 watch
},
"user.nickname": {
handler: "changed",
immediate: true // 第一次渲染是也触发 watch
},
"user.phone": {
handler: "changed",
immediate: true // 第一次渲染是也触发 watch
}
},
// 不如用 computed 来计算 displayName
template: `
<div>
{{displayName}}
<button @click="user.nickname=undefined">remove nickname</button>
</div>
`,
methods: {
changed() {
console.log(arguments);
const user = this.user;
this.displayName = user.nickname || user.email || user.phone;
}
}
}).$mount("#app");

directives - 指令

  • 内置指令 v-if / v-for / v-bind / v-on
  • 自定义指令
  • 指令是为了减少重复的 dom 操作

mixin - 混入

  • 重复三次之后的出路
  • 混入 V.S. 全局混入
  • 选项自动合并
  • 混入就是为了减少重复的构造选项

还是使用上一个例子:

1
2
3
4
5
6
7
8
9
10
11
const MyVue = Vue.extend({
data(){ return {name:'', time: undefined} },
created(){
if(!this.name){ console.erroe('no name!') }
this.time = new Date()
},
beforeDestroy(){
const duration = (new Date()) - this.time
console.log(`${this.time} 存活时间 ${duration}`)
}
})

extends - 继承

  • 先了解一下 Vue.extend
  • 你觉得用了 mixin 还是重复
  • 于是你自己写了一个 View, 他继承 Vue
  • 你还可以预先定义其他构造选项
  • 继承就是为了减少重复的构造选项
  • 那为什么不用 ES6 的extends 呢?

provide / inject

  • 爷爷想和孙子讲话怎么办
  • 祖宗想跟他的所有后代讲话怎么办
  • 答案是全局变量,但是全局变量太 low
  • 所以我们需要 局部的全局变量

响应式原理

数据响应式是指,在改变数据的时候,视图也会跟着更新

当数据改变后,Vue会通知到使用该数据的代码。视图渲染中使用了数据,数据改变后,视图也会自动更新。

Vue的响应式原理依赖于 Object.defineProperty, 通过设置对象的 getter/setter 方法来监听数据。

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter.

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。

由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。

对于对象

Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:

1
2
3
4
5
6
7
8
9
10
var vm = new Vue({
data:{
a:1
}
})

// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 是非响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。例如,对于:

1
Vue.set(vm.someObject, 'b', 2)

您还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:

1
this.$set(this.someObject,'b',2)

有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign()_.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。

1
2
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

对于数组

通过 vue 数组的变更方法来更新视图。

官方 深入响应式原理

##vue 版本

Vue 完整版 Vue 非完整版 评价
特点 有 compiler 无 compiler compiler 占 30% 体积
视图 写在 HTML 里或者写在 template 中 写在 render 函数里 用 h 来创建把标签 h 是尤雨溪写好穿给 render 的
cdn 引入 vue.js vue.runtime.js 文件名不同,生成文件后缀为 .min.js
webpack 引入 需要配置 alias 默认使用此版 尤雨溪配置的
@vue/cli 引入 需要额外配置 默认使用此版 尤雨溪、蒋豪群配置的

最佳实践: 总是使用非完整版,然后配合 vue-loader 和 vue 文件

  1. 保证用户体验,用户下载的 JS 文件体积更小,但只支持 h 函数
  2. 保证开发体验,开发者可直接在 vue 文件里写 HTML 标签,而不写 h 函数
  3. 让 loader 去做,vue-loader 会把 vue 文件中的 HTML 转为 h 函数,这样既保证了用户体验,也顾及到了开发体验。

官方文档

template render

完整版 template

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id='app'>
{{n}}
<button @click="add">+ 1</button>
</div>

<script>
new Vue({
el: '#app',
data: {
n: 9
},
methods: {
add(){
this.n += 1
}
}
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template : `
<div id="app">
{{n}}
<button @click="add">+1</button>
</div>`

//
<template>
<div id="#app">
{{n}}
<button @click="add">+1</button>
</div>
</template>

非完整版 render

Snipaste_2021-10-18_00-50-35-1

使用 codesandbox.io 创建 vue项目

  1. 打开 codesandbox
  2. 创建 vue 项目

Snipaste_2021-10-17_20-20-10Snipaste_2021-10-17_20-20-51

Snipaste_2021-10-17_20-21-58
创建成功

导出到本地:

Snipaste_2021-10-17_20-22-26