指令
v-bind (绑定特性)
简介
动态地绑定一个或多个特性,: 后的为传递的参数,简写 为( : )
绑定一个属性
<img :src="imageSrc">
<img v-bind:src="imageSrc">
<script>
export default {
data(){
return{
imageSrc:'值',
}
}
}
</script>
对象/数组 语法
class
<!--对象语法-->
如果isRed为真,那么class就会被设置为active
<div id="app" :class="{'active':isRed}"></div>
<!--数组语法-->
可以把一个数组传给 v-bind:class,应用一个 class 列表
<div id="app" :class="[classList]"></div>
也可以使用三目运算符来进行切换,v-bind可以和普通class共存
<div id="app" class="index" :class="[isActive?'ok':'no']"></div>
在数组语法中也可以使用对象语法
<div id="app" :class="[classList,{'active':isActive,'red':isRed}]"></div>
<script>
export default {
data(){
return{
isRed:false,
isActive:true,
classList:'arr1 arr2 arr3'
}
}
}
</script>
style
看着比较像CSS,但其实是一个JavaScript对象
CSS属性名可以用驼峰式或者短横线分隔来命名
绑定style时,有的css属性需要添加前缀的,Vue会自动添加
<div id="app" :style="{
fontSize: '20px',
color:'red'
}">v-bind</div>
也可以直接绑定一个样式对象
<div id="app" :style={styleObj1}>v-bind</div>
数组语法可以将多个样式对象应用到同一个元素
<div id="app" :style=[styleObj1,styleObj2]>v-bind</div>
<script>
export default {
data(){
return{
styleObj1:{
fontSize:'20px'
},
styleObj2:{
color:'red'
}
}
}
}
</script>
v-for (循环渲染)
- 注意key 唯一且独一无二,重复会导致错误
- 不要用数据索引作为key,不然会引发很多奇葩的BUG
- 永远不要把 v-if 和 v-for 同时用在同一个元素上。v-for比v-if优先级高,所以使用的话,每次v-for都会执行v-if,造成不必要的计算,影响性能。但是这情况可以使用计算属性computed**
v-on (事件)
-
监听 DOM 事件,事件类型由参数指定,简写@后面跟事件名
-
$event: 为事件对象
-
可以动态绑定事件,但是必须使用全称 v-on:[‘事件类型’]=‘事件参数’
<div v-on:[event]="btnChange($event)">点击</div> <script> export default { data(){ return{ event:'click' } }, methods:{ btnChange(e){ console.log('事件对象:',e); } } } </script>
-
可以绑定一个对象,但是不能传参,不能使用事件修饰符
<div v-on="{ mousedown: doThis, mouseup: doThat }"></div> <script> export default { methods:{ doThis(){ console.log('不能传参,不能使用事件修饰符'); }, doThat(){ console.log('不能传参,不能使用事件修饰符'); } } } </script>
事件修饰符
- [ .stop ],调用 event.stop,阻止事件冒泡
- [ .prevent ],调用 event.preventDefault(),阻止默认事件
- [ .capture ],事件捕获模式
- [ .self ],.self事件是从侦听器绑定的元素本身触发时才触发回调,也就是你绑定那个,你点那个那个才生效
- [ .once ],事件只触发一次,再次触发无效
- [ .passive ],设置 addEventListener 中的 passive 选项,能够提升移动端的性能,加了就是你滑动,网页也就跟着滑动,不加,等你滑动完了网页在滑动。.passive 会告诉浏览器你不想阻止事件的默认行为
按键修饰符
这玩意用到查
<!-- 只有在 `key` 是 `Enter` 时调用 -->
<!-- 这玩意的修饰符就按键的key-->
<!-- keyCode 也可以,很简单-->
<input v-on:keyup.enter="submit">
v-if
没什么好解释的,就是不能和v-for一起使
v-show
根据表达式之真假值,切换元素的 display CSS 属性。
v-if 对比 v-show
- v-if 如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块。v-show则不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
- v-if 有更高的切换开销,v-show 有更高的初始渲染开销,如果需要非常频繁地切换,则使用 v-show 较好,如果在运行时条件很少改变,则使用 v-if 较好
- v-show不支持
<template>
元素 - v-show不支持v-else/v-else-if
v-if能够控制是否生成vnode,也就间接控制了是否生成对应的dom。当v-if为true时,会生成对应的vnode,并生成对应的dom元素;当其为false时,不会生成对应的vnode,自然不会生成任何的dom元素。
v-show始终会生成vnode,也就间接导致了始终生成dom。它只是控制dom的display属性,当v-show为true时,不做任何处理;当其为false时,生成的dom的display属性为none。
使用v-if可以有效的减少树的节点和渲染量,但也会导致树的不稳定;而使用v-show可以保持树的稳定,但不能减少树的节点和渲染量。
因此,在实际开发中,显示状态变化频繁的情况下应该使用v-show,以保持树的稳定;显示状态变化较少时应该使用v-if,以减少树的节点和渲染量。
v-slot(插槽)
东西不多,文字不太好表达,缩写为#
基础用法
<template>
<div id="app">
<slot> <!-- 插槽 -->
<demo/>
</slot>
</div>
</template>
<script>
import demo from "./components/demo";
export default {
components: {
demo
},
</script>
<!-- demo内容 -->
<template>
<p>插入到指定容器</p>
</template>
<!-- 解析结果-->
<div id="app">
<p>插入到指定容器</p>
</div>
具名插槽
- 如果某个组件中需要父元素传递多个区域的内容,也就意味着需要提供多个插槽
- 为了避免冲突,就需要给不同的插槽赋予不同的名字
- 一个不带 name 的
<slot>
出口会带有隐含的名字“default”。 在使用插槽的时候不提供名称默认渲染 - 注意v-slot (#)只能添加在
<template>
上
<template>
<div class="layout-container" >
<slot name="left"/>
<slot name="main"/>
<slot name="right"/>
</div>
</template>
<!-- 这个组件导出名称为Layout -->
<template>
<Layout>
<template #left>
<div>我是左边的内容</div>
</template>
<template #right>
<div>我是右边的内容</div>
</template>
<template #main>
<div>我是中间内容</div>
</template>
</Layout>
</template>
<!-- 解析结果-->
<template>
<div class="layout-container" >
<div>我是左边的内容</div>
<div>我是中间内容</div>
<div>我是右边的内容</div>
</div>
</template>
v-model (双向数据绑定)
语法什么的没啥好说的,几乎都用在表单上面,但是面试十成一百会问原理(原理我也不太懂)
实现原理
简单阐述v-model原理
- v-model 就是一个语法糖 !!!!!!!!!!
用于表单元素时,vue会根据表单元素类型生成合适的属性和事件,用于文本输入框的情况下,vue会生成一个value属性,一个input事件,如果用于单选框或者多选框 ,vue会生成checked属性 和 change事件 、
v-model 也可作用于自定义组件,当其作用于自定义组件时,默认情况下,它会生成一个
value
属性和input
事件。<!--简易实现v-model,虚拟DOM里面就这么写的--> <template> <div id="app"> <input type="text" :value="text" @input="myModel($event)"> <p>{{text}}</p> </div> </template> <script> export default { data(){ return{ text:'' } }, methods:{ myModel(e){ console.log(e); this.text = e.target.value } }, } </script>
在自定义组件使用v-model
v-model 也可作用于自定义组件,当其作用于自定义组件时,默认情况下,它会生成一个value
属性和input
事件。
在自定义组件内可以通过 model 的配置来改变生成的属性和事件
<!--这一段代码是模拟默认情况-->
<!--父组件-->
<template>
<div id="app">
<HelloWorld :number="number" @input="numberClickNumber($event)"/>
<!--上面这句话就等效于下面这句话-->
<HelloWorld :number="number" v-model="number"/>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
components: {
HelloWorld
},
data(){
return{
number:0
}
},
methods:{
numberClickNumber(data){
this.number = data
}
},
mounted() {
console.log(this._vnode);
},
}
</script>
<!--HelloWorld 子组件-->
<template>
<div>
<button type="button" @click="numberChange(number -1)">-</button>
<span>{{ number }}</span>
<button type="button" @click="numberChange(number +1)">+</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
number: {
type: Number,
default: 0
}
},
methods: {
numberChange(data) {
//这里抛出的事件也是一个input事件
return this.$emit('input', data)
}
}
}
</script>
model配置
在自定义组件内可以通过 model 的配置来改变生成的属性和事件
<script>
export default {
name: 'HelloWorld',
model:{
prop:'number',//修改生成属性的名字,默认为value 在vNode -> componentOptions里面可以看到
event:'click'//修改事件类型,默认是input
}
}
</script>
v-model修饰符
- [ lazy ],在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步。如果要变为使用change事件同步可以添加lazy修饰符: (人话就是加了lazy 输入框的内容不会实时变化,等输入框失去焦点才会触发变化)
- [ number ],自动将用户的输入值转为数值类型
- [ trim ],自动过滤用户输入的**首尾**空白字符,失去焦点就触发
自定义指令
其他指令
v-html
- 更新元素的innerHTML
- 内容按普通 HTML 插入,不会作为 Vue 模板进行编译
- 容易被XSS攻击,不能用在用用户提交的内容上
- 可以用于渲染富文本数据
v-pre
- 跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache(大胡子) 标签。跳过大量没有指令的节点或者静态资源会加快编译。
v-cloak
- 可以解决闪烁的问题,在网速不好的情况下会直接显示插值表达式
- 和 CSS 规则如 [v-cloak] { display: none } 一起用时,在网速不好的情况下,不会显示插值表达式,也就是页面不显示内容,等待编译结束,才会显示内容
v-once
- 只渲染元素一次。随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能
v-text
- 更新元素的 textContent
- v-text替换元素中所有的文本,Mustache(大胡子)只替换自己,不清空元素内容
- v-text 优先级高于 Mustache
- 渲染字符串有性能优势 ???鸡肋?
配置
data
和界面相关的数据
components
局部注册组件
methods
任何可能需要调用的方法(事件)
computed
计算属性
-
计算属性是基于响应式依赖进行缓存的
-
计算属性的值一直存于缓存中,只要它依赖的data数据不改变,每次访问计算属性,都会立刻返回缓存的结果,而不是再次执行函数。(比如某个要渲染的值经常变,我就用计算属性来搞,不然每次都得重新渲染)
-
计算属性除了写成一个函数之外,还可以写成一个对象,对象内有两个属性,getter 和 setter,这两个属性皆为函数,他们的this都指向vue实例,写法如下:
computed: { fullName: { getter () { // getter 读取 //在获取某个属性值的时候执行 }, setter () { // setter 设置 //set函数在给计算属性重新赋值时会执行。参数,为被重新设置的值。 } } }
-
给计算属性赋了值,计算属性也不会改变,只有当计算的属性变化了,计算属性才会重新计算。
watch
- 侦听的属性数据变化时,会立刻执行对应函数,
data: {
msg: 'hello,你好呀',
},
watch: {
//侦听器函数,会接收两个参数,第一个参数为newVal(被改变的数据),第二个参数为oldVal(赋值新值之前的值)。
msg (newVal,oldVal) {
conosle.log('被改变的数据:',newVal, '赋值新值之前的值:',oldVal);
}
deep: true // 开启深度侦听
}
//更改msg的值就会触发
this.$watch()
- 用法与watch选项部分一致,略有不同。以下为使用方法。
// 1. 三个参数,一参为被侦听的数据;二参为数据改变时执行的回调函数;三参可选,为设置的选项对象
vm.$watch(
'msg',
function () {
},
{
deep: Boolean,
immediate: Boolean
}
)
// 2. 二个参数,一参为被侦听的数据;二参为选项对象,其中handler属性为必需,是数据改变时执行的回调函数,其他属性可选。
vm.$watch(
'msg',
{
handler () {
},
deep: Boolean,
immediate: Boolean
}
)
计算属性computed vs 侦听器watch
-
两者都可以观察和响应Vue实例上的数据的变动。
-
watch擅长处理的场景是:一个数据影响多个数据。计算属性擅长处理的场景是:多个数据影响一个数据。
-
在侦听器中可以执行异步,但是在计算属性中不可以
computed 和 methods 区别
简单的标准答案
- 在使用时,computed当做属性使用,而methods则当做方法调用
- computed具有getter和setter,因此可以赋值,而methods不行
- computed无法接收多个参数,而methods可以
- computed具有缓存,而methods没有,(比如computed定义一个a,这个a在组件内多个地方使用,但是a只会运行一次)
重点来了,把面试官整懵逼,直呼大佬的回答
vue对methods的处理比较简单,只需要遍历methods配置中的每个属性,将其对应的函数使用bind绑定当前组件实例后复制其引用到组件实例中即可
而vue对computed的处理会稍微复杂一些。
当组件实例触发生命周期函数
beforeCreate
后,它会做一系列事情,其中就包括对computed的处理它会遍历computed配置中的所有属性,为每一个属性创建一个Watcher对象,并传入一个函数,该函数的本质其实就是computed配置中的getter,这样一来,getter运行过程中就会收集依赖
但是和渲染函数不同,为计算属性创建的Watcher不会立即执行,因为要考虑到该计算属性是否会被渲染函数使用,如果没有使用,就不会得到执行。因此,在创建Watcher的时候,它使用了lazy配置,lazy配置可以让Watcher不会立即执行。
收到
lazy
的影响,Watcher内部会保存两个关键属性来实现缓存,一个是value
,一个是dirty
value`属性用于保存Watcher运行的结果,受`lazy`的影响,该值在最开始是`undefined dirty`属性用于指示当前的`value`是否已经过时了,即是否为脏值,受`lazy`的影响,该值在最开始是`true
Watcher创建好后,vue会使用代理模式,将计算属性挂载到组件实例中
当读取计算属性时,vue检查其对应的Watcher是否是脏值,如果是,则运行函数,计算依赖,并得到对应的值,保存在Watcher的value中,然后设置dirty为false,然后返回。
如果dirty为false,则直接返回watcher的value
巧妙的是,在依赖收集时,被依赖的数据不仅会收集到计算属性的Watcher,还会收集到组件的Watcher
当计算属性的依赖变化时,会先触发计算属性的Watcher执行,此时,它只需设置
dirty
为true即可,不做任何处理。由于依赖同时会收集到组件的Watcher,因此组件会重新渲染,而重新渲染时又读取到了计算属性,由于计算属性目前已为dirty,因此会重新运行getter进行运算
而对于计算属性的setter,则极其简单,当设置计算属性时,直接运行setter即可
Axios
Axios是一个基于promise的HTTP库
config 配置对象
axios({
method: 'get', // post、get、put....
baseURL: '', // 请求的域名,基本地址
url: '', // 请求的路径
params: {}, // 会将请求参数拼接在url上
data: {}, // 会将请求参数放在请求体中
headers: {}, // 设置请求头,例如设置token等
timeout: 1000, // 设置请求超时时长,单位:ms
})
拦截器
interceptors,在发起请求之前做一些处理,或者在响应回来之后做一些处理。
请求拦截器
axios.interceptors.request.use(config => {
// 在发送请求之前做些什么
return config;
})
响应拦截器
axios.interceptors.response.use(response => {
// 对响应数据做点什么
return response;
})
移除拦截器
const myInterceptor = axios.interceptors.request.use(config => {});
axios.interceptors.request.eject(myInterceptor);
为axios实例添加拦截器
const instance = axios.create();
instance.interceptors.request.use(config => {});
取消请求
用于取消正在进行的http请求
const source = axios.CancelToken;
const source = CancelToken.source();
axios.get('/getUserInfo', {
cancelToken: source.token
}).then(res => {
console.log(res);
}).catch(error => {
if(axios.isCancel(error)) {
// 取消请求
console.log(error.message);
} else {
// 处理错误
}
})
// 取消请求 参数 可选
source.cancel('取消请求');
错误处理
在请求错误时进行的处理
request / response 是error的上下文,标志着请求发送 / 得到响应
在错误中,如果响应有值,则说明是响应时出现了错误。
如果响应没值,则说明是请求时出现了错误。
在错误中,如果请求无值,则说明是请求未发送出去,如取消请求。
axios.get('/user/12345')
.catch(function (error) {
// 错误可能是请求错误,也可能是响应错误
if (error.response) {
// 响应错误
} else if (error.request) {
// 请求错误
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
在实际开发过程中,一般在拦截器中统一添加错误处理
请求拦截器中的错误,会当请求未成功发出时执行,但是要注意的是:取消请求后,请求拦截器的错误函数也不会执行,因为取消请求不会抛出异常,axios对其进行了单独的处理。
在更多的情况下,我们会在响应拦截器中处理错误。
const instance = axios.create({});
instance.interceptors.request(config => {
}, error => {
return Promise.reject(error);
})
instance.interceptors.response(response => {
}, error => {
return Promise.reject(error);
})
axios 预检
当axios的请求为非简单请求时,浏览器会进行预检,及发送OPTIONS请求。请求到服务器,询问是否允许跨域。如果响应中允许预检中请求的跨域行为,则浏览器会进行真正的请求。否则会报405错误。
Vue 生命周期
创建前后
beforeCreate
实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
create
- 实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
- 在实例创建完成后被立即调用。
- 如果要在第一时间调用methods中的方法,或者操作data中的数据,可在此钩子中进行操作。
需要注意的是,执行此钩子时,挂载阶段还未开始,$el 属性目前不可见。- 此时,可以进行数据请求,将请求回来的值赋值给data中的数据。
挂载前后
beforeMount
- 在挂载开始之前被调用,此时模板已经编译完成,只是未将生成的模板替换el对应的元素。(也就是模板编译完成,没挂到页面上),在这里操作DOM节点最终都无效
- 在此钩子函数中,可以获取到模板最初始的状态。
- 此时,可以拿到$el,只不过为旧模板
mount
- 此时,已经将编译好的模板,挂载到了页面指定的容器中显示
- 可以操作DOM节点;
- 此时组件已经脱离初始化阶段,进入运行阶段,一般在此阶段:开启定时器、发送网络请求等准备工作
更新前后
beforeUpdate
- 页面数据发生更变时触发,此时data内的数据是最新的,但页面上的数据还是旧的,因为此时还没开始重新渲染
update
- 当数据更变完毕触发,此时data内的数据和页面上的已经完成更新,是同步的
- 此时数据和DOM是同步的
销毁前后
beforeDestroyed
- 实例销毁前调用,此时并没有到真正的销毁过程
- 此时实例里面的data,methods,指令等等都还还处于工作状态
- 一般情况下此钩子用于关闭定时器,取消一系列操作等等
destroyed
- 实例销毁后调用
- 此时指示的所有东西都会解除绑定,所有的事件监听会被移除。所有的子实例也都会被销毁
- 此时并没有销毁自身的事件监听
组件
分为全局组件和局部组件还有函数式组件,没啥好赘述的
父子组件通信
通信方式很多:比如这个列表
但是最好是用 props和$emit,因为他们俩遵守了单向数据流,其余的了解下就好
prop
没什么好说的,基础操作
prop验证
几个注意点:
- 名称最好避开 type这些有特殊意义的,如果你给input使用,原本的的type为text,你传了参数只会破坏原本的属性值,但是class和style会合并,但最好还是不要这样操作
我们可以为组件的 prop 指定验证要求,例如你可以要求一个 prop 的类型为什么,默认值是什么。如果说需求没有被满足的话,VUE会给一个报错,开发组件给别人用的时候有用
prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:
<template>
<div :class="myClass">{{myNumber}}</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
myClass: {
type:String,//类型为String
required:true//必传参数,不然报错
},
myNumber:{
type:[String,Number],//可以使用数组规定可能是多种类型
default:1,//默认值
validator(value) {//自定义验证规则,value为传过来的值,验证完成后返回一个布尔值,也可以对数据进行操作返回
return value>99//这里只是演示,这里会报错
}
}
}
}
</script>
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
总结成人话就是,爹打儿子天经地义,儿子打爹五雷轰顶,也就是子组件不能修改父组件传过来的数据。当然子组件可以抛出一个事件给父组件,让父组件来处理更变,也就是爹可以给儿子任何东西,但是你儿子没权利改,想改给爹申请
$emit()
子组件无权更改父组件数据,此时子组件可以抛出一个事件给父组件处理
示例:
<!--子组件抛出事件-->
<template>
<div @click="change('我是子组件抛出的数据')"></div>
</template>
<script>
export default {
name: 'HelloWorld',
methods: {
change(i) {
//子组件抛出了一个名为childComponent的事件,i 为子组件抛出的数据
return this.$emit('childComponent', i)
}
}
}
</script>
<!--父组件接收-->
<template>
<div id="app">
<!--父组件接收子组件抛出的事件,并指定一个处理函数-->
<HelloWorld @childComponent="childCli"/>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
components: {
HelloWorld
},
methods:{
childCli(data){
//这里的data 是子组件抛出的数据
console.log('父组件接收的数据:',data);
}
}
}
</script>
<!---->
style和class
父组件可以向子组件传递style
和class
,它们会合并到子组件的根元素中
$attrs
如果父组件传递了一些属性到子组件,但子组件并没有声明这些属性,则它们称之为attribute,这些属性会直接附着在子组件的根元素上,但是不包括style和class
没有通过props声明的属性,传过来了,就叫attribute
子组件可以通过inheritAttrs: false
配置,禁止将attribute
附着在子组件的根元素上,但不影响通过$attrs
获取,(就是看不到,能获取到,inheritAttrs写在默认导出里面)
<!--父组件 传递一个data-index,abc到子组件-->
<HelloWorld data-index="1" abc="888"/>
<!--子组件-->
<template>
<div></div>
</template>
<script>
export default {
name: 'HelloWorld',
inheritAttrs:false,// inheritAttrs:false 传过来的自定义属性不会附着在子组件的根元素上面
created() {
console.log(this.$attrs);//此时控制台输出了一个对象 {data-index:1,abc:888}
}
}
</script>
<!---->
.natvie 修饰符
在注册事件时,父组件可以使用native修饰符,将事件注册到子组件的根元素上(没啥卵用,父组件传过来的东西,子组件貌似是不能修改这些数据的)
<!--父组件-->
<template>
<div id="app">
<HelloWorld @click.native="childClick(childArr)"/>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
components: {
HelloWorld
},
data() {
return {
childArr:[1,2,3,4,5,6,7]
}
},
methods:{
childClick(data){
console.log(data);
}
}
}
</script>
<!--子组件-->
<template>
<div>子组件</div>
</template>
<script>
export default {
name: 'HelloWorld',
}
</script>
$listeners
子组件可以通过**$listeners
**获取父组件传递过来的所有事件处理函数
$listeners还有其他用法,可以把子组件抛出的事件持续向上抛
<!--父组件-->
<template>
<div id="app">
<!--父组件传递两个事件-->
<HelloWorld @event1="childEvent1" @event2="childEvent2"/>
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
components: {
HelloWorld
},
methods:{
childEvent1(){
console.log('event1');
},
childEvent2(){
console.log('event2');
},
}
}
</script>
<!--子组件-->
<template>
<div @click="event">子组件</div>
</template>
<script>
export default {
name: 'HelloWorld',
methods:{
event(){
//子组件可以通过this.$listeners一次性接收
this.$listeners.event1()
this.$listeners.event2()
}
}
}
</script>
v-model
这个也可以
.sync 修饰符
和v-model的作用类似,用于双向绑定,不同点在于v-model只能针对一个数据进行双向绑定,而sync修饰符没有限制
<!--子组件-->
<template>
<div>
<p>
<!--注意 !!!!!!!!!
使用.sync这个修饰符,子组件抛出事件的时候必须用这个奇葩语法
-->
<button @click="$emit(`update:num1`, num1 - 1)">-</button>
{{ num1 }}
<button @click="$emit(`update:num1`, num1 + 1)">+</button>
</p>
<p>
<button @click="$emit(`update:num2`, num2 - 1)">-</button>
{{ num2 }}
<button @click="$emit(`update:num2`, num2 + 1)">+</button>
</p>
</div>
</template>
<script>
export default {
props: ["num1", "num2"],
};
</script>
<!--父组件-->
<template>
<div id="app">
<Numbers :num1.sync="n1" :num2.sync="n2" />
<!-- 等同于 -->
<Numbers :num1="n1" @update:num1="n1 = $event" :num2="n2" @update:num2="n2 = $event"/>
</div>
</template>
<script>
import Numbers from "./components/Numbers.vue";
export default {
components: {
Numbers,
},
data() {
return {n1: 0,n2: 0,};
},
};
</script>
p a r e n t 和 parent和 parent和children
在组件内部,可以通过$parent
和$children
属性,分别得到当前组件的父组件和子组件实例,实例里面包含所有数据还有DOM节点
$slots 和 $scopedSlots
ref
<div id="app" ref="appElement"></div>
<!--获取-->
<script>
this.$refs.appElement //返回的是拿到的DOM元素的实例,里面啥都有
</script>
跨组件通信
Provide和Inject
provide定义数据,lnject接收数据
示例
<!--祖先组件-->
<template>
<div id="app">
<HelloWorld />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
components: {
HelloWorld
},
provide:{ //给孙子组件传递 a 和 b
a:1,
b:2
}
}
</script>
<!--孙子组件-->
<template>
<p>孙子组件</p>
</template>
<script>
export default {
name: "demo",
inject:['a','b'],//接收
created() {
console.log(this.a, this.b);
}
}
</script>
详见:https://cn.vuejs.org/v2/api/?#provide-inject
router
如果一个组件改变了地址栏,所有监听地址栏的组件都会做出相应反应
最常见的场景就是通过点击router-link
组件改变了地址,router-view
组件就渲染其他内容
vuex
适用于大型项目的数据仓库
store模式
适用于中小型项目的数据仓库
直接建个文件,各个组件都可以使用…,只要一个组件修改了数据,所有的组件都会跟着改变
// store.js
const store = {
loginUser: ...,
setting: ...
}
// compA
const compA = {
data(){
return {
loginUser: store.loginUser
}
}
}
// compB
const compB = {
data(){
return {
setting: store.setting,
loginUser: store.loginUser
}
}
}
eventbus
组件通知事件总线发生了某件事,事件总线通知其他监听该事件的所有组件运行某个函数
虚拟DOM
什么是虚拟dom?
虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构
在vue中,每个组件都有一个
render
函数,每个render
函数都会返回一个虚拟dom树,这也就意味着每个组件都对应一棵虚拟DOM树
为什么需要虚拟dom?
在
vue
中,渲染视图会调用render
函数,渲染不仅发生在组件创建时,同时视图上的数据更新时。如果在渲染时,直接使用真实DOM
,由于真实DOM
的创建、更新、插入等操作会带来大量的性能损耗,从而就会降低渲染效率。所以,
vue
在渲染时,使用虚拟dom来替代真实dom,主要为解决渲染效率的问题。
虚拟dom是如何转换为真实dom的?
如果一 个组件实例首次被渲染时,它先生成虚拟dom树,然后根据虚拟dom树创建真实dom,并把真实dom挂载到页面中合适的位置,此时,每个虚拟dom便会对应一个真实的dom。
如果一个组件受响应式数据变化的影响,需要重新渲染时,就会重新调用render函数,创建出一个新的虚拟dom树,用新树和旧树对比,通过对比,vue会找到最小更新量,然后更新必要的虚拟dom节点,最后,这些更新过的虚拟节点,会去修改它们对应的真实dom
这样一来,就保证了对真实dom达到最小的改动。
Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
模板和虚拟dom的关系
vue框架中有一个
compile
模块,它主要负责将模板转换为render
函数,而render
函数调用后将得到虚拟dom。编译的过程分两步:
- 将模板字符串转换成为
AST
- 将
AST
转换为render
函数如果使用传统的引入方式,则编译时间发生在组件第一次加载时,这称之为运行时编译。
如果是在
vue-cli
的默认配置下,编译发生在打包时,这称之为模板预编译。编译是一个极其耗费性能的操作,预编译可以有效的提高运行时的性能,而且,由于运行的时候已不需要编译,
vue-cli
在打包时会排除掉vue
中的compile
模块,以减少打包体积模板的存在,仅仅是为了让开发人员更加方便的书写界面代码
vue最终运行的时候,最终需要的是render函数,而不是模板,因此,模板中的各种语法,在虚拟dom中都是不存在的,它们都会变成虚拟dom的配置
Vue响应式原理
数据变化去做一些事情,Object.defineProperty
router(路由)
基本语法没什么说的
路由模式
路由模式决定了:
- 路由从哪里获取访问路径
- 路由如何改变访问路径
vue-router
提供了三种路由模式:
-
hash:默认值。路由从浏览器地址栏中的hash部分获取路径,改变路径也是改变的hash部分。该模式兼容性最好。
http://localhost:8081/#/blog --> /blog http://localhost:8081/about#/blog --> /blog
-
history:路由从浏览器地址栏的
location.pathname
中获取路径,改变路径使用的H5的history api
。该模式可以让地址栏最友好,但是需要浏览器支持history api
,http://localhost:8081/#/blog --> / http://localhost:8081/about#/blog --> /about http://localhost:8081/blog --> /blog //当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id, 也好看! //不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。 //所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。
-
abstract:路由从内存中获取路径,改变路径也只是改动内存中的值。这种模式通常应用到非浏览器环境中。
内存: / --> / 内存: /about --> /about 内存: /blog --> /blog
命名路由
使用命名路由可以解除系统与路径之间的耦合
{
//现在不管path路径怎么变,点击首页依然会匹配到
path: '/activity/personal',
name: 'Home',
component: Personal,
}
要链接到一个命名路由,可以给 router-link
的 to 属性传一个对象:
<router-link :to="{ name: 'Home' }">首页</router-link>
激活状态
exact 开启路由精确匹配
<!-- 这个链接只会在地址为 / 的时候被激活 -->
<router-link active-class="active" exact-active-class="" to="/" exact></router-link>
<!-- 这个链接会在地址为 /aacc 开头的时候被激活 -->
<router-link active-class="active" exact-active-class="" to="/aacc" :exact=false></router-link>
<!--这里也没什么说的 开启exact后把这两个粘贴到link上面,匹配的路由类名为active,active-class="active" exact-active-class=""-->
$router
$router.push
// 字符串
this.$router.push('home')
// 对象
this.$router.push({ path: 'home' })
// 命名的路由
this.$router.push({ name: 'user' })
$router.replace
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是替换掉当前的 history 记录。
$router.go(n)
这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)。
$route 只读,路由信息对象
$route.path
字符串,对应当前路由的路径,总是解析为绝对路径,如 “/foo/bar”。
$route.params
一个 key/value 对象,包含了动态片段和全匹配片段,如果没有路由参数,就是一个空对象。
$route.query
一个 key/value 对象,表示 URL 查询参数。例如,对于路径 /foo?user=1,则有 $route.query.user == 1,如果没有查询参数,则是个空对象。
$route.hash
路由的 hash 值 (带 #) ,如果没有 hash 值,则为空字符串。
$route.fullPath
完成解析后的 URL,包含查询参数和 hash 的完整路径。
$route.matched
一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是 routes 配置数组中的对象副本 (还有在 children 数组)。
$route.name
当前路由的名称,如果有的话
$route.redirectedFrom
如果存在重定向,即为重定向来源的路由的名字。
动态路由
同一个组件,渲染不同内容
参数值会被设置到 this.$route.params
// 动态路径参数 以冒号开头
{ path: '/user/:id',name:'User', component: User }
<router-link :to="{
name:'User',
params:{
id:item.id
}
}">
</router-link>
导航守卫
导航守卫被分成三种:全局的、单个路由独享的、组件内的。
参数说明:
- to 目标路由对象 (去哪里)
- from 即将要离开的路由对象 (哪里来的)
- next 三个参数中最重要的参数。
- 必须调用next(),才能继续往下执行一个钩子,否则路由跳转会停止
- 若要中断当前的导航,可以调用next(false)。
- 可以使用next跳转到一个不同的地址。终端当前导航,进入一个新的导航。next参数值和$routet.push一致。
- next(error)。2.4+,如果传入的参数是一个Error实例,则导航会被终止,且该错误会被传递给router.onError() 注册过的回调。
Vuex(数据仓库)
杂项
过滤器
组件内过滤器
<template>
<div id="app">
<!--语法,一个竖线代表name要过滤处理,没什么难得-->
{{name | isName}}
</div>
</template>
<script>
export default {
data(){
return{
name:['ding','shuai']
}
},
filters:{
isName:(value)=>{
let name = [],str=''
value.forEach(i=>name.push(i.charAt(0).toUpperCase()+i.slice(1)))
name.forEach(i=>str+=i)
return str
}
}
}
</script>
全局过滤器
//第一个参数是 过滤器名字.....第二个参数处理函数,形参是需要处理的值
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
串联过滤器
//message 代表要处理的值,filterA处理完了,再给feilterB拿去处理
{{ message | filterA | filterB }}
//这玩意是可以接收参数的,第一个参数就是message,也就是需要过滤的值,第二个参数,第三个参数自定义
{{ message | filterA('arg1', arg2) }}
过滤器和计算属性的区别
计算属性
1、计算属性适合用在单个属性的计算;
2、计算属性只能在单个vue实例中使用;
3、计算属性不能接收参数,只能使用data中定义的变量进行计算;
4、计算属性有缓存机制,可减少调用次数;
5、计算属性相当于定义一个变量
过滤器
1、过滤器适合多个同样计算方法的属性的计算;
2、过滤器可以定义为全局过滤器,在多个vue实例中使用;
3、过滤器可以接收多个参数进行计算;
4、过滤器没有缓存机制,每调用一次都会计算一次;
**5、过滤器相当于定义一个特殊的方法