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

这篇具有很好参考价值的文章主要介绍了一文搞懂 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 

在模板中的使用

<ul>
  <li v-for="{ item, index } in list" :key="item.id">
    {{ item }}
  </li>
</ul>

在 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
<div>
  <div v-if="flag">出现</div>
  <span v-else>隐藏</span>
</div>

在 h 函数中的使用

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

事件 

在模板中的使用

<Button @click='submit'>提交</Buttom>

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 及以上版本中,如果你使用了单文件组件,那么基于模板的函数式组件可以这样声明: 

<template functional>
  <button
    class="btn btn-primary"
    v-bind="data.attrs"
    v-on="listeners"
  >
    <slot/>
  </button>
</template>

 slots() 和 children 对比

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

<my-functional-component>
  <p v-slot:foo>
    first
  </p>
  <p>second</p>
</my-functional-component>

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

简单的例子

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

<div v-if="type === 'success'">success</div>
<div v-else-if="type === 'error'">error</div>
<div v-else-if="type === 'warm'">warm</div>
<div v-else>default</div>

 初看是没有问题的,但这样并不好扩展,若想扩展就难道一直 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 <div class={ type }>{type}</div>
	}
}

 这就是一个简单的函数式组件,根据传入的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函数

基本使用

案例一

<script>
import { h } from 'vue'

export default {
    setup() {
        return () => h("h2", null, "Hello World")
    }
}

</script>

案例二 

// 除了类型必填以外,其他的参数都是可选的
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'
)
<button
  onClick={(event) => {
    /* ... */
  }}
>
  click me
</button>

 

组件

在给组件创建 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 () => [
      // 默认插槽:
      // <div><slot /></div>
      h('div', slots.default()),

      // 具名插槽:
      // <div><slot name="footer" :text="message" /></div>
      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 的名字将不会基于驼峰命名法被一般化处理。

<script>
// dynameic 组件
import { h } from "vue";

// Vue3 中函数式组件只能用函数式声明
const DynamicHeading = (props, context) => {
  console.log(context);
  return h(`h${props.level}`, context.attrs, context.slots);
};

DynamicHeading.props = ["level"];

export default DynamicHeading;
</script>

一文搞懂 vue2 与 vue3 函数式组件,vue.js,前端,javascript

父组件里面 

<template>
<dynamic-heading level="5">年后</dynamic-heading>
</template>
<script>
import dynamicHeading from './dynameic.vue'
export default {
  components:{
     dynamicHeading
  }
}
</script>

一文搞懂 vue2 与 vue3 函数式组件,vue.js,前端,javascript

案例二

渲染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) 的 <template> 中移除
  • 非兼容{ functional: true } 选项已从通过函数创建的组件中移除

介绍

在 Vue 2 中,函数式组件主要有两个应用场景:

  • 作为性能优化,因为它们的初始化速度比有状态组件快得多
  • 返回多个根节点

然而,在 Vue 3 中,有状态组件的性能已经提高到它们之间的区别可以忽略不计的程度。此外,有状态组件现在也支持返回多个根节点。

因此,函数式组件剩下的唯一应用场景就是简单组件,比如创建动态标题的组件。否则,建议你像平常一样使用有状态组件。

2.x 语法 

使用 <dynamic-heading> 组件,负责提供适当的标题 (即:h1h2h3 等等),在 2.x 中,这可以通过单文件组件编写:

// Vue 2 函数式组件示例
export default {
  functional: true,
  props: ['level'],
  render(h, { props, data, children }) {
    return h(`h${props.level}`, data, children)
  }
}

或者,对于喜欢在单文件组件中使用 <template> 的用户:

<!-- Vue 2 结合 <template> 的函数式组件示例 -->
<template functional>
  <component
    :is="`h${props.level}`"
    v-bind="attrs"
    v-on="listeners"
  />
</template>

<script>
export default {
  props: ['level']
}
</script>

3.x 语法

现在,在 Vue 3 中,所有的函数式组件都是用普通函数创建的。换句话说,不需要定义 { functional: true } 组件选项。

它们将接收两个参数:props 和 contextcontext 参数是一个对象,包含组件的 attrsslots 和 emit property。

此外,h 现在是全局导入的,而不是在 render 函数中隐式提供。

以前面提到的 <dynamic-heading> 组件为例,下面是它现在的样子。

import { h } from 'vue'

const DynamicHeading = (props, context) => {
  return h(`h${props.level}`, context.attrs, context.slots)
}

DynamicHeading.props = ['level']

export default DynamicHeading

单文件组件 (SFC)

在 3.x 中,有状态组件和函数式组件之间的性能差异已经大大减少,并且在大多数用例中是微不足道的。因此,在单文件组件上使用 functional 的开发者的迁移路径是删除该 attribute,并将 props 的所有引用重命名为 $props,以及将 attrs 重命名为 $attrs

以之前的 <dynamic-heading> 为例,下面是它现在的样子。

<template>
  <component
    v-bind:is="`h${$props.level}`"
    v-bind="$attrs"
  />
</template>

<script>
export default {
  props: ['level']
}
</script>

