1、回顾复习
<!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>Document</title>
</head>
<body>
<div id="app">
</div>
<script src="./vue.js"></script>
<script>
// 资料站:
// 1 XSS攻击: http://qingbob.com/Excess-XSS/
// 2 官网(Vue)
// 3 知乎
// 4 掘金
// 5 思否 https://segmentfault.com/channel/frontend
// 6 简书
// 7 CSDN
// 8 总结:科学上网,善用搜索工具(google) 蓝灯
// 0 安装插件:Settings Sync
// 1 按F1,出现命令窗口
// 2 输入 sync ,选择 下载设置
// 3 到 https://github.com/settings/tokens 中创建自己的 token
// 创建的时候,勾选 gist
// 4 将github生成的 token 值粘贴到文本框中
// 5 再输入 694238a721b9444078617c5ac2100776 (老师vscode的gist id)
// 6 下载完成后,重新打开vscode就可以了
</script>
</body>
</html>
2、虚拟DOM的说明
<!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>Document</title>
</head>
<body>
<div id="app"></div>
<script>
const dv = document.getElementById('app')
console.dir(dv)
let count = 0, str = ''
for (let k in dv) {
count++
str += k + ' '
}
console.log(str, count)
</script>
</body>
</html>
Virtual DOM and Diff
Virtual DOM
-
虚拟 DOM,实际上就是一个 js 对象
Vue 模板渲染(render)和更新(patch)
-
1 第一次渲染创建一棵
虚拟DOM树
(js 对象) -
2 数据发生改变后,生成一棵新的虚拟 DOM 树(js 对象)
-
3 对比新旧两棵虚拟 DOM 树(js 对象),通过
diff算法
找到并记录差异的地方 -
4 只将差异的地方重新渲染到页面中
图例
3、Vue中的组件
<!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>Document</title>
</head>
<body>
<div id="app">
<!-- 局部组件 -->
<!-- <local></local> -->
<child></child>
<!-- 全局组件 -->
<hello></hello>
</div>
<script src="./vue.js"></script>
<script>
// 两种组件:
// 1 全局组件
// 2 局部组件:只能在当前 父组件 的模板中使用,而不能在其他地方使用!!!
// 创建全局组件
Vue.component('hello', {
template: `
<div>
<child></child>
<h1 @click="fn">这是 Hello 组件 => {{ msg }}</h1>
</div>
`,
// 数据
data() {
return {
msg: 'Hello component'
}
},
created() {
console.log('钩子函数执行了')
},
methods: {
fn() {
this.msg = '修改'
}
},
components: {
child: {
template: `
<p>Hello 组件的局部组件</p>
`
}
}
})
const localConfig = {
template: `
<p>这是 Vue 实例中的局部组件</p>
`
}
const vm = new Vue({
el: '#app',
data: {
},
// 通过 components 配置项来创建局部组件
components: {
local: localConfig
}
})
</script>
</body>
</html>
4、组件的说明
<!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>Document</title>
</head>
<body>
<div id="app">
<hello></hello>
</div>
<script src="./vue.js"></script>
<script>
// 组件是一个独立、封闭的个体
// 所以,组件是无法直接使用外部数据的!!!
// 如果想让组件能够使用外部的数据,那就用到 组件通讯 的知识了
// 组件通讯的三种情况:
// 1 父组件将数据传递个子组件:父 -> 子
// 2 子组件将数据传递给父组件:子 -> 父
// 3 兄弟组件(非父子组件)
// Vue.component('hello', {
// template: `
// <div>这是 Hello 组件 -- {{ msg }}</div>
// `
// })
const vm = new Vue({
el: '#app',
data: {
msg: '这是 Vue 实例提供的数据'
},
components: {
hello: {
template: `
<div>这是 Hello 子组件 -- {{ msg }}</div>
`
}
}
})
</script>
</body>
</html>
5、组件通讯 -- 父到子
<!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>Document</title>
</head>
<body>
<!-- 父组件的模板 -->
<div id="app">
<!-- 子组件的模板 -->
<hello ppp="18" gender="male" :parent-msg="msg"></hello>
</div>
<script src="./vue.js"></script>
<script>
// 1 父到子
// 父组件:vm实例
// 子组件:hello组件
// 1 在子组件标签上添加一个属性(ppp)
// 2 在子组件中通过 props 属性,来接受这个数据
// 3 就可以使用接受到的 ppp 数据了
const vm = new Vue({
el: '#app',
data: {
msg: '这是 Vue 实例提供的数据'
},
components: {
hello: {
template: `
<div>这是 Hello 子组件 -- {{ ppp }} | {{ gender }} | {{ parentMsg }}</div>
`,
// 通过 props 属性,来指定该组件接受的数据
// props 的使用方式与 data 相同
props: ['ppp', 'gender', 'parentMsg']
}
}
})
</script>
</body>
</html>
6、组件通讯 --- 子到父
<!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>Document</title>
</head>
<body>
<div id="app">
<h1>{{ age }}</h1>
<!-- 2 给子组件传递一个自定义事件 getmsg ,它的值是 getChildMsg 方法 -->
<child @getmsg="getChildMsg"></child>
</div>
<script src="./vue.js"></script>
<script>
// 子到父:
// 子组件:child组件
// 父组件:vm实例
// 步骤:
// 1 父组件提供一个方法
// 这个方法是子组件调用的,数据通过方法的参数拿到
// 2 将这个方法传递给子组件
// 3 由子组件触发这个方法,将要传递的数据作为方法的参数传递
const vm = new Vue({
el: '#app',
data: {
age: 0
},
// 1 准备一个方法
methods: {
getChildMsg(data) {
console.log('接受到子组件传递过来的数据为:', data)
this.age = data
}
},
components: {
child: {
template: `
<div>
<button @click="fn">传递数据给父组件</button>
</div>
`,
methods: {
fn() {
// 3 触发父组件中传递过来的方法
this.$emit('getmsg', 19)
}
}
}
}
})
</script>
</body>
</html>
SPA 以及 单页面应用程序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
div {
height: 500px;
width: 100%;
background-color: hotpink;
}
</style>
</head>
<body>
<ul>
<li>
<a href="#/find">发现音乐</a>
</li>
<li>
<a href="#/my">我的音乐</a>
</li>
<li>
<a href="#/friend">朋友</a>
</li>
</ul>
<div id="content">
<!-- 这是内容区域 -->
</div>
<script src="./node_modules/axios/dist/axios.js"></script>
<script type="text/javascript">
// a标签锚点值的作用:页面内部跳转
// 如何实现单页面应用程序???
//
// 1 ajax
// 2 hash 哈希值(a标签的锚点值)
// 3 hashchange 事件:监视 hash 值的变化
hashChangeHanlder()
window.addEventListener('hashchange', hashChangeHanlder)
function hashChangeHanlder() {
// console.log('URL中的哈希值改变了', location.hash)
// 获取哈希值
const hash = location.hash
switch (hash) {
case '#/my':
axios
.get('./my.json')
.then(res => {
// console.log(res.data)
document.getElementById('content').innerText = res.data.content
})
break;
case '#/friend':
axios
.get('./friend.json')
.then(res => {
// console.log(res.data)
document.getElementById('content').innerText = res.data.content
})
break;
default:
axios
.get('./find.json')
.then(res => {
// console.log(res.data)
document.getElementById('content').innerText = res.data.content
})
break;
}
}
</script>
</body>
</html>
7、总结
<!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>Document</title>
</head>
<body>
<div id="app">
<child :msg="msg" @get-child-msg="getChildMsg"></child>
</div>
<script src="./vue.js"></script>
<script>
// 父到子: props
// 子到父:
// 1 父组件提供一个方法
// 2 将这个方法传递给子组件
// 3 由子组件触发这个方法,将数据作为参数传递
const vm = new Vue({
el: '#app',
data: {
msg: '父组件'
},
// 1 准备一个方法
methods: {
getChildMsg(data) {
console.log('接受到子组件传递过来的数据为:', data)
}
},
components: {
child: {
template: `
<div>
<div>子组件,接受到父组件中传递过来的数据为:{{ msg }}</div>
<button @click="fn">给父组件传递数据</button>
</div>
`,
props: ['msg'],
// 进入页面,就将数据传递给父组件
created() {
this.$emit('get-child-msg', { color: 'red' })
},
methods: {
fn() {
this.$emit('get-child-msg', ['red', 'pink'])
}
}
}
}
})
</script>
</body>
</html>
8、todomvc -app组件通讯 - 1 抽离结构
9、todomvc - app 组件通讯
10、props是 只读的
<!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>Document</title>
</head>
<body>
<!-- 父组件的模板 -->
<div id="app">
<!-- 子组件的模板 -->
<hello :parentmsg="msg"></hello>
</div>
<script src="./vue.js"></script>
<script>
// props 是只读的,不能直接修改 props 中的数据
// 如果要修改数据的话,而应该使用 data 或者 computed 计算属性来代替
const vm = new Vue({
el: '#app',
data: {
msg: '这是 Vue 实例提供的数据'
},
components: {
hello: {
template: `
<div>
<div>这是 Hello 子组件 -- {{ mymsg }}</div>
<button @click="fn">修改props</button>
</div>
`,
props: ['parentmsg'],
data() {
return {
mymsg: this.parentmsg
}
},
methods: {
fn() {
console.log(this.mymsg)
this.mymsg = '修改'
}
}
}
}
})
</script>
</body>
</html>
11、JS中的引用类型
<!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>Document</title>
</head>
<body>
<script>
// 父组件的数据:
const obj = [
{ id: 1, name: '吃饭', completed: false }
]
// 传递给子组件,obj1 就是子组件中的数据
const obj1 = obj
// 只是改变了对象中的某个属性,而没有改变 obj 的指向
obj1.completed = true
// 改变obj的指向:
// obj = []
</script>
</body>
</html>
12、Vue中的单向数据流
<!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>Document</title>
</head>
<body>
<div id="app">
<button @click="changeMsg">修改父组件中的数据</button>
<child :msg="msg"></child>
</div>
<script src="./vue.js"></script>
<script>
// 两个组件之间的数据流动:
//
// 单项数据流: 父组件中的数据可以通过props流动到子组件中,并且当父组件中的数据
// 发生改变的时候,子组件会自动接收到这个修改后的数据,
// 并且更新页面中的内容。这就是 Vue 中的单项数据流
// 所以,数据一般是由父组件提供的,当父组件中的数据发生了改变,子组件就会自动接收到
// 这个数据的变化,从而更新子组件
// 双向数据绑定: data中的数据(Model) 与 视图中的表单元素(View)
const vm = new Vue({
el: '#app',
data: {
msg: 'parent'
},
methods: {
changeMsg() {
this.msg = 'change'
}
},
components: {
child: {
template: `
<p>接收到父组件中的数据为:{{ msg }}</p>
`,
props: ['msg']
}
}
})
</script>
</body>
</html>
13、组件通讯 - 兄弟组件
<!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>Document</title>
</head>
<body>
<div id="app">
<jack></jack>
<rose></rose>
</div>
<script src="./vue.js"></script>
<script>
// 兄弟组件之间的通讯:
// 两个组件: jack和rose
// jack 要对 rose 说明: i love u
// 步骤:
// 1 创建一个空Vue实例,也就是一个 bus ( 事件总线 )
// 2 一个组件注册事件( bus.$on(事件名称, () => {}) )
// 3 另一个组件触发事件( bus.$emit(事件名称, 数据) )
// 注意:一定要是同一个 bus!!!
// 创建一个bus
const bus = new Vue()
Vue.component('jack', {
template: `
<div>
我是jack,我要对 rose 说:
<button @click="fn">告诉rose:i love u</button>
</div>
`,
methods: {
fn() {
// 触发事件
// 第一个参数表示:事件名称,需要与 注册事件 名称一致
// 第二个参数表示:要传递的数据
bus.$emit('love', 'i love u')
}
}
})
Vue.component('rose', {
template: `
<div>
我是rose,jack 对我说:{{ msg }}
</div>
`,
data() {
return {
msg: ''
}
},
created() {
// 注册事件
// 第一个参数表示:事件名称
// 第二个参数表示:事件处理程序
bus.$on('love', (data) => {
console.log('rose 接收到 jack 传递过来的数据:', data)
this.msg = data
})
}
})
const vm = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</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>Document</title>
<style>
/* 容器 */
.container {
width: 150px;
}
/* 灯 */
.light {
width: 100px;
height: 100px;
border-radius: 50%;
text-align: center;
line-height: 100px;
margin: 0 auto;
color: #fff;
background-color: #000;
}
/* 开灯 */
.turn-on {
background-color: #ff0;
color: #000;
}
/* 灯座 */
.bottom {
width: 150px;
height: 50px;
margin-top: 10px;
line-height: 50px;
text-align: center;
color: #fff;
background-color: #000;
}
</style>
</head>
<body>
<div id="app" class="container">
<light-head></light-head>
<light-bottom></light-bottom>
</div>
<script src="./vue.js"></script>
<script>
const bus = new Vue()
Vue.component('light-head', {
template: `
<div class="light" :class="{ 'turn-on': isOn }">
{{ msg }}
</div>
`,
data() {
return {
msg: '我是一盏灯',
isOn: false
}
},
created() {
bus.$on('来电了', (state) => {
this.isOn = state
})
}
})
Vue.component('light-bottom', {
template: `
<div class="bottom">
<button @click="on">开</button>
<button @click="off">关</button>
</div>
`,
methods: {
on() {
bus.$emit('来电了', true)
},
off() {
bus.$emit('来电了', false)
}
}
})
var vm = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
14、总结
<!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>Document</title>
</head>
<body>
<script>
// todomvc 案例,有点复杂
// 但是,至少要完成:
// 1 抽离三个组件
// 2 渲染列表数据( 父 -> 子 )
// 3 添加任务 ( 子 -> 父 )
</script>
</body>
</html>