一文搞懂 vue2 与 vue3 函数式组件

目录

vue2函数式组件

h函数

h函数有那些参数配置

在h函数中如何使用条件判断及循环语句 

事件 

函数式组件

 slots() 和 children 对比

简单的例子

业务场景下的函数式组件

vue3函数式组件

h函数

基本使用

v-on 

组件

渲染插槽 

resolveComponent()

函数式组件

案例一

案例二

总结

概览

介绍

2.x 语法 

3.x 语法

单文件组件 (SFC)


vue2函数式组件

没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数。在这样的场景下,我们可以将组件标记为 functional,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。一个函数式组件就像这样:

h函数

h函数有那些参数配置

export declare function h(
	type: string, 
	props?: RawProps | null, 
	children?: RawChildren | RawSlots
): VNode;

type

  • 类型:String | Object | Function
  • 描述:HTML 标签名、组件、异步组件或函数式组件 (注意:Vue3 不支持组件名用字符串表示了,必须直接使用组件名)

props

  • Object
  • 描述:与我们将在模板中使用的 attribute、prop 和事件相对应。可选html元素的 attribute ,如id name class,vue 的 props参数

children

  • 类型:String | Object | Array
  • 描述:children是子节点 VNode,使用 h() 生成,或者使用字符串来获取“文本 VNode”
	render(h) {
  	// 如果使用原生的则
  	// return h('componentName', {
  	// 这个是挂载组件
  	return h('div', {
  		// 此处是给 SelectEdit 组件传值的(props传值)
 		props: {
	      value: 1,
	      type: 'on'
	    },
	    // class可以数组的形式也可以对象的形式
	    // class: ['speci-class'],
	   	class: {
		  'speci-class': true
		},
		// 样式有-的注意小驼峰 或者使用 string 模式
		style: {
		  color: 'red',
		  fontSize: '14px',
		  // 或者这样
		  'font-size': '14px'
		},
		// 普通的 HTML attribute
		attrs: {
		  placeholder: '这是给原生html赋值placeholder属性'
		},
		// DOM property
		domProps: {
    	  innerHTML: 'DOM property',
    	  // 这个参数等同于h函数的第三个参数
    	  innerText: 'xxxxxxx'
  		},
	    // 这里是挂载方法的但不再支持如 `v-on:keyup.enter` 这样的修饰器
	    on: {
	      // 方法名可以自定义(组件内 $emit('xxxchange', {name: 'zs'}))
	      'xxxchange': val => {
	        this.name = val.name;
	      },
	      'click': val => {
	        this.name = val.name;
	      },
	    },
	    // 仅用于组件,用于监听原生事件,而不是组件内部使用
	    // `vm.$emit` 触发的事件。
	    nativeOn: {
	      click: this.nativeClickHandler
	    },
	    // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
	    directives: [
	      {
	        name: 'my-custom-directive',
	        value: '2',
	        expression: '1 + 1',
	        arg: 'foo',
	        modifiers: {
	          bar: true
	        }
		  }
		],
		// 作用域插槽的格式为
		scopedSlots: {
	     default: props => createElement('span', props.text)
	    },
	     // 如果组件是其它组件的子组件,需为插槽指定名称
	    slot: 'name-of-slot',
	    // 其它特殊顶层 property
	    key: 'myKey',
	    ref: 'myRef',
	    // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  	    // 那么 `$refs.myRef` 会变成一个数组。
	    refInFor: true
	 }, '这里是显示文本')
  }
}


h函数中如何使用条件判断及循环语句 

v-for 

在模板中的使用

  • {{ item }}

在 h 函数中的使用

let arr = [1,2,3,4,5]
render(h){
  return h(
    'ul',
    {},
    [
      arr.map(item => return(h('li',{},'item'))
    ]
  )
}

 

v-if

在模板中的使用

let flag = true
出现
隐藏

在 h 函数中的使用

let flag = true
render(h){
  return h(
    'ul',
    {},
    flag ? '出现' : '隐藏'
  )
}

事件 

在模板中的使用

h函数中的使用

render(h){
  return h(
    'ul',
    {
      on:{
        click: () => {
          // 处理函数
        }
      }
    },
    '提交按钮'
  )
}

 

函数式组件

在普通组件中,没有被定义为 prop 的 attribute 会自动添加到组件的根元素上,将已有的同名 attribute 进行替换或与其进行智能合并。

然而函数式组件要求你显式定义该行为:

Vue.component('my-component', {
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // 完全透传任何 attribute、事件监听器、子节点等。
    return createElement('button', context.data, context.children)
  }
})

context 中包含以下字段的对象:

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

 

注意:在 2.3.0 之前的版本中,如果一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你可以省略 props 选项,所有组件上的 attribute 都会被自动隐式解析为 prop。

当使用函数式组件时,该引用将会是 HTMLElement,因为他们是无状态的也是无实例的

在 2.5.0 及以上版本中,如果你使用了单文件组件,那么基于模板的函数式组件可以这样声明: 

 slots() 和 children 对比

可能想知道为什么同时需要 slots() 和 childrenslots().default 不是和 children 类似的吗?在一些场景中,是这样——但如果是如下的带有子节点的函数式组件呢?


  

first

second

对于这个组件,children 会给你两个段落标签,而 slots().default 只会传递第二个匿名段落标签,slots().foo 会传递第一个具名段落标签。同时拥有 children 和 slots(),因此你可以选择让组件感知某个插槽机制,还是简单地通过传递 children,移交给其它组件去处理。 

简单的例子

假若我们封装一套按钮组件,通过后台返回的type进行渲染,首先可能想到的是 v-if 实现