主要的区别在于:文章来源地址https://www.toymoban.com/news/detail-728255.html

  1. 从 <template> 中移除 functional attribute
  2. listeners 现在作为 $attrs 的一部分传递,可以将其删除

到了这里,关于一文搞懂 vue2 与 vue3 函数式组件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【前端|Javascript第1篇】一文搞懂Javascript的基本语法

    欢迎来到JavaScript的奇妙世界!作为前端开发的基石,JavaScript为网页增色不少,赋予了静态页面活力与交互性。如果你是一名前端小白,对编程一无所知,或者只是听说过JavaScript却从未涉足过,那么你来对了地方!本篇博客将带领你逐步进入JavaScript的大门,一步一步地探索这

    2024年02月14日
    浏览(30)
  • 持续不断更新中... 自己整理的一些前端知识点以及前端面试题,包括vue2,vue3,js,ts,css,微信小程序等

    答: 在普通的前端项目工程中,在script标签中增加setup即可使用api 使用setup()钩子函数 答: 不要在计算属性中进行异步请求或者更改DOM 不要直接修改computed的值 区别: 计算属性值基于其响应式依赖被缓存,意思就是只要他之前的依赖不发生变化,那么调用他只会返回之前缓

    2024年02月11日
    浏览(39)
  • TinyVue - 华为云 OpenTiny 出品的企业级前端 UI 组件库,免费开源,同时支持 Vue2 / Vue3,自带 TinyPro 中后台管理系统

    华为最新发布的前端 UI 组件库,支持 PC 和移动端,自带了 admin 后台系统,完成度很高,web 项目开发又多一个选择。 关于 OpenTiny 和 TinyVue 在上个月结束的华为开发者大会2023上,官方正式进行发布了 OpenTiny,这是华为云出品的企业级设计体系统,一套前端 UI 组件库。适配

    2024年02月11日
    浏览(44)
  • vue3到vue2组件重构方法笔记

    这两天的任务是把一批做好的vue3组件放在vue2项目中使用,将组合式api分散开有一些零散的技巧,所以写一篇转化笔记以供大家参考 先上vue3一个组件的示例代码 上面是其中一个vue3组件样式表,在vue3的项目中展示效果为下图所示: 组件中还有一些其他的功能都要完整保留,

    2024年02月07日
    浏览(33)
  • vue2和vue3 子组件父组件之间的传值方法

    在组件化开发的过程中难免会遇见 子组件和父组件之间的通讯那么这里讲关于vue2和vue3不同的通讯方式 先看一下vue2 父组件向子组件传递参数 父组件通过 : 语法 其实就是v-bind 来传递参数 子组件通过 props 来获取父组件传递的方法 亿点小知识:子组件接收到数据之后,不能直接

    2024年02月09日
    浏览(31)
  • vue父子组件之间双向数据绑定的(vue2/vue3)

    vue父子组件之间双向数据绑定的四种方法(vue2/vue3) vue考虑到组件的可维护性,是不允许子组件改变父组件传的props值的。父组件通过绑定属性的方式向子组件传值,而在子组件中可以通过$emit向父组件通信(第一种方式),通过这种间接的方式改变父组件的data,从而实现子组

    2024年02月08日
    浏览(57)
  • 【前端面经】Vue3和Vue2的区别

    Vue是一种非常流行的JavaScript框架,因其易用性和灵活性在开发人员中备受欢迎。Vue2是Vue框架的上一个重要版本,于2016年发布。但是,Vue3是最新版本的Vue框架,于2020年正式发布并带来了一些重大变化。本文将探讨Vue3和Vue2之间的主要区别。 Vue3的一个显着优势是其更小的代码

    2024年02月02日
    浏览(73)
  • vue2升级为vue3 vuedraggable 拖动组件报错

    升级组件 npm i -S vuedraggable@next 使用示例 2、Item slot must have only one child 官方GitHub - SortableJS/vue.draggable.next: Vue 3 compatible drag-and-drop component based on Sortable.js  官方demo https://sortablejs.github.io/vue.draggable.next/#/two-lists

    2024年02月16日
    浏览(31)
  • 从Vue2到Vue3, 一键升级前端开发技能

    本文的目的,是为了让已经有 Vue2 开发经验的   人   ,快速掌握 Vue3 的写法。 因此,   本篇假定你已经掌握 Vue 的核心内容   ,只为你介绍编写 Vue3 代码,需要了解的内容。 首先,Vue3 新增了一个叫做组合式 api 的东西,英文名叫 Composition API 。因此 Vue3 的  script  现在支

    2024年02月08日
    浏览(63)
  • 关于前端框架vue2升级为vue3的相关说明

    一些框架需要升级 当前(202306) Vue 的最新稳定版本是 v3.3.4 。Vue 框架升级为最新的3.0版本,涉及的相关依赖变更有: 前提条件:已安装 16.0 或更高版本的Node.js(摘) 必须的变更:核心库vue@23、路由vue-router@34、状态管理vuex@34 构建工具链: Vue CLI Vite(摘) 状态管理: Vuex Pi

    2024年02月15日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包