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:classv-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. }