共计 6782 个字符,预计需要花费 17 分钟才能阅读完成。
函数式组件
函数式组件(functional component)是一个不持有状态 data
、实例this
和生命周期的组件。
函数式组件没有 data、生命周期和
this
,函数式组件又叫无状态组件(stateless component)。
模板定义:
<template functional>
<div>
<h1>{{props.title}}</h1>
</div>
</template>
<script>
export default {
name: 'FunOne',
props: {title: [String],
},
}
</script>
<style></style>
render 函数定义
export default {
name: 'FunTwo',
functional: true,
props: {title: [String],
},
render(h, { props}) {return h('div', {}, [h('h1', {}, props.title)])
},
}
不能这样定义:
<template>
<div>
<h1>{{title}}</h1>
</div>
</template>
<script>
export default {
name: 'FunOne',
functional: true,
props: {title: [String],
},
}
</script>
<style></style>
应用 render 函数定义输入框
MyInput.jsx
export default {
name: 'MyInput',
functional: true,
props: {
value: {type: [String, Number],
default: '',
},
},
// NOTE 函数式组件没有 this
render(h, context) {const { props, listeners, data} = context
return h('input', {
// DOM 属性
domProps: {value: props.value,},
on: {input: ({ target}) => {data.on['my-change'](Math.random().toString(36))
// listeners 是 data.on 的别名
listeners['my-input'](target.value)
listeners.input(target.value)
},
},
})
},
}
在 render 函数中应用 MyInput
import MyInput from './MyInput'
export default {
name: 'MyInputExample',
data() {return { value: ''}
},
render(h) {
// MyInput 是一个组件对象选项
return h(MyInput, {
model: {
value: this.value,
callback: value => {console.log('model', value)
this.value = value
},
},
on: {
// NOTE 在父组件的 MyInputExample 上监听 event-name 事件,// 在函数式组件的 listeners 对象
// 上就会有一个 event-name 办法
// 用于发送数据到内部
'my-input': value => {console.log('my-input', value)
},
'my-change': value => {console.log('my-change', value)
},
},
})
},
}
在父组件的 MyInputExample 上监听 event-name 事件,在函数式组件的 listeners 对象上
才会有
一个 event-name 办法。
用 render 定义函数式组件
vue 在 render 函数的第二个参数中提供了 context
,用于拜访 props
、slots
等属性:
props: 组件 props 对象。data: 组件的数据对象,即 h 的第二个参数。listeners: 组件上监听的事件对象,在组件上监听 `event-name`,listeners 对象就有 `event-name` 属性,值为函数,数据可通过该函数的参数抛到父组件。listeners 是 `data.on` 的别名。slots: 函数,返回了蕴含所有插槽的对象。scopedSlots: 对象,每个属性为返回插槽的 VNode 的函数,可传递参数。children:子节点数组,可间接传入 `h` 函数的第三个参数。parent: 父组件,可通过它批改父组件的 data 或者调用父组件的办法。injection:注入对象。
props
和一般组件的 props 一样,不要求强制,然而申明后可对其类型进行束缚,组件接口也更加清晰。
slots() 和 children 的区别?
slots()
返回所有插槽的 对象
,children 是一个VNode 数组
,不蕴含 template 上的 v-slot
‘)
<FunTwo>
<p slot="left">left</p>
<span style="color:red;"> 按钮 </span>
<template v-slot:right>
<div>right</div>
</template>
<template slot="middle">
<span>left</span>
</template>
</FunTwo>
children 中蕴含p
、span
、span
,不蕴含 div
。
同时提供,由你决定渲染谁。
slots 和 scopedSlots 的区别?
slots
是函数,返回蕴含所有插槽的 VNode 的对象,属性为插槽名字,不能传参数。
scopedSlots
是对象,属性为插槽名,是一个函数,该函数返回对插槽的 VNode,可传参。
scopedSlots
更加弱小。
children、slots、scopedSlots 应用谁?
组件内部应用 v-slot
指定插槽,优先应用 scopedSlots
,因为它能够传参。
data 对象
<FunTwo
:class="'fun-com'"
class="class2"
:style="{'background-color':'#ccc', color, padding:'20px'}"
style="font-size:20px;"
:title="title"
dataKey="title"
@click="onClick"
/>
蕴含下列属性:
on
属性是一个对象,key 是组件上监听的事件,on.click(params)
可把 params 产生到父组件,即和this.$emit('click',params)
一样。
attrs
非 props 属性。
款式解决:蕴含动静的 class、动态 staticClass 动态 staticStyle、动静 style,vue 会把 style 归一化。
如何在函数式组件中触发自定义事件?
data.on['event-name'](params)
event-name 是组件上监听的事件名称,
listeners['event-name'](params)
.
event-name 是组件上监听的事件名称,params 是事件的实参。
父组件监听不监听该事件,就没有该属性。
injection
父组件提供实例:
provide() {return { parent: this}
},
在子组件中引入:
inject: ['parent'],
injection
就是一个蕴含parent
属性的对象了。
不能用
injection.parent.$emit()
触发自定义事件。$emit 会在外部绑定 this,而 函数式组件没有实例。
如何应用 computed 和 methods
函数式组件不是响应式的,不能像一般组件那样应用计算属性和办法。
模板定义的函数式组件有一个方法,间接定义函数,而后在模板中调用。
<template functional>
<div>
<h1>{{props.title}} {{$options.fullName(props) }}</h1>
</div>
</template>
<script>
export default {
name: 'FunOne',
props: {title: [String],
},
fullName(props) {return props.title + 'jack' + 'chou'},
}
</script>
render 定义的函数式组件,不能把函数申明在 props 同级的中央,可在 render 外部申明。
export default {
name: 'FunButton',
functional: true,
props: {title: [String],
},
render(h, { data, props, children, slots, scopedSlots, injections, parent}) {
const fullName = props => {return props.title + 'jack' + 'chou'}
return h('div', data, [h('button', {}, fullName(props)), props.title])
},
}
定义一个函数式组件的 MyButton
MyButton.jsx
export default {
name: 'MyButton',
functional: true,
props: {
person: {
type: Object,
default: () => ({ name: 'jack', age: 23}),
},
},
render(h, { props, scopedSlots, listeners}) {
// NOTE default 是关键字,须要重命名
const {left, right, default: _defaultSlot} = scopedSlots
const defaultSlot = (_defaultSlot && _defaultSlot({ person: props.person})) || <span> 按钮 </span>
const leftSlot = (left && left()) || ''
const rightSlot = right && right(props.person)
const button = h(
'button',
{
on: {click: () => {listeners.click && listeners.click(props.person)
},
},
},
[defaultSlot]
)
return (
<div>
{leftSlot}
{button}
{rightSlot}
</div>
)
},
}
模板定义形式
<template functional>
<div>
<slot name="left"></slot>
<button @click="listeners['click'] && listeners['click'](props.person)">
<slot :person="props.person">
<span> 按钮 </span>
</slot>
</button>
<slot name="right" :age="props.age"></slot>
</div>
</template>
<script>
export default {
name: 'MyButton',
props: {
person: {
type: Object,
default: () => ({ name: '函数式组件', age: 24}),
},
},
}
</script>
函数式组件有何劣势
函数式组件可读性差,为何还有呢?
疾速,即性能好。
函数式组件没有状态,也就不须要针对 Vue 反应式零碎等额定的初始化了
。
会对新传入的 props 等做出反馈,但对于组件本身,并不通晓其数据何时扭转,因为其并不保护本人的状态,即没有 data。
哪种场景适宜应用函数式组件
- 纯展现的组件,这类组件往往逻辑简略。
- v-for 循环很多的状况,把这部分代码提取成函数式组件。
- 动静得抉择多个组件中一个来渲染。
- 在将 children、props、data 传递给子组件之前操作它们 —-
相当于应用函数式组件作为其父组件,对其二次封装
。 - 高阶组件(HOC)— 通过 props 接管一个返回 VNode 的函数,
函数的第一个参数为 h
,在一个函数式组件
中执行该函数,此函数式组件就是高阶组件。
高阶组件的例子:
export default {
name: 'Container',
functional: true,
render(h, { props}) {return props.renderContainer(h, props.data)
},
}
在模板中应用高阶组件:
<template>
<div class="zm-form-table">
<ul>
<li
v-for="(item, index) in titleList"
:key="index"
>
<Container v-if="typeof item.prop ==='function'":renderContainer="item.prop":data="data" />
<span v-else>
{{![null, void 0, ''].includes(data[item.prop] &&
data[item.prop] ||''
}}
</span>
</div>
</li>
</ul>
</div>
</template>
<script>
import Container from './container.js'
export default {
name: 'FormTable',
components: {Container,},
props: {
titleList: {
type: Array,
default: () => {
return [
// NOTE 测试数据
/*
{title: '题目 3', prop: 'key3'},
{
title: '题目 3',
// prop 能够是字符串,也能够是函数
prop: (h, data) => {
return (
<el-button size='mini' type='primary'>
按钮
</el-button>
)
},
},
*/
]
},
},
data: {
type: Object,
default: () => {
return {
// 测试数据
/*
key1: '测试 1',
key2: '测试 2',
key3: '测试 3',
*/
}
},
},
},
}
</script>
这样传递 props:
<template>
<FormTable :titleList="titleList" :data="detail" />
</template>
<script>
export default {
name: 'BaseInfo',
data() {
return {detail: {},
titleList: [
{
title: '家长身份',
// NOTE data 是从 Container 的 render 函数里传入的,prop 返回 VNode 能是组件扩展性更好
prop: (h, data) => {
const options = [{ label: '爸爸', value: 'father'},
{label: '妈妈', value: 'mother'},
{label: '爷爷', value: 'grandpa'},
{label: '奶奶', value: 'grandma'},
{label: '外公', value: 'grandfather'},
{label: '外婆', value: 'grandmother'},
{label: '其余', value: 'other'},
]
const identity = options.find(item => data[key] === item.value)
return <span>{identity && identity.label}</span>
},
},
{
title: '家长微信',
prop: 'weiXin',
},
],
}
},
created() {setTimeout(() => {
// 接口返回
this.detail = {
identity: 'father',
weiXin: 'jack8848',
}
}, 1000)
},
}
</script>
把 render 函数通过 props 传入组件,这种模式极为有用,在稍后的组件封装中可看到它的益处。
react 中高阶组件:组件作为入参,新组件作为返回值的函数,在返回之前,能够做一些其余解决,做其余解决才是高阶组件的目标。vue 也能实现这样的组件,然而有点麻烦了。
可参考这里: 摸索 Vue 高阶组件
函数式组件的问题
- 款式的 scoped 在函数式组件和状态组件体现不同。
scoped 款式的函数式组件把 scoped 款式的函数式组件作为子组件,css 选择器雷同,父组件的款式失效,即子组件的 scoped 没有失效。
参考
Scoped styles inconsistent between functional and stateful components
Renderless Components in Vue.js