第一篇传送门:终于搞懂了vue 的 render 函数(一) -_-|||
注:本文代码都是在单文件组件中编写。代码地址
先来了解下 vm.$scopedSlots
用来访问作用域插槽。对于包括
默认 slot
在内的每一个插槽,该对象都包含一个返回相应 VNode 的函数。
注意: 从 2.6.0 开始,这个 property 有两个变化:
- 作用域插槽函数现在保证返回一个 VNode 数组,除非在返回值无效的情况下返回
undefined
。- 所有的
$slots
现在都会作为函数暴露在$scopedSlots
中。如果你在使用渲染函数,不论当前插槽是否带有作用域,我们都推荐始终通过$scopedSlots
访问它们。这不仅仅使得在未来添加作用域变得简单,也可以让你最终轻松迁移到所有插槽都是函数的 Vue 3。
也就是说通过 $scopedSlots
就可以访问所有插槽,只不过原来 $slots
是直接返回 VNode,而 $scopedSlots
是返回一个 VNode 函数(函数的返回值是 VNode)。
⚠️ 修改代码之前先要把 vue
升级到 2.6.0 之后才可以,vue-template-compiler
版本也必须与 vue
版本保持一致。
如果你的项目报这个错 Error: [vue-loader] vue-template-compiler must be installed as a peer depend
那就是它俩的版本不一致导致的。升级命令:
npm update vue vue-template-compiler
上一章的 🌰 只需把 header
和 default
分别改为:
- let _header = _this.$slots.header
+ let _header = _this.$scopedSlots.header()
- _this.$slots.default
+ _this.$scopedSlots.default()
深入数据对象
表格中渲染操作按钮的时候用到的比较多,我们直接看 🌰:
// 按钮组件 BaseButton.vue
<template>
<div class="button-card">
<div>
<slot name="title"></slot>
</div>
<button :class="[`${type}`, `${size}`]" @click="handleClick">
<slot name="button" v-bind:children="contentObj">{{ contentObj.text }}</slot>
</button>
<p>
<slot>help</slot>
</p>
</div>
</template>
<script>
export default {
name: 'BaseButton',
props: {
type: {
type: String,
default: ''
},
size: {
type: String,
default: ''
},
contentObj: {
type: Object,
default() {
return {}
}
}
},
methods: {
handleClick() {
this.$emit('click')
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.button-card {
background: #eee;
border-radius: 8px;
padding: 24px;
}
button {
border-radius: 4px;
}
.success {
background-color: rgb(149, 204, 149);
border: 1px solid rgb(149, 204, 149);
color: #fff;
}
.warning {
background-color: orange;
border: 1px solid orange;
color: #fff;
}
.danger {
background-color: red;
border: 1px solid red;
color: #fff;
}
.small {
height: 20px;
padding: 0 8px;
}
.middle {
height: 32px;
padding: 0 12px;
}
.large {
height: 40px;
padding: 0 20px;
}
</style>
// 父组件
// 1. 引入组件
import BaseButton from './BaseButton'
// 2. render 函数中渲染
renderButton: {
render: function(createElement) {
const _this = this['$options'].parent
return createElement(BaseButton, {
// 与 `v-bind:class` 的 API 相同,接受一个字符串、对象或字符串和对象组成的数组
class: {
'base-button': true,
'ui-button': false
},
// 与 `v-bind:style` 的 API 相同,接受一个字符串、对象,或对象组成的数组
style: {
// color: 'red',
fontSize: '14px'
},
// 普通的 HTML attribute,会加到 BaseButton 最外层的元素上
attrs: {
id: 'base-button'
},
// 组件 prop,就是我们正常使用子组件时的那些 props
props: {
type: 'warning',
size: 'large',
contentObj: {
text: 'Confirm',
icon: '❓'
}
},
// DOM property,以子组件最外层元素为父元素,将其内容替换为 `innerHTML` 中的内容
// domProps: {
// innerHTML: 'button 没了,变成这段文字'
// },
// 事件监听器在 `on` 内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
// 子组件 `$emit` 的事件才能接收到,否则在下一个属性才能监听到
on: {
click: _this.clickHandler,
dblclick: _this.dblclickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用 `vm.$emit` 触发的事件。
nativeOn: {
dblclick: _this.nativeClickHandler
},
// 自定义指令。略
// directives: [],
// 作用域插槽的格式为:{ name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => {
if (props.children) {
return createElement('span', props.children.text + ' ?')
}
return createElement('span', 'parent slot defult')
}
},
// 如果组件是其它组件的子组件,需为插槽指定名称。这个属性我也没明白
slot: 'name-of-slot'
// 其它特殊顶层 property。略
// key: '',
// ref: '',
// refInFor: true
})
}
},
// 3. 使用组件 `renderButton`
<renderButton />
JSX
render 函数虽然解决了我们的问题,但实在是太麻烦了。
这就是为什么会有一个 Babel 插件,用于在 Vue 中使用 JSX 语法,它可以让我们回到更接近于模板的语法上。
使用 JSX:
- 安装插件
npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
- 新建一个
.babelrc
文件(如果你的项目里没有的话),并添加以下配置{ "presets": ["@vue/babel-preset-jsx"] }
现在我们将上面的组件改成 JSX 的写法:
renderButtonWidthJSX: {
render(h) {
const _this = this['$options'].parent
const contentObj = { text: 'jsx' }
return (
<BaseButton
type="danger"
size="small"
contentObj={contentObj}
style={{ fontSize: '12px' }}
class="jsx-button"
onclick={_this.clickHandler}
scopedSlots={{
title: () => <h1>jsx title</h1>,
// button: () => 'Delete',
default: () => <span>default</span>
}}
/>
)
}
}
也可以这样写:
renderButtonWidthJSX: {
render(h) {
const _this = this['$options'].parent
const contentObj = { text: 'jsx' }
return (
<BaseButton
{...{
props: {
type: 'danger',
size: 'small',
contentObj,
style: { fontSize: '12px' },
class: 'jsx-button'
},
on: {
click: _this.clickHandler,
dblclick: _this.dblclickHandler
},
nativeOn: {
dblclick: _this.nativeClickHandler
},
scopedSlots: {
title: () => <h1>jsx title</h1>,
// button: () => 'Delete',
default: () => <span>default</span>
}
}}
/>
)
}
}
⚠️ render
函数虽然没有用到 h
参数还是要传,不然会报错:
将
h
作为createElement
的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。从 Vue 的 Babel 插件的 3.4.0 版本开始,我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入const h = this.$createElement
,这样你就可以去掉(h)
参数了。对于更早版本的插件,如果h
在当前作用域中不可用,应用会抛错。