大纲
基础知识→案例实践→TodoList→Vue-cli→TodoList
VUE基础知识点
使用VUE不会有任何DOM的操作,着重于数据的编写。vue是数据驱动的框架。
VUE引入 VUE挂载点、模板与实例绑定方式
- 引入:复制vue.js脚本,放到vue项目下命名为vue.js。
- 挂载点:div标签即为vue实例的挂载点,vue只会处理挂载点内的内容。
- 模板:挂载点内部的内容叫做模板内容,模板不仅可以写在挂载点内,也可以放在vue实例里的template属性里。
- 实例:实例里指定挂载点和模板,vue会自动根据你的模板和数据生成最终要展示的内容。
VUE实例中的数据、事件与方法
模板内容+数据
{{}}
这种语法叫做插值表达式,<h1>{{number}}</h1>
<h1 v-text="number"></h1>
会转义<h1 v-html="number"></h1>
不会转义
事件的绑定
在想要改变的标签上,要写一个绑定事件。 事件写在VUE实例的methods对象里面。
<body>
<div id="root">
<div v-on:click="handleClick">{{content}}</div>
<!-- 给标签绑定一个click事件-->
</div>
<script>
new Vue({ //新建一个VUE实例
el:"#root",//挂载点 指定让VUE接管页面上的哪一个元素
data: {//数据放到data里
msg:"hello, world!",
content:"hello"
},
methods: {
handleClick:function(){
this.content="world"//this.content指的是VUE实例下的data里的vue变量
}
},
})
</script>
</body>
事件绑定的简写v-on:click
→@click
VUE中的属性绑定和双向绑定语法
在VUE里要想改变数据的显示,不用去改变DOM,直接去改变数据。
属性的绑定
- 让标签里的属性等于实例里数据的某个值:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>属性绑定和双向绑定语法</title> <script src="./vue.js"></script> </head> <body> <div id="root"> <div v-bind:title="'dell li '+ title">hello world</div> </div> <script> new Vue({ el:"#root", data:{ title:"this is hello world" } }) </script> </body>
属性绑定的简写v-bind:title
→:bind
数据的双向绑定
数据能决定页面的显示,那么如何让页面上的操作去修改数据呢?
- 学习一个input框和div双向绑定的实例。
v-model
。<body> <div id="root"> <div :title="'dell li '+ title">hello world</div> <input v-model="content"/> <div>{{content}}</div> </div> <script> new Vue({ el:"#root", data:{ title:"this is hello world", content:"this is content" } }) </script> </body>
Vue 中的计算属性 computed 和和侦听器 watch
计算属性
什么是计算属性?
根据其他属性计算出来的新的结果。
特点:计算属性自带缓存机制,语法较简单。
例:
-
fullName
这个属性是由firstName和lastName属性计算得到的,需要计算属性computed。(和React中的reselect库特别像)<body> <div id="root"> 姓:<input v-model="firstName"> 名:<input v-model="lastName"> <div>{{fullName}}</div> </div> <script> new Vue({ el: "#root", data:{ firstName:'', lastName:'' }, computed:{ fullName: function(){//fullNAme是一个计算属性 它的值是一个函数 return this.firstName+' '+this.lastName } } }) </script>
-
计算属性是内置缓存的,当fullName依赖的值firstName和lastName没变的时候,fullName会使用上一次的缓存值,当其中一个属性发生改变的时候,fullName会重新计算。
-
利用方法methods也可以实现上述功能。但不如计算属性有效,没有内置缓存机制,性能没有计算属性高。
<div>{{fullName()}}</div>
methods:{ fullName: function(){ return this.firstName+' '+this.lastName }
-
侦听器也可以实现这个功能,侦听first和lastname的变化,加一个冗余的data:fullName ,性能还可以,因为也有缓存的功能。但是watch的语法复杂了很多,所以一个功能优先使用computed。
侦听器
功能:监测某个数据的变化,一旦某个数据发生变化,就在侦听器里去做自己的业务逻辑。
<body>
<div id="root">
姓:<input v-model="firstName">
名:<input v-model="lastName">
<div>{{fullName}}</div>
<div>{{count}}</div>
</div>
<script>
new Vue({
el: "#root",
data:{
firstName:'',
lastName:'',
count:0
},
computed:{
fullName: function(){//fullNAme是一个计算属性
return this.firstName+' '+this.lastName
}
},
watch:{//侦听器
fullName:function(){
this.count++
}
}
})
</script>
</body>
Vue中常见的三个指令——v-if、v-show、v-for
v-if 条件渲染
-
v-if
后跟的表达式的返回值,决定了这个标签是否真正地被挂载到页面之上。body> <div id="root"> <div v-if="show">hello world</div> <!-- v-if的值是false的时候div里的东西就不存在 --> <button @click="handleClick">toggle</button> </div> <script> new Vue({ el:"#root", data:{ show:false }, methods: { handleClick:function(){ this.show=!this.show; //每次点一次按钮 show都取反一次 } }, }) </script> </body>
-
v-if还能和else紧贴着一起,当show值改变成true的时候,会显示Hello world!
-
v-if v-else-if v-else
-
Vue在重新渲染页面的时候,会尽量地复用页面上已经存在的DOM。
当你打开网页时,显示的是邮箱名:input,你在input中输入自己的邮箱名,然后在调试工具里把show改成true,你会发现邮箱名变成了用户名,但是input还是那个input,也没有被清空。- 解决方法:要有个key值
- 解决方法:要有个key值
v-show
- 已经渲染到页面上了。只不过加了一个display:none;的形式。比v-if性能更高一些。
<div id="root"> <div v-show="show">hello world</div> <!-- v-show的值是false的时候div加一个属性 --> <button @click="handleClick">toggle</button> </div>
v-for
列表循环
-
把数据中列表的数据循环展示到一个ul中,v-for使用时,加一个key属性,提升每一项渲染的效率,要求每一个值都不同。
<div id="root"> <ul> <!-- 遍历list这个数据项 --> <li v-for="item of list" :key="item">{{item}}</li> </ul> </div> <script> new Vue({ el:"#root", data:{ list:[1,2,3] } }) </script>
-
key 的作用
详解v-for 中 key 的作用是什么?如果不添加 key 程序会不会报错?为了搞清楚这个问题,我们需要先了解什么是虚拟 DOM 以及 diff 算法。
-
虚拟 DOM
虚拟 DOM 简称 VNode ,是一棵以 JavaScript 对象作为基础的树, 是对真实 DOM 的抽象。虚拟 DOM 经过一系列转换可以变成真实 DOM 并渲染到页面上。我们可以用虚拟 DOM 来描述一个简单的 vue 组件,如下所示:
<template> <span class="demo" v-show="isShow"> This is a span. </span> </template>
对应的 VNode:
{ tag: 'span', data: { /* 指令集合数组 */ directives: [ { /* v-show指令 */ rawName: 'v-show', expression: 'isShow', name: 'show', value: true } ], /* 静态class */ staticClass: 'demo' }, text: undefined, children: [ /* 子节点是一个文本VNode节点 */ { tag: undefined, data: undefined, text: 'This is a span.', children: undefined } ] }
-
diff 算法
假设现在我们已经有一棵 VNode 树,现在又有一棵 VNode 新树,现在我们需要把最新的这棵树更新上去要怎么操作呢? 最简单粗暴的方式就是直接拿最新的树把之前的树替换掉,然后页面重新渲染即可,这样确实可以解决问题,但是肯定不是最佳解决方案。我们肯定是想可不可以只更新那些变化的地方,那些没有变化的元素不更新,这样不就更高效吗? 要想实现这种高效的更新,就需要我们在两棵树中找出变化的地方从而进行更新。而 diff 算法就是专门来干这件事情的一种算法。 接下来我们再来说说
v-for
中的 key。不使用 key 的一段代码:<template> <ul> <li v-for="v in arr">{{v}}</li> </ul> </template> <script> export default { data() { arr: ["A", "B", "C"]; }, create() { setTimeout(() => { this.arr.splice(1, 0, "D"); // [A,D,B,C] }, 2000); }, }; </script>
上面的代码把一个数组
[A,B,C]
变成[A,D,B,C]
同时页面也更新,一共做了两次更新一次插入操作。(diff过程在下边)
有 key 的情况:<template> <ul> <li v-for="v in arr" :key="v">{{v}}</li> </ul> </template> <script> export default { data() { arr: ["A", "B", "C"]; }, create() { setTimeout(() => { this.arr.splice(1, 0, "D"); // [A,D,B,C] }, 2000); }, }; </script>
diff 过程如下所示,在有 key 的情况下,只是执行了一次插入操作。
v-for
中key
的作用就是让每个被循环元素有一个唯一的身份标识,这样 Vue 就可以更加精准的追踪到每个元素,从而更加高效的更新页面。当然如果没有key
程序也不会报错,只不过此时的程序变得非常的“笨”。
-
-
列表渲染进阶:
key
参考TodoList
把index作为key值,频繁操作DOM元素相对应的数据的时候,还是费性能的,无法充分复用DOM节点。那么用什么呢? 后端返给前端数据的时候,一般会携带一个唯一数据条目的标识符id
和数据内容:使用
:key=item.id
就可以了。 -
通过数组变异方法改变数组:
push
pop
shift
unshift
splice
sort
reverse
去改变数组!! -
通过改变数据的引用地址去改变数组:
-
template模板占位符可以用来包裹一些元素,但是在循环的过程中并不会真正被渲染到页面上
-
set方法
对象循环
set方法改变对象元素
Vue中的组件
组件与实例的关系
一个组件就是一个实例,任何VUE项目都是由千千万万个实例组成。在TodoList案例中,把li标签作为组件,组件又是实例。根组件若你不定义模板,则要去他的挂载点下找模板。 父组件到子组件传值,是通过属性的形式传递的。
TodoList
TodoList的个功能
- 实现输入inputValue显示到ul的li里面,需要监听button的点击事件,点击的时候,触发并把输入的push到数据的list里,模板要接受list,写一个v-for
- VUE不是操作DOM而是操作数据
- 需求:点击li删除该li
- 步骤:
- 1:写最外层父组件(实例),它的模板在挂载点下。
- 2:写最外层实例的模板
- 3:写子组件
- 4:实现父子组件之间的传值,父组件通过属性传值给子组件,子组件通过向外触发一个事件传给父组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TodoList</title>
<script src="./vue.js"></script>
</head>
<body>
<!-- 2. 最外层实例的模板 使用组件-->
<div id="root">
<div>
<input v-model="inputValue" />
<button @click="handleClick">Submit</button>
</div>
<ul>
<!-- <li v-for="(item,index) of list" :key="index">
{{item}}
</li> -->
<!-- <li>2</li> -->
<todo-item
v-for="(item, index) of list"
:key="index"
:content="item"
:index="index"
@delete="handleDelete"
> <!--传参 用content属性 把item传递给todo-item组件-->
</todo-item>
</ul>
</div>
<script>
// 3.定义一个全局组件 不需要声明注册
Vue.component('todo-item', {
props:['content','index'],//接受从外部传递过来的一个名字叫content的属性
template:'<li @click="handleClick">{{content}}{{index}}</li>',
methods: {
handleClick:function(){
this.$emit('delete',this.index)//向外触发(发布)一个事件,告诉父组件要删除我自己 触发一个自定义事件delete 传递一个index值
}
}
})
//如果定义局部组件的话,需要做一个组件的注册(声明)如下行
// var TodoItem = {
// template:'<li>item</li>'
// }
//1.最外层父组件:VUE实例 它有自己的模板是挂载点下的内容
new Vue({
el:"#root",
// components:{
// 'todo-item':TodoItem//如果定义的是局部组件,需要做一个声明(注册)
// },
data:{
inputValue:'hello',
list:[]
},
methods: {
handleClick:function(){
this.list.push(this.inputValue)
this.inputValue=''
},
handleDelete:function(index){
this.list.splice(index,1)
}
}
})
</script>
</body>
</html>
Vue-cli的简介与使用
快速构建一个VUE项目,同时自带了webpack各种配置,迅速上手工程级别VUE项目的开发。
node -v
npm -v
vue install --gloabl vue-cli
vue init webpack my-project
cd my-project
npm run dev
- build:webpack配置文件
- config:针对开发环境和线上环境的配置文件
- node_moudles 项目依赖
- src 源码 主要编写
- main.js 整个项目的入口文件
- static 静态资源
- babel编译 浏览器配置 eslint检测规则。
- index.html整个网页最外层的html文件。
App.vue单文件组件 :一个.vue文件里包含了一个组件里必须的所有内容
- template 模板
- script 逻辑
- css 样式
以前会把html和各种逻辑耦合在一起进行编码,用了vue-cli提供了一种单文件组件编码的形式,一个vue问价里就是一个完整的组件。
全局样式和局部样式
scoped