函数式组件(functional component)是一个不持有状态data
、实例this
和生命周期的组件。css
函数式组件没有 data、生命周期和
this
,函数式组件又叫无状态组件(stateless component)。
模板定义:html
<template functional> <div> <h1>{{ props.title }}</h1> </div> </template> <script> export default { name: 'FunOne', props: { title: [String], }, } </script> <style></style>
render 函数定义vue
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
react
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 函数中使用 MyInputgit
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 方法。
vue 在 render 函数的第二个参数中提供了context
,用于访问 props
、slots
等属性:github
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-slotbash
')微信
<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
。less
同时提供,由你决定渲染谁。
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 是事件的实参。
父组件监听不监听该事件,就没有该属性。
父组件提供实例:
provide() { return { parent: this } },
在子组件中引入:
inject: ['parent'],
injection
就是一个包含parent
属性的对象了。
不能用
injection.parent.$emit()
触发自定义事件。$emit 会在内部绑定 this,而 函数式组件没有实例。
函数式组件不是响应式的,不能像普通组件那样使用计算属性和方法。
模板定义的函数式组件有一个办法,直接定义函数,而后在模板中调用。
<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.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。
至关于使用函数式组件做为其父组件,对其二次封装
。函数的第一个参数为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 样式的函数式组件做为子组件,css 选择器相同,父组件的样式生效,即子组件的 scoped 没有生效。
Scoped styles inconsistent between functional and stateful components