Vue2
Vue实例结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const app = new Vue({ el:'#app', data:{ }, methods:{ }, computed:{ }, watch:{ } })
|
computed
一般情况下,如果只是计算简单的数字,一般直接用简写即可例如,相对于method,computed数据有缓存
1 2 3 4 5 6 7 8 9 10 11
| const app = new Vue({ el:'#app', data:{ a: 8, b: 6 }, computed:{ add(){return this.a + this.b}, sub(){return this.a - this.b} } })
|
但有时候计算涉及了复杂的拼接,并且需要额外修改,则需要使用完整写法
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
| <body> <div id="app"> 姓:<input type="text" v-model="firstName"> + 名:<input type="text" v-model="lastName"> = <span>{{ fullName }}</span><br><br> <button @click="changeName">改名卡</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { firstName: '刘', lastName: '备', }, methods: { changeName () { this.fullName = '黄忠' } }, computed: { fullName: { get () { return this.firstName + this.lastName }, // 当fullName被修改时,执行set()函数 set (value) { this.firstName = value.slice(0, 1) this.lastName = value.slice(1) } } } }) </script> </body>
|
watch
使用方法为,一般情况下,watch里需要防抖
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const app = new Vue({ data: { words: "苹果", obj: { words: "苹果", }, }, watch: { 数据属性名(newValue, oldValue) {}, "对象.属性名"(newValue, oldValue) {}, }, });
|
上面也属于简写,完整写法如下
1 2 3 4 5 6 7 8 9
| watch: { obj: { deep: true, immediate: true, handler (newValue) { } } }
|
Vue指令
插值表达式
Vue可以在插值表达式中使用变量,方法,js语句,并且这些内容是相应式的,可以实时修改
1 2 3 4 5 6 7 8
| <div id="app"> <p>{{ nickname }}</p> <p>{{ nickname.toUpperCase() }}</p> <p>{{ nickname + '你好' }}</p> <p>{{ age >= 18 ? '成年' : '未成年' }}</p> <p>{{ friend.name }}</p> <p>{{ friend.desc }}</p> </div>
|
v-text
将内容以文本的形式渲染到标签中,并覆盖原有的值,类似innerText
v-html
将文本内容作为html元素渲染,并覆盖原有的值,类似innerHTML
v-show
接受布尔值,true时为显示,false隐藏
通过控制display属性实现
v-if
接受布尔值,true时创建元素,false时不创建元素
当为false时,v-show是隐藏元素,v-if是直接没有这个元素
v-else v-else-if
这两个不能单独使用,必须配合v-if
与js中的判断语句相同,作为判断元素是否显示的条件补充
v-on
对操作进行监听,例如对点击事件进行监听
1 2 3
| <button v-on:click="count--">-</button> // 简写 <button @click="count--">-</button>
|
可以绑定方法并传递参数,例如下面这个例子,当按下对应按钮就会扣除对应的金额:
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
| <!DOCTYPE html> <body> <div id="app"> <div class="box"> <h3>小黑自动售货机</h3> <button @click="buy(5)">可乐5元</button> <button @click="buy(10)">咖啡10元</button> <button @click="buy(8)">牛奶8元</button> </div> <p>银行卡余额:{{ money }}元</p> </div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { money: 100 }, methods: { buy (price) { this.money -= price } } }) </script> </body>
|
@keyup.enter
当按下回车时触发,例如搜索框
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <body> <div id="app"> <h3>@keyup.enter → 监听键盘回车事件</h3> <input @keyup.enter="fn" v-model="username" type="text"> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { username: '' }, methods: { fn () { console.log('键盘回车的时候触发', this.username) } } }) </script> </body>
|
@event.stop
阻止冒泡
@event.prevent
阻止默认行为
@event.stop.prevent
可以连用,阻止冒泡和默认行为
v-bind
对标签的属性进行绑定操作,例如对图片元素绑定src、title
1 2 3
| <img v-bind:src="imgUrl" v-bind:title="msg" alt=""> // 简写 <img :src="imgUrl" :title="msg" alt="">
|
可以通过绑定class和style来控制元素的样式
1 2 3 4 5 6
| // 当为对象时,通过布尔值来控制样式 <div class="box" :class="{ pink: true, big: false }">黑马程序员</div> // 当为数列时全部应用 <div class="box" :class="['pink', 'big']">黑马程序员</div> // 也可以直接操作style <div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
|
可以通过这样来操控被点击元素高亮:
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 59 60 61 62
| <!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>Document</title> <style> * { margin: 0; padding: 0; } ul { display: flex; border-bottom: 2px solid #e01222; padding: 0 10px; } li { width: 100px; height: 50px; line-height: 50px; list-style: none; text-align: center; } li a { display: block; text-decoration: none; font-weight: bold; color: #333333; } li a.active { background-color: #e01222; color: #fff; }
</style> </head> <body>
<div id="app"> <ul> <li v-for="(item, index) in list" :key="item.id" @click="activeIndex = index"> <a :class="{ active: index === activeIndex }" href="#">{{ item.name }}</a> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { activeIndex: 2, list: [ { id: 1, name: '京东秒杀' }, { id: 2, name: '每日特价' }, { id: 3, name: '品类秒杀' } ] } }) </script> </body> </html>
|
v-for
用于创建多个元素,其模版如下:
1 2 3 4 5 6
| (item, index) in arr
|
示例:
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
| <body> <div id="app"> <h3>小黑的书架</h3> <ul> <li v-for="(item, index) in booksList" :key="item.id"> <span>{{ item.name }}</span> <span>{{ item.author }}</span> <button @click="del(item.id)">删除</button> </li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { booksList: [ { id: 1, name: '《红楼梦》', author: '曹雪芹' }, { id: 2, name: '《西游记》', author: '吴承恩' }, { id: 3, name: '《水浒传》', author: '施耐庵' }, { id: 4, name: '《三国演义》', author: '罗贯中' } ] }, methods: { del (id) { this.booksList = this.booksList.filter(item => item.id !== id) } } }) </script> </body>
|
:key=""
是vue中的一个特殊属性,用来标识某一个元素,保证数据变化时保持排序,所以需要绑定一个固定的值
v-model
专门给表单用的,实时将表单中的内容更新到变量中
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
| <body> <div id="app"> 账户:<input type="text" v-model="username"> <br><br> 密码:<input type="password" v-model="password"> <br><br> <button @click="login">登录</button> <button @click="reset">重置</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { username: '', password: '' }, methods: { login () { console.log(this.username, this.password) }, reset () { this.username = '' this.password = '' } } }) </script> </body>
|
v-model原理
v-model其实是一个语法糖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div class="app"> <input type="text" v-model="msg1" /> <br /> <!-- v-model的底层其实就是:value和 @input的简写 --> <!-- 注意:$event用于获取时间形参,相当于e --> <input type="text" :value="msg2" @input="msg2 = $event.target.value" /> </div> </template>
<script> export default { data() { return { msg1: '', msg2: '', } }, } </script>
<style> </style>
|
v-model.trim
去除首位空格
v-model.number
转数字
自定义指令
vue自带的指令未必能满足我们的需求,vue可以根据需求自定义指令
局部自定义指令注册
1 2 3 4 5 6 7 8 9 10 11
| <script> export default { directives: { focus: { inserted (el) { el.focus() } } } } </script>
|
全局自定义组件注册
1 2 3 4 5 6
| Vue.directive('指令名', { inserted (el) { el.focus() } })
|
指令的值
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
| <template> <div> <h1 v-color="color1">指令的值1测试</h1> <h1 v-color="color2">指令的值2测试</h1> </div> </template>
<script> export default { data() { return { color1: "red", color2: "orange", }; }, directives: { color: { // 1. inserted 提供的是元素被添加到页面中时的逻辑,即只是初始化的值 inserted(el, binding) { // console.log(el, binding.value); // binding.value 就是指令的值 el.style.color = binding.value; }, // 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑 update(el, binding) { console.log("指令的值修改了"); el.style.color = binding.value; }, }, }, }; </script>
<style></style>
|
Vue生命周期和钩子
Vue生命周期分为四个阶段
- 创建阶段:创建响应式数据
- 挂载阶段:渲染模板
- 更新阶段:修改数据,更新视图
- 销毁阶段:销毁Vue实例
每个阶段和每个阶段前都有对应的钩子对操作进行绑定,也就是一共有八个钩子
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
| const app = new Vue({ el: '#app', data: { count: 100, title: '计数器' }, beforeCreate () { console.log('beforeCreate 响应式数据准备好之前', this.count) }, created () { console.log('created 响应式数据准备好之后', this.count) },
beforeMount () { console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML) }, mounted () { console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML) },
beforeUpdate () { console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML) }, updated () { console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML) },
beforeDestroy () { console.log('beforeDestroy, 卸载前') console.log('清除掉一些Vue以外的资源占用,定时器,延时器...') }, destroyed () { console.log('destroyed,卸载后') } })
|
@vue/cli脚手架开发
脚手架实现和部件介绍
使用步骤:
- 全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g
- 查看vue/cli版本: vue --version
- 创建项目架子:vue create projectName(项目名不能使用中文)
- 启动项目:yarn serve 或者 npm run serve(命令不固定,找package.json)
组件目录如下
index.html
模版html文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <!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>
<div id="app"> </div>
</body> </html>
|
main.js
功能实现入口,用于导入vue根组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import Vue from 'vue' import App from './App.vue'
Vue.config.productionTip = false
new Vue({ render: h => h(App),
}).$mount('#app')
|
App.vue
根组件,所有子组件的归宿
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
| <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template>
<script> // 导入子组件 import HelloWorld from './components/HelloWorld.vue'
export default { // 向外导出名字 name: 'App', components: { // 组件注册 HelloWorld } } </script>
<style> /*样式,可添加lang="less"添加预处理器,前提需要对应的包 yarn add less less-loader -D*/ #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
|
普通组件注册
局部注册
在compone文件下编写.vue文件,其他组件需要先导入再注册才能使用组件
.vue文件需要采用大驼峰命名法,而且需要有足够的长度才行
下面是在App.vue中使用子组件的注册使用方法
1 2 3 4 5 6 7 8 9 10 11
| <script> import HtmlHeader from './components/HtmlHeader' import HtmlMain from './components/HtmlMain' export default { components: { // '组件名': 组件对象 HtmlHeader: HtmlHeader, HtmlMain } } </script>
|
全局注册
要求与局部注册相同,但全局注册要在main.js中注册,注册之后所有组件都可以直接使用,一般用于多个组价都拥有的组件
1 2
| import HtmlButton from './components/HtmlButton' Vue.component('HmButton', HmButton)
|
scoped样式独立
有时不同的组件有相同的类名元素,这样会发生冲突
1 2 3 4 5 6 7 8 9 10
| <style scoped> /* 1. 当前组件内标签都被添加data-v-hash值的属性 2. css选择器都被添加 [data-v-hash值] 的属性选择器 */ div{ border: 3px solid red; margin: 30px; } </style>
|
data函数
一个组件的 data 选项必须是一个函数。目的是为了:保证每个组件实例,维护独立的一份数据对象。
1 2 3 4 5 6 7 8 9
| <script> export default { data: function () { return { count: 100, } }, } </script>
|
组件通信
不同组件之间相互处理数据
父子通信
- 父组件通过 props 将数据传递给子组件
- 子组件利用 $emit 通知父组件修改更新
子组件接收父组件的数据,一共分为4步
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
| <!-- 父组件App.vue --> <template> <div class="app" style="border: 3px solid #000; margin: 10px"> 我是APP组件 <!-- 2. 将数据作为属性传给子组件--> <Son :title="myTitle"></Son> </div> </template>
<script> import Son from './components/Son.vue' export default { name: 'App', data() { return { <!-- 1. 父组件的数据 --> myTitle: '你好,Vue', } }, components: { Son, }, } </script>
<style> </style> <!-- 父组件App.vue --> <!-- ********************************************************* --> <!-- 子组件Son.vue --> <template> <div class="son" style="border:3px solid #000;margin:10px"> <!-- 4. 直接使用props的值 --> 我是Son组件 {{title}} </div> </template>
<script> export default { name: 'Son-Child', // 3. 通过props来接收 props:['title'] } </script>
<style> </style> /<!-- 子组件Son.vue -->
|
子组件通知父组件修改更新数据,分4步
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 59
| <!-- 子组件Son.vue --> <template> <div class="son" style="border: 3px solid #000; margin: 10px"> 我是Son组件 {{ title }} <!-- 1. 触发数据改变的事件 --> <button @click="changeFn">修改title</button> </div> </template>
<script> export default { name: 'Son-Child', props: ['title'], methods: { changeFn() { <!-- 2. 通知父组件更新修改,第一个值为修改的事件名,后面的为可选变量 --> this.$emit('changTitle','修改后的标题') }, }, } </script>
<style> </style> <!-- 子组件Son.vue --> <!-- ********************************************************* --> <!-- 父组件App.vue --> <template> <div class="app" style="border: 3px solid #000; margin: 10px"> 我是APP组件 <!-- 3.父组件对子组件的消息进行监听,并给出处理函数 --> <Son :title="myTitle" @changTitle="handleChange"></Son> </div> </template>
<script> import Son from './components/Son.vue' export default { name: 'App', data() { return { myTitle: '你好,Vue', } }, components: { Son, }, methods: { <!-- 4.更新数据 --> handleChange(newTitle) { this.myTitle = newTitle }, }, } </script>
<style> </style> <!-- 父组件App.vue -->
|
props校验
为了保证数据不出问题,需要对传来的数据进行校验,这时候就需要将props改为对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script> export default { props: { 校验的属性名: { type: 类型, // Number String Boolean ... required: true, // 是否必填 default: 默认值, // 默认值 validator(value) { // 自定义校验逻辑 return 是否通过校验; }, }, }, }; </script>
|
props和data数据的区别
props和data都可以为组件提供数据,但是他们实际上是有区别的
- props的数据不是自己的,不能随意更改,只能向父组件发送更改的请求,利用父组件的方法进行更改
- data的数据是自己的,可以随意更改
这其中的原因是Vue的单向数据流管理,每个组件只能管好自己的数据,通过props影响子组件的数据,如果数据可以随意修改就难以管理
父子组件表单模块
有的时候子模块只负责让用户能够选择数据,而真正的数据存放在父组件中,这个时候就不能直接使用v-model绑定数据,而要通过原理下手
对于子组件
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
| <template> <div> <select :value="value" @change="selectCity"> <!-- 从父组件处获取值并显示,当改变值时向父组件发送数据更改请求--> <option value="101">北京</option> <option value="102">上海</option> <option value="103">武汉</option> <option value="104">广州</option> <option value="105">深圳</option> </select> </div> </template>
<script> export default { props: { value: String, }, methods: { selectCity(e) { this.$emit('input', e.target.value) }, }, } </script>
<style> </style>
|
父组件修改数据
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
| <template> <div class="app"> <!-- 完整写法,数据用:绑定,修改用@绑定事件,通过$event拿到形参 <BaseSelect :selectId="selectId" @changeCity="selectId = $event" ></BaseSelect> --> <BaseSelect v-model="selectId" ></BaseSelect> </div> </template>
<script> import BaseSelect from './components/BaseSelect.vue' export default { data() { return { selectId: '102', } }, components: { BaseSelect, }, } </script>
<style> </style>
|
非父子通信
对于非父子组件的简易消息传输,使用EventBus进行通信,在复杂场景下使用vuex共享数据
实际上非父子通信利用的是创建一个所有组件都能访问的vue实例,因此先要在创建utils/EventBus.js
1 2 3
| import Vue from 'vue' const Bus = new Vue() export default Bus
|
例如让两个App.vue下的两个同级子组件BaseA.vue和BaseB.vue进行通信
首先让BaseA监听事件总线中的消息:
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
| <template> <div class="base-a"> 我是A组件(接受方) <p>{{msg}}</p> </div> </template>
<script> import Bus from '../utils/EventBus' export default { data() { return { msg: '', } }, <!-- 在创建完成之后就进行消息监听sendMsg--> created() { Bus.$on('sendMsg', (msg) => { this.msg = msg }) }, } </script>
<style scoped> .base-a { width: 200px; height: 200px; border: 3px solid #000; border-radius: 3px; margin: 10px; } </style>
|
BaseB要向BaseA发送消息
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
| <template> <div class="base-b"> <div>我是B组件(发布方)</div> <button @click="sendMsgFn">发送消息</button> </div> </template>
<script> import Bus from '../utils/EventBus' export default { methods: { sendMsgFn() { <!-- 发送sendMsg消息 --> Bus.$emit('sendMsg', '今天天气不错,适合旅游') }, }, } </script>
<style scoped> .base-b { width: 200px; height: 200px; border: 3px solid #000; border-radius: 3px; margin: 10px; } </style>
|
跨层级通信
子组件和祖先组件进行通信使用provide和inject跨层级共享数据
祖先组件提供数据
1 2 3 4 5 6 7 8 9 10
| export default { provide () { return { color: this.color, userInfo: this.userInfo, } } }
|
子组件接收数据
1 2 3 4 5 6
| export default { inject: ['color','userInfo'], created () { console.log(this.color, this.userInfo) } }
|
但是一般非父子通信都会使用vuex进行数据共享,因为跨层级通信存在缺陷
- provide提供的简单类型的数据不是响应式的,复杂类型数据是响应式
- 子组件通过inject获取的数据,不能在自身组件内修改
.sync修饰符
实现父子组件数据双向绑定,和v-model不同的的是.sync可以用于表单之外的属性,例如通过v-show控制弹框
父组件的数据控制子组件的显示隐藏
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
| <template> <div class="app"> <button @click="openDialog">退出按钮</button> <!-- isShow.sync => :isShow="isShow" @update:isShow="isShow=$event" --> <!-- 共享数据并且接收数据修改 --> <BaseDialog :isShow.sync="isShow"></BaseDialog> </div> </template>
<script> import BaseDialog from './components/BaseDialog.vue' export default { data() { return { isShow: false, } }, methods: { openDialog() { this.isShow = true }, }, components: { BaseDialog, }, } </script>
<style> </style>
|
子组件进行操作
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
| <template> <div class="base-dialog-wrap" v-show="isShow"> <div class="base-dialog"> <div class="title"> <h3>温馨提示:</h3> <button class="close" @click="closeDialog">x</button> </div> <div class="content"> <p>你确认要退出本系统么?</p> </div> <div class="footer"> <button>确认</button> <button>取消</button> </div> </div> </div> </template>
<script> export default { props: { isShow: Boolean, }, methods:{ closeDialog(){ <!-- 固定写法updata:修改的数据名 --> this.$emit('update:isShow',false) } } } </script>
<style> </style>
|
ref和$refs获取元素
js中document.querySelect(’.box’) 查找的范围是整个页面元素,而vue是组件化开发,继续用这个方法查找元素显然是不合适的
我们用ref标记需要查找的元素
1
| <div class="base-chart-box" ref="baseChartBox">子组件</div>
|
在dom元素完成渲染之后获取,使用这种方法只会获取当前组件的元素
1 2 3
| mounted(){ var myChart = this.$refs.baseChartBox }
|
如果是一个组件被绑定了ref,那么可以通过$refs获取组件方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <BaseForm ref="BaseForm"></BaseForm> </template>
<script> export default { methods: { // 通过子组件的方法获取表单数据 getFormData() { return this.$refs.BaseForm.getFormData(); }, }, }; </script>
<style></style>
|
vue异步更新和.$nextTick
有的时候当增加dom元素之后想要操作对应元素,会操作失败,因为vue的dom元素更新是异步的,这时候需要用到.$nextTick,这个方法会等待dom元素更新完成之后再进行对dom元素的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <template> <BaseForm ref="BaseForm" v-if="handleEdit"></BaseForm> </template>
<script> export default { data() { return { isCreated: false, }; }, methods: { handleEdit() { this.isCreated = true; this.$nextTick(() => { this.$refs.BaseForm.focus(); }); }, }, }; </script>
<style></style>
|
插槽
默认插槽
当一个组件多次被使用而只有部分内容发送变化时,例如弹窗警告,就需要用到插槽
基本语法为:
- 组件内需要定制的结构部分,改用**
<slot>
默认内容</slot>
**占位
- 给组件标签内传入内容,可以传入纯文本、html标签、组件,替换slot
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <!-- MyDialog组件--> <template> <div class="dialog"> <div class="dialog-header"> <h3>友情提示</h3> <span class="close">✖️</span> </div>
<div class="dialog-content"> <!-- 1. 在需要定制的位置,使用slot占位 --> <slot>我是默认内容</slot> </div> <div class="dialog-footer"> <button>取消</button> <button>确认</button> </div> </div> </template>
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <!-- App.vue使用 --> <template> <div> <!-- 2. 在使用组件时,组件标签内填入内容 --> <MyDialog> <div>你确认要删除么</div> </MyDialog>
<MyDialog> <p>你确认要退出么</p> </MyDialog> </div> </template>
|
具名插槽
有的时候一个插槽不够用,就需要给每个插槽取一个名字,调用的时候按照名字传值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!-- MyDialog组件--> <template> <div class="dialog"> <div class="dialog-header"> <!-- 一旦插槽起了名字,就是具名插槽,只支持定向分发 --> <slot name="head">页眉</slot> </div>
<div class="dialog-content"> <slot name="content">内容</slot> </div> <div class="dialog-footer"> <slot name="footer">页脚</slot> </div> </div> </template>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <!-- App.vue使用 --> <template> <div> <MyDialog> <!-- 需要通过template标签包裹需要分发的结构,包成一个整体 --> <template v-slot:head> <div>我是大标题</div> </template> <template v-slot:content> <div>我是内容</div> </template> <!-- 可以直接简写成一个# --> <template #footer> <button>取消</button> <button>确认</button> </template> </MyDialog> </div> </template>
|
作用域插槽
可以给插槽绑定上数据,可以传值
1 2
| <!-- 通过给子组件slot赋予属性名 --> <slot :id="item.id" msg="测试文本"></slot>
|
1 2
| <!-- 在父组件中接收数据,所有的数据都会以对象的形式储存,组件默认名为default--> <template #default="obj"></template>
|
路由
创建单页面程序,页面能按需求更新
VueRouter的使用
-
下载 VueRouter 模块到当前工程,版本3.6.5
1
| yarn add vue-router@3.6.5
|
-
在src目录下建立router目录创建index.js文件管理工程的路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import Find from '@/views/Find' import My from '@/views/My' import Friend from '@/views/Friend'
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)
const router = new VueRouter({ routes: [ { path: '/find', component: Find }, { path: '/my', component: My }, { path: '/friend', component: Friend }, ] })
export default router
|
-
在src/views文件夹中存放路由界面
-
App.vue中放置路由出口,即组件所展示的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <div> <div class="footer_wrap"> <a href="#/find">发现音乐</a> <a href="#/my">我的音乐</a> <a href="#/friend">朋友</a> </div> <div class="top"> <!-- 路由出口 → 匹配的组件所展示的位置 --> <router-view></router-view> <!--<router-view/>自闭和 --> </div> </div> </template>
<script> export default {}; </script>
|
- main.js中注册
1 2 3 4 5 6 7 8 9 10
| import Vue from 'vue' import App from './App.vue' import router from './router/index'
Vue.config.productionTip = false
new Vue({ render: h => h(App), router }).$mount('#app')
|
导航链接
如果采取传统JS写法要高亮当前导航栏要不断添加和移除样式,为此Vue专门有解决方法
1 2 3 4 5 6 7 8 9 10 11
| <div> <div class="footer_wrap"> <router-link to="/find">发现音乐</router-link> <router-link to="/my">我的音乐</router-link> <router-link to="/friend">朋友</router-link> </div> <div class="top"> <!-- 路由出口 → 匹配的组件所展示的位置 --> <router-view></router-view> </div> </div>
|
使用router-link跳转后,当前点击的链接默认加了两个class的值 router-link-exact-active
和 router-link-active
- router-link-active:模糊匹配,其子路由都会应用该样式,如/my/a、/my/b都起作用,多用这个类名
- router-link-exact-active:精准匹配
也可以自定义这两个名称,在创建路由对象时可以加上:
1 2 3 4 5
| const router = new VueRouter({ routes: [...], linkActiveClass: "类名1", linkExactActiveClass: "类名2" })
|
跳转传参
现在我们在搜索页点击了热门搜索链接,跳转到详情页,需要把点击的内容带到详情页
查询参数传参
传递参数写法:
1
| <router-link to="/path?参数名=值"></router-link>
|
接收参数的一方:
动态路由传参
动态路由也可以传多个参数,但一般只传一个
首先创建路由时就需要配置好,?表示可选参数
1 2 3 4 5 6 7 8 9
| const router = new VueRouter({ routes: [ ..., { path: '/search/:words?', component: Search } ] })
|
然后需要发送参数的一方直接写要传递的内容
1
| <router-link to="/search/前端培训">前端培训</router-link>
|
接收的一方接受参数,最后需要和创建路由时的参数名一致
1
| <p>搜索关键字: {{ $route.params.words }} </p>
|
路由重定向
网页打开时默认是根路径,但是一般主页在/home中,没有匹配项就会出现空白,就需要用到重定向
或者错误的页面会提示没有该界面
1 2 3 4 5 6 7 8 9
| const router = new VueRouter({ routes: [ { path: '/', redirect:'/home'}, { path: '/home', component: Home }, { path: '/about', component: About }, { path: '/contact', component: Contact }, { path: '*', component: NotFind} ] })
|
路由模式
路由的路径看起来不自然, 有#,能否切成真正路径形式?
1 2 3 4
| const router = new VueRouter({ mode:'histroy', routes:[] })
|
路由跳转和传参
实现点击搜索按钮传递参数
- path路径跳转(简易方便)
- name路径跳转(需要在注册路由时添加name)
步骤:
- 按钮绑定点击事件
- 添加method方法
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| <template> <div class="home"> <div class="logo-box"></div> <div class="search-box"> <input v-model="inpValue" type="text"> <button @click="goSearch">搜索一下</button> </div> <div class="hot-link"> 热门搜索: <router-link to="/search/黑马程序员">黑马程序员</router-link> <router-link to="/search/前端培训">前端培训</router-link> <router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link> </div> </div> </template>
<script> export default { name: 'FindMusic', data () { return { inpValue: '' } }, methods: { goSearch () { // 1. 通过路径的方式跳转 // (1) this.$router.push('路由路径') [简写] // this.$router.push('路由路径?参数名=参数值') // this.$router.push('/search') // this.$router.push(`/search?key=${this.inpValue}`) // this.$router.push(`/search/${this.inpValue}`)
// (2) this.$router.push({ [完整写法] 更适合传参 // path: '路由路径' // query: { // 参数名: 参数值, // 参数名: 参数值 // } // }) // this.$router.push({ // path: '/search', // query: { // key: this.inpValue // } // }) // this.$router.push({ // path: `/search/${this.inpValue}` // })
// 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径 // this.$router.push({ // name: '路由名' // query: { 参数名: 参数值 }, // params: { 参数名: 参数值 } // }) this.$router.push({ name: 'search', // query: { // key: this.inpValue // } params: { words: this.inpValue } }) } } } </script>
|
二级路由
首先需要配置:
1 2 3 4 5 6 7 8 9 10
| { path: '/', component: Layout, children: [ { path: '/home', component: Home }, { path: '/category', component: Cartgory }, { path: '/cart', component: Cart }, { path: '/user', component: User } ] }
|
然后在对应的一级路由配置路由出口即可
VueCli自定义项目创建
1.安装脚手架 (已安装)
2.创建项目
1
| vue create hm-exp-mobile
|
配置项介绍:
- Babel:es语法降级
- Router:路由
- CSS Pre-Processors:css预处理器
- Linter/Formatter:代码格式校验
ESlint代码规范
一般使用标准规范:
- 单引号
- 无分号
Vuex状态管理工具
管理多组件共享数据,如购物车这样包含多个组件的功能
Vuex创建仓库
-
安装vuex
-
创建新建 store/index.js
存放 vuex配置,类似router
1 2 3 4 5 6 7 8 9 10 11 12
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store()
export default store
|
-
在main.js导入挂载到vue示例上
1 2 3 4 5 6 7 8 9 10
| import Vue from 'vue' import App from './App.vue' import store from './store'
Vue.config.productionTip = false
new Vue({ render: h => h(App), store }).$mount('#app')
|
state提供数据
1 2 3 4 5 6 7 8 9 10
| const store = new Vuex.Store({ state: { count: 101 } })
|
访问数据
$store直接访问
-
模板中使用
1
| <h1>state的数据 - {{ $store.state.count }}</h1>
|
-
组件逻辑中使用
1 2 3 4 5 6 7 8
| <h1>state的数据 - {{ count }}</h1>
computed: { count () { return this.$store.state.count } }
|
-
js文件中使用
1 2
| import store from "@/store" console.log(store.state.count)
|
辅助函数mapState
mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便的用法
-
第一步:导入mapState (mapState是vuex中的一个函数)
1
| import { mapState } from 'vuex'
|
-
第二步:采用数组形式引入state属性
1 2 3 4
| count () { return this.$store.state.count }
|
-
第三步:利用展开运算符将导出的状态映射给计算属性
1 2 3
| computed: { ...mapState(['count', 'title']) }
|
1
| <div> state的数据:{{ count }}</div>
|
修改Vuex中的数据
vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据,通过开启严格模式直接修改state会报错
开启严格模式
1 2 3 4 5 6 7 8
| const store = new Vuex.Store( { strict:true, state:{ title:"标题“ } } )
|
同步修改mutations
state数据的修改只能通过mutations,并且mutations必须是同步的,同样的修改数据要通过调用其中的方法进行修改
注意:
- 第一个参数固定为state
- 传入多个参数要使用对象传入
- mutations中不能存在异步操作,异步操作需放到actions中
1 2 3 4 5 6 7 8 9 10 11 12 13
| const store = new Vuex.Store({ state: { count: 0, }, mutations: { addCount(state) { state.count += 1; }, }, });
|
组件调用
1 2 3 4 5
| methods: { handleInput () { this.$store.commit('addCount') } }
|
辅助函数mapMutations
更加简单的调用mutations,类似mapState
-
先导入并且加入到方法当中
1 2 3 4
| import { mapMutations } from 'vuex' methods: { ...mapMutations(['addCount']) }
|
-
直接使用
1
| <button @click="addCount">值+1</button>
|
异步修改actions
state存放数据,mutations同步更新数据,actions则负责进行异步操作
-
定义actions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const store = new Vuex.Store({ mutations: { changeCount(state, newCount) { state.count = newCount; }, }, actions: { setAsyncCount(context, num) { setTimeout(() => { context.commit("changeCount", num); }, 1000); }, }, });
|
-
组件调用
1 2 3
| setAsyncCount () { this.$store.dispatch('setAsyncCount', 666) }
|
即actions中存放的只是异步操作,修改还是交给mutations
辅助函数mapActions
与上面的辅助函数功能相同
导入
1 2 3 4
| import { mapActions } from 'vuex' methods: { ...mapActions(['changeCountAction']) }
|
使用
1
| <button @click="changeCountAction(200)">+异步</button>
|
筛选Vuex中的数据
使用getters筛选数据:
1 2 3 4 5 6 7 8
| getters: { filterList (state) { return state.list.filter(item => item > 5) } }
|
原始使用getters
1
| <div>{{ $store.getters.filterList }}</div>
|
辅助函数mapGetters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div> <p>Count: {{ count }}</p> <p>Double Count: {{ doubleCount }}</p> </div> </template>
<script> import { mapGetters } from 'vuex';
export default { computed: { ...mapGetters(['count', 'doubleCount']) } }; </script>
|
Vuex module
当状态太庞大时,就需要进行模块化
创建 modules/user.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const state = { userInfo: { name: 'zs', age: 18 } }
const mutations = {}
const actions = {}
const getters = {}
export default { state, mutations, actions, getters }
|
创建 modules/setting.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const state = { theme: 'dark' desc: '描述真呀真不错' }
const mutations = {}
const actions = {}
const getters = {}
export default { state, mutations, actions, getters }
|
在 store/index.js
中注册
1 2 3 4 5 6 7 8 9
| import user from './modules/user' import setting from './modules/setting'
const store = new Vuex.Store({ modules:{ user, setting } })
|
获取模块内的state数据
- 直接访问 : $store.state.模块名.xxx
- mapState映射
- 默认根级别的映射 :mapState([ ‘xxx’ ])
- 子模块的映射 :mapState(‘模块名’, [‘xxx’]) - 需要在模块导出添加命名空间 namespaced:true
获取模块内的getters数据
- 直接通过模块名访问`$store.getters['模块名/xxx ']
- 通过 mapGetters 映射
- 默认根级别的映射 : mapGetters([ ‘xxx’ ])
- 子模块的映射 : mapGetters(‘模块名’, [‘xxx’]) 需要开启命名空间
获取模块内的mutations方法
默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块
- 直接通过 store 调用 $store.commit('模块名/xxx ', 额外参数)
- 通过 mapMutations 映射
- 默认根级别的映射 mapMutations([ ‘xxx’ ])
- 子模块的映射 mapMutations(‘模块名’, [‘xxx’]) - 需要开启命名空间
获取模块内的actions方法
- 直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)
- 通过 mapActions 映射
- 默认根级别的映射 mapActions([ ‘xxx’ ])
- 子模块的映射 mapActions(‘模块名’, [‘xxx’]) - 需要开启命名空间