草庐IT

vue2.x的h函数(createElement)与vue3中的h函数

闲鱼_JavaScript 2023-04-09 原文

1. vue2.x的 h 函数(createElement)

  1. 使用方法及介绍:(参考官网提取)
  2. h函数第一个是标签名字 或者是组件名字,第二个参数是配置项,第三个参数是 innerText ,不会帮你转换节点,如果需要转换成节点(v-html)请去第二个参数中的 domProps 配置 innerHTML
  3. 当第二个参数是字符串的时候则会直接当 innerText 渲染(相当于配置项参数为空对象)【h(‘span’, ‘姓名’)】
  4. 第三个参数如果为字符串的时候可以理解为默认值!(innerText的默认值)如果同时设置了这两个则配置中的权重更高
  5. 第三个参数也是该元素的子集合、插槽设置的地方。
  6. 温馨提示:vue2.x的h函数跟vue3.x的有点不一样,第二个参数配置项格式变了,第三个参数为函数返回,具体情况看本文第二要点
  7. 以下为常见的常规配置
import SelectEdit from './SelectEdit'
export default {
  data() {
  	return {
  	  name: ''
  	}
  },
  render(h) {
  	// 如果使用原生的则
  	// return h('div', {
  	// 这个是挂载组件
  	return h(SelectEdit, {
  		// 此处是给 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
	 }, '这里是显示文本')
  }
}

  1. 举例子了:如果你想要实现以下效果(v-model)
	<div class="row zs active info" name="zs">
		<span style="background-color: red;font-size: 16px;" @click="handleName">姓名:</span>
		<i >{{ name }}</i>
		<input v-model="name" />
	</div>

目测没问题

	data() {
		return {
			name: ''
		}
	},
	render(h) {
		return h('div', {
					class: ['row zs', 'active', 'info'],
					attrs: {
					  name: 'zs'
					}
				}, [
					h('span', {
						style: {
							backgroundColor: red,
							'font-size': '16px'
						},
						on: {
							click: handleName
						}
					}, '姓名:'),
					h('i', '张三'),
					h('input', {
						domProps: {
      						value: this.name
					    },
					    on: {
					      input: function (event) {
					        this.$emit('input', event.target.value)
					      }
					    }
					})
				  ]
				)
	}
	

2. vue3 h函数配置项

  1. 与2.x相比,第一个参数格式没有更变,第二个参数格式更变了,第三个参数变为建议使用函数返回了
  2. 第三个参数如果为字符串的时候可以理解为默认值!(innerText的默认值)如果同时设置了这两个则配置中的权重更高
  3. 第三个参数也是该元素的子集合、插槽设置的地方。
  4. 第三个参数不使用函数会有一个vue警告(所以说直接函数吧)
  5. 具体使用如下:
import { h } from 'vue';
import { ElButton } from 'element-plus'
h(
  ElButton,
  {
    type: 'primary',
    innerText: '修改11',
    onClick: () => {
      console.log(11);
    }
  },
  () => '修改'
)

2.1 v-model实现(以下开始为官网实现)

props: ['modelValue'],
emits: ['update:modelValue'],
render() {
  return h(SomeComponent, {
    modelValue: this.modelValue,
    'onUpdate:modelValue': value => this.$emit('update:modelValue', value)
  })
}

自己搞忘记后重新弄的踩坑记录:

  1. 双向绑定实现:2.x中是value,但是到了3.x中不是value了而是modelValue
  2. onUpdate:modelValue 相当于就是 v-model,只不过这个变成了函数,在这个函数里面需要你自己给绑定元素赋值。不赋值则会出现双向绑定失效的问题!

2.2 v-on

给定有效的事件名称,例如(onClick, onChange)或自定义的名称

render() {
  return h('div', {
    onClick: $event => console.log('clicked', $event.target)
  })
}

2.3 事件修饰符

对于 .passive 、.capture 和 .once 事件修饰符,可以使用驼峰写法将他们拼接在事件名后面:

render() {
  return h('input', {
    onClickCapture: this.doThisInCapturingMode,
    onKeyupOnce: this.doThisOnce,
    onMouseoverOnceCapture: this.doThisOnceInCapturingMode
  })
}

