Vue里createElement 参数详解

adminVue里createElement 参数详解已关闭评论条评论 966 次浏览

createElement 参数

接下来你需要熟悉的是如何在 createElement 函数中使用模板中的那些功能。这里是 createElement 接受的参数:

  1. // @returns {VNode}
  2. createElement(
  3. // {String | Object | Function}
  4.  // 一个 HTML 标签名、组件选项对象,或者
  5. // resolve 了上述任何一种的一个 async 函数。必填项。
  6.  'div',
  7. // {Object}
  8.  // 一个与模板中属性对应的数据对象。可选。
  9. {
  10.    // (详情见下一节)
  11. },
  12. // {String | Array}
  13.  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  14. // 也可以使用字符串来生成“文本虚拟节点”。可选。
  15. [
  16. '先写一些文字',
  17. createElement('h1', '一则头条'),
  18. createElement(MyComponent, {
  19. props: {
  20. someProp: 'foobar'
  21. }
  22. })
  23. ]
  24. )

深入数据对象

有一点要注意:正如 v-bind:class 和 v-bind:style 在模板语法中会被特别对待一样,它们在 VNode 数据对象中也有对应的顶层字段。该对象也允许你绑定普通的 HTML 特性,也允许绑定如 innerHTML 这样的 DOM 属性 (这会覆盖 v-html 指令)。

  1. {
  2. // 与 `v-bind:class` 的 API 相同,
  3. // 接受一个字符串、对象或字符串和对象组成的数组
  4. 'class': {
  5. foo: true,
  6. bar: false
  7. },
  8. // 与 `v-bind:style` 的 API 相同,
  9. // 接受一个字符串、对象,或对象组成的数组
  10. style: {
  11. color: 'red',
  12. fontSize: '14px'
  13. },
  14. // 普通的 HTML 特性
  15. attrs: {
  16. id: 'foo'
  17. },
  18. // 组件 prop
  19. props: {
  20. myProp: 'bar'
  21. },
  22. // DOM 属性
  23. domProps: {
  24. innerHTML: 'baz'
  25. },
  26. // 事件监听器在 `on` 属性内,
  27. // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  28. // 需要在处理函数中手动检查 keyCode。
  29. on: {
  30. click: this.clickHandler
  31. },
  32.  // 仅用于组件,用于监听原生事件,而不是组件内部使用
  33. // `vm.$emit` 触发的事件。
  34. nativeOn: {
  35. click: this.nativeClickHandler
  36. },
  37.  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  38. // 赋值,因为 Vue 已经自动为你进行了同步。
  39.  directives: [
  40. {
  41. name: 'my-custom-directive',
  42. value: '2',
  43. expression: '1 + 1',
  44. arg: 'foo',
  45. modifiers: {
  46. bar: true
  47. }
  48. }
  49. ],
  50.  // 作用域插槽的格式为
  51. // { name: props => VNode | Array<VNode> }
  52. scopedSlots: {
  53. default: props => createElement('span', props.text)
  54. },
  55.  // 如果组件是其它组件的子组件,需为插槽指定名称
  56.  slot: 'name-of-slot',
  57. // 其它特殊顶层属性
  58. key: 'myKey',
  59. ref: 'myRef',
  60. // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  61. // 那么 `$refs.myRef` 会变成一个数组。
  62. refInFor: true
  63. }

完整示例

有了这些知识,我们现在可以完成我们最开始想实现的组件:

  1. var getChildrenTextContent = function (children) {
  2. return children.map(function (node) {
  3. return node.children
  4. ? getChildrenTextContent(node.children)
  5. : node.text
  6. }).join('')
  7. }
  8. Vue.component('anchored-heading', {
  9. render: function (createElement) {
  10.    // 创建 kebab-case 风格的 ID
  11.   var headingId = getChildrenTextContent(this.$slots.default)
  12. .toLowerCase()
  13. .replace(/\W+/g, '-')
  14. .replace(/(^-|-$)/g, '')
  15. return createElement(
  16. 'h' + this.level,
  17. [
  18. createElement('a', {
  19. attrs: {
  20. name: headingId,
  21. href: '#' + headingId
  22. }
  23. }, this.$slots.default)
  24. ]
  25. )
  26. },
  27. props: {
  28. level: {
  29. type: Number,
  30. required: true
  31. }
  32. }
  33. })

约束

VNode 必须唯一

