本文已参与「新人创作礼」活动,一起开启掘金创作之路
第一章 Vue使用
脚手架文件结构介绍
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
Vue基础知识
Vue指令
v-text:用于操作纯文本 它会替代显示对应的数据对象的值 但是注意:此处为单项数据绑定,数据对象上的值改变了,插值会发生改变;但是改变插值并不会影响数据对象的值。v-text的简写就是{{}}
v-html:用于输出html,它与v-text的区别在于v-text是输出纯文本的内容,浏览器不会再对其进行html解析,但v-html会将其进行html解析
<div id="app">
<p v-html="html"><p>
</div>
let app = new Vue({
el:"#app",
data:{
html:'<span style='color:red'>v-html</span>'
}
})
v-if:如果为true,当前标签才会显示到页面上
<h2 v-if="false">欢迎来到{{name}}</h2>
<h2 v-if="1 === 1">欢迎来到{{name}}</h2> //这里的“1 === 1”为true
v-else:与v-if v-else-if 一起使用 当v-if v-else都为false时 它控制的标签才会显示
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div>
v-show:通过控制css样式 display来显示和隐藏标签 适用于标签反复显示隐藏的情况
v-for:用于循环渲染,遍历数组和对象把它们的数据展示出来
v-on:绑定事件监听 一般可以简写为@
v-bind:单向数据绑定,数据只能从data流向页面 ,页面的数据改变了并不会影响data
v-bind:src 可以简写为 :src
<div id="root">
<!-- 普通写法 -->
<!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name"><br/> -->
<!-- 上面可以简写为下面 -->
单向数据绑定:<input type="text" :value="name"><br />
双向数据绑定:<input type="text" v-model="name"><br />
<!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
<!-- <h2 v-model:x="name">你好啊</h2> -->
</div>
v-model:v-model可以实现数据的双向绑定 应用于input,textarea,select元素上创建数据的双向绑定
ref:为某个元素指定唯一的标识,vue对象通过$refs访问这个元素
// 子组件
<template>
<div>
我是子组件
</div>
</template>
<script>
export default {
data() {
return {
name: "myhua"
};
}
};
</script>
// 父组件
<template>
<div id="app">
<Son ref="myref"></Son>
</div>
</template>
<script>
import Son from "./components/son";
export default {
mounted() {
console.log(this.$refs.myref.name); //输出子组件data中的name的值:myhua
},
components: {
Son
}
};
</script>
Vue基本使用
computed和watch
两者有何区别?
computed(计算属性)是依赖已有的变量来计算的一个目标变量,大多数情况都是多个变量凑在一起计算出一个变量,并且computed具有缓存机制,依赖值不变的情况下其会直接读取缓存进行复用,computed不能进行异步操作
watch(监听属性)是监听某一个变量的变化,并执行相对应的回调函数,通常是一个变量的变化决定多个变量的变化,watch可以进行异步操作
简单记就是 一般情况下 computed是多对一 watch是一对多
Vue中的数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
<body>
<!--
1.Vue中的数据代理:
通过vm对象来代理data对象中属性(name address)的操作(读/写) vm最终改的都是data中的name address
2.Vue中数据代理的好处:
更加方便的操作data中的数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
-->
<!-- 准备好一个容器-->
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
address: '宏福科技园'
}
})
//在控制台利用vm.name = 'hhh' 修改name的值 这里是setter发挥了作用
//利用 vm._data.name === vm.name 验证通过vm去修改name是否可以修改data.name
</script>
动态绑定class和style
使用动态属性
命名时驼峰命名法
<template>
<div>
<p :class="{ black: isBlack, yellow: isYellow }">使用 class(对象)</p>
<p :class="[pink1, yellow]">使用 class (数组)</p>
<p :style="styleData">使用 style</p>
</div>
</template>
<script>
export default {
data() {
return {
//对象
isBlack: true,
isYellow: true,
//数组
pink1: "pink",
black: "black",
yellow: "yellow",
//内联样式style
styleData: {
fontSize: "40px", // 转换为驼峰式
color: "red",
backgroundColor: "#ccc", // 转换为驼峰式
},
};
},
};
</script>
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
.pink {
background-color: hotpink;
}
</style>
条件渲染
v-if v-else的用法,可使用变量,也可以使用 === 表达式
<h2 v-if="false">欢迎来到{{name}}</h2>
<h2 v-if="1 === 1">欢迎来到{{name}}</h2> //这里的“1 === 1”为true
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div>
v-if 和 v-show 的区别?
v-if是通过控制dom元素的删除和生成来实现显隐的,每一次显隐都会使组件重新跑一遍生命周期,因为显隐决定了组件的生成和销毁
v-show是通过控制dom元素的css样式来实现显隐的,不会销毁
实现一次性切换或者切换不频繁的话用 v-if v-else-if
切换功能频繁的话我们就用v-show
v-if 和 v-show 的使用场景
实现一次性切换或者切换不频繁的话用 v-if v-else-if
切换功能频繁的话我们就用v-show
循环(列表)渲染
如何遍历数组和对象?
<template>
<div>
<p>遍历数组</p>
<ul>
<li v-for="(item, index) in listArr" :key="item.id">
{{ index }} - {{ item.id }} - {{ item.title }}
</li>
</ul>
<p>遍历对象</p>
<ul>
<li v-for="(val, key, index) in listObj" :key="key">
{{ index }} - {{ key }} - {{ val.title }}
<!-- 遍历对象时key就是下面的 a b c -->
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
flag: false,
listArr: [
{ id: "a", title: "标题1" }, // 数据结构中,最好有 id ,方便使用 key
{ id: "b", title: "标题2" },
{ id: "c", title: "标题3" },
],
listObj: {
a: { title: "标题1" },
b: { title: "标题2" },
c: { title: "标题3" },
},
};
},
};
</script>
key的重要性以及key为什么不能写成index或者random?(但是实际写了也不会报错)
key的主要作用是为了高效的更新虚拟DOM。key需要使用唯一的标识 ,而index,和random并不是对象的唯一的标识(因为index对应的只是一个固定的位置 当我们在数组中间插入一条数据时 其余后面的index都会发生改变 那么就会导致重新渲染)
<div v-for="(item,index) in list" :key='index'>{{item.name}}</div>
//没有插入时的数据
const list = [
{
id: 1 ,
name:'1'
},
{
id: 2 ,
name:'2'
},
{
id: 3 ,
name:'3'
},
]
//在中间插入一条数据
const list = [
{
id: 1 ,
name:'1'
},
{
id: 4 ,
name:'4'
},
{
id: 1 ,
name:'1'
},
{
id: 1 ,
name:'1'
},
]
//那么此时
之前的数据 之后的数据
key: 0 index: 0 name: 1 key: 0 index: 0 name: 1
key: 1 index: 1 name: 2 key: 1 index: 1 name: 我是插队的那条数据
key: 2 index: 2 name: 3 key: 2 index: 2 name: 2
key: 3 index: 3 name: 3
//之后的数据除了第一条的index没有改变之后 其余的index都改变了 故只插入一条数据的情况小要引起三条数据的渲染 增加了耗能
因为Vue是数据驱动视图,通过改变数据进而达到改变视图,加上key之后Vue的虚拟DOM的diff算法更容易定位到相应的元素,避免去遍历DOM造成性能的浪费
v-for和v-if不能一起使用!
事件(event)
没有参数时默认传入的是event参数,有参数时显示在最后传入$event
<template>
<div>
<p>{{ num }}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 0,
};
},
methods: {
increment1(event) {
// eslint-disable-next-line
console.log("event1", event, "--", event.__proto__.constructor); // event是原生的 event 对象
// eslint-disable-next-line
console.log(event.target);
// eslint-disable-next-line
console.log(event.currentTarget); //事件是被注册到当前元素的,和 React 不一样
this.num++;
// 1. event 是原生的
// 2. 事件被挂载到当前元素
// 和 DOM 事件一样
},
increment2(val, event) {
// eslint-disable-next-line
console.log(event.target);
this.num = this.num + val;
},
loadHandler() {
// do some thing
},
},
mounted() {
window.addEventListener("load", this.loadHandler);
},
beforeDestroy() {
//【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
// 自己绑定的事件,需要自己销毁!!!
window.removeEventListener("load", this.loadHandler);
},
};
</script>
Vue组件间通信
props 父 => 子
props配置项
-
功能:让子组件组件接收父组件传过来的数据 父 => 子
-
传递数据:
<Demo name="xxx"/> -
接收数据:
-
第一种方式(只接收):
props:['name'] -
第二种方式(限制类型):
props:{name:String} -
第三种方式(限制类型、限制必要性、指定默认值):
props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } }
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
-
$emit 子组件 ===> 父组件 自定义事件
自定义事件
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
-
绑定自定义事件:
-
第一种方式,在父组件中:
<Demo @atguigu="test"/>或<Demo v-on:atguigu="test"/> -
第二种方式,在父组件中:
<Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('atguigu',this.test) } -
若想让自定义事件只能触发一次,可以使用
once修饰符,或$once方法。
-
-
触发自定义事件:
this.$emit('atguigu',数据) -
解绑自定义事件
this.$off('atguigu') -
组件上也可以绑定原生DOM事件,需要使用
native修饰符。 -
注意:通过
this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
任意组件间通信
全局事件总线
-
一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:
new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... }) -
使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) } -
提供数据:
this.$bus.$emit('xxxx',数据)
-
-
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
vuex
Vuex是专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
何时使用? 多个组件需要共享数据时使用
搭建过程
1.创建文件:在src下 创建store/index.js
内部代码如下
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex
Vue.use(Vuex)
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
2.在main.js中创建vm时传入store配置项
......
//引入store
import store from './store'
......
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
基本使用
1.在store下面的index.js中 初始化数据 ,配置actions,mutations
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)
//actions——用于响应组件中的动作(服务员)这里面是有业务逻辑的
const actions = {
//响应组件中加的动作 context是上下文对象 用于触发commit里面的方法 value为外部传进来的参数
jiaOdd(context,value){
console.log('actions中的jiaOdd被调用了')
//if里面默认的是不等于0即true才会进入
if (context.state.sum % 2) {
// context.state.sum += value //这样写也可以实现加的操作但是开发者工具失效了(即开发者工具里面捕获不到操作步骤了) 开发中我们是需要开发者工具的介入的 因为开发者工具一直是在跟Mutations对话
context.commit('JIA',value)
}
},
jiaWait(context,value){
console.log('actions中的jiaWait被调用了')
setTimeout(()=>{
context.commit('JIA',value)
},500)
}
}
const mutations = {
//执行加
JIAN(state,value){//state为store里面的index.js配置的state 用来给其他组件调用 这一步是真正改变state的步骤
// console.log('mutations中的JIA被调用了',state,value)
state.sum += value
}
}
//初始化数据
const state = {
sum:0
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
2.组件中读取Vuex中的数据:$store.state.sum
3.组件修改Vuex中的数据:$store.dispatch('actions中的方法名',需要传入的数据) 或者
$store.commit('mutations中的方法名',需要传入的数据) 备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit
//实际使用vuex button { margin-left: 5px; }
getters的使用
1.概念:当state里面的数据需要加工的时候再使用 可以用getters
2.使用方法:直接在store/index.js 里面追加getters配置
下面代码和上面代码的不同之处
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并暴露store
export default new Vuex.Store({
......
getters
})
//实际使用vuex
<template>
<div>
<h1>当前求和为:{{ $store.state.sum }}</h1> //这里读取了vuex里面的数据
<h1>当前求和为:{{ $store.getters.bigSum }}</h1> //这里读取了state加工后的数据
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
四个map方法的使用
引入
import { mapState, mapGetters } from "vuex"; //帮助生成代码的函数
-
mapState方法: 用于帮助我们映射
state中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), }, -
mapGetters方法: 用于帮助我们映射
getters中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) }, -
mapActions方法: 用于帮助我们生成与
actions对话的方法,即:包含$store.dispatch(xxx)的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) } -
mapMutations方法: 用于帮助我们生成与
mutations对话的方法,即:包含$store.commit(xxx)的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象
消息订阅与发布
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
-
安装pubsub:
npm i pubsub-js -
引入:
import pubsub from 'pubsub-js' -
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 } -
提供数据:
pubsub.publish('xxx',数据) -
最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)去取消订阅。 \
-