success
error
warm
default

 初看是没有问题的,但这样并不好扩展,若想扩展就难道一直 v-else-if , 难顶

这里我们就可以用到函数式组件了,提前说明:

  1. 对比传统的 Vue.component 这样定义,我更喜欢用变量定义。

  2. h 即是 createElement ,以下是尤雨溪在一个回复中提到的

这个单词通常用在 virtual-dom 的实现中。Hyperscript 本身是指
生成HTML 结构的 script 脚本,因为 HTML 是 hyper-text markup language 的缩写(超文本标记语言) 

const typeButton = {
	functional:true,
	render(h , { props }){
		const { type } = props
        return 
{type}
} }

 这就是一个简单的函数式组件,根据传入的type不同,渲染出不同的样式的按钮

业务场景下的函数式组件

var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }

Vue.component('smart-list', {
  functional: true,
  props: {
    items: {
      type: Array,
      required: true
    },
    isOrdered: Boolean
  },
  render: function (createElement, context) {
    function appropriateListComponent () {
      var items = context.props.items

      if (items.length === 0)           return EmptyList
      if (typeof items[0] === 'object') return TableList
      if (context.props.isOrdered)      return OrderedList

      return UnorderedList
    }

    return createElement(
      appropriateListComponent(),
      context.data,
      context.children
    )
  }
})

vue3函数式组件

区别vue3对外暴露了h函数 导入使用

h函数

基本使用

案例一


案例二 

// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })

// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
h('div', { class: 'bar', innerHTML: 'hello' })

// 像 `.prop` 和 `.attr` 这样的的属性修饰符
// 可以分别通过 `.` 和 `^` 前缀来添加
h('div', { '.name': 'some-name', '^width': '100' })

// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })

// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')

// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])

// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])

v-on 

以 on 开头,并跟着大写字母的 props 会被当作事件监听器。比如,onClick 与模板中的 @click 等价。

h(
  'button',
  {
    onClick(event) {
      /* ... */
    }
  },
  'click me'
)

 

组件

在给组件创建 vnode 时,传递给 h() 函数的第一个参数应当是组件的定义。这意味着使用渲染函数时不再需要注册组件了 —— 可以直接使用导入的组件:

import Foo from './Foo.vue'
import Bar from './Bar.jsx'

function render() {
  return h('div', [h(Foo), h(Bar)])
}

不管是什么类型的文件,只要从中导入的是有效的 Vue 组件,h 就能正常运作。

如果一个组件是用名字注册的,不能直接导入 (例如,由一个库全局注册),可以使用 resolveComponent() 来解决这个问题。 
与vue2不同 vue2 可以直接使用字符串

渲染插槽 

export default {
  props: ['message'],
  setup(props, { slots }) {
    return () => [
      // 默认插槽:
      // 
h('div', slots.default()), // 具名插槽: //
h( 'div', slots.footer({ text: props.message }) ) ] } }

resolveComponent()

按名称手动解析已注册的组件。

function resolveComponent(name: string): Component | string

备注:如果你可以直接引入组件就不需使用此方法。

为了能从正确的组件上下文进行解析,resolveComponent() 必须在setup() 或渲染函数内调用。

如果组件未找到,会抛出一个运行时警告,并返回组件名字符串。

const { h, resolveComponent } = Vue

export default {
  setup() {
    const ButtonCounter = resolveComponent('ButtonCounter')

    return () => {
      return h(ButtonCounter)
    }
  }
}

 

函数式组件

函数式组件是一种定义自身没有任何状态的组件的方式。它们很像纯函数:接收 props,返回 vnodes。函数式组件在渲染过程中不会创建组件实例 (也就是说,没有 this),也不会触发常规的组件生命周期钩子。

我们用一个普通的函数而不是一个选项对象来创建函数式组件。该函数实际上就是该组件的渲染函数。

函数式组件的签名与 setup() 钩子相同:

function MyComponent(props, { slots, emit, attrs }) {
  // ...
}

案例一

如果这个 props 选项没有被定义,那么被传入函数的 props 对象就会像 attrs 一样会包含所有 attribute。除非指定了 props 选项,否则每个 prop 的名字将不会基于驼峰命名法被一般化处理。

一文搞懂 vue2 与 vue3 函数式组件_第1张图片

父组件里面 


一文搞懂 vue2 与 vue3 函数式组件_第2张图片

案例二

渲染elemen plus 组件

import { h, resolveComponent } from "vue";

// Vue3 中函数式组件需要提供一个渲染函数
const CustomComponent = (props, context) => {
  let dom =
    typeof props.component == "string"
      ? resolveComponent(hyphenToPascal(props.component))
      : "Input";

  // 返回一个渲染函数,可以使用 h 函数创建虚拟节点
  return h(dom, props, context.slots);
};

function hyphenToPascal(str) {
  let arr = str.split("-");

  let resStr = arr.reduce(function (prev, cur) {
    let str = prev + cur.slice(0, 1).toUpperCase() + cur.slice(1);
    return str;
  });

  // 转小驼峰这一行不需要
  resStr = resStr.slice(0, 1).toUpperCase() + resStr.slice(1);

  return resStr;
}

export default CustomComponent;

总结

概览

对变化的总体概述:

  • 2.x 中函数式组件带来的性能提升在 3.x 中已经可以忽略不计,因此我们建议只使用有状态的组件
  • 函数式组件只能由接收 props 和 context (即:slotsattrsemit) 的普通函数创建
  • 非兼容functional attribute 已从单文件组件 (SFC) 的