关于vue.js:vuu2-中的函数式组件

函数式组件

函数式组件(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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理