侦听器
watch侦听器可以监视数据变化。侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为函数名。
<!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>watch侦听器</title> </head> <body> <div id="app"> <input type="text" v-model="username"> </div> <script src="./vue-2.6.12.js"></script> <script> const vm = new Vue({ el: '#app', data: { username:'' }, watch:{ //侦听器本质上是一个函数,要监视哪个数据的变化,就把数据名作为函数名 //新值在前,只要发生了改变,就会调用username这个韩顺 username(newVal,oldVal){ console.log('值发生了改变',newVal,oldVal) } } //简写wtach:{username(newVal,oldVla){}} }) </script> </body> </html>
immediate选项
上述写的方法(函数)格式的侦听器有缺点:
- 无法在刚进入页面的时候自动触发
- 如果侦听的是一个对象,对象中的属性发生了变化,不会触发侦听器。
此时可以使用对象格式的侦听器,
- 因为它可以通过immediate选项,让侦听器自动触发。
- 可以通过deep选项,让侦听器深度监听对象中每个属性的变化。
<!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> </head> <body> <div id="app"> <input type="text" v-model="username"> </div> <script src="./vue-2.6.12.js"></script> <script> const vm = new Vue({ el: '#app', data: { username:'admin' }, watch:{ //定义对象格式的侦听器 username:{ //只要username发生了改变,就触发handler的函数 handler(newVal,oldVal){ console.log('我被触发了') }, //控制侦听器是否自动触发一次,它的默认值是false immediate:true } } }) </script> </body> </html>结果演示
deep选项深度监听
- 可以通过deep选项,让侦听器深度监听对象中每个属性的变化。
<!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>deep深度监听</title> </head> <body> <h1>开发者工具查看</h1> <div id="app"> <input type="text" v-model="info.username"> </div> <script src="./vue-2.6.12.js"></script> <script> const vm = new Vue({ el: '#app', data: { //用户的信息对象 info:{ username:'admin' } }, watch: { //监听的是对象 info:{ handler(newVal){ console.log('发生了改变',newVal.username) }, //开启深度监听,只要对象中任何一个属性变化,都会触发对象的监听器 deep:true }, //如果监听的是对象的子属性的变化,就用下面的写法 'info.username'(newVal){ console.log('发生了改变:',newVal) } } }) </script> </body> </html>结果演示
计算属性computed
计算属性指的是通过一系列运算之后,最终得到的一个属性值,这个动态计算出来的属性值可以被插值表达式或methods方法使用。
优点:
- 提高代码复用
- 只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值
<!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>计算属性</title> <script src="./vue-2.6.12.js"></script> <style> .box { width: 200px; height: 200px; border: 1px solid #ccc; } </style> </head> <body> <div id="app"> <div> <span>R:</span> <input type="text" v-model.number="r"> </div> <div> <span>G:</span> <input type="text" v-model.number="g"> </div> <div> <span>B:</span> <input type="text" v-model.number="b"> </div> <hr> <!-- 呈现颜色的div --> <!-- 在属性身上,v-bind: 是属性绑定 --> <!-- :style 代表动态绑定一个样式对象,它的值是一个 { } 样式对象 --> <!-- 下面的的样式对象中,只包含 backgroundColor 背景颜色 --> <div class="box" :style="{ backgroundColor: rgb }"> {{rgb}} </div> <button @click="show">按钮</button> </div> <script> var vm = new Vue({ el: '#app', data: { // 红色 r: 0, // 绿色 g: 0, // 蓝色 b: 0 }, methods: { show() { console.log(this.rgb) } }, //计算属性定义到computed节点下,并且要定义成 方法格式 computed: { //rgb作为一个计算属性,被定义成了方法格式, //最终要返回一个生成好的rgb(x,x,x)的字符串 // (用的时候把结果当普通属性来用) rgb() { return `rgb(${this.r},${this.g},${this.b})` } } }); </script> </body> </html>结果演示
watch:{ firstname(val){ setTimeout(()=>{ this.fullname=val+this.lastname }) }, lastname(val){ this.fullname=this.firstname+val } } }
computed与watch的区别
- computed能完成的功能,watch都可以完成。
- watch能完成的功能,computed不一定能完成,例如watch可以进行异步操作。
this指向问题
- 所有被Vue管理的函数,最好写成普通函数,这样this的志向才是vm或组件实例对象
- 所有不被vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数等,最好写成箭头函数,这样this的指向才是vm或组件实例对象。
绑定样式
绑定class
最终div会用到divcolor的样式和单项绑定的divcolor2属性的样式,是追加样式,不是覆盖的,适用于动态指定样式。 <div class="divcolor" :class="divcolor2" @click="change"></div> 最终div只会用到divcolor2的样式,是覆盖样式。 <div class="divcolor" class="divcolor2" @click="change"></div>
vue-cli
vue-cli简化了开发者基于webpaxk创建工程化的vue项目的过程。
创建vue项目的步骤
vue create name
- Default ([Vue 2] babel, eslint) 自动创建vue2的项目
- Default (Vue 3) ([Vue 3] babel, eslint) 同上
- Manually select featureds 自定义创建项目(推荐选这个,可以自定义选择需要哪些配置)
使用空格来进行选择与取消
选择less作为预处理器
把给出的文件放到单独的配置文件里,而不是package里
Save this as a preset for future projects? (y/N) 是否把刚才创建的步骤存一个预设
vi ~/.vuerc
{ "useTaobaoRegistry": false, "packageManager": "npm" }
项目目录构成
assets文件夹
- 存放项目中用到的静态资源文件,例如:css样式表、图片资源
compontnts文件夹
- 存放开发者封装的、可复用的组件
main.js
- 是项目的入口文件,整个项目的运行,要先执行main.js
App.vue
- 项目的根组件,用户看到的页面就是它。用来编写待渲染的模版结构
index.html
- 它需要预留一个el区域
main.js
- 它把App.vue渲染到了index.html所预留的区域中
vue项目的运行流程
通过main.js把App.vue渲染到index.html的指定区域(<div id=”app”></div>)中。
简单体验
App.vue
<template> <h1> zzl </h1> </template>
main.js
//导入vue包,得到Vue构造函数 import Vue from 'vue' //导入App.vue根组件,将来可以把App.vue中的模版结构, //渲染到html页面中 import App from './App.vue' Vue.config.productionTip = false //创建Vue的实例对象 new Vue({ //el:'#app', // 把render函数指定的组件:App,渲染到html页面中 //即用render指定的App结构替换掉el:'#app'所在的结构 //render函数中,渲染的是哪个.vue组件,那么这个组件就是根组件 render: h => h(App) }).$mount('#app')//两种指定el的写法
index.html
<!DOCTYPE html> <html lang=""> <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"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <!-- app可以理解为el预留区域 --> <div id="app"></div> <!-- 下面注释的意思就是帮我们new了一个vue的构造函数放在了下面 --> <!-- built files will be auto injected --> </body> </html>
vue组件
组件的三个组成部分
组件就是ul结构的复用,它有以下三个组成部分:
- template->组件的模版结构
- script->组件的javaScript行为
- style->组件的样式
自定义一个组件test.vue
<template> <div class="col"> <h3> zzl----{{username}} </h3> <button @click="xiugainame">修改名字</button> </div> </template> <script> //默认导出,这是固定写法 export default{ //vue组件中的data不能像之前那样指向对象, // vue组件中的data必须是一个函数,其他还是老样子。 data(){ //return出去一个数据对象 return{ username:'wxl' } }, methods:{ xiugainame(){ //在组件中,this就表示当前组件的实例对象(组件的实例) this.username='ll' } }, //当前组件的侦听器 watch:{}, //当前组件的计算属性 commputed:{}, //... } </script> //<style lang='less'> 启用less语法 <style> .col{ color: rgb(153, 51, 51); } </style>
组件之间的父子关系
- 组件被封装好之后,彼此之间是相互独立的,不存在父子/兄弟关系。
- 在使用组件的时候,根据彼此的嵌套关系,形成了父子/兄弟关系。
使用组件的三个步骤
1.使用import语法导入需要的组件
import Left from '@/components/Left.vue' //@相当于webpack里的@,也是配置文件里指定好的目录, //只不过vue已经帮我们自动指定到了src目录
2.使用components节点注册私有子组件
export default{ components:{ Left } }
3.以标签的形式使用刚才注册的组件
<div> <Left><Left> </div>
具体请看App.vue代码
<template> <div class="rootbox"> <h1>App根组件</h1> <hr> <div class="box"> <!-- 渲染left组件和right组件 --> <!-- 3.以标签的形式使用刚才注册的组件 --> <left></left> <right></right> </div> </div> </template> <script> //1.导入需要使用的组件 import left from '@/components/left.vue' import right from '@/components/right.vue' export default{ // data(){} //... //2.使用components节点注册私有子组件 // 在组件App的components节点下,注册了组件left与right, // 则组件left与right只能用在组件App中,不能被用在其他组件中 components:{ 'left': left,//键值相同,可以省略 right } } </script> <style> .rootbox{ background-color: rgb(167, 167, 167); margin: 10px; } .box{ background-color: red; margin: 10px; } </style>
注册全局组件
在vue项目的main.js入口文件中,通过Vue.component()方法,可以注册全局组件,一次注册,到处使用。
//导入需要被全局注册的那个组件 import count from '@/components/count.vue' // biaoqian就是注册后的标签名 Vue.component('biaoqian',count)
注册后可以在任意组件中使用全局组件,比如下面。
<template> <div> <h3> left子组件 </h3> <hr> <!-- 以标签的形式使用刚才注册的组件 --> <biaoqian></biaoqian> </div> </template> <script> </script>
props
props是组件的自定义属性,在封装通用(全局)组件的时候,合理使用它可以提高组件的复用性。
另外props是只读的,需要用一个值来接收。
语法:
在count.vue中的自定义属性
export default{ //props是自定义属性,允许使用者通过自定义属性, //为当前组件指定初始值 //props:['自定义属性A','自定义属性B'], props:['init'] data(){ return{ count:this.init } } }
指定初始值:
在left.vue指定初始值
<!-- 这个传的是字符串 --> <biaoqian init="9"></biaoqian> <!-- 因为v-bind里写的是js语句, 所以此时去掉引号9就变成了数字 --> <biaoqian :init="9"></biaoqian>
props的default默认值
//改成对象的格式 props:{ init:{ //如果外界使用count组件的时候, // 没有传递init属性,则默认值生效,即init=2 default:2 } },
props的type值类型
props:{ init:{ default:2, // 用type属性定义属性的值类型 // 如果传递过来的值不符合此类型,则会在终端报错 type:Number } },
props的required必填项
props:{ init:{ default:2, //必填项校验 // 用这个组件,必须传值,否则报错 requitred:true } },
组件之间的样式冲突
冲突原因
默认情况下,写在.vue组件中的样式会在全局生效,导致组件之间样式冲突的根本原因:
- 单页面程序中,所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的。
- 每个组件中的样式都会影响整个index.html页面中的DOM元素。
使用scoped解决冲突
<style lang="less" scoped> h3{ color: blue; } </style>
在样式中加入scoped后,会自动给每个标签加上属性选择器。但是scoped还会有一个缺点,就是在嵌套的父组件中直接改子组件包含的标签样式,此时在父组件中包含子组件标签的样式不会发生改变,因为在父组件中修改子组件标签的样式,会生成一个包含这个属性的标签选择器,比如h5[data-v-xxx],但是包含这个属性的标签选择器在子组件(源组件)的标签中找不到对应的标签(这个子组件的标签中没有这个属性h5[data-v-xxx])。
使用deep解决在父组件中改造组件的样式
// 不加deep之前:h5[data-v-xxx] //表示包括这个属性的标签选择器 // 加deep之后:[data-v-3c83f0b7] h5 // 表示这个属性选择器的后代选择器h5 /deep/ h5{ color: yellow; }
只要父节点包含这个属性,就会选中这个h5。