对于所有其它的修饰符,私有前缀都不是必须的,因为你可以在事件处理函数中使用事件方法:

这里是一个使用所有修饰符的例子:

render() {
  return h('input', {
    onKeyUp: event => {
      // 如果触发事件的元素不是事件绑定的元素
      // 则返回
      if (event.target !== event.currentTarget) return
      // 如果向上键不是回车键,则终止
      // 没有同时按下按键 (13) 和 shift 键
      if (!event.shiftKey || event.keyCode !== 13) return
      // 停止事件传播
      event.stopPropagation()
      // 阻止该元素默认的 keyup 事件
      event.preventDefault()
      // ...
    }
  })
}

2.4 插槽

你可以通过 this.$slots 访问静态插槽的内容,每个插槽都是一个 VNode 数组:

render() {
  // `<div><slot></slot></div>`
  return h('div', {}, this.$slots.default())
}
props: ['message'],
render() {
  // `<div><slot :text="message"></slot></div>`
  return h('div', {}, this.$slots.default({
    text: this.message
  }))
}

渲染函数将插槽传递给子组件

render() {
  // `<div><child v-slot="props"><span>{{ props.text }}</span></child></div>`
  return h('div', [
    h(
      resolveComponent('child'),
      {},
      // 将 `slots` 以 { name: props => VNode | Array<VNode> } 的形式传递给子对象。
      {
        default: (props) => h('span', props.text)
      }
    )
  ])
}

插槽以函数的形式传递,允许子组件控制每个插槽内容的创建。任何响应式数据都应该在插槽函数内访问,以确保它被注册为子组件的依赖关系,而不是父组件。相反,对 resolveComponent 的调用应该在插槽函数之外进行,否则它们会相对于错误的组件进行解析。

// `<MyButton><MyIcon :name="icon" />{{ text }}</MyButton>`
render() {
  // 应该是在插槽函数外面调用 resolveComponent。
  const Button = resolveComponent('MyButton')
  const Icon = resolveComponent('MyIcon')

  return h(
    Button,
    null,
    {
      // 使用箭头函数保存 `this` 的值
      default: (props) => {
        // 响应式 property 应该在插槽函数内部读取,
        // 这样它们就会成为 children 渲染的依赖。
        return [
          h(Icon, { name: this.icon }),
          this.text
        ]
      }
    }
  )
}

如果一个组件从它的父组件中接收到插槽,它们可以直接传递给子组件。

render() {
  return h(Panel, null, this.$slots)
}

也可以根据情况单独传递或包裹住。

render() {
  return h(
    Panel,
    null,
    {
      // 如果我们想传递一个槽函数,我们可以通过
      header: this.$slots.header,

      // 如果我们需要以某种方式对插槽进行操作,
      // 那么我们需要用一个新的函数来包裹它
      default: (props) => {
        const children = this.$slots.default ? this.$slots.default(props) : []

        return children.concat(h('div', 'Extra child'))
      }
    }
  )
}

2.5 component 和 is

在底层实现里,模板使用 resolveDynamicComponent 来实现 is attribute。如果我们在 render 函数中需要 is 提供的所有灵活性,我们可以使用同样的函数:

const { h, resolveDynamicComponent } = Vue

// ...

// `<component :is="name"></component>`
render() {
  const Component = resolveDynamicComponent(this.name)
  return h(Component)
}

就像 is, resolveDynamicComponent 支持传递一个组件名称、一个 HTML 元素名称或一个组件选项对象。

通常这种程度的灵活性是不需要的。通常 resolveDynamicComponent 可以被换做一个更直接的替代方案。

例如,如果我们只需要支持组件名称,那么可以使用 resolveComponent 来代替。

如果 VNode 始终是一个 HTML 元素,那么我们可以直接把它的名字传递给 h:

// `<component :is="bold ? 'strong' : 'em'"></component>`
render() {
  return h(this.bold ? 'strong' : 'em')
}

同样,如果传递给 is 的值是一个组件选项对象,那么不需要解析什么,可以直接作为 h 的第一个参数传递。

