函數式組件(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