函数式组件

函数式组件(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,用于拜访 propsslots等属性:

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 中蕴含pspanspan,不蕴含 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() 触发自定义事件。&dollar;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。

哪种场景适宜应用函数式组件

  1. 纯展现的组件,这类组件往往逻辑简略。
  2. v-for 循环很多的状况,把这部分代码提取成函数式组件。
  3. 动静得抉择多个组件中一个来渲染。
  4. 在将 children、props、data 传递给子组件之前操作它们 ---- 相当于应用函数式组件作为其父组件,对其二次封装
  5. 高阶组件(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 高阶组件

函数式组件的问题

  1. 款式的 scoped 在函数式组件和状态组件体现不同。
scoped 款式的函数式组件把 scoped 款式的函数式组件作为子组件,css 选择器雷同,父组件的款式失效,即子组件的 scoped 没有失效。

参考

Scoped styles inconsistent between functional and stateful components

Renderless Components in Vue.js