多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用vuex,为此Vue2.4 版本提供了其他方法----$attrs
/$listeners
/$props
$attrs
:父组件传到当前组件的参数$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件$props
:当前组件接收的参数–当前组件接收到的 props 对象。Vue 实例代理了对其 props 对象属性的访问。// App.vue
<child1-vue
msg="child1"
:foo="foo"
:boo="boo"
:doo="doo"
:coo="coo"
@test-listen.native='testListen'
@test-listen1='testListen1'
@get='getNum'/>
<script>
export default {
methods:{
//可以看到$listeners中不含 带有 .native 修饰符的函数
testListenNative() { window.console.log('主页接收') },
testListen() { window.console.log('主页接收,没有native'); },
getNum(num) { window.console.log('获取值'+num); },
}
}
script>
// child1.vue
<template>
<div class="hello">
<div>父组件传递的 $attrs:{{$attrs}}div>
<div> 当前页面接收的 $props:{{$props}}div>
<child2-vue v-bind="$attrs" v-on="$listeners" @other-get="otherGet">child2-vue>
div>
template>
<script>
export default {
props: {
msg: String,
// foo: String // foo作为props属性绑定
},
components:{
Child2Vue
},
inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
}
script>
// child2.vue
<template>
<div class="hello">
<div>父组件传递的 $attrs:{{$attrs}}div>
<div> 当前页面接收的 $props:{{$props}}div>
<child3-vue v-bind="$attrs">child3-vue>
div>
template>
<script>
export default {
props: {
// boo: String // foo作为props属性绑定
},
components:{
Child3Vue
},
}
script>
// child3.vue
<template>
<div class="hello">
<div>父组件传递的 $attrs:{{$attrs}}div>
<div> 当前页面接收的 $props:{{$props}}div>
div>
template>
<script>
export default {
props: {
// coo: String // foo作为props属性绑定
},
}
script>
页面效果如下图所示:
从上文可以看出,$attrs
/$listeners
/$props
都是变量,可以层层传递。
注意:如果中途有一个参数被写在props属性中(如:props:{foo:String}),那么$attrs
中会相应的除去该属性。将$attrs
中的属性写到$props
中,会出现如下效果:
在日常的开发过程中,父子组件之间相互通信,父组件监听子组件是否已经创建data或是dom是否挂载完毕,通俗的用this.$emit来实现父子之间的频繁通信有些过于麻烦,父组件直接监听子组件的生命周期,在特定的时间做逻辑处理。
在父组件使用@hook:mounted="method"
,子组件在对应的生命周期函数结束后执行method。
// child1.vue
<template>
<div class="hello">
<div>父组件传递的 $attrs:{{$attrs}}div>
<div> 当前页面接收的 $props:{{$props}}div>
<child2-vue
v-bind="$attrs"
v-on="$listeners"
@other-get="otherGet"
@hook:mounted="doSomething">child2-vue>
div>
template>
<script>
export default {
methods: {
// 当子组件mounted执行过后,控制台打印 子组件mounted完成
doSomething() { window.console.log("子组件mounted完成"); }
}
}
script>
(没必要,但可以有)
使用Vue.observable(data),可以写一个简易的store状态管理;
Vue.observable()
:参数{Object} object// miniStore.js
// 简易的状态管理(2.6新出的状态分享) 公用属性
import Vue from "vue";
// 参数为定义的公共 变量
export const store = Vue.observable({ count: 0 });
// 暴露设置值的方法
export const mutations = {
setCount(count) {
store.count = count;
}
};
// App.js
<template>
<div id="app">
...
// 引入自定义store中的
<p>count:{{count}}p>
<button @click="setCount(count+1)">+1button>
<button @click="setCount(count-1)">-1button>
<hello-world />
...
template>
<script>
import HelloWorld from './components/HelloWorld.vue';
// 1. 把自定义的文件引入需要引入的页面
import { store, mutations } from './utils/miniStore';
export default {
...
// 2. 计算属性监听count值变化
computed: {
count() {
return store.count;
}
},
methods: {
// 3. 单一页面修改值作用到所有页面,实现属性公用
setCount: mutations.setCount
}
}
script>
之前的组件通信,父–>儿子–> 孙子,如果想实现父到孙子节点的数据传递,那么中间必须经过儿子节点中转,如果中间层数少还好,如果层数过多,那么逐层传递非常麻烦,因此vue提供了provide / inject
来进行跨越多层级之间的通信。
- Vue2.2.0新增API,
- 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中
// 父
export default {
provide: {
name: 'provide传的值'
},
}
// 孙子
export default {
reject:['name'],
created() {
this.$log(this.name); // 打印结果:provide传的值
}
}
可以看到使用 provide / inject
无需经过儿子节点进行值得中转。
需要注意的一点是,这里的provide / inject
是不能响应式修改的。
vue中style标签的scoped属性表示它的样式只作用于当前模块,是样式私有化, 设计的初衷就是让样式变得不可修改.
渲染的规则/原理:
<style scoped>
.demo{
color:green;
text-align:left;
border:1px solid green;
margin: 20px;
}
style>
在浏览器中查看:
.demo[data-v-3a2e0245] {
color:green;
text-align:left;
border:1px solid green;
margin: 20px;
}
注意:scoped
添加后,父组件无法修改子组件中的样式
上一节scoped添加后无法修改,但在开发过程中有时需要修改子组件中的样式达到自己的效果(类似修改组件库样式)这样的情况,在这种情况下,使用/deep/
修改包含scoped定义的子组件样式。
// 父组件
<child class="deep-class" />
<style scoped>
.deep-class /deep/ .demo{
border-color:red
}
style>
// 子组件
<template>
<div class="hello">
<div class="demo">div>
div>
template>
<style scoped>
.demo{
border:1px solid green;
}
style>
用/deep/
后,页面样式生效,边框为红色。去掉/deep/
,边框颜色为绿色。
Object.defineProperty()
,Object.defineProperty
把data中的的属性全部转为 getter/setter
,这些getter/setter
对用户来说是不可见的,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。Object.freeze()
方法冻结该数据,达到性能优化的目的。Object.freeze()
方法冻结该数据,这样vue就不会对该对象进行getter和setter
。created() {
let list = Array.from({ length: 100000 }, (item, index) => ({ content: index }))
// this.list = list;
this.list = Object.freeze(list);
window.console.log("初始化");
},
// 开发模式开启性能检测
const isDev = process.env.NODE_ENV !== "production";
Vue.config.performance = isDev;
检测模式开启后,可以在chorme dev-tool中的的性能检测(performance)中看到其中的timings栏中看见各个组件的渲染时间,如下图所示:
从上图可以看到,未使用Object.freeze(list) 的渲染总时长2207ms,其中hello-world组件加载时间为329ms、413ms、587ms。
从上图可以看到,使用Object.freeze(list) 后,渲染总时长1657ms,其中hello-world组件加载时间为34ms、180ms、468ms。
像素管道一般由 5 个部分组成。JavaScript、样式、布局、绘制、合成。如下图所示:
函数式组件,即无状态,无法实例化,内部没有任何生命周期处理方法,非常轻量,因而渲染性能高,特别适合用来只依赖外部数据传递而变化的组件。
watch
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的
watch一个对象
,键是需要观察的表达式,值是对应回调函数。主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作;可以看作是computed
和methods
的结合体;watch
更加适用于监听某一个值的变化并做对应的操作,比如请求后台接口等,而computed
适用于计算已有的值并返回结果watch
监听路由,监听数值变化.stop
- 调用 event.stopPropagation()。.prevent
- 调用 event.preventDefault()。.capture
- 添加事件侦听器时使用 capture 模式。.self
- 只当事件是从侦听器绑定的元素本身触发时才触发回调。.{keyCode | keyAlias}
- 只当事件是从特定键触发时才触发回调。.native
- 监听组件根元素的原生事件。.once
- 只触发一次回调。.left
- (2.2.0) 只当点击鼠标左键时触发。.right
- (2.2.0) 只当点击鼠标右键时触发。.middle
- (2.2.0) 只当点击鼠标中键时触发。.passive
- (2.3.0) 以 { passive: true } 模式添加侦听器在了解nextTick前,需要知道Vue 在更新 DOM 时是异步执行的,当你设置 vm.someData = ‘new value’,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。
这时就需要我们用到Vue.nextTick
。
文章示例:仓库地址