Vue3$View-RenderFunction&VueJSX
0. 基本概念
0.1 渲染函数 render function
通常情况下我们使用 Vue 的 template 来创建应用。当使用 template 不方便时,可以使用 渲染函数 render function。
在 setup() 中 return 的函数就是 render function。Render function 的返回值分三种情况:
- 字符串(如果不需要实例状态,可简写成函数。函数式组件)
- vnode(虚拟 DOM 节点,可通过 Vue 的
h()生成。) - 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() 中返回:
- 字符串(如果不需要实例状态,可简写成函数。函数式组件)
- vnode
- 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: 只会继承 class、style 和 onXxx。
MyComponent.inheritAttrs = false
如果你将一个函数作为第一个参数传入 h,它将会被当作一个函数式组件来对待。