优雅更新props
更新 prop
在业务中是很常见的需求,但在子组件中不允许直接修改 prop
,因为这种做法不符合单向数据流的原则,在开发模式下还会报出警告。因此大多数人会通过 $emit
触发自定义事件,在父组件中接收该事件的传值来更新 prop
。
child.vue:(子组件)
export defalut { props: { title: String }, methods: { changeTitle(){ this.$emit('change-title', 'hello') } } }
parent.vue:
<child :title="title" @change-title="changeTitle"></child>
export default {
data(){
return {
title: 'title'
}
},
methods: {
changeTitle(title){
this.title = title
}
}
}
这种做法没有问题,我也常用这种手段来更新 prop
。但如果你只是想单纯的更新 prop
,没有其他的操作。那么 sync
修饰符能够让这一切都变得特别简单。
parent.vue:
<child :title.sync="title"></child>
child.vue:
export defalut { props: { title: String }, methods: { changeTitle(){ this.$emit('update:title', 'hello') } } }
只需要在绑定属性上添加 .sync
,在子组件内部就可以触发 update:属性名
来更新 prop
。可以看到这种手段确实简洁且优雅,这让父组件的代码中减少一个“没必要的函数”。
provide/inject
2.2.0 新增
类型:
provide:Object | () => Object
inject:Array<string> | { [key: string]: string | Symbol | Object }
provide
选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol
和 Reflect.ownKeys
的环境下可工作。
inject
选项应该是:
一个字符串数组;
一个对象,对象的 key 是本地的绑定名
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
简单来说,一个组件将自己的属性通过 provide
暴露出去,其下面的子孙组件 inject
即可接收到暴露的属性。
App.vue:
export default { provide() { return { app: this } } }
child.vue:
export default { inject: ['app'], created() { console.log(this.app) // App.vue实例 } }
在 2.5.0+ 版本可以通过设置默认值使其变成可选项:
export default { inject: { app: { default: () => ({}) } }, created() { console.log(this.app) } }
如果你想为 inject
的属性变更名称,可以使用 from
来表示其来源:
export default { inject: { myApp: { // from的值和provide的属性名保持一致 from: 'app', default: () => ({}) } }, created() { console.log(this.myApp) } }
需要注意的是 provide
和 inject
主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。但是某些时候,或许它能帮助到我们日常开发。
作用域插槽
利用好作用域插槽可以做一些很有意思的事情,比如定义一个基础布局组件A,只负责布局,不管数据逻辑,然后另外定义一个组件B 负责数据处理,布局组件A 需要数据的时候就去 B 里面去取。假设,某一天我们的布局变了,我们只需要去修改组件A 就行,而不用去修改组件B,从而就能充分复用组件B 的数据处理逻辑。
这里涉及到的一个最重要的点就是父组件要去获取子组件里面的数据,之前是利用 slot-scope
,自 vue 2.6.0 起,提供了更好的支持 slot
和 slot-scope
特性的 API 替代方案。
函数式组件
简单说一下函数式组件
函数式组件就是函数是组件。使用过 React 的同学,应该不会对函数式组件感到陌生
函数式组件,我们可以理解为没有内部状态,没有生命周期钩子函数,没有 this(不需要实例化的组件)
由于它像函数一样轻巧,没有实例引用,所以渲染性能提高了不少
在日常开发中,经常会开发一些纯展示性的业务组件,比如一些详情页面,列表界面等,它们有一个共同的特点是只需要将外部传入的数据进行展现,不需要有内部状态,不需要在生命周期钩子函数里面做处理,这时候你就可以考虑使用函数式组件
export default { // 通过配置 functional 属性指定组件为函数式组件 functional: true, // 组件接收的外部属性,也可无需显式声明 props props: { avatar: { type: String } }, /** * 渲染函数 * @param {*} h * @param {*} context 函数式组件没有 this, props, slots 等,都在 context 上面挂着 */ render(h, context) { const { props } = context if (props.avatar) { return <img src={props.avatar}></img> } return <img src="default-avatar.png"></img> } }
使用函数式组件的原因:
- 最主要最关键的原因是函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件
- 函数式组件结构比较简单,代码结构更清晰
函数式组件与普通组件的区别
- 函数式组件需要在组件上声明functional
- 函数式组件不需要实例化,所以没有 this,this通过render函数的第二个参数来代替
- 函数式组件没有生命周期钩子函数,不能使用计算属性、watch 等等
- 函数式组件不能通过 $emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件
- 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是 HTMLElement
- 函数式组件的props可以不用显式声明,所以没有在props里面声明的属性都会被自动隐式解析为 prop,而普通组件所有未声明的属性都被解析到 $attrs 里面,并自动挂载到组件根元素上面(可以通过 inheritAttrs 属性禁止)
模板语法声明函数式组件
在 Vue2.5 之前,使用函数式组件只能通过 JSX 的方式,在之后可以通过模板语法来声明函数式组件
<!-- 在 template 上面添加 functional 属性 --> <template functional> <img :src="props.avatar" /> </template> <!-- 上面第 6 点,可不用显示声明 props -->
样式穿透
在我们日常开发中修改第三方组件样式是很常见,但由于 scoped
属性的样式隔离,可能需要去除 scoped
或是另起一个 style
。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在css预处理器中使用才生效。
我们可以使用 >>>
或 /deep/
解决这一问题:
<style scoped> 外层 >>> .el-checkbox { display: block; font-size: 26px; .el-checkbox__label { font-size: 16px; } } </style> <style scoped> /deep/ .el-checkbox { display: block; font-size: 26px; .el-checkbox__label { font-size: 16px; } } </style>
v-for循环中不要使用index作为key
我们会给列表渲染设置属性key,这个key属性主要用在虚拟DOM算法上,在对比新旧虚拟节点时辨识虚拟节点。但如果key用得不合理,就会出现bug。
watch高阶使用
立即执行(运用handler方法和immediate属性)
watch: { firstName: { handler(newName, oldName) { this.fullName = newName + ' ' + this.lastName; }, // 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法 immediate: true }
深度监听(deep: true
)
export default { data: { studen: { name: 'Joe', skill: { run: { speed: 'fast' } } } }, watch: { studen: { handler: 'sayName', deep: true } }, methods: { sayName() { console.log(this.studen) } } }
watch监听多个变量
watch本身无法监听多个变量。但我们可以将需要监听的多个变量通过计算属性返回对象,再监听这个对象来实现“监听多个变量”
export default { data() { return { msg1: 'xiaoxue', msg2: 'liner' } }, compouted: { msgObj() { //es6 新语法 const { msg1, msg2 } = this return { msg1, msg2 } } }, watch: { msgObj: { handler(newVal, oldVal) { if (newVal.msg1 != oldVal.msg1) { console.log('msg1 is change') } if (newVal.msg2 != oldVal.msg2) { console.log('msg2 is change') } }, deep: true } } }