Vue3$View-RenderFunction&VueJSX

86 阅读4分钟

Vue3$View-RenderFunction&VueJSX

0. 基本概念

0.1 渲染函数 render function

通常情况下我们使用 Vue 的 template 来创建应用。当使用 template 不方便时,可以使用 渲染函数 render function

setup() 中 return 的函数就是 render function。Render function 的返回值分三种情况:

  1. 字符串(如果不需要实例状态,可简写成函数。函数式组件)
  2. vnode(虚拟 DOM 节点,可通过 Vue 的 h() 生成。)
  3. vnode 数组

0.2 JSX

JSX 是 JavaScript 的一个类似 XML 的扩展;和 template 类似,是另一种表达“动态 HTML”的语法。

Vue 的 JSX 语法和 React 的有些差别:

  • 可以使用 HTML attributes 作为 props ( class 和 for - 而不是 className 或 htmlFor。)
  • 传递子元素给组件 (比如 slots) 的方式不同
const vnode = <div id={dynamicId}>hello, {userName}</div>

1. 基本使用

Vnodes

import { h } from 'vue'

const vnode = h(
  'div', // type
  { id: 'foo', class: 'bar' }, // props
  [
    /* children */
  ]
)
// all arguments except the type are optional
h('div')
h('div', { id: 'foo' })

// both attributes and properties can be used in props
// Vue automatically picks the right way to assign it
h('div', { class: 'bar', innerHTML: 'hello' })

// props modifiers such as `.prop` and `.attr` can be added
// with `.` and `^` prefixes respectively
h('div', { '.name': 'some-name', '^width': '100' })

// class and style have the same object / array
// value support that they have in templates
h('div', { class: [foo, { bar }], style: { color: 'red' } })

// event listeners should be passed as onXxx
h('div', { onClick: () => {} })

// children can be a string
h('div', { id: 'foo' }, 'hello')

// props can be omitted when there are no props
h('div', 'hello')
h('div', [h('span', 'hello')])

// children array can contain mixed vnodes and strings
h('div', ['hello', h('span', 'hello')])
const vnode = h('div', { id: 'foo' }, [])

vnode.type // 'div'
vnode.props // { id: 'foo' }
vnode.children // []
vnode.key // null

Vnodes Must Be Unique

function render() {
  const p = h('p', 'hi')
  return h('div', [
    // Yikes - duplicate vnodes!
    p,
    p
  ])
}
// use factory function
function render() {
  return h(
    'div',
    Array.from({ length: 20 }).map(() => {
      return h('p', 'hi')
    })
  )
}

声明渲染函数

setup() 中返回:

  1. 字符串(如果不需要实例状态,可简写成函数。函数式组件)
  2. vnode
  3. vnode 数组
export default {
  setup() {
    return () => 'hello world!'
  }
}
function Hello() {
  return 'hello world!'
}
import { ref, h } from 'vue'

export default {
  props: {
    /* ... */
  },
  setup(props) {
    const count = ref(1)

    // 返回渲染函数
    return () => h('div', props.msg + count.value)
  }
}
import { h } from 'vue'

export default {
  setup() {
    // 使用数组返回多个根节点
    return () => [
      h('div'),
      h('div'),
      h('div')
    ]
  }
}

2. Render Function Recipes

1. v-if

<div>
  <div v-if="ok">yes</div>
  <span v-else>no</span>
</div>
h('div', [ok.value ? h('div', 'yes') : h('span', 'no')])
<div>{ok.value ? <div>yes</div> : <span>no</span>}</div>

2. v-for

<ul>
  <li v-for="{ id, text } in items" :key="id">
    {{ text }}
  </li>
</ul>
h(
  'ul',
  // assuming `items` is a ref with array value
  items.value.map(({ id, text }) => {
    return h('li', { key: id }, text)
  })
)
<ul>
  {items.value.map(({ id, text }) => {
    return <li key={id}>{text}</li>
  })}
</ul>

3. v-on

h(
  'button',
  {
    onClick(event) {
      /* ... */
    }
  },
  'Click Me'
)
<button
  onClick={(event) => {
    /* ... */
  }}
>
  Click Me
</button>

Event Modifiers

.passive.capture, and .once:

h('input', {
  onClickCapture() {
    /* listener in capture mode */
  },
  onKeyupOnce() {
    /* triggers only once */
  },
  onMouseoverOnceCapture() {
    /* once + capture */
  }
})
<input
  onClickCapture={() => {}}
  onKeyupOnce={() => {}}
  onMouseoverOnceCapture={() => {}}
