渲染函数

[TOC]

渲染函数

  • 绝大多数情况 使用模板创建HTML
  • 特定场景需要js 此时使用渲染函数render

DOM树

当浏览器读到代码时,会根据代码建立一个DOM节点树来追踪所有内容

  • 每个元素都是一个节点
  • 注释也是一个节点
  • 每个节点都可以有孩子节点
  • 高效更新所有节点比较困难
    • 不需要手动
    • 需要高速Vue 希望页面的HTML是什么 可以是在一个模板里或者一个渲染函数里
    • 这两种情况 Vue都会自动保持页面的更新

虚拟DOM树

Vue通过建立一个虚拟DOM(VNode)来追踪如何改变真实DOM

  • h() 是一个用于创建vnode的函数
  • 参数

    • tag {String | Object | Function | null} 一个HTML标签名、一个组件、一个异步组件或者null,使用null 会渲染成一个注释。==必须的==
    • props {Object} 与attribute、prop和事件相对应的对象。 ==可选的==
    • children {String | Array | Object} 子VNode 使用h()构建 或者使用字符串获取文本Vnode

      或者有插槽的对象。==可选的==

完整实例

const { createApp, h } = Vue

const app = createApp({})

/** Recursively get text from children nodes */
function getChildrenTextContent(children) {
  return children
    .map(node => {
      return typeof node.children === 'string'
        ? node.children
        : Array.isArray(node.children)
        ? getChildrenTextContent(node.children)
        : ''
    })
    .join('')
}

app.component('anchored-heading', {
  render() {
    // create kebab-case id from the text contents of the children
    const headingId = getChildrenTextContent(this.$slots.default())
      .toLowerCase()
      .replace(/\W+/g, '-') // replace non-word characters with dash
      .replace(/(^-|-$)/g, '') // remove leading and trailing dashes

    return h('h' + this.level, [
      h(
        'a',
        {
          name: headingId,
          href: '#' + headingId
        },
        this.$slots.default()
      )
    ])
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

约束

VNodes 必须唯一

  • 组件树中所有VNode必须是唯一的
  • 需要重复很多次的元素/组件 使用工厂函数实现

    render() {
      return h('div',
        Array.from({ length: 20 }).map(() => {
          return h('p', 'hi')
        })
      )
    } // 合法的渲染了20个相同的段落
    

使用JavaScript代替模板功能

v-if 和 v-for

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

利用渲染函数重写

props: ['items'],
render() {
  if (this.items.length) {
    return h('ul', this.items.map((item) => {
      return h('li', item.name)
    }))
  } else {
    return h('p', 'No items found.')
  }
}

v-model

v-model扩展为

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

v-on

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

事件修饰符

  • 对于 .passive.capture.once 事件修饰符,可以使用驼峰写法将他们拼接在事件名后面:
render() {
  return h('input', {
    onClickCapture: this.doThisInCapturingMode,
    onKeyupOnce: this.doThisOnce,
    onMouseoverOnceCapture: this.doThisOnceInCapturingMode
  })
}
  • 对于其他修饰符,在事件处理函数中使用事件方法

    | 修饰符 | 处理函数中的等价操作 | | :--- | :--- | | .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 分别修改为 altKey, shiftKey, 或 metaKey) |

插槽

  • 通过 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
      }))
    }
    
  • 要使用渲染函数将插槽传递给子组件,执行以下操作

    const { h, resolveComponent } = Vue
    
    render() {
      // `<div><child v-slot="props"><span></span></child></div>`
      return h('div', [
        h(
          resolveComponent('child'),
          {},
          // pass `slots` as the children object
          // in the form of { name: props => VNode | Array<VNode> }
          {
            default: (props) => Vue.h('span', props.text)
          }
        )
      ])
    }
    

JSX

  • 语法更接近于模板的语法 (Babel插件jsx-next 用于在Vue 中使用JSX语法)

    import AnchoredHeading from './AnchoredHeading.vue'
    
    const app = createApp({
      render() {
        return (
          <AnchoredHeading level={1}>
            <span>Hello</span> world!
          </AnchoredHeading>
        )
      }
    })
    
    app.mount('#demo')
    

模板编译

results matching ""

    No results matching ""