Skip to content
文章目录

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()