Vue 函数式组件原理和使用详解

Vue 提供了一种称为函数式组件的组件类型,用来定义那些没有响应数据,也不需要有任何生命周期的场景,它只接受一些props 来显示组件。
一:使用方法

Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
})

或者单文件定义函数式组件(2.5版本后)


二:参数

  1. functional
    设置为true 即表示该组件为一个函数组件
  2. props(可选)
    传递值到组件内部,2.3.0版本后可以省略,框架会自动将组件上的特性解析为prop
  3. render函数
    提供渲染函数来返回一个vnode

三:和正常自定义组件的区别?

  1. 不维护响应数据
  2. 无钩子函数
  3. 没有instance实例
    所以在组件内部没有办法像传统组件一样通过this来访问组件属性
    实现原理见下面代码中的中文注释
function createComponent (
    Ctor,
    data,
    context,
    children,
    tag
  ) {
    if (isUndef(Ctor)) {
      return
    }

    var baseCtor = context.$options._base;

    // 省略N行
    // functional component
    if (isTrue(Ctor.options.functional)) { // 在此判断是否是函数式组件,如果是return 自定义render函数返回的Vnode,跳过底下初始化的流程
      return createFunctionalComponent(Ctor, propsData, data, context, children)
    }
    // 省略N行
    // install component management hooks onto the placeholder node
    installComponentHooks(data); // 正常的组件是在此进行初始化方法(包括响应数据和钩子函数的执行)

    // return a placeholder vnode
    var name = Ctor.options.name || tag;
    var vnode = new VNode(
      ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
      data, undefined, undefined, undefined, context,
      { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
      asyncFactory
    );

    return vnode
  }

正是因为函数式组件精简了很多例如响应式和钩子函数的处理,因此渲染性能会有一定的提高,所以如果你的业务组件是一个纯展示且不需要有响应式数据状态的处理的,那函数式组件会是一个非常好的选择。

四:render函数

  1. render函数是函数式组件最重要的参数,且是必须的。

  2. render函数有两个参数,一个是createElement,一个是context
    createElement 是创建虚拟dom的函数
    context 是函数式组件的上下文,它包括:

props:提供所有 prop 的对象
children: VNode 子节点的数组
slots: 一个函数,返回了包含所有插槽的对象
scopedSlots: (2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
parent:对父组件的引用
listeners: (2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
injections: (2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的属性。

由于函数式组件没有创建组件实例,所有传统的通过this来调用的属性,在这里都需要通过context来调用。例如:

Vue.component('my-functional-button', {
  functional: true,
  render: function (createElement, context) {
    return createElement('button', context.data, context.children)
  }
})

context 上下文的源码:

function FunctionalRenderContext (
    data,
    props,
    children,
    parent,
    Ctor
  ) {
    var this$1 = this;

    var options = Ctor.options;
    // ensure the createElement function in functional components
    // gets a unique context - this is necessary for correct named slot check
    var contextVm;
    if (hasOwn(parent, '_uid')) {
      contextVm = Object.create(parent);
      // $flow-disable-line
      contextVm._original = parent;
    } else {
      // the context vm passed in is a functional context as well.
      // in this case we want to make sure we are able to get a hold to the
      // real context instance.
      contextVm = parent;
      // $flow-disable-line
      parent = parent._original;
    }
    var isCompiled = isTrue(options._compiled);
    var needNormalization = !isCompiled;

    this.data = data;
    this.props = props;
    this.children = children;
    this.parent = parent;
    this.listeners = data.on || emptyObject; // data.on的别名
    this.injections = resolveInject(options.inject, parent);
    this.slots = function () { // 获取slots的函数
      if (!this$1.$slots) {
        normalizeScopedSlots(
          data.scopedSlots,
          this$1.$slots = resolveSlots(children, parent)
        );
      }
      return this$1.$slots
    };

    Object.defineProperty(this, 'scopedSlots', ({
      enumerable: true,
      get: function get () {
        return normalizeScopedSlots(data.scopedSlots, this.slots())
      }
    }));
    // ****
  }

五:context中的 slots()和children

slots 返回的是map化的非作用域插槽,key是slot的名字,value是slot的内容,所有我们可以通过slots().default 来调用指定的插槽。
children 是一个数组,包含了所有的非作用域插槽。所以我们可以很简单的把所有插槽传递给下一个函数进行处理。

六:context中的scopedSlots使用演示


   
demo functional component {{scope.a}}
Vue.component('func-comp', {
    functional: true,
    props: {
      name: String
    },
    render (createElement, context) {
      return createElement('div', context.data, [context.scopedSlots.default({
         a:1
      })])
    }
  })

显示结果:demo functional component 1

你可能感兴趣的:(前端)