vue组件多种写法
render + h 方式
js
import { h } from 'vue'
export default {
data() {
return {
count: 1,
}
},
render() {
return h('div', {}, [h('p', {}, this.count), h('button', { onClick: this.AddCount }, 'Add')])
},
methods: {
AddCount() {
this.count++
},
},
}
render 函数说明与使用
可以使用 render 选项来声明渲染函数
js
import { h } from 'vue'
export default {
data() {
return {
msg: 'hello',
}
},
render() {
return h('div', this.msg)
},
}
可以返回字符串或是数组
js
export default {
render() {
return 'hello world!'
},
}
js
import { h } from 'vue'
export default {
render() {
// 用数组来返回多个根节点
return [h('div'), h('div'), h('div')]
},
}
h 函数参数说明,使用与返回结果
h 函数参数说明
js
import { h } from 'vue'
const vnode = h(
'div', // type
{ id: 'foo', class: 'bar' }, // props
[
/* children */
]
)
h 函数使用方式
js
// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })
// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
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' })
// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })
// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')
// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])
// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])
h 函数中使用插槽
js
// 单个默认插槽
h(MyComponent, () => 'hello')
// 具名插槽
// 注意 `null` 是必需的
// 以避免 slot 对象被当成 prop 处理
h(MyComponent, null, {
default: () => 'default slot',
foo: () => h('div', 'foo'),
bar: () => [h('span', 'one'), h('span', 'two')],
})
h 函数使用内置组件
js
import { h, KeepAlive, Teleport, Transition, TransitionGroup } from 'vue'
export default {
render() {
return h(Transition, { mode: 'out-in' } /* ... */)
},
}
使用v-model
js
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
render() {
return h(SomeComponent, {
modelValue: this.modelValue,
'onUpdate:modelValue': value => this.$emit('update:modelValue', value),
})
},
}
使用自定义指令
js
import { h, withDirectives } from 'vue'
// 自定义指令
const pin = {
mounted() {
/* ... */
},
updated() {
/* ... */
},
}
// <div v-pin:top.animate="200"></div>
const vnode = withDirectives(h('div'), [[pin, 200, 'top', { animate: true }]])
h 函数返回结果为 vnode
js
const vnode = h('div', { id: 'foo' }, [])
vnode.type // 'div'
vnode.props // { id: 'foo' }
vnode.children // []
vnode.key // null
如果你想在页面上渲染多个重复的元素或者组件,必须使用一个工厂函数来做这件事。比如下面的这个渲染函数就可以完美渲染出 20 个相同的段落:
js
function render() {
return h(
'div',
Array.from({ length: 20 }).map(() => {
return h('p', 'hi')
})
)
}
jsx 方式
基本使用
js
const vnode = <div>hello</div>
const vnode = <div id={dynamicId}>hello, {userName}</div>
jsx
import { ref } from 'vue'
export default {
name: 'ComponentA',
setup() {
const counter = ref(0)
const btnClick = function () {
counter.value++
}
// 这里需要setup返回一个函数 函数内再返回jsx模板
return () => {
return (
<div>
<div>
// 引用变量需在jsx中用 {} 包裹 当前计数: {counter.value}
</div>
<button onClick={btnClick}>btn</button>
</div>
)
}
},
}
jsx 中获取插槽
jsx
import { h, defineComponent } from 'vue'
// 需要传递插槽的组件
const TestSlotComponent = defineComponent({
name: 'TestSlotComponent',
data() {
return {
propData: 'cqc',
}
},
render() {
// 基本我们定义的插槽都在这儿
const { default: defaultSlot, headerSlot } = this.$slots
return (
<div>
<div>
// 可以插槽内传递数据, 这也就是我们通常说的作用域插槽 这里是headerSlot
{headerSlot && headerSlot(this.propData)}
</div>
<div>
这里是默认插槽
{defaultSlot && defaultSlot()}
</div>
</div>
)
},
})
jsx 中使用插槽
jsx
// 默认插槽
<MyComponent>{() => 'hello'}</MyComponent>
// 具名插槽
<MyComponent>{
{
default: () => 'default slot',
foo: () => <div>foo</div>,
bar: () => [<span>one</span>, <span>two</span>]
}
}</MyComponent>
jsx
// 再另外组件中使用我们定义好的插槽
const UseSlotComponent = {
name: 'UseSlotComponent',
setup() {
return () => {
// 方式一
const Test1 = (
<Test-Slot-Component>
<div>这里即为默认default插槽, 直接写入即可</div>
</Test-Slot-Component>
)
const TestSlot2 = {
default: () => {
return <div>默认插槽</div>
},
headerSlot: props => {
return (
<div>
自定义插槽 props 是作用域数据
{props}
</div>
)
},
}
// 方式二
const Test2 = <Test-Slot-Component>{TestSlot2}</Test-Slot-Component>
// 方式三
const Test3 = <Test-Slot-Component v-slots={TestSlot2} />
// 方式四
const Test4 = h(TestSlotComponent, null, TestSlot2)
return (
<div>
{Test1} // 如果只使用default默认插槽
{Test2} // 使用多个模板插槽
{Test3} // 使用多个模板插槽
{Test4} // 使用多个模板插槽
</div>
)
}
},
}
createVNode 方式
html
// confirm.vue 组件封装
<template>
<div class="confirm" @click.self="cancelCallback" :class="{ fade }">
<div class="wrapper" :class="{ fade }">
<div class="header">
<h3>{{ title }}</h3>
<a @click.stop="cancelCallback" href="JavaScript:;" class="iconfont icon-close-new"></a>
</div>
<div class="body">
<i class="iconfont icon-warning"></i>
<span>{{ text }}</span>
</div>
<div class="footer">
<button size="mini" @click.stop="cancelCallback" type="gray">取消</button>
<button size="mini" @click.stop="submitCallback" type="primary">确认</button>
</div>
</div>
</div>
</template>
<script>
import { onMounted, ref } from 'vue'
import Button from './Button.vue'
export default {
name: 'Confirm',
props: {
title: {
type: String,
default: '温馨提示',
},
text: {
type: String,
default: '',
},
submitCallback: {
type: Function, // 确认事件
},
cancelCallback: {
type: Function, // 取消事件
},
},
setup(props) {
const fade = ref(false)
onMounted(() => {
// 当元素渲染完毕立即过渡的动画不会触发
setTimeout(() => {
fade.value = true
}, 0)
})
return { fade }
},
components: { Button },
}
</script>
<style scoped lang="less">
.confirm {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 8888;
background: rgba(0, 0, 0, 0);
&.fade {
transition: all 0.4s;
background: rgba(0, 0, 0, 0.5);
}
.wrapper {
width: 400px;
background: #fff;
border-radius: 4px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -60%);
opacity: 0;
&.fade {
transition: all 0.4s;
transform: translate(-50%, -50%);
opacity: 1;
}
.header,
.footer {
height: 50px;
line-height: 50px;
padding: 0 20px;
}
.body {
padding: 20px 40px;
font-size: 16px;
.icon-warning {
color: @priceColor;
margin-right: 3px;
font-size: 16px;
}
}
.footer {
text-align: right;
.button {
margin-left: 20px;
}
}
.header {
position: relative;
h3 {
font-weight: normal;
font-size: 18px;
}
a {
position: absolute;
right: 15px;
top: 15px;
font-size: 20px;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
color: #999;
&:hover {
color: #666;
}
}
}
}
}
</style>
jsx
// Confirm.js 封装调用 Confirm.vue
//创建一个虚拟节点
import { createVNode, render } from 'vue'
import XtxConfirm from './confirm.vue'
// 动态创建一个DOM容器
const container = document.createElement('div')
container.setAttribute('class', 'confirm-container')
document.body.appendChild(container)
export default ({ title, text }) => {
return new Promise((resolve, reject) => {
// 如果想让then触发,需要调用resolve:点击确认按钮触发
// 如果想让catch触发,需要调用reject:点击取消按钮触发
// 点击确认按钮
const submitCallback = () => {
// 销毁确认框
render(null, container)
resolve()
}
// 点击取消按钮
const cancelCallback = () => {
// 销毁确认框
render(null, container)
// eslint-disable-next-line prefer-promise-reject-errors
reject('cancel')
}
// 把组件渲染到页面中
const vnode = createVNode(XtxConfirm, { title, text, cancelCallback, submitCallback })
// 把虚拟节点渲染DOM中
render(vnode, container)
})
}
全局挂载这类组件
js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Confirm from '@/components/Confirm.js' //按自己文件路径导入
const app = createApp(App)
app.config.globalProperties.$Confirm = Confirm //全局挂载Confirm
app.use(router)
app.mount('#app')
Confirm.js 使用
js
// 非全局挂载使用时导入Confirm
import Confirm from '@/cpompoents/Confirm.js'
const delete = () => {
Confirm({ text: '您确定从删除该此项吗?' }).then(() => {
console.log('点击确认事件处理')
}).catch(e => {
console.log('点击取消事件处理')
})
}
普通 sfc
html
<template>
<div>
<p>hello</p>
<button @click="addCount">按钮</button>
</div>
</template>
用 createVNode 挂载/卸载组件
js
import { createVNode, render } from 'vue'
import MyComp from './MyComp.vue'
// 动态创建一个DOM容器
const container = document.createElement('div')
container.setAttribute('class', 'confirm-container')
document.body.appendChild(container)
// 把组件渲染到页面中
const vnode = createVNode(MyComp, { title, text, cancelCallback, submitCallback })
// 把虚拟节点渲染DOM中
render(vnode, container)
// 卸载这个组件
render(null, container)
用 createApp 挂载/卸载组件
js
import { createApp } from 'vue'
import MyComp from './MyComp.vue'
// 动态创建一个DOM容器
const container = document.createElement('div')
container.setAttribute('class', 'confirm-container')
document.body.appendChild(container)
let app = createApp(HelloWorld)
// 把app渲染到DOM中
app.mount(container)
// 卸载应用实例
app.unmount()