Vue学习笔记
Vue概念:
为什么学习Vue:
更少的时间,干更多的活,开发网站速度快
原生js ---------------jQuery------------Vue
案例:把数组数据–循环渲染到页面上
原生js:
<body><ul id="myul"></ul></body><script>let arr = ['aa', 'bb', 'cc', 'dd', 'ee']let myUl = document.getElementById('myul')for (let i = 0; i < arr.length; i++) {let thenLi = document.createElement('li')thenLi.innerHTML = arr[i]console.log(thenLi)myUl.appendChild(thenLi)}</script>
vue:
<body><div id="app"><ul><li v-for="item in arr">{{item}}</li></ul></div></body><script>new Vue({el: '#app',data() {return {arr: ['aa', 'bb', 'cc', 'dd', 'ee'],}},})</script>
注意:vue写起来很爽,vue的底层还是原生js
vue开发更加的效率和简洁,易于维护,快快快,现在很多的项目都是用vue开发的
vue是什么:
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架
什么是渐进式:
渐进式:逐渐进步,想用什么就用什么,不用全部都用
vue从基础开始学习,循序渐进向前学习
声明式渲染—组件系统—路由—vuex
什么是库和框架:
库: 封装的属性和方法(jquery)
框架:拥有自己的规则和元素,比库强大的多(vue.js)
vue如何学:
- 每天的知识点做到了如指掌,花时间去记住结论和公式(语法)
- 记住vue指令的作用,基础语法,整理笔记
- 课上的例子,练习,案例,作业,项目反复的练习
- 学会查找问题和解决问题(弄个报错总结文档.md)
vue是渐进式的框架,有自己的规则,我们要记住语法,特点和作用,反复练习,多总结
vue的学习:
传统的开发模式:
基于html/css/js文件开发vue:
<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>Document</title><!-- 第一步:引入vue.js --><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head><body><!-- 第二步:创建vue容器 vue的语法代码只能写在下面的div中 --><div id="app"></div></body><!-- 第三步:创建vue实例 --><script>// console.log(Vue)var vm = new Vue({el: '#app', //提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标data: {//Vue 实例的数据对象msg: 'hello word',flag: false,arr: [11, 2, 3, 4, 56, 6],num: 2,},})</script>
</html>
工程化的开发模式:
webpack环境中开发vue,最推荐,企业常用的方式
插值表达式:
目的:在dom标签中,直接插入内容
又叫:声明式渲染/文本插值
语法: {{表达式}}
<div id="app"><!-- 插值表达式中不能写循环语法,不能写条件语句 --><p>{{num}}</p><p>{{num+1}}</p><p>{{34}}</p><p>{{flag}}</p><p>{{flag?'hello':"nihao"}}</p><p>{{str}}</p><p>{{str +"vue是必须要学会的"}}</p><p>{{'hello world'}}</p><p>{{arr}}</p><p>{{arr.reverse()}}</p><p>{{obj}}</p><p>{{obj.name}}</p></div></body><script>var vm = new Vue({el: '#app',data: {num: 3,flag: false,str: '大家一定要好好学习vue',arr: [11, 22, 333, 44],obj: {name: 'zs',age: 12,},},})</script>
总结:dom中插值表达式赋值,vue的变量必须在data中声明
vue基础——MVVM设计模式:
转变思想,用数据驱动视图改变; 操作dom的事,vue源码干了
设计模式:一套被反复使用,多数人知晓的,经过分类编目的,代码设计经验的总结
MVVM:
一种软件架构模式,决定了写代码的思想和层次
M : model 数据模型 (data中定义)
V :view视图 (html页面)
VM:ViewModel 视图模型(vue.js源码)
MVVM通过数据双向绑定让数据自动双向同步,不再需要操作DOM
V (修改视图)-----> M(数据自动同步)
M(修改数据)------>V(视图自动同步)
总结:vue源码采用MVVM设计模式思想,大大减少了DOM操作,提高开发效率
Vue指令:
指令 (Directives) 是带有
v-
前缀的特殊 attribute每个指令,都有独立的作用
v-bind:
给标签属性设置变量的值
语法: v-bind:属性名=“vue变量”
简写: :属性名=“vue变量”
<body><div id="app"><!-- v-bind 属性绑定: 把vue变量的值,赋予给dom属性上,影响标签的显示效果 --><a v-bind:href="url">我是a标记</a><a :href="url">我是a标记</a><hr /><img v-bind:src="imgurl" alt="" /><img :src="imgurl" alt="" /></div></body><script>var vm = new Vue({el: '#app',data: {url: 'http://www.baidu.com',imgurl: 'https://img01.yzcdn.cn/vant/ipad.jpeg',},})</script>
v-on:
给标签定义事件
语法:
- v-on:事件名=“要执行的少量代码”
- v-on:事件名=“methods中的函数”
- v-on:事件名=“methods中的函数(实参)”
- 简写:@事件名=“methods中的函数”
<body><div id="app"><!-- v-on 事件绑定 --><p>要购买的商品的数量{{count}}</p><button v-on:click="count=count+1">增加1</button><button v-on:click="addFn">增加1</button><button v-on:click="addCount(5)">一次增5</button><button @click="subFn">减1</button></div></body><script>var vm = new Vue({el: '#app',data: {count: 2,},methods: {//专门用来定义函数(方法)的addFn() {//this代表的是当前的vue实例对象console.log(this)this.count++},addCount(num) {this.count += num //},subFn() {this.count--},},})</script>
v-on事件对象:
vue处理函数中,拿到事件对象
语法:
- 无传参: 通过形参直接接收
- 传参: 通过$event指定事件对象传给事件处理函数
<body><div id="app"><a @click="fn" href="http://www.baidu.com">阻止百度</a><hr /><a @click="eveFn(10,$event)" href="http://www.baidu.com">阻止跳转到百度</a></div></body><script>var vm = new Vue({el: '#app',data: {},methods: {fn(e) {// 阻止默认行为e.preventDefault()},eveFn(num, e) {e.preventDefault()},},})</script>
v-on修饰符:
在事件后面.修饰符名,给事件带来更强大的功能
修饰符是由点开头的指令后缀来表示的
语法:
- @事件名.修饰符 =“methods函数”
- .stop 阻止事件冒泡
- .prevent 阻止默认行为
- .once 程序运行期间,只触发一次事件处理函数
- .self 只当在 event.target 是当前元素自身时触发处理函数
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><style>.main {width: 400px;height: 200px;background-color: yellow;}</style></head><body><div id="app"><div @click="fn1"><button @click.stop="btnFn">stop阻止事件冒泡</button><a @click.prevent.stop="aFn" href="http://www.baidu.com">prevent阻止默认行为</a><button @click.once="onceFn">once触发一次</button></div><!--.self 只当在 event.target 是当前元素自身时触发处理函数 --><div @click.self="fn1" class="main"><button @click="btnFn">stop阻止事件冒泡</button><a @click.prevent="aFn" href="http://www.baidu.com">prevent阻止默认行为</a><button @click.once="onceFn">once触发一次</button></div></div></body><script>var vm = new Vue({el: '#app',data: {},methods: {fn1() {alert('fn1')},btnFn() {alert('btnFn')},aFn() {alert('aFn')},onceFn() {alert('onceFn')},},})</script>
v-on按键修饰符:
给按键事件添加修饰符
语法:
- @keyup.enter 监听回车按键
- @keyup.esc 监听返回按键
<body><div id="app"><input type="text" @keyup.enter="enterFn" /><br /><input type="text" @keyup.esc="escFn" /></div></body><script>var vm = new Vue({el: '#app',data: {},methods: {enterFn() {alert('enterFn')},escFn() {alert('escFn')},},})</script>
v-text/v-html:
更新DOM对象的einnerText和innerHTML
语法:
- v-text=“vue数据变量”
- v-html=“vue数据变量"
<body><div id="app"><p>{{str1}}</p><p>{{str2}}</p><hr /><p v-text="str1"></p><p v-text="str2"></p><hr /><p v-html="str1"></p><p v-html="str2"></p><!-- v-text把值当做普通字符串显示 v-html 把值当做html解析--></div></body><script>var vm = new Vue({el: '#app',data: {str1: '<span>我是一个span标签</span>',str2: 'hello',},methods: {},})</script>
v-model:
主要用于表单元素,把value属性和vue数据变量,双向绑定到一起
语法:
v-model=‘vue数据变量’
- 双向数据绑定(vue内部采用MVVM设计模式)
- 数据变化–>视图自动同步
- 视图变化–>数据自动同步
<body><div id="app"><!-- v-model:实现vue变量和表单标签value属性,双向绑定的指令 --><div><span>用户名</span><input type="text" v-model="username" /></div><div><span>密码</span><input type="password" v-model="pass" /></div><div><span> 来自于</span><!-- 下拉菜单要绑定在select上 --><select v-model="city"><option value="北京市">北京</option><option value="南京市">南京</option><option value="天津市">天津</option></select></div><div><span>性别</span><input type="radio" value="男" v-model="gender" />男<input type="radio" value="女" v-model="gender" />女</div><!--
遇到复选框 v-model的变量值
数组 关联的是复选框的value值
非数组 关联的是复选框的checked属性--><div><span>爱好</span><input type="checkbox" v-model="hobby" value="抽烟" />抽烟<input type="checkbox" v-model="hobby" value="喝酒" />喝酒<input type="checkbox" v-model="hobby" value="打游戏" />打游戏</div><div><span>全选</span><input type="checkbox" v-model="checkall" /></div><div><span>自我评价</span><textarea v-model="content"></textarea></div><hr /><div>{{username}}</div><div>{{pass}}</div><div>{{city}}</div><div>{{gender}}</div><div>{{hobby}}</div><div>{{checkall}}</div><div>{{content}}</div></div></body><script>var vm = new Vue({el: '#app',data: {username: '用户名',pass: '',city: '南京市',gender: '男',hobby: ['打游戏'],checkall: true,content: '',},methods: {},})</script>
v-model修饰符:
让v-model拥有更强大的功能
语法:
-
v-model.修饰符=“vue数据变量”
- .number 以parseFloat转成数字类型
- .trim 去除首尾空白字符
- .lazy change时触发而非input 不会每次输入都执行事件
<body><div id="app"><div><span>年龄</span><input type="text" v-model.number="age" /></div><div><span>人生格言</span><input type="text" v-model.trim="motto" /></div><div><span>自我介绍</span><textarea v-model.lazy="content"></textarea></div>{{content}}</div></body><script>var vm = new Vue({el: '#app',data: {age: 0,motto: '',content: '',},methods: {},})</script>
v-show和v-if:
控制标签的显示隐藏
语法:
- v-show=“vue变量”
- v-if=“vue变量”
原理:
- v-show用的是display:none控制标签的显示隐藏 (频繁切换)
- v-if 用的是插入和删除节点来控制标签的显示隐藏
- v-else
<body><div id="app"><h1 v-show="isOk">v-show的盒子</h1><h1 v-if="isOk">v-if的盒子</h1><div><!-- v-if和v-else的两个标签必须紧挨着 --><p v-if="age>15">我成年了</p><p v-else>未成年</p></div></div></body><script>var vm = new Vue({el: '#app',data: {isOk: true,age: 15,},methods: {},})</script>
</html>
v-for:
列表渲染,所在标签结构,按照数据数量 循环生成
语法:
- v-for=“(值,索引) in 目标结构”
- v-for=“值 in 目标结构”
目标结构
- 可以遍历数组/对象/数字/字符串
<body><div id="app"><ul><li v-for="(item,index) in arr">{{index}} ---{{item}}</li></ul><hr /><ul><li v-for="obj in stuArr">{{obj.name}}---{{obj.sex}}---{{obj.hobby}}</li></ul><hr /><div v-for="(value,key) in tObj">{{key}}---{{value}}</div></div></body><script>var vm = new Vue({el: '#app',data: {arr: ['aaa', 'bbb', 'ccc', 'ddd'],stuArr: [{id: 1001,name: '孙悟空',sex: '男',hobby: '唱歌',},{id: 1002,name: '猪八戒',sex: '男',hobby: '背媳妇',},],tObj: {name: '小妞',age: 12,class: '1班',},},methods: {},})</script>
</html>
vue数据监听-key的作用:
1、数组翻转 2、数组截取 3、更新值
-
数组变更方法,就会导致v-for更新,页面更新
(push,pop,shift,unshift,splice,sort,reverse)
-
数组非变更方法,返回新数组,不会导致v-for更新,可采用覆盖数组 或this.$set
(filter,concat,slice)
<body><div id="app"><ul><li v-for="(val,index) in arr" :key="index">{{val}}</li></ul><button @click="revBtn">数组翻转</button><button @click="sliceFn">截取前3个</button><button @click="updateFn">更新第一个元素值</button></div></body><script>var vm = new Vue({el: '#app',data: {arr: [4, 3, 2, 1, 4, 5, 7],},methods: {revBtn() {// 数组翻转 可以让v-for更新this.arr.reverse()// console.log(this.arr)},sliceFn() {// 数组slice 不会造成v-for更新// slice不会改变原数组// this.arr.slice(0, 3)// console.log(this.arr)// 解决v-for更新,覆盖原始数组let newArr = this.arr.slice(0, 3)this.arr = newArr},updateFn() {// 更新某个值的时候 v-for是监测不到的this.arr[0] = 1999// 解决 this.$set// 参数1 更新目标结构(对象/数组)// 参数2 更新位置// 参数3 更新值this.$set(this.arr, 0, 1999)},},})</script>
v-for如何更新DOM呢:
真实DOM:
document对象上,渲染到浏览器上显示的标签
虚拟DOM:
本质是保存节点信息 ,属性和内容的一个js对象
内存中虚拟DOM比较:
内存中比较变化的部分,然后给真实DOM打补丁(更新)
虚拟DOM好处:
提高DOM更新的性能,不频繁操作真实的DOM,在内存中找到变化的部分,更新真实的DOM(打补丁)
问题:
- 新的虚拟DOM根元素,或者属性变化,如何更新
- 具体如何比较新旧虚拟DOM
diff算法:
diff算法是通过同级比较 来比较新旧虚拟dom
根元素变化 删除重新建立整个dom树
根元素未变 属性改变 DOM复用 只更新属性
同级比较,根元素变化-整个dom树删除重建:
同级比较,根元素不变-属性改变 更新属性:
标签内子元素/内容改变:
无key:
从第二个往后更新内容–性能不高
有key,值为索引:
有key属性,基于key的来比较新旧虚拟dom。移除key不存在元素
有key,key值唯一:
vue基础-动态class:
使用v-bind给标签class设置动态的值
语法:
- :class=“{类名:布尔值}”
<style>.red_str {color: red;}</style></head><body><div id="app"><!-- :class="{类名:布尔值}"场景: vue 变量控制标签是否应该有类名--><p :class="{'red_str':bool}">动态class</p></div></body><script>var vm = new Vue({el: '#app',data: {bool: true,},})</script>
vue基础-动态style:
给标签动态设置style的值
语法:
- :style=“{css属性:值}”
<body><div id="app"><!-- 语法::style="{css属性:值}"--><p :style="{backgroundColor:colorStr,color:str,border:borStr}">动态style</p><p :style="{'background-color':colorStr,color:str,border:borStr}">动态style</p></div></body><script>var vm = new Vue({el: '#app',data: {colorStr: 'red',str: '#fff',borStr: '5px solid blue',},})</script>
vue过滤器:
定义使用:
转换格式,过滤器就是一个函数,传入值返回处理后的值
过滤器只能用在插值表达式和v-bind表达式
场景:
- 字母转大写:输入 hello 输出 ‘HELLO’
- 字符串翻转 输入’hello world’ 输出 ’dlrow olleh‘
- 时间戳转换为时间日期格式
语法:
- 全局: Vue.filter(‘过滤器名’,(值)=>{return 返回处理后的值})
- 局部: filters:(过滤器名字,(值)=>{return 返回处理后的值})
<body><div id="app"><p>原来的样子:{{msg}}</p><!-- 2:过滤器使用语法: {{值|过滤器名字}}--><p>{{msg | toUp}}</p><p>{{msg | reverse}}</p></div><hr /><div id="main">{{str }}<p>{{str | reverse}}</p></div></body><script>// 全局过滤器 任何一个vue文件中都可以使用/* Vue.filter("过滤器的名字",(val)=>{return 处理后的值})*/Vue.filter('reverse', (val) => {return val.split('').reverse().join('')})var vm = new Vue({el: '#app',data: {msg: 'hello vue',},// 局部--局部--过滤器// 只能在当前vue文件(vue实例)中使用/* filters:{过滤器1(val){return 处理后的值},过滤器2(val){return 处理后的值}}*/filters: {toUp(val) {return val.toUpperCase()},},})// ---------------------------var aa = new Vue({el: '#main',data: {str: 'good bye',},})</script>
处理日期的工具模块moment:
调用:
<script src="http://cdn.staticfile.org/moment.js/2.24.0/moment.min.js"></script>
<body><div id="app"><!-- 定义初始数据,渲染到购物车页面点击对应的删除按钮,删除对应的数据当数据没有了,显示一条提示消息--><table class="tb"><tr><th>编号</th><th>名称</th><th>创立时间</th><th>操作</th></tr><tr v-for="(item,index) in arr" :key="item.id"><td>{{index+1}}</td><td>{{item.name}}</td><td>{{item.time |formatData}}</td><td><button @click="del(index)">删除</button></td></tr><tr v-if="arr.length===0"><td colspan="4">没有数据</td></tr></table></div></body><script>var vm = new Vue({el: '#app',data: {arr: [{id: 1,name: '奔驰',time: new Date(),},{id: 2,name: '宝马',time: new Date(),},{id: 3,name: '奥迪',time: new Date(),},],},methods: {del(index) {this.arr.splice(index, 1)},},filters: {formatData(val) {return moment(val).format('YYYY-MM-DD, h:mm:ss')},},})</script>
传参和多过滤器:
可同时使用多个过滤器,或给过滤器传参
语法:
- 过滤器传参 vue变量 | 过滤器(实参)
- 多个过滤器 vue变量|过滤器1|过滤器2
<body><div id="app"><p>原来的样子{{msg}}</p><!-- 给过滤器传参语法 vue变量 | 过滤器名(值)--><p>翻转过滤器{{msg | reverse("-")}}</p><!-- 多个过滤器使用语法 vue变量 | 过滤器1 | 过滤器2--><p>{{msg|toUp|reverse("|")}}</p><p :title="msg|toUp|reverse('|')">鼠标停留</p></div></body><script>var vm = new Vue({el: '#app',data: {msg: 'hello world',},filters: {toUp(val) {return val.toUpperCase()},reverse(val, s) {return val.split('').reverse().join(s)},},})</script>
vue计算属性:
computed:
一个数据依赖另外一些数据计算而来的结果
(一个变量,值要通过计算得到,变量要在computed中定义)
语法:
computed:{'计算属性名'(){return 值}
}
代码:
<body><div id="app"><p>{{num}}</p></div></body><script>var vm = new Vue({el: '#app',data: {a: 10,b: 20,},/* 一个变量的值 ,需要用另外的变量计算得来computed:{计算属性名(){return 值}}注意:计算属性和data都是变量---不能重名2函数内依赖的变量变化,会自动重新计算结果返回*/computed: {// 1页面加载会默认执行一次 给变量num赋初始值num() {console.log(1)return this.a + this.b},},})</script>
vue计算属性–缓存:
计算属性 基于他们的依赖项的值结果进行缓存的,只要依赖的变量不变,都直接从缓存取结果
<body><div id="app"><p>{{reverseMessage}}</p><p>{{reverseMessage}}</p><p>{{reverseMessage}}</p><p>{{getMessage()}}</p><p>{{getMessage()}}</p><p>{{getMessage()}}</p></div></body><script>var vm = new Vue({el: '#app',data: {msg: 'hello world',},computed: {/* 带缓存计算属性对应的函数执行后,会把return的值缓存起来依赖项不变,多次调用都是从缓存取值依赖项变化 函数会自动 重新执行--并缓存新的值*/reverseMessage() {console.log('计算属性执行了')return this.msg.split('').reverse().join('')},},methods: {getMessage() {console.log('函数执行了')return this.msg.split('').reverse().join('')},},})</script>
计算属性-完整写法:
计算属性也是变量 ,如果想要直接赋值,需要使用完整写法
语法:
computed:{‘属性名’:{set(值){},get(){}}
}
需求:
计算属性给v-model使用
<body><div id="app"><div><span>姓名</span><input type="text" v-model="full" /></div></div></body><script>// 给计算属性赋值 需要setter/* computed:{‘属性名’:{set(值){},get(){}}
}*/var vm = new Vue({el: '#app',data: {},computed: {full: {set(val) {// 给full赋值触发set方法console.log(val, 'set')},get() {console.log('get')//使用full的值 触发get方法return '无名氏'},},},})</script>
vue侦听器:
可以侦听data/computed属性值改变
语法:
watch:{'被侦听的属性名'(newValue,oldValue){}
}
代码:
<body><div id="app"><input type="text" v-model="name" /></div></body><script>var vm = new Vue({el: '#app',data: {name: '',},/* 目标:侦听name值的改变watch:{ '被侦听的属性名'(newValue,oldValue){ }}*/watch: {// newValue 当前最新值// oldValue 旧值 上一刻值name(newValue, oldValue) {console.log(newValue, oldValue)},},})</script>
想要侦听一个属性变化,使用侦听属性watch
深度侦听和立即执行:
侦听复杂类型,或者立即执行侦听函数
语法:
watch:{'被侦听的属性名':{immediate:true,//立即执行deep:true, //深度侦听复杂类型变化handler(newValue,oldValue){}}
}
@vue/cli脚手架:
webpack自己配置环境很麻烦,下载@vue/cli包,用vue命令创建脚手架项目
@vue/cli是vue官方提供的一个全局模块包(vue命令),此包用于创建脚手架项目
好处:
-
0配置webpack
-
babel支持、
-
css,less支持
-
开发服务器支持
安装:
把@vue/cli模块包安装到全局,电脑拥有vue命令,才能创建脚手架工程
全局安装:
npm install -g @vue/cli
# OR
yarn global add @vue/cli
查看版本:
vue -V
如果出现版本号,安装成功,否则失败
@vue/cli创建项目启动服务:
使用vue命令创建脚手架项目(项目名不能带大写字母,中文和特殊符号)
创建项目:
//vue create 是命令 vuecli-demo是文件夹名
vue create vuecli-demo
选择模板:
可以上下箭头选择,弄错了ctrl+c重来
回车,等待生成项目文件夹+文件+下载必须的第三方包
进入脚手架项目,启动内置的热更新本地服务器
cd vuecli-demonpm run serve
or
yarn serve
启动成功了(底层node+webpack热更新服务)
打开浏览器输入上述地址
@vue/cli目录和代码分析:
vuecli-demo #项目目录node_modules 项目依赖的第三方包public 静态文件目录index.html 但页面的html文件(网页浏览的就是它)src 业务文件夹 &****assets 静态资源components 组件目录HelloWorld.vue 欢迎页面的vue代码文件APP.vue 整个应用的根组件main.js 主入口文件.gitignore git提交忽略的配置babel.config.js babel配置package.json 依赖包列表README.md 项目说明vue.config.js vue的配置文件
主要文件及含义
node_modules 下都是下载的第三方包
public/index.html ---浏览器运行的网页
src/main.js webpack打包的入口文件
src/App.vue vue项目入口页面
package.json 依赖包列表文件
vue.config.js vue的配置文件
@vue/cli项目架构了解:
知道项目入口,以及代码执行顺序和引入关系
@vue/cli自定义配置:
项目中没有webpack.config.js文件,应为@vue/cli用的vue.config.js
module.exports = defineConfig({// 覆盖webpack的配置devServer: {//自定义服务配置open: true, //自动打开浏览器port: 3000,},})
eslint了解:
- main.js随便声明一个变量,但是使用,终端和页面都报错了(在main里面写入let)
- 代码步严谨
解决:
1:手动解决掉错误,后面项目中会讲如何解决
2:暂时关闭eslint检查,在vue.config.js中配置后重启服务
module.exports = defineConfig({...lintOnSave: false, // 关闭eslint检查transpileDependencies: true,
})
@vue/cli单文件:
单vue文件好处,独立作用域互不影响
- vue推荐采用.vue文件来开发项目
- template里只能有一个根标签
- vue文件-独立模块 --作用域互不影响
- style配合scoped属性,保证样式只针对当前template内标签生效
- vue配合webpack,把他们打包起来插入到index.html
// template 只能有一个根标签
<template><div></div>
</template><script>export default {}
</script>
// 当前组件的样式 设置scoped 可以保证样式只对当前页面有效
<style scoped></style>
@vue/cli欢迎界面清理:
- src/App.vue 默认有很多内容,可以全部删除留下框
- assets和components文件夹下一切都删除掉(不要默认的欢迎页面)
vscolde插件补充:
vue文件代码高亮插件:
代码提示插件:
快速生成:
脚手架项目中定义全局过滤器:
过滤时间
- 项目中下载moment模块
yarn add moment
- main.js引入moment并创建全局过滤器
import moment from 'moment'
// 全局过滤器Vue.filter('formData', (val) => {return moment(val).format('YYYY-MM-DD h:mm:ss')
})
- 项目中的任何一个vue文件都可以使用
{{ obj.time | formData }}
vue组件:
为什么用组件:
需求:想要多个收起 展开的部分
方法一:复制代码
- 代码重复 冗余
- 不利于维护
<template><div id="app"><!-- 目标:点击展开或收起时,把内容区域显示或者隐藏 --><h3>折叠面板</h3><div><div class="title"><h4>芙蓉楼送辛渐</h4><span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span></div><div class="container" v-show="isShow"><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p></div></div><!-- 复制 --><div><div class="title"><h4>芙蓉楼送辛渐</h4><span @click="isShow1 = !isShow1">{{ isShow1 ? '收起' : '展开' }}</span></div><div class="container" v-show="isShow1"><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p></div></div><!-- 复制 --><div><div class="title"><h4>芙蓉楼送辛渐</h4><span @click="isShow2 = !isShow2">{{ isShow2 ? '收起' : '展开' }}</span></div><div class="container" v-show="isShow2"><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p></div></div></div>
</template><script>
export default {data() {return {isShow: false,isShow1: false,isShow2: false,}},
}
</script><style scoped>
#app {width: 400px;margin: 20px auto;background-color: yellow;border: 4px solid burlywood;border-radius: 2px;padding: 10px;
}
h3 {text-align: center;
}
.title {display: flex;justify-content: space-between;align-items: center;border: 1px solid #ccc;padding: 0 5px;
}
</style>
vue组件-概念:
可复用的vue实例,封装标签,样式和代码
组件化: 封装的思想,把页面上 可复用的部分,封装为组件,从而方便项目的开发和维护
一个页面,可以拆分成一个个组件(一个vue文件),一个组件就是一个整体,每个组件可以有自己独立的结构,样式和行为(html,css,js)
vue组件–基础使用:
每个组件都是一个独立的个体,代码里体现为一个独立的vue文件
哪部分标签复用,就把哪部分封装到组件内
- 组件内template只能有一个标签
- 组件内的data必须是一个函数,独立作用域
步骤:
1.创建组件
- 创建组件 components/Pannel.vue
封装结构+样式+js 组件都是独立的,为了复用
<template><div><div class="title"><h4>芙蓉楼送辛渐</h4><span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span></div><div class="container" v-show="isShow"><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p><p>一片冰心在玉壶</p></div></div>
</template><script>
export default {data() {return {isShow: false,}},
}
</script><style scoped>
.title {display: flex;justify-content: space-between;align-items: center;border: 1px solid #ccc;padding: 0 5px;
}
</style>
2.注册组件
注册组件,创建后需要注册后再使用
全局注册使用
全局入口main.js 在new Vue之上注册
语法:
// 全局注册组件
// 1 引入组件
import Pannel from './components/Pannel'
// 2 全局注册组件
/* Vue.component('组件名',组件对象)
*/
Vue.component('PannelG', Pannel)
全局注册组件PannelG,就可以当做标签在任意vue文件中的template中使用
单双标签都可以或者小写加 - 形式,运行后,会把这个自定义标签当做组件解析,使用 组件里封装的标签替换到这个位置
<PannelG></PannelG>
<PannelG />
<pannel-g></pannel-g>
局部注册使用:
语法
import 组件对象 from 'vue文件路径'export default {components:{'组件名':组件对象}
}
在vue文件中引入、注册、使用
<template><div id="app"><hr /><MyPannel></MyPannel><my-pannel></my-pannel></div>
</template><script>
// 1引入组件
import MyPannel from './components/MyPannel.vue'
export default {//2 局部注册组件components: {MyPannel: MyPannel,},
}
</script>
组件使用总结:
- (创建)封装html+css+js到独立的.vue文件中
- (引入注册)组件文件==>得到组件配置对象
- (使用)当前页面当做标签使用
vue组件-scoped作用:
解决多个组件样式名相同,冲突问题
需求:div标签名选择器,设置背景色
问题: 组件里的div和外面的div都生效了
解决:给Pannel.vue组件里style标签上加scoped属性
<style scoped>
在style上假如scoped属性,就会在此组件的标签上加上一个随机生成的data-v开头的属性
而且必须是当前组件的元素,才会有这个自定义属性,才会被这个样式作用到
style上加scoped,组件内的样式只在当前vue组件生效
组件通信:
每个组件的变量和值都是独立的
父:使用其他组件的vue文件(App.vue)
子:被引入的组件(MyPannel.vue)
从外面给组件内传值,学会语法,练习,项目看使用场景
父向子:
- 创建组件componets/MyProduct.vue
- 组件内在props定义变量,用于接收外部传入的值
- App.vue中引入注册组件,使用时,传入具体数据给组件显示
口诀:
1:子组件--props--变量(准备接收)2:父组件--自定义属性-传值进去
父组件–App.vue
<template><div><!-- 每次组件显示不同的数据信息2-自定义属性:给子组件传值口诀:1:子组件--props--变量(准备接收)2:父组件--自定义属性-传值进去--><my-product title="超好吃的口水鸡" price="199"></my-product><my-product title="超级难吃的浏览" price="299"></my-product><my-product :title="tit" :price="p"></my-product></div>
</template><script>
import MyProduct from './components/MyProduct.vue'
export default {data() {return {tit: '超级大的西瓜',p: 50,}},components: {// MyProduct: MyProduct,MyProduct,},
}
</script>
子组件 MyProduct.vue
<template><div class="list"><h3>标题:{{ title }}</h3><p>价格:{{ price }}</p><p>开业大酬宾,全场8折</p></div>
</template><script>
export default {// 1-组件内props定义变量 用来接收外部传入的值props: ['title', 'price'],
}
</script><style scoped>
.list {width: 300px;height: 140px;border: 1px solid;border-radius: 10px;
}
</style>
父传子–配合循环:
把数据循环分别传入给组件内显示
<template><div><!-- 每次组件显示不同的数据信息2-自定义属性:给子组件传值口诀:1:子组件--props--变量(准备接收)2:父组件--自定义属性-传值进去--><my-productv-for="item in arr":key="item.id":title="item.tit":price="item.price"></my-product></div>
</template><script>
import MyProduct from './components/MyProduct.vue'
export default {data() {return {arr: [{id: 1,tit: '超好吃的口水鸡',price: 199,},{id: 2,tit: '超好吃的老鸭',price: 49,},{id: 3,tit: '超级无敌的冰激凌',price: 1999,},],}},components: {// MyProduct: MyProduct,MyProduct,},
}
</script>
单向数据流:
在vue中需要遵循单向数据流原则
1:父组件的数据发生了改变,子组件会自动跟着变
2:子组件不能直接修改父组件传递过来的props,props是只读的
父组件传给子组件是一个对象,子组件修改对象的属性, 是不会报错的,对象是引用类型,互相更新
总结:props的值不能重新赋值,对象引用关系属性值改变,互相影响
子传父:
从子组件把值传出来给外面使用
语法:
- 父 : @自定义事件名=“父methods函数”
- 子 : this.$emit(‘自定义事件名’,传值)—执行父methods里函数
父组件
<template><div><!-- 11-父组件:@自定义事件名="父methods函数"--><my-product @priceevent="priceFn" :title="tit" :price="price"></my-product></div>
</template><script>
import MyProduct from './components/MyProduct.vue'
export default {data() {return {tit: '超级好吃的口水鸡',price: 199,}},methods: {priceFn(val) {// val就是从子组件传递过来的值console.log(val)this.price = val},},components: {// MyProduct: MyProduct,MyProduct,},
}
</script>
子组件
<template><div class="list"><h3>标题:{{ title }}</h3><p>价格:{{ price }}</p><p>开业大酬宾,全场8折</p><button @click="changPrice">更改</button></div>
</template><script>
export default {// 1-组件内props定义变量 用来接收外部传入的值props: ['title', 'price'],methods: {changPrice() {// 从子组件中把值传递出去// this.$emit触发自定义事件// this.$emit('自定义事件名',传递参数)this.$emit('priceevent', 599)},},
}
</script>
非父子:
两个组件的关系非常复杂,通过父子组件通信是非常麻烦的,使用通用的组件通讯方式:事件总线(event-bus)
第一步:定义事件总线对象EventBus:
src/EventBus/index.js
import Vue from 'vue'// 导出空白的vue对象
export default new Vue()
MyProduct.vue触发事件:
<script>
import EventBus from '../EventBus'
export default {methods: {transmitFn() {// 传递数据方// 触发自定义事件EventBus.$emit('transmitEvent', 'hello')},},
}
</script>
MyChild.vue注册事件:
import EventBus from '../EventBus'
export default {data() {return {msg: '',}},mounted() {// 组件创建完毕 监听transmitEvent事件// 接收方// 自定义事件 transmitEventEventBus.$on('transmitEvent', (v) => {console.log(v, '接收方')this.msg = v})},
}
总结:空的vue对象,只负责on注册事件emit触发事件 一定要确保$on先执行
vue生命周期:
组件从创建到销毁的整个过程就是生命周期
钩子函数:
vue框架内置函数,随着组件的生命周期阶段,自动执行
作用:
特定的时间点,执行特定的操作
场景:
组件创建完毕后,可以在created生命周期函数中发起ajax请求,初始化data数据;mounted函数中,操作Dom元素; beforeDestory生命周期函数中 ,解绑事件,清除定时器…
4大阶段 8个方法:
创建:
- beforeCreate
- created
挂载:
- beforeMount
- mounted
更新:
- beforeUpdate
- updated
代码:
<template><div><p>学习vue生命周期</p><p id="myP">{{ msg }}</p><ul id="myul"><li v-for="(val, index) in arr">{{ val }}</li></ul><button @click="arr.push(122)">点击添加</button></div>
</template><script>
export default {data() {return {msg: 'hello world',arr: [5, 8, 2, 1],timer: null, //保存定时器}},/* 一:创建阶段new Vue()以后,vue内部给实例添加了一些属性和方法 data和methods*/beforeCreate() {//创建前 没有数据 没有方法console.log('beforeCreate')console.log(this.msg) //undefined},created() {// 创建后// data和methods初始化之后// 场景:网络请求 注册全局事件 开启定时器console.log('created')console.log(this.msg) // "hello world"this.timer = setInterval(() => {console.log('娃哈哈')}, 1000)},/* 挂载真实dom挂载之前场景:预处理data,不会触发updated钩子函数*/beforeMount() {console.log('beforeMount')console.log(document.getElementById('myP')) //null},/*真实dom挂载以后场景: ajax请求 dom操作 echarts */mounted() {console.log('mounted')console.log(document.getElementById('myP')) //<p data-v-7ba5bd90="" id="myP">hello world</p>},/* 以上四个生命周期钩子函数在整个生命周期过程中只执行一次*//* 更新阶段:data数据改变才会执行*/beforeUpdate() {// 更新之前console.log('beforeUpdate')console.log(document.querySelectorAll('#myul>li')[4]) //undefined},updated() {// 更新之后// 获取更新后的真实domconsole.log('updated')console.log(document.querySelectorAll('#myul>li')[4]) //<li data-v-7ba5bd90=""> 122 </li>},
}
</script><style scoped></style>
销毁:
- beforeDestory
- destoryed
$refs:
$refs获取DOM:
利用ref和$refs获取DOM元素
<template><div><p>获取原生Dom元素</p><h1 id="myH" ref="myH">我是一名学员</h1></div>
</template><script>
export default {mounted() {// console.log(document.getElementById('myH'))/* 1:给标签定义ref属性2:通过this.$refs.属性名 获取元素*/console.log(this.$refs.myH)},
}
</script>
$refs获取组件对象:
- 创建组件MyProduct.vue
<template><div>MyProduct组件</div>
</template><script>
export default {data() {return {msg: 'hello',}},methods: {fn() {console.log('组件被调用了')},},
}
</script>
- 获取组件对象,调用组件方法
<template><div><h2>获取组件对象--可以调用组件内的一切</h2><my-product ref="myP"></my-product></div>
</template><script>
/* 1:创建组件/引入组件/注册组件/使用组件2: 给组件起别名 ref3: 恰当时机,获取组件对象
*/
import MyProduct from './components/MyProduct'
export default {components: {MyProduct,},mounted() {let myPobj = this.$refs.myPconsole.log(myPobj.msg)myPobj.fn()},
}
</script>
$nextTick:
vue更新DOM–异步的:
需求:点击count++,通过原生DOM拿标签内容,无法拿到新值
<template><div><h2>$nextTick</h2><p ref="myP">{{ count }}</p><button @click="addCount">count++</button></div>
</template><script>
export default {data() {return {count: 0,}},methods: {addCount() {this.count++ //vue监听数据更新,开启一个DOM更新队列(异步任务)// console.log(this.$refs.myP.innerHTML) //0/* vue更新DOM 异步任务解决:this.$nextTick过程: DOM更新完会挨个触发$nextTick里的函数体*/this.$nextTick(() => {console.log(this.$refs.myP.innerHTML)})},},
}
</script>
场景:
点击搜索按钮,弹出文本框并获取焦点,按钮消失
$nextTick和async、await
<template><div><button @click="btnFn" v-if="isShow">搜索</button><input v-else type="text" placeholder="这是一个文本框" ref="myInp" /></div>
</template><script>
export default {data() {return {isShow: true,}},methods: {async btnFn() {this.isShow = false// this.$refs.myInp.focus()// 原因:DOM更新是异步的 文本框还没有挂载到真实DOM上// this.$nextTick(() => {// this.$refs.myInp.focus()// })// 等同于// await取代回调函数await this.$nextTick()this.$refs.myInp.focus()},},
}
</script>
组件name使用:
可以用组件的name属性值,来注册组件名字
我们封装的组件,可以自己定义name属性组件名 让使用者有个统一的前缀风格
components/MyCom.vue
<template><div>我是一个Com组件</div>
</template><script>
export default {name: 'ComNameHaHa', //注册时可以定义自己的名字
}
</script><style scoped></style>
App.vue中注册和使用:
<template><div><ComNameHaHa></ComNameHaHa><MyProducts></MyProducts></div>
</template><script>
import Com from './components/MyCom.vue'
import Pro from './components/MyProduct.vue'
export default {components: {[Pro.name]: Pro,[Com.name]: Com, //对象里的key是变量的话 [] 属性名表达式// 相当于// ComNameHaHa: Com,},
}
</script>
动态组件:
多个组件使用一个挂载点,并动态切换,就是动态组件
vue内置component组件,配合is属性,设置要显示的组件
需求:完成一个注册功能页面,2个按钮切换,一个填写注册信息,一个填写用户简介信息
- 定义两个组件 UserName.vue , UserInfo.vue 2个组件
- 引入到App.vue组件中
- data中定义变量来存放要显示的组件名
- 要设置挂载点
<component>
使用is属性来设置要显示那个组件 - 点击按钮 修改变量里的组件名
<template><div><button @click="comName = 'UserName'">账号密码</button><button @click="comName = 'UserInfo'">个人信息</button><p>下面显示动态切换组件</p><div style="border: 1px solid red"><component :is="comName"></component></div></div>
</template><script>
/*
同一个挂载点要切换不同组件 显示1:创建要切换的组件 标签+样式2:引入到要展示的vue文件内,注册3:变量 承载要显示的组件名4:设置挂载点 <component :is="变量"></component>5:点击按钮 切换comName值为要显示的组件名*/
import UserName from './components/UserName.vue'
import UserInfo from './components/UserInfo.vue'
export default {data() {return {comName: 'UserName',}},components: {UserName,UserInfo,},
}
</script>
组件缓存:
组件切换会导致组件被频繁的销毁和重新创建,性能不高
使用vue内置的keep-alive组件,可以让包裹的组件保存在内存中不被销毁
演示:给UserName.vue 和 UserInfo.vue 注册created 和destroyed生命周期事件,观察创建和销毁的过程
使用keep-alive内置的vue组件,让动态组件缓存
语法:
Vue内置的keep-alive组件,包裹要频繁切换的组件
App.vue:
<div style="border: 1px solid red"><!-- vue内置的keep-alive组件,把包起来的组件缓存起来 --><keep-alive><component :is="comName"></component></keep-alive>
</div>
补充生命周期:(组件缓存下新增的两个钩子)
-
activated 激活时触发
-
deactivated 失去激活状态触发
<template><div><p>用户名:<input type="text" /></p><p>密码: <input type="text" /></p></div>
</template><script>
export default {created() {console.log('UserName创建')},destroyed() {console.log('UserName销毁')},// 组件缓存下 多了两个钩子函数activated() {console.log('activated') //激活状态},deactivated() {console.log('deactivated') // 失去激活状态},
}
</script>
组件插槽slot:
用来实现组件内容的分发,通过slot标签,可以接收到写在组件标签内的内容
vue提供组件插槽的能力,允许开发者在封装组件时,把不确定的部分定义为插槽
基础使用–匿名插槽:
- 组件内使用slot 占位
- 使用组件时
<pannel></pannel>
中间传入标签替换slot
Pannel.vue:
<template><div><div class="title"><h4>鹅鹅鹅</h4><span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span></div><div class="container" v-show="isShow"><!--1: 组件内使用slot 占位 --><slot></slot></div></div>
</template><script>
export default {data() {return {isShow: false,}},
}
</script><style scoped>
h4 {text-align: center;
}
.title {display: flex;justify-content: space-between;align-items: center;border: 1px solid #ccc;padding: 0 10px;
}.title h4 {line-height: 2;margin: 0;
}.container {border: 1px solid #ccc;padding: 0 10px;
}
</style>
App.vue:
<template><div><!-- 2 使用组件时 pannel 在组件中间 传入标签替换slot --><pannel><p>曲项向天歌</p><p>曲项向天歌</p><p>曲项向天歌</p><p>曲项向天歌</p><p>曲项向天歌</p></pannel><pannel><img src="./assets/logo.png" alt="" /></pannel></div>
</template><script>
import Pannel from './components/Pannel.vue'
export default {components: { Pannel },
}
</script>
默认内容:
如果外面不给传,想给个默认显示内容
<slot>
夹着内容默认显示内容,如果不给插槽slot传东西,就使用slot夹着的内容在原地显示
<slot>默认内容</slot>
具名插槽v-slot:
当一个组件内有2处以上需要外部传入标签的地方
传入的标签可以分别派发给不同的slot位置
v-slot一般跟template标签使用(template是HTML5新出标签内容模板元素,不会渲染在页面上,一般被vue解析内部标签)
Pannel.vue:
<template><div><div class="title"><slot name="title"></slot><span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span></div><div class="container" v-show="isShow"><!--1: 组件内使用slot 占位 --><slot name="content"></slot></div></div>
</template><script>
export default {data() {return {isShow: false,}},
}
</script>
App.vue:
<template><div><!-- 2 使用组件时 pannel 在组件中间 传入标签替换slot --><pannel><template v-slot:title><h4>鹅鹅鹅</h4></template><template v-slot:content><span>我是内容</span></template></pannel><!-- v-slot: 简写为 # --><pannel><template #title><h2>hello</h2></template><template #content><p>123</p></template></div>
</template>
总结:
v-slot: 可以简写为 #
slot的name属性起插槽名,使用组件时,template配合#插槽名传入具体的标签
作用域插槽(可以将主组件的属性获取到):
子组件里的值,在给插槽赋值时在父组件环境下使用
步骤:
- 子组件:在slot上绑定属性和子组件内的值
- 使用组件时,传入自定义标签,用template和v-slot=“自定义变量名scope”
- scope变量名自动绑定slot上所有的属性和值
Pannel.vue:
<template><div><div class="title"><!-- 1:slot上绑定属性和子组件内的值 --><slot name="title" :msg="msg" tit="nihao"></slot><span @click="isShow = !isShow">{{ isShow ? '收起' : '展开' }}</span></div><div class="container" v-show="isShow"><!--1: 组件内使用slot 占位 --><slot name="content"> </slot></div></div>
</template><script>
export default {data() {return {isShow: false,msg: 'hello world',}},
}
</script>
App.vue:
<template><div><pannel><!--2 传入自定义标签 template和v-slot:插槽名="自定义变量名" --><template v-slot:title="scope"><!--3 scope上自动绑定slot上所有属性和值 --><h4>{{ scope.tit }}</h4></template><template v-slot:content><span>我是内容</span></template></pannel><!-- v-slot: 简写为 # --><pannel><template #title><h2>hello</h2></template><template #content><p>123</p></template></pannel></div>
</template>
总结:
组件内变量绑定到slot上,使用组件v-slot:插槽名=“自定义变量”,变量上会自动绑定属性和值
使用场景:
需求:封装一个表格组件,在表格组件内循环产生单元格
- 准备MyTable.vue组件—内置表格,传入数组循环铺设页面,把对象每个内容显示在单元格里
- App.vue组件里,准备数据传入给MyTable.vue使用
- 分析
- 想要给td内显示图片,传入自定义的img标签(td中准备slot占位符,但是外面需要把图片地址赋予给src属性,在slot上把item数据进行绑定)
MyTable.vue:
需求:封装一个表格组件,在表格组件内循环产生单元格
- 准备MyTable.vue组件—内置表格,传入数组循环铺设页面,把对象每个内容显示在单元格里
- App.vue组件里,准备数据传入给MyTable.vue使用
- 分析
- 想要给td内显示图片,传入自定义的img标签(td中准备slot占位符,但是外面需要把图片地址赋予给src属性,在slot上把item数据进行绑定)
MyTable.vue:
<template><div><table><thead><tr><th>序号</th><th>姓名</th><th>年龄</th><th>头像</th></tr></thead><tbody><tr v-for="(item, index) in list" :key="index"><td>{{ index + 1 }}</td><td>{{ item.name }}</td><td>{{ item.age }}</td><td><slot :row="item"><!-- 默认值,使用组件不自定义标签显示默认文字 -->{{ item.headImgUrl }}</slot></td></tr></tbody></table></div>
</template><script>
export default {props: ['list'],
}
</script>
App.vue:
<template><div><my-table :list="list"><template v-slot="scope"><img :src="scope.row.headImgUrl" width="50" alt="" /></template></my-table><my-table :list="list"><template v-slot="{ row }"> // 解构<a :href="row.headImgUrl">{{ row.headImgUrl }}</a></template></my-table></div>
</template><script>
import MyTable from './components/MyTable.vue'
export default {components: { MyTable },data() {return {list: [{name: '张三',age: 18,headImgUrl: 'https://img01.yzcdn.cn/vant/ipad.jpeg',},{name: '李四',age: 23,headImgUrl: 'https://img01.yzcdn.cn/vant/ipad.jpeg',},{name: '王五',age: 45,headImgUrl: 'https://img01.yzcdn.cn/vant/ipad.jpeg',},],}},
}
</script>
自定义指令:
除了核心功能默认内置的指令(v-model和v-show),vue允许我们注册自定义指令。 v-xxxx
html+css的复用的主要形式是组件
你需要对普通DOM元素进行底层操作,这时候就会用到自定义指令
目标:获取标签 扩展额外的功能
局部注册和使用
只能在当前组件中使用
<template><div><div class="main"><input type="text" v-focus /></div></div>
</template><script>
/*
局部指令
directives:{'指令名':{bind(el,binding,vnode){},inserted(el,binding,vnode){}}
}在标签上使用自定义指令 v-指令名inserted 指令所在标签 被插入到页面上触发(一次)
update 指令对应的数据/标签更新时 触发
*/
export default {directives: {// 页面加载时获取焦点focus: {inserted(el, binding, vnode) {// console.log(el)el.focus()},},},
}
</script>
全局注册和使用
在任何的.vue文件中使用
main.js中用Vue.directive 全局注册指令
// 全局注册
Vue.directive('gfocus', {inserted(el) {el.focus()},
})
自定义指令-传值:
使用自定义指令,传入一个值
需求:定义color指令,传入一个颜色,给标签设置文字颜色
<template><div><div class="main"><input type="text" v-focus /><input type="text" v-gfocus /></div><p v-color="theColor" @click="changeColor">修改文字颜色</p></div>
</template><script>
/*
局部指令
directives:{'指令名':{bind(el,binding,vnode){},inserted(el,binding,vnode){}}
}在标签上使用自定义指令 v-指令名inserted 指令所在标签 被插入到页面上触发(一次)
update 指令对应的数据/标签更新时 触发
*/
export default {data() {return {theColor: 'blue',}},methods: {changeColor() {this.theColor = 'yellow'},},directives: {// 页面加载时获取焦点focus: {inserted(el, binding, vnode) {// console.log(el)el.focus()},},// 给标签设置文字颜色color: {inserted(el, binding) {el.style.color = binding.value},update(el, binding) {el.style.color = binding.value},},},
}
</script>
vue路由:
生活中的路由:
设备和ip的映射关系
后端路由:
接口和服务的映射关系
前端路由:
路径和组件的映射关系
为什么使用路由:
在一个页面里,切换业务场景
vue单页面应用(SPA)所有的功能在一个html页面上实现
优点:
- 整体不刷新页面,用户体验好
- 数据传递容易,开发效率高
缺点:
- 开发成本高(需要学习专门知识)
- 首次加载比较慢,不利于SEO
vue-router介绍:
vue集成路由
vue-router模块包,它和vue.js深度集 成
定义映射规则-----模块化—提供2个内置全局组件
路由-组件分类:
.vue文件分2类,一个页面组件,一个是复用组件
.vue文件本质无区别
src/views(pages)文件夹 —页面组件—配合路由使用
src/components文件夹 —复用组件(展示数据–复用)
总结:views下的页面组件,配合路由切换;components下的一般引入到views下的vue中复用展示数据
vue-router使用:
安装:
npm i vue-router@3.5.1
导入路由:
src下创建router/index.js
import Vue from 'vue'
// 1导入路由
import VueRouter from 'vue-router'
// 引入组件
// import Home from '../views/Home'
// import Login from '../views/Login'
// import Register from '../views/Register'// 2 使用路由插件
// 在vue中 使用vue的插件 需要调用Vue.use()
Vue.use(VueRouter)// 3 创建vue路由规则
const routes = [// 路径和组件的映射关系{path: '/',// component: Home,// 路由懒加载component: () => import('../views/Home'),},{path: '/login',component: () => import('../views/Login'),},{path: '/register',component: () => import('../views/Register'),},
]// 4创建路由对象 传入规则
const router = new VueRouter({// routes: routes,routes,
})// 导出路由对象
export default router
main.js:
import router from './router'new Vue({router,render: (h) => h(App),
}).$mount('#app')
路由出口:
App.vue
<router-view></router-view>
声明式导航:
使用全局组件 router-link来替代a标签
- vue-router提供了一个全局组件 router-link
- router-link 实质上最终会渲染成a链接,to属性等价于提供href属性(to无需#)
- router-link提供了声明式导航高亮的功能(自带类名)
<template><div><div class="nav"><router-link to="/">首页</router-link><router-link to="/login">登陆</router-link><router-link to="/register">注册</router-link></div><router-view></router-view></div>
</template><script>
export default {}
</script><style scoped>
.nav {display: flex;width: 400px;justify-content: space-around;
}.router-link-exact-active { // 高亮显示,点击谁谁变红color: red;
}
</style>
重定向:
强制切换到目标path上
- 网页打开url默认hash值是 / 路径
- redirect 是设置要重定向到那个路由路径
需求:网页默认打开,匹配路由 ‘/’ 强制切换到 ‘/home’上
const routes = [{path: '/',redirect: '/home', //重定向到 /home},}
总结: 强制重定向后,还会重新来数组中匹配一次规则
路由-404页面:
如果路由hash值没有和数组里规则匹配
默认给一个404页面
路由最后,path匹配 *(任意路径) --前面都不匹配,就匹配最后这个,显示对应的组件
- 创建NotFound页面
<template><div>404</div>
</template><script>
export default {}
</script><style scoped></style>
- 修改路由配置
const routes = [// 写在最后// {// path: '*',// redirect: '/home',// },{path: '*',component: () => import('../views/NotFound'),},
]
**总结:**如果路由未命中任何规则,给出一个兜底的404页面
路由模式设置:
修改路由在地址栏的模式
hash路由 : 地址栏URL中的#符号 http://localhost:3000/#/abc ,不会被包括在HTTP请求中,对后端完全没有影响,改变hash不会重新加载页面
history路由:http://localhost:3000/abc(需要服务器支持,否则找的是文件夹)
利用了HTML5 新增的pushState() 和replaceState()方法
// 4创建路由对象 传入规则
const router = new VueRouter({// routes: routes,routes,mode: 'history', //默认是hash
})
vue路由-编程式导航:
编程式导航:用js代码跳转
声明式导航:router-link实现跳转
语法:
this.$router.push({path:"路由路径",name:"路由名"
})
router/index.js 路由规则里,给路由起名字
const routes = [// 路径和组件的映射关系{path: '/home',name: 'home',component: () => import('../views/Home'),},{path: '/login',name:"login",component: () => import('../views/Login'),},{path: '/register', name:"register",component: () => import('../views/Register'),},]
App.vue中router-link换成span,配合js的编程式导航跳转
<template><div><div class="nav"><!-- <router-link to="/">首页</router-link><router-link to="/login">登陆</router-link><router-link to="/register">注册</router-link> --><span @click="goTo('/home', 'home')">首页</span><span @click="goTo('/login', 'login')">登录</span><span @click="goTo('/register', 'register')">注册</span@click=></div><router-view></router-view></div>
</template><script>/*
编程式导航: js方式跳转路由
语法:
this.$router.push({path:"路由路径"})
this.$router.push({name:"路由名"})
注意:
虽然用name跳转,但是url的hash值还是切换path路径值
场景
方便修改,name路由名(在页面上看不见,随便修改)
path可以在url的hash值看到(尽量符合组件内规范)
*/
export default {methods: {goTo(targetPath,targetName) {this.$router.push({// path:targetPathname:targetName})}},
}
</script>
嵌套路由:
在现有的一级路由下,再嵌套二级路由
- 创建需要用的所有组件
- src/views/Find.vue ----发现音乐
- src/views/My.vue—我的音乐
- src/views/Second/Recommend.vue —发现音乐/推荐页面
- src/views/Second/Ranking.vue —发现音乐/排行榜
- src/views/Second/SongList.vue —发现音乐/歌单页面
- main.js 配置2级路由
- 一级路由由path从/开始定义
- 二级路由往后path 直接写名字 ,无需 / 开头
- 二级路由在上级路由的children数组里编写路由信息对象
- 说明
- App.vue 的router-view 负责发现音乐 和我的音乐页面 切换
- Find.vue的router-view负责发现音乐下的三个页面切换
配置路由规则:
const routes = [{path: '/',redirect: '/find', //重定向到 /home},{path: '/find',redirect: '/find/recommend',component: () => import('../views/Find'),children: [{path: 'recommend',component: () => import('../views/Second/Recommend'),},{path: 'ranking',component: () => import('../views/Second/Ranking'),},{path: 'songlist',component: () => import('../views/Second/SongList'),},],},{path: '/my',component: () => import('../views/My'),},{path: '*',component: () => import('../views/NotFound'),},
]
App.vue写路由出口:
App.vue 的router-view 负责发现音乐 和我的音乐页面 切换
<template><div><div class="nav"><router-link to="/find">发现音乐</router-link><router-link to="/my">我的音乐</router-link></div><router-view></router-view></div>
</template><script>
export default {}
</script><style scoped>
.nav {display: flex;width: 400px;justify-content: space-around;
}
</style>
Find.vue写二级路由出口:
Find.vue的router-view负责发现音乐下的三个页面切换
<template><div><div class="nav_main"><router-link to="/find/recommend">推荐</router-link><router-link to="/find/ranking">排行榜</router-link><router-link to="/find/songlist">歌单</router-link></div><div style="1px solid red"><router-view></router-view></div></div>
</template><script>
export default {}
</script><style scoped>
.nav_main {background-color: red;color: white;padding: 10px 0;
}
.nav_main a {text-align: center;text-decoration: none;color: white;margin: 7px 20px;padding: 0px 15px;height: 20px;display: inline-block;line-height: 20px;
}
.nav_main a:hover {background-color: brown;
}.nav_main .router-link-exact-active {background-color: brown;
}
</style>
**总结:**嵌套路由,找准在那个页面里写router-view和对应的规则里写children
声明式导航-类名区别:
router-link 自带的2个类名的区别是什么
router-link-active(模糊匹配) url中hash值,包含href属性值这个路径
router-link-exact-active(精确匹配) url中hash值路径,与href属性值完全相同,设置此类名
路由传参:
跳转路由时 可以给路由对应的组件内传参
声明式导航:
router-link 上的to属性,语法格式
/path?参数名=值
/path/值 ------需要路由对象提前配置 path:'/path/:参数名'
对应的页面组件接收传递过来的值
$route.query.参数名
$route.params.参数名
router/index.js
// 3 创建vue路由规则
const routes = [{path: '/',redirect: '/list', //重定向到 /home},{path: '/list',component: () => import('../views/List'),},{path: '/part',component: () => import('../views/Part'),},{path: '/detail/:name',component: () => import('../views/Detail'),},{path: '*',component: () => import('../views/NotFound'),},
]
List.vue:
<template><div><router-link to="/part?name=小川">朋友--小川</router-link><router-link to="/detail/小妞">朋友--小妞</router-link><router-link :to="'/part?name=' + n1">朋友--{{ n1 }}</router-link><router-link :to="'/detail/' + n1">朋友--{{ n1 }}</router-link></div>
</template>
<script>
export default {data() {return {n1: '花姐',n2: '露露',}},
}
</script>
Part.vue:
<template><div><p>关注明星</p>{{ $route.query.name }}<hr />{{ name }}</div>
</template><script>
export default {data() {return {name: '',}},created() {// 创建完成 第一次操作data中数据 执行一次this.name = this.$route.query.nameconsole.log(this.name)},
}
</script>
Detail.vue:
<template><div>detail{{ $route.params.name }}<hr />{{ name }}</div>
</template><script>
export default {data() {return {name: '',}},created() {this.name = this.$route.params.name},
}
</script>
编程式导航:
语法:
query/params 任选一个
this.$router.push({path:"路由路径",name:"路由名",query:{"参数名":"值"},params:{"参数名":"值"}
})
List.vue:
<template><div><router-link to="/part?name=小川">朋友--小川</router-link><router-link to="/detail/小妞">朋友--小妞</router-link><router-link :to="'/part?name=' + n1">朋友--{{ n1 }}</router-link><router-link :to="'/detail/' + n1">朋友--{{ n1 }}</router-link><hr /><span @click="oneFn">朋友--小川</span><span @click="twoFn">朋友--小妞</span><span>朋友--{{ n1 }}</span><span>朋友--{{ n1 }}</span></div>
</template><script>
export default {data() {return {n1: '花姐',n2: '露露',}},methods: {oneFn() {this.$router.push({path: '/part',query: {// name: '小川',name: this.n2,},})},twoFn() {// path会自动的忽略params// this.$router.push({// name: 'detail',// params: {// name: '小妞',// },// })this.$router.push('/detail/' + this.n1)},},
}
</script>
路由守卫:
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。
全局前置守卫:
需求:在跳转路由前,判断用户是否登录,登录了才能跳转到“我的音乐“页面,未登录弹窗提示
在路由对象上使用固定的方法 beforeEach
// 1 在路由对象上使用固定的方法 beforeEach
/*
路由跳转"之前" 先执行这里,决定是否跳转
router.beforeEach((to,from ,next)=>{
to 要跳转到的路由 (路由对象信息) 目标
from 从哪里跳转的路由(路由对象信息) 来源
next 函数体, next() 才会让路由正常的跳转切换, next(false)在原地停留 next("路由路径") 强制修改到另一个路由路径上
不调用next 页面留在原地
})*/
// 在跳转路由前,判断用户是否登录,登录了才能跳转到“part“页面,未登录弹窗提示
const isLogin = false //登陆状态 (未登陆)
router.beforeEach((to, from, next) => {// console.log(to)// console.log(from)if (to.path === '/part' && isLogin === true) {alert('请登陆')next(false)} else {next() //正常放行}
})
Vant组件库:
vant 轻量、可靠的移动端 Vue 组件库
安装:
yarn add vant@latest-v2 -S
导入所有的组件:
main.js中导入:
// 导入所有的组件
import Vant from 'vant'
import 'vant/lib/index.css'Vue.use(Vant)
使用组件:
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>
手动按需引入:
只引入使用的组件
在不使用插件的情况下,可以手动引入需要的组件。—每一个组件中引入
import Button from 'vant/lib/button';
import 'vant/lib/button/style';
注册–使用:
<template><div><van-button type="primary">主要按钮</van-button><van-button type="info">信息按钮</van-button><van-button type="default">默认按钮</van-button><van-button type="warning">警告按钮</van-button><van-button type="danger">危险按钮</van-button></div>
</template><script>
import Button from 'vant/lib/button'
import 'vant/lib/button/style'
export default {components: {// VanButton: Button,[Button.name]: Button,},
}
</script>
自动按需引入组件:
babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。
安装插件:
yarn add babel-plugin-import -D
babel.config.js配置,重新启动项目:
module.exports = {plugins: [['import', {libraryName: 'vant',libraryDirectory: 'es',style: true}, 'vant']]
};
引入:
main.js
import { Button, Icon, Tabbar, TabbarItem, Tag } from 'vant'Vue.use(Tabbar)
Vue.use(TabbarItem)
Vue.use(Button)
Vue.use(Icon)Vue.use(Tag)
组件中使用:
<template><div><van-button type="primary">主要按钮</van-button><van-button type="info">信息按钮</van-button><van-button type="default">默认按钮</van-button><van-button type="warning">警告按钮</van-button><van-button type="danger">危险按钮</van-button><van-icon name="chat-o" /><van-icon name="https://b.yzcdn.cn/vant/icon-demo-1126.png" /></div>
</template><script>
export default {}
</script><style scoped></style>
案例:
路由配置:
二级路由
组件:
- Layout.vue —总的框架
- List.vue ----商品列表
- Search.vue -----商品搜索
- My.vue-----我的信息
配置路由:
const routes = [{path: '/',redirect: '/list',component: () => import('../views/Layout'),children: [{path: 'list',component: () => import('../views/List'),},{path: 'search',component: () => import('../views/Search'),},{path: 'my',component: () => import('../views/My'),},],},{path: '*',component: () => import('../views/NotFound'),},
]
底部封装:
- 创建MyTabBar.vue组件
<template><van-tabbar v-model="active" route><van-tabbar-item icon="home-o" to="/list">商品列表</van-tabbar-item><van-tabbar-item icon="search" to="/search">商品搜索</van-tabbar-item><van-tabbar-item icon="friends-o" to="/my">我的信息</van-tabbar-item></van-tabbar>
</template><script>
export default {data() {return {active: 0,}},
}
</script>
顶部封装:
- 创建MyHead.vue组件
<template><div class="head">TabBar案例</div>
</template><script>
export default {}
</script><style scoped>
.head {height: 50px;line-height: 50px;background-color: blue;text-align: center;color: white;
}
</style>
商品列表:
- 封装MyTable.vue ===标签和样式
- axios 在MyGoodList.vue请求数据回来
- 请求地址https://www.escook.cn/api/goods
- 传入MyTable.vue 中循环数据显示
axios 在MyGoodList.vue请求数据回来
- 下载axios
yarn add axios
- MyGoodList.vue
<template><div><my-table :list="list"></my-table></div>
</template><script>
import axios from 'axios'
import MyTable from './MyTable.vue'
export default {components: {MyTable,},data() {return {list: [],}},created() {// axios({// url: 'https://www.escook.cn/api/goods',// }).then((res) => {// let {// data: { data: result },// } = res// console.log(result)// })this.getDate()},methods: {async getDate() {let {data: { data: result },} = await axios({ url: 'https://www.escook.cn/api/goods' })console.log(result)this.list = result},},
}
</script>
- MyTable.vue
<template><table class="table"><thead><tr><th>#</th><th>名称</th><th>价格</th><th>标签</th><th>操作</th></tr></thead><tbody><tr v-for="(item, index) in list"><td>{{ item.id }}</td><td>{{ item.goods_name }}</td><td>{{ item.goods_price }}</td><td>{{ item.tags }}</td><td><van-button type="primary">删除</van-button></td></tr></tbody></table>
</template><script>
export default {props: {list: Array,},
}
</script><style scoped>
.table {width: 100%;margin: 20px auto;border: 1px solid #333;border-collapse: collapse;
}
td,
th {border: 1px solid #333;height: 30px;
}
</style>
商品表格–插槽:
使用插槽技术,和作用域插槽技术,给MyTable.vue组件,自定义列标题,自定义表格内容
需求:允许用户自定义表个头和表格单元格内容
- 把MyTable.vue里准备slot
- 使用MyTable组件时传入具体标签
步骤:
- 提高组件==复用性和灵活性 ==,把表格列标题thead部分预留slot ,设置name属性
- 使用MyTable.vue时,传入列标题标签
- 表格内容td部分也可以让组件使用者自定义,也给tbody预留slot 和name属性
- 使用插槽需要用到插槽内的item对象上的数据,作用域插槽
MyTable.vue:
<template><table class="table"><thead><tr><!-- <th>#</th><th>名称</th><th>价格</th><th>标签</th><th>操作</th> --><slot name="header"></slot></tr></thead><tbody><tr v-for="(item, index) in list"><!-- <td>{{ item.id }}</td><td>{{ item.goods_name }}</td><td>{{ item.goods_price }}</td><td>{{ item.tags }}</td><td><van-button type="primary">删除</van-button></td> --><slot name="body" :row="item" :index="index"></slot></tr></tbody></table>
</template><script>
export default {props: {list: Array,},
}
</script>
MyGoodList.vue:
<template><div><my-table :list="list"><template #header><th>#</th><th>名称</th><th>价格</th><th>标签</th><th>操作</th></template><template #body="{ row, index }"><td>{{ row.id }}</td><td>{{ row.goods_name }}</td><td>{{ row.goods_price }}</td><td>{{ row.tags }}</td><td><van-button type="primary">删除</van-button></td></template></my-table></div>
</template>
商品表格tags:
<td><van-tag v-for="(str, ind) in row.tags" :key="ind" type="primary">{{str}}</van-tag>
</td>
商品表格–删除功能:
点击删除按钮删除数据
分析:
- 删除按钮绑定点击事件
- 作用域插槽绑定id出来
- 传给删除方法,删除MyGoodList.vue里数组的数据
MyGoodList.vue—注册点击事件:
<van-button type="primary" @click="removeBtn(row.id)">删除</van-button>
MyGoodList.vue 根据id删除:
methods:{removeBtn(id) {// 1 根据id查找下标let index = this.list.findIndex((obj) => obj.id === id)// 2 实现删除this.list.splice(index, 1)},
}
商品表格–添加tab:
需求:点击tab按钮,出现输入框自动获取焦点,失去焦点关闭输入框,会出新增tag,esc清空内容
- 点击tab,按钮消失,输入框出现
- 输入框自动获焦
- 失去焦点,输入框消失,按钮出现
- 检测输入框回车,无数据拦截
- 输入框取消,清空数据
- 检测输入框回车,有数据添加
点击tab,按钮消失,输入框出现:
<div class="top"><van-fieldv-model="value"v-if="row.inputVisible"placeholder="请输入tab内容"/><van-button v-else @click="row.inputVisible = true" type="info">tag+</van-button></div>
输入框自动获焦:
<van-fieldv-model="value"v-if="row.inputVisible"placeholder="请输入tab内容":autofocus="true"/>
失去焦点,输入框消失,按钮出现:
<van-fieldv-model="value"v-if="row.inputVisible"placeholder="请输入tab内容":autofocus="true"@blur="row.inputVisible = false"/>
输入框回车新增tag:
1:监听input的回车事件
<van-fieldv-model="row.inputValue"v-if="row.inputVisible"placeholder="请输入tab内容":autofocus="true"@blur="row.inputVisible = false"@keydown.enter="enterFn(row)"/>
2.事件处理函数
// 新增tagenterFn(row) {// console.log(row, 0)// 非空判断if (row.inputValue.trim().length === 0) {return alert('请输入数据')}// 添加row.tags.push(row.inputValue)row.inputValue = ''},
input框 esc清空内容:
@keydown.esc="row.inputValue = ''"
Vuex基础–介绍:
为什么会有vuex?
Vuex 是一个专为 Vue.js 应用程序开发的状态 管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
Vuex是采用集中式管理组件依赖的共享数据的一个工具,可以解决不同组件数据共享问题
结论:
- 修改state状态必须通过mutations
- mutations只能执行同步代码,类似ajax,定时器之类的代码不能在mutations中执行
- 执行异步代码,通过actions,然后将数据提交给mutations才可以完成
- state的状态即共享数据可以在组件中引用
- 组件中可以调用aciton
Vuex的使用:
下载:
yarn add vuex@3.5.1
src/store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)const store = new Vuex.Store({})export default store
main.js引入:
import store from './store/index'
new Vue({router,store,render: (h) => h(App),
}).$mount('#app')
Vuex–state:
state 放置所有公共状态的属性,如果你有一个公共状态数据,你只需要定义在state对象中
定义state:
// 初始化vuex对象
const store = new Vuex.Store({state: {// 管理数据count: 0,},
})
如何在组件中获取count?
原始形式–插值表达式:
Good.vue:
组件中可以使用this.$store 获取到vuex中的store对象实例,可通过state属性获取count
<div>state的数据{{ $store.state.count }}</div><script>
export default {created() {console.log(this.$store.state.count)},
}
</script>
辅助函数–mapState:
mapState是辅助函数,帮助我们把store中的数据映射到组件的计算属性中,属于一种方便用法
List.vue:
第一步:导入mapState
第二步:采用数组形式引入state属性
第三部:利用展开运算法将导出的状态映射给计算属性
<template><div>list{{ count }}</div>
</template><script>
//导入mapState
import { mapState } from 'vuex'
export default {
//采用数组形式引入state属性
//利用展开运算法将导出的状态映射给计算属性computed: {...mapState(['count']),// 类似于// count() {// return this.$store.state.count// },},
}
</script>
vuex–mutations:
state数据的修改只能通过mutatons,并且mutations必须是同步更新,目的是形成数据快照
数据快照:一次mutation的执行,立刻得到一种视图状态,因为是立刻,所以必须是同步
定义mutations:
const store = new Vuex.Store({state: {// 管理数据count: 70,},// d定义mutationsmutations: {}
})
mutations是一个对象,对象中存放修改state的方法
mutations: {// 方法里的参数 第一个参数是当前store的state属性// 第二个参数payload 运输参数 调用mutations的时候 可以传递参数addCount(state) {state.count += 1},addCountN(state, n) {state.count += n},},
如何在组件中调用mutations?
原始形式 $store:
Good.vue:
<template><div>state的数据{{ $store.state.count }}<hr /><button @click="addCount">+1</button></div>
</template><script>
export default {created() {console.log(this.$store.state.count)},methods: {addCount() {// 调用store中的mutations 提交给mutations// commit('mutations方法名',参数)this.$store.commit('addCount')},},
}
</script>
带参数的传递:
<button @click="addCountN(9)">+n</button>methods: {addCountN(n) {this.$store.commit('addCountN', n)},},
辅助参数–mapMutations:
mapMutations和mapState很像,把位于mutations中的方法提取出来,可以将它导入到methods中
<template><div>list{{ count }}<button @click="addCount">+1</button><button @click="addCountN(8)">+n</button></div>
</template><script>
import { mapState, mapMutations } from 'vuex'
export default {computed: {...mapState(['count']),// 类似于// count() {// return this.$store.state.count// },},methods: {// 把位于mutations中的方法提取出来,可以将它导入到methods中...mapMutations(['addCount', 'addCountN']),},
}
</script>
vuex-actions:
state是存放数据的,mutations是同步更新数据,actions是负责进行异步操作
定义actions:
actions: {// 获取异步的数据 context 表示当前的store实例// 可以通过context.state 获取状态// 也可以通过context.commit 来提交mutations// 也可以context.dispatch调用其它的actiongetAsyncCount(context) {setTimeout(() => {// 1秒后,要去修改statecontext.commit('addCount')}, 1000)},},
原始调用–$store.dispatch:
addAsyncCount() {this.$store.dispatch('getAsyncCount')},
传参函数:
- actions
actions: {getAsyncCountN(context, n) {setTimeout(() => {// 1秒后,要去修改statecontext.commit('addCountN', n)}, 1000)},},
- 调用
<button @click="addAsyncCountN(6)">+nAsync</button>addAsyncCountN(m) {this.$store.dispatch('getAsyncCountN', m)},
辅助函数–mapActions
actions也有辅助函数,可以将action导入到组件中
<template><div>list{{ count }}<button @click="getAsyncCount">+1Async</button><button @click="getAsyncCountN(6)">+nAsync</button></div>
</template><script>
import { mapState, mapActions } from 'vuex'
export default {computed: {...mapState(['count']),// 类似于// count() {// return this.$store.state.count// },},methods: {...mapActions(['getAsyncCount', 'getAsyncCountN']),},
}
</script>
vuex-getters:
除了state之外,有时还需要从state中派生出一些状态,这些状态是依赖state的,会用到getters
state中定义了list,是1~10的数组
state: {list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],},
组件中需要显示大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它
定义getters:
getters: {// getters函数第一个参数是state// 必须要有返回值filterList: (state) => state.list.fiter((item) => item > 5),},
原始方式–getters:
<ul><li v-for="(item, index) in $store.getters.filterList">{{ item }}</li></ul>
辅助函数–mapGetters:
<template><div>list{{ count }}<ul><li v-for="(item, index) in filterList" :key="index">{{ item }}</li></ul></div>
</template><script>
import { mapGetters } from 'vuex'
export default {computed: {...mapGetters(['filterList']),}
}
</script>
vuex-Module:
为什么会有模块化:
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂的时候,store对象就会变得相当臃肿。
我们把所有的状态都放在state中,项目变得越来越大的时候,vuex会变得越来越难以维护------Vuex模块化
模块化简单应用:
- 定义两个模块 countModule 和 arrModule
- countModule 管理状态count
- arrModule 管理状态 arr
const store = new Vuex.Store({modules: {countModule: {state: {count: 0,},},arrModule: {state: {arr: [2, 4, 67, 23, 12, 34, 68, 76],},},},
})
Good.vue组件中,分别显示count 和arr:
<div>count:{{ $store.state.countModule.count }}</div><div><ul><li v-for="item in $store.state.arrModule.arr">{{ item }}</li></ul></div>
注意:获取子模块的状态,通过$store.state.模块名.属性名来获取
上面的获取有点麻烦,可以getters 来改变一下
//getters是根级别的getters
getters: {count: (state) => state.countModule.count,arr: (state) => state.arrModule.arr,},
组件中通过mapGetters获取数据
<template><div><div>count:{{ count }}</div><ul><li v-for="(item, index) in arr" :key="index">{{ item }}</li></ul></div>
</template><script>
import { mapGetters } from 'vuex'
export default {computed: {...mapGetters(['count', 'arr']),},
}
</script>
模块化中的命名空间:
namespaced 命名空间
默认情况下,模块内部的action,mutation,getter 是注册在全局命名空间的,—这样使得多个模块对同一mutation或者action作出响应
我们想要保证内部模块的高封闭性,可以采用namespaced来进行设置
const store = new Vuex.Store({modules: {countModule: {namespaced: true, //命名空间state: {count: 0,},action: {},mutations: {addCount (state) {// 这里的state表示的是countModule的statestate.count++},},getters: {},}
})
调用:
方法一: 直接调用-带上模块的属性名路径
this.$store.commit('countModule/addCount')
方法二:辅助函数–带上模块的属性名路径
<button @click="add">+1</button>methods: {...mapMutations(['countModule/addCount', 'arrModule/delArr']),add() {this['countModule/addCount']()}},
v-model语法糖:
父子组件通信,单项的,很多时候需要双向通信
父组件使用 msg.sync=“aa”,子组件使用 $emit(“update:msg”,参数)
父组件
<Good :msg.sync="test"></Good>
子组件
<template><div>{{ msg }}<button @click="fn">更改msg</button></div>
</template><script>
export default {props: {msg: {default: '',},},methods: {fn() {this.$emit('update:msg', 'world')},},
}
</script>
父组件传值传对象:
父组件使用:v-model
- 第一种
vue2.x中,v-model语法糖的简写
<List :msg="msg" @ccEvent="fn"/>
父组件App.vue
<template><div><List v-model="test"></List></div>
</template><script>
import List from './views/List.vue'
export default {components: {List,},data() {return {test: 'hello',}},
}
</script>
子组件 List.vue
<template><div>{{ '值为' + msg }}<button @click="fn">更改父组件的值</button></div>
</template><script>
export default {// model 有2个属性,// prop属性将msg作为该组件被使用时,v-model能取到的值// event 就是自定义事件 是emit('ccEvent')的时候 参数的值就是父组件v-model收到的值model: {prop: 'msg',event: 'ccEvent',},props: {msg: '',},methods: {fn() {this.$emit('ccEvent', this.msg + 2)},},
}
</script>
- 第二种
<List :value="msg" @input="fn"/>
父组件 App.vue
<List v-model="test"></List>
子组件 List.vue
<template><div>{{ '值为' + value }}<button @click="fn">更改父组件的值</button></div>
</template><script>
export default {props: {value: {//必须使用valuedefault: '',},},methods: {fn() {// 这里必须是input发送数据,// 发送数据会被父级v-model="test" 接收到,// 再被value=test传回来this.$emit('input', this.value + 2)},},
}
</script>
st: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
},
组件中需要显示大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它##### 定义getters:
getters: {
// getters函数第一个参数是state
// 必须要有返回值
filterList: (state) => state.list.fiter((item) => item > 5),
},
##### 原始方式--getters:
- {{ item }}
辅助函数–mapGetters:
<template><div>list{{ count }}<ul><li v-for="(item, index) in filterList" :key="index">{{ item }}</li></ul></div>
</template><script>
import { mapGetters } from 'vuex'
export default {computed: {...mapGetters(['filterList']),}
}
</script>
vuex-Module:
为什么会有模块化:
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象,当应用变得非常复杂的时候,store对象就会变得相当臃肿。
我们把所有的状态都放在state中,项目变得越来越大的时候,vuex会变得越来越难以维护------Vuex模块化
[外链图片转存中…(img-UdJZ2hDM-1728799233543)]
模块化简单应用:
- 定义两个模块 countModule 和 arrModule
- countModule 管理状态count
- arrModule 管理状态 arr
const store = new Vuex.Store({modules: {countModule: {state: {count: 0,},},arrModule: {state: {arr: [2, 4, 67, 23, 12, 34, 68, 76],},},},
})
Good.vue组件中,分别显示count 和arr:
<div>count:{{ $store.state.countModule.count }}</div><div><ul><li v-for="item in $store.state.arrModule.arr">{{ item }}</li></ul></div>
注意:获取子模块的状态,通过$store.state.模块名.属性名来获取
上面的获取有点麻烦,可以getters 来改变一下
//getters是根级别的getters
getters: {count: (state) => state.countModule.count,arr: (state) => state.arrModule.arr,},
组件中通过mapGetters获取数据
<template><div><div>count:{{ count }}</div><ul><li v-for="(item, index) in arr" :key="index">{{ item }}</li></ul></div>
</template><script>
import { mapGetters } from 'vuex'
export default {computed: {...mapGetters(['count', 'arr']),},
}
</script>
模块化中的命名空间:
namespaced 命名空间
默认情况下,模块内部的action,mutation,getter 是注册在全局命名空间的,—这样使得多个模块对同一mutation或者action作出响应
[外链图片转存中…(img-xaBI50xX-1728799233543)]
我们想要保证内部模块的高封闭性,可以采用namespaced来进行设置
const store = new Vuex.Store({modules: {countModule: {namespaced: true, //命名空间state: {count: 0,},action: {},mutations: {addCount (state) {// 这里的state表示的是countModule的statestate.count++},},getters: {},}
})
调用:
方法一: 直接调用-带上模块的属性名路径
this.$store.commit('countModule/addCount')
方法二:辅助函数–带上模块的属性名路径
<button @click="add">+1</button>methods: {...mapMutations(['countModule/addCount', 'arrModule/delArr']),add() {this['countModule/addCount']()}},
v-model语法糖:
父子组件通信,单项的,很多时候需要双向通信
父组件使用 msg.sync=“aa”,子组件使用 $emit(“update:msg”,参数)
父组件
<Good :msg.sync="test"></Good>
子组件
<template><div>{{ msg }}<button @click="fn">更改msg</button></div>
</template><script>
export default {props: {msg: {default: '',},},methods: {fn() {this.$emit('update:msg', 'world')},},
}
</script>
父组件传值传对象:
父组件使用:v-model
- 第一种
vue2.x中,v-model语法糖的简写
<List :msg="msg" @ccEvent="fn"/>
父组件App.vue
<template><div><List v-model="test"></List></div>
</template><script>
import List from './views/List.vue'
export default {components: {List,},data() {return {test: 'hello',}},
}
</script>
子组件 List.vue
<template><div>{{ '值为' + msg }}<button @click="fn">更改父组件的值</button></div>
</template><script>
export default {// model 有2个属性,// prop属性将msg作为该组件被使用时,v-model能取到的值// event 就是自定义事件 是emit('ccEvent')的时候 参数的值就是父组件v-model收到的值model: {prop: 'msg',event: 'ccEvent',},props: {msg: '',},methods: {fn() {this.$emit('ccEvent', this.msg + 2)},},
}
</script>
- 第二种
<List :value="msg" @input="fn"/>
父组件 App.vue
<List v-model="test"></List>
子组件 List.vue
<template><div>{{ '值为' + value }}<button @click="fn">更改父组件的值</button></div>
</template><script>
export default {props: {value: {//必须使用valuedefault: '',},},methods: {fn() {// 这里必须是input发送数据,// 发送数据会被父级v-model="test" 接收到,// 再被value=test传回来this.$emit('input', this.value + 2)},},
}
</script>