组件树中的所有 VNode 必须是唯一的。这意味着,下面的渲染函数是不合法的:

  1. render: function (createElement) {
  2. var myParagraphVNode = createElement('p', 'hi')
  3. return createElement('div', [
  4.    // 错误 - 重复的 VNode
  5.    myParagraphVNode, myParagraphVNode
  6. ])
  7. }

如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落:

  1. render: function (createElement) {
  2. return createElement('div',
  3. Array.apply(null, { length: 20 }).map(function () {
  4. return createElement('p', 'hi')
  5. })
  6. )
  7. }

使用 JavaScript 代替模板功能

v-if 和 v-for

只要在原生的 JavaScript 中可以轻松完成的操作,Vue 的渲染函数就不会提供专有的替代方法。比如,在模板中使用的 v-if 和 v-for

  1. <ul v-if="items.length">
  2. <li v-for="item in items">{{ item.name }}</li>
  3. </ul>
  4. <p v-else>No items found.</p>

这些都可以在渲染函数中用 JavaScript 的 if/else 和 map 来重写:

  1. props: ['items'],
  2. render: function (createElement) {
  3. if (this.items.length) {
  4. return createElement('ul', this.items.map(function (item) {
  5. return createElement('li', item.name)
  6. }))
  7. } else {
  8. return createElement('p', 'No items found.')
  9. }
  10. }

v-model

渲染函数中没有与 v-model 的直接对应——你必须自己实现相应的逻辑:

  1. props: ['value'],
  2. render: function (createElement) {
  3. var self = this
  4. return createElement('input', {
  5. domProps: {
  6. value: self.value
  7. },
  8. on: {
  9. input: function (event) {
  10. self.$emit('input', event.target.value)
  11. }
  12. }
  13. })
  14. }

这就是深入底层的代价,但与 v-model 相比,这可以让你更好地控制交互细节。

事件 & 按键修饰符

对于 .passive.capture 和 .once 这些事件修饰符, Vue 提供了相应的前缀可以用于 on

修饰符 前缀
.passive &
.capture !
.once ~
.capture.once 或
.once.capture
~!

例如:

  1. on: {
  2. '!click': this.doThisInCapturingMode,
  3. '~keyup': this.doThisOnce,
  4. '~!mouseover': this.doThisOnceInCapturingMode
  5. }

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

修饰符 处理函数中的等价操作
.stop event.stopPropagation()
.prevent event.preventDefault()
.self if (event.target !== event.currentTarget) return
按键:
.enter.13
if (event.keyCode !== 13) return (对于别的按键修饰符来说,可将 13 改为另一个按键码)
修饰键:
.ctrl.alt.shift.meta
if (!event.ctrlKey) return (将 ctrlKey 分别修改为 altKeyshiftKey 或者 metaKey)

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

  1. on: {
  2. keyup: function (event) {
  3.    // 如果触发事件的元素不是事件绑定的元素
  4.    // 则返回
  5.    if (event.target !== event.currentTarget) return
  6.    // 如果按下去的不是 enter 键或者
  7.    // 没有同时按下 shift 键
  8.    // 则返回
  9.    if (!event.shiftKey || event.keyCode !== 13) return
  10.    // 阻止 事件冒泡
  11.   event.stopPropagation()
  12.    // 阻止该元素默认的 keyup 事件
  13.    event.preventDefault()
  14. // ...
  15. }
  16. }

插槽

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

  1. render: function (createElement) {
  2. // `<div><slot></slot></div>`
  3. return createElement('div', this.$slots.default)
  4. }

也可以通过 this.$scopedSlots 访问作用域插槽,每个作用域插槽都是一个返回若干 VNode 的函数:

  1. props: ['message'],
  2. render: function (createElement) {
  3. // `<div><slot :text="message"></slot></div>`
  4. return createElement('div', [
  5. this.$scopedSlots.default({
  6. text: this.message
  7. })
  8. ])
  9. }

如果要用渲染函数向子组件中传递作用域插槽,可以利用 VNode 数据对象中的 scopedSlots 字段:

  1. render: function (createElement) {
  2. return createElement('div', [
  3. createElement('child', {
  4. // 在数据对象中传递 `scopedSlots`
  5. // 格式为 { name: props => VNode | Array<VNode> }
  6. scopedSlots: {
  7. default: function (props) {
  8. return createElement('span', props.text)
  9. }
  10. }
  11. })
  12. ])
  13. }

转载于:https://cn.vuejs.org/v2/guide/render-function.html#createElement-参数