/>

others:  withModifiers

import { withModifiers } from 'vue'

h('div', {
  onClick: withModifiers(() => {}, ['self'])
})
<div onClick={withModifiers(() => {}, ['self'])} />

4. Components

Basic Usage

import Foo from './Foo.vue'
import Bar from './Bar.jsx'

function render() {
  return h('div', [h(Foo), h(Bar)])
}
function render() {
  return (
    <div>
      <Foo />
      <Bar />
    </div>
  )
}

Dynamic Components

import Foo from './Foo.vue'
import Bar from './Bar.jsx'

function render() {
  return ok.value ? h(Foo) : h(Bar)
}
function render() {
  return ok.value ? <Foo /> : <Bar />
}

不能够导入的组件

有时候组件不能导入,这时可以通过 resolveComponent() 根据组件名来使用该组件。

function render() {
  return h('div', [resolveComponent('a-input')])
}
const Component = resolveComponent('MyComponent');
function render() {
  <div>
  <Component />
  </div>
}

Built-in Components

import { h, KeepAlive, Teleport, Transition, TransitionGroup } from 'vue'

export default {
  setup () {
    return () => h(Transition, { mode: 'out-in' }, /* ... */)
  }
}

5. Slots

Rendering Slots

In render functions, slots can be accessed from the setup() context. Each slot on the slots object is a function that returns an array of vnodes:

export default {
  props: ['message'],
  setup(props, { slots }) {
    return () => [
      // default slot:
      // <div><slot /></div>
      h('div', slots.default()),

      // named slot:
      // <div><slot name="footer" :text="message" /></div>
      h(
        'div',
        slots.footer({
          text: props.message
        })
      )
    ]
  }
}
// default
<div>{slots.default()}</div>

// named
<div>{slots.footer({ text: props.message })}</div>

Passing Slots

functions / object with functions

// single default slot
h(MyComponent, () => 'hello')

// named slots
// notice the `null` is required to avoid
// the slots object being treated as props
h(MyComponent, null, {
  default: () => 'default slot',
  foo: () => h('div', 'foo'),
  bar: () => [h('span', 'one'), h('span', 'two')]
})
// default
<MyComponent>{() => 'hello'}</MyComponent>

// named
<MyComponent>{{
  default: () => 'default slot',
  foo: () => <div>foo</div>,
  bar: () => [<span>one</span>, <span>two</span>]
}}</MyComponent>

Scoped Slots

// parent component
export default {
  setup() {
    return () => h(MyComp, null, {
      default: ({ text }) => h('p', text)
    })
  }
}
// child component
export default {
  setup(props, { slots }) {
    const text = ref('hi')
    return () => h('div', null, slots.default({ text: text.value }))
  }
}
<MyComponent>{{
  default: ({ text }) => <p>{ text }</p>  
}}</MyComponent>

6. v-model

export default {
  props: ['modelValue'],
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    return () =>
      h(SomeComponent, {
        modelValue: props.modelValue,
        'onUpdate:modelValue': (value) => emit('update:modelValue', value)
      })
  }
}

7. Custom Directives

 如果能够获取指令,使用 withDirectives。否则使用resolveDirective

import { h, withDirectives } from 'vue'

// a custom directive
const pin = {
  mounted() { /* ... */ },
  updated() { /* ... */ }
}

// <div v-pin:top.animate="200"></div>
const vnode = withDirectives(h('div'), [
  [pin, 200, 'top', { animate: true }]
])

8. Template Refs

import { h, ref } from 'vue'

export default {
  setup() {
    const divEl = ref()

    // <div ref="divEl">
    return () => h('div', { ref: divEl })
  }
}

3. Functional Components

函数式组件没有状态。不会创建组件实例,不会触发生命周期钩子。 我们用一个普通的函数而不是一个选项对象来创建函数式组件。该函数实际上就是该组件的渲染函数。

function MyComponent(props, { slots, emit, attrs }) {
  // ...
}
MyComponent.props = ['value']
MyComponent.emits = ['click']

attribute fallthrough: 只会继承 classstyle 和 onXxx

MyComponent.inheritAttrs = false

如果你将一个函数作为第一个参数传入 h,它将会被当作一个函数式组件来对待。

Links

VueRenderFunction&JSX