与 < template > 标签一样,< component > 标签仅在模板中作为语法占位符需要,当迁移到 render 函数时,应被丢弃。

2.6 自定义指令

可以使用 withDirectives 将自定义指令应用于 VNode:

const { h, resolveDirective, withDirectives } = Vue

// ...

// <div v-pin:top.animate="200"></div>
render () {
  const pin = resolveDirective('pin')

  return withDirectives(h('div'), [
    [pin, 200, 'top', { animate: true }]
  ])
}

resolveDirective 是模板内部用来解析指令名称的同一个函数。只有当你还没有直接访问指令的定义对象时,才需要这样做

2.7 内置组件

诸如 < keep-alive >、< transition >、< transition-group > 和 < teleport > 等内置组件默认并没有被全局注册。这使得打包工具可以 tree-shake,因此这些组件只会在被用到的时候被引入构建。不过这也意味着我们无法通过 resolveComponent 或 resolveDynamicComponent 访问它们。

在模板中这些组件会被特殊处理,即在它们被用到的时候自动导入。当我们编写自己的 render 函数时,需要自行导入它们:

const { h, KeepAlive, Teleport, Transition, TransitionGroup } = Vue
// ...
render () {
  return h(Transition, { mode: 'out-in' }, /* ... */)
}

2.8 渲染函数的返回值

在我们目前看过的所有示例中,render 函数返回的是单个根 VNode。但其实也有别的选项。

返回一个字符串时会创建一个文本 VNode,而不被包裹任何元素:

render() {
  return 'Hello world!'
}

我们也可以返回一个子元素数组,而不把它们包裹在一个根结点里。这会创建一个片段 (fragment):

// 相当于模板 `Hello<br>world!`
render() {
  return [
    'Hello',
    h('br'),
    'world!'
  ]
}

可能是因为数据依然在加载中的关系,组件不需要渲染,这时它可以返回 null。这样我们在 DOM 中会渲染一个注释节点

2.9 JSX

如果你写了很多渲染函数,可能会觉得下面这样的代码写起来很痛苦:

h(
  'anchored-heading',
  {
    level: 1
  },
  {
    default: () => [h('span', 'Hello'), ' world!']
  }
)

特别是对应的模板如此简单的情况下:

<anchored-heading :level="1"> <span>Hello</span> world! </anchored-heading>

这就是为什么会有一个 Babel 插件,用于在 Vue 中使用 JSX 语法,它可以让我们回到更接近于模板的语法上。

import AnchoredHeading from './AnchoredHeading.vue'

const app = createApp({
  render() {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
})

app.mount('#demo')

有关 JSX 如何映射到 JavaScript 的更多信息,请参阅使用文档

参考链接

  1. vue2.x官网渲染函数
  2. vue3.x官网dom-树
  3. vue3.x官网渲染函数
  4. 本文仅做于笔记

有关vue2.x的h函数(createElement)与vue3中的h函数的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  6. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

  9. ruby - rspec 需要 .rspec 文件中的 spec_helper - 2

    我注意到像bundler这样的项目在每个specfile中执行requirespec_helper我还注意到rspec使用选项--require,它允许您在引导rspec时要求一个文件。您还可以将其添加到.rspec文件中,因此只要您运行不带参数的rspec就会添加它。使用上述方法有什么缺点可以解释为什么像bundler这样的项目选择在每个规范文件中都需要spec_helper吗? 最佳答案 我不在Bundler上工作,所以我不能直接谈论他们的做法。并非所有项目都checkin.rspec文件。原因是这个文件,通常按照当前的惯例,只

  10. ruby-on-rails - active_admin 目录中的常量警告重新声明 - 2

    我正在使用active_admin,我在Rails3应用程序的应用程序中有一个目录管理,其中包含模型和页面的声明。时不时地我也有一个类,当那个类有一个常量时,就像这样:classFooBAR="bar"end然后,我在每个必须在我的Rails应用程序中重新加载一些代码的请求中收到此警告:/Users/pupeno/helloworld/app/admin/billing.rb:12:warning:alreadyinitializedconstantBAR知道发生了什么以及如何避免这些警告吗? 最佳答案 在纯Ruby中:classA

随机推荐