Urara-Blog/urara/2022-08-12-vue-challenges.md
2022-08-14 14:10:39 +08:00

1237 lines
25 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Vue.js 挑战练习
created: 2022-08-12
summary: 我的答案以及相关知识点
tags:
- Vue
---
最近做了一下这个[Vue.js 挑战](https://cn-vuejs-challenges.netlify.app/questions/14-dynamic-css-values/README.zh-CN.html),其中的题目大多出自[Vue3 文档](https://staging-cn.vuejs.org/),都不是很难,但涉及到的知识点
比较琐碎,用来复习挺好的。
然后这是我的答案和题目涉及到的知识点,除了[鼠标指针](###鼠标指针)这个部分没通过单元测试之外,其他都都通过了,然后这个鼠标指针为什么没通过单元测试我也没弄明白,试了下其他人的也通过不了,好奇怪……
这里省去部分题目,主要写答案。
## Built-ins
### DOM 传送门
Vue.js 提供了一个内置组件,将其插槽内容渲染到另一个 DOM成为该 DOM 的一部分。
```vue
<script setup>
const msg = 'Hello World'
</script>
<template>
<teleport to="body">
<span>{{ msg }}</span>
</teleport>
</template>
```
相关知识点 [Teleport | Vue.js](https://v3.cn.vuejs.org/guide/teleport.html#teleport)
> 有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置[^1]。
- 有点像传送门,将相应元素渲染到制定位置
- to 后面写 css selector
### 优化性能的指令
Vue.js 提供了一个指令,以便只渲染一次元素和组件,并且跳过以后的更新。
```vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
setInterval(() => {
count.value++
}, 1000)
</script>
<template>
<span v-once>使它从不更新: {{ count }}</span>
</template>
```
相关知识点:`Vue-事件修饰符`
## CSS Features
### 动态 CSS
Vue 单文件组件 `<style>` 模块支持给 CSS 绑定动态值。
```vue
<script setup>
import { ref } from 'vue'
const theme = ref('red')
const colors = ['blue', 'yellow', 'red', 'green']
setInterval(() => {
theme.value = colors[Math.floor(Math.random() * 4)]
}, 1000)
</script>
<template>
<p>hello</p>
</template>
<style scoped>
/* Modify the code to bind the dynamic color */
p {
color: v-bind(theme);
}
</style>
```
相关知识点:`v-bind` `Dynamic Styling动态绑定样式`
### 全局 CSS
给具有 CSS 作用域的 Vue 单文件组件设置全局 CSS 样式
```vue
<template>
<p>Hello Vue.js</p>
</template>
<style scoped>
p {
font-size: 20px;
color: red;
text-align: center;
line-height: 50px;
}
/* Make it work */
:global(body) {
width: 100vw;
height: 100vh;
background-color: burlywood;
}
</style>
```
或者
```vue
<template>
<p>Hello Vue.js</p>
</template>
<style scoped>
p {
font-size: 20px;
color: red;
text-align: center;
line-height: 50px;
}
</style>
<style>
/* Make it work */
body {
width: 100vw;
height: 100vh;
background-color: burlywood;
}
</style>
```
相关知识点:[单文件组件 CSS 功能 | Vue.js](https://staging-cn.vuejs.org/api/sfc-css-features.html)
## Components
### DOM 传送门
见上面
### Props 验证
验证 Button 组件的 Prop 类型 ,使它只接收: primary | ghost | dashed | link | text | default ,且默认值为 default
```vue
<script setup>
import Button from './Button.vue'
defineProps({
type: {
type: String,
default: 'default',
validator: value => {
;['primary', 'ghost', 'dashed', 'link', 'text', 'default'].includes(value)
}
}
})
</script>
<template>
<Button type="dashed" />
</template>
```
相关知识点:[Props | Vue.js](https://staging-cn.vuejs.org/guide/components/props.html#prop-validation)
### 函数式组件
这题我不是很懂,翻了一下大家的解决方案,感觉这个比较能看懂:[21 - functional component · Issue #322 · webfansplz/vuejs-challenges · GitHub](https://github.com/webfansplz/vuejs-challenges/issues/322)
```vue
<script setup lang="ts">
import { ref, h } from 'vue'
/**
* Implement a functional component :
* 1. Render the list elements (ul/li) with the list data
* 2. Change the list item text color to red when clicked.
*/
const ListComponent = (props, { emit }) =>
h(
// 创建 ul
'ul',
// 根据传入的props创建li
props.list.map((item: { name: string }, index: number) =>
h(
'li',
{
// 点击时处罚toggle。并将当前index作为参数传入toggle
onClick: () => emit('toggle', index),
// 将当前点击的li颜色设置为红色
style: index === props.activeIndex ? { color: 'red' } : null
},
// li 默认值
item.name
)
)
)
ListComponent.props = ['list', 'activeIndex']
ListComponent.emits = ['toggle']
const list = [
{
name: 'John'
},
{
name: 'Doe'
},
{
name: 'Smith'
}
]
const activeIndex = ref(0)
function toggle(index: number) {
activeIndex.value = index
}
</script>
<template>
<list-component :list="list" :active-index="activeIndex" @toggle="toggle" />
</template>
```
相关知识点:
- [渲染函数 & JSX | Vue.js](https://staging-cn.vuejs.org/guide/extras/render-function.html#functional-components)
- [渲染机制 | Vue.js](https://staging-cn.vuejs.org/guide/extras/rendering-mechanism.html)
### 渲染函数[h()]
> 使用 h 渲染函数来实现一个组件。
```vue
import { defineComponent, h } from 'vue'; export default defineComponent({ name: 'MyButton', props: { disabled: { type: Boolean,
default: false, }, }, emits: ['custom-click'], setup(props, { emit, slots }) { return () => h( 'button', { disabled:
props.disabled, onClick: () => emit('custom-click'), }, slots.default?.() ); }, });
```
### 树组件
实现一个树组件
```vue
<script setup lang="ts">
import { defineComponent } from 'vue'
interface TreeData {
key: string
title: string
children: TreeData[]
}
defineProps<{ data: TreeData[] }>()
</script>
<template>
<ul v-for="node in data">
<li>{{ node.title }}</li>
<template v-if="node.children && node.children.length">
// 用递归的方法来实现
<TreeComponent :data="node.children" />
</template>
</ul>
</template>
```
参考:
- [208 - Tree Component · Issue #659 · webfansplz/vuejs-challenges · GitHub](https://github.com/webfansplz/vuejs-challenges/issues/659)
- [Creating a Recursive Tree Component in Vue.js | DigitalOcean](https://www.digitalocean.com/community/tutorials/vuejs-recursive-components)
相关知识点:[单文件组件 `<script setup>` | Vue.js](https://staging-cn.vuejs.org/api/sfc-script-setup.html#recursive-components)
## Composable Function
本节相关知识点:[组合式函数 | Vue.js](https://staging-cn.vuejs.org/guide/reusability/composables.html)
### 切换器
尝试编写可组合函数
```vue
<script setup lang="ts">
import { ref } from 'vue'
/**
* Implement a composable function that toggles the state
* Make the function work correctly
*/
function useToggle(init: boolean) {
const state = ref(init)
const toggle = () => (state.value = !state.value)
return [state, toggle]
}
const [state, toggle] = useToggle(false)
</script>
<template>
<p>State: {{ state ? 'ON' : 'OFF' }}</p>
<p @click="toggle">Toggle state</p>
</template>
```
### 计数器
实现一个计数器
```vue
<script setup lang="ts">
import { ref } from 'vue'
interface UseCounterOptions {
min?: number
max?: number
}
/**
* Implement the composable function
* Make sure the function works correctly
*/
function useCounter(initialValue = 0, options: UseCounterOptions = {}) {
const count = ref(initialValue)
const inc = () => {
if (count.value < options.max) count.value++
}
const dec = () => {
if (count.value > options.min) count.value--
}
const reset = () => (count.value = initialValue)
return { count, inc, dec, reset }
}
const { count, inc, dec, reset } = useCounter(0, { min: 0, max: 10 })
</script>
```
### 实现本地存储函数
封装一个`localStorage`API
```vue
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
/**
* Implement the composable function
* Make sure the function works correctly
*/
function useLocalStorage(key: string, initialValue: any) {
const value = ref(localStorage.getItem(key) || initialValue)
watchEffect(() => {
localStorage.setItem(key, value.value)
})
return value
}
const counter = useLocalStorage('counter', 0)
// We can get localStorage by triggering the getter:
console.log(counter.value)
// And we can also set localStorage by triggering the setter:
const update = () => counter.value++
</script>
<template>
<p>Counter: {{ counter }}</p>
<button @click="update">Update</button>
</template>
```
相关知识点:
- [watchEffect()](https://staging-cn.vuejs.org/api/reactivity-core.html#watcheffect)
- [Window.localStorage - Web API 接口参考 | MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/localStorage)
### 鼠标坐标
这个没通过单元测试,不知道什么原因,试了下其他人的也没能通过……
```vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
// Implement ...
function useEventListener(target, event, callback) {
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
// Implement ...
function useMouse() {
const x = ref(0)
const y = ref(0)
useEventListener(window, 'mousemove', e => {
x.value = e.pageX
y.value = e.pageY
})
return { x, y }
}
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
```
## Composition API
### 生命周期钩子
```vue
<script setup lang="ts">
import { onMounted, inject, onUnmounted } from 'vue'
const timer = inject('timer')
const count = inject('count')
onMounted(() => {
timer.value = window.setInterval(() => {
count.value++
}, 1000)
})
// 计时器要清除
onUnmounted(() => {
window.clearInterval(timer.value)
})
</script>
<template>
<div>
<p>Child Component: {{ count }}</p>
</div>
</template>
```
- 相关知识点:
- [组合式 API依赖注入 | Vue.js](https://staging-cn.vuejs.org/api/composition-api-dependency-injection.html)
- [组合式 API生命周期钩子 | Vue.js](https://staging-cn.vuejs.org/api/composition-api-lifecycle.html#onunmounted)
### ref 全家桶
```vue
<script setup lang="ts">
import { ref, Ref, reactive, isRef, unref, toRef } from 'vue'
const initial = ref(10)
const count = ref(0)
// Challenge 1: Update ref
function update(value) {
count.value = value
}
/**
* Challenge 2: Check if the `count` is a ref object.
* Make the output be 1
*/
console.log(isRef(count) ? 1 : 0)
/**
* Challenge 3: Unwrap ref
* Make the output be true
*/
function initialCount(value: number | Ref<number>) {
// Make the output be true
console.log(unref(value) === 10)
}
initialCount(initial)
/**
* Challenge 4:
* create a ref for a property on a source reactive object.
* The created ref is synced with its source property:
* mutating the source property will update the ref, and vice-versa.
* Make the output be true
*/
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo') // change the impl...
// mutating the ref updates the original
fooRef.value++
console.log(state.foo === 2)
// mutating the original also updates the ref
state.foo++
console.log(fooRef.value === 3)
</script>
<template>
<div>
<p>
<span @click="update(count - 1)">-</span>
{{ count }}
<span @click="update(count + 1)">+</span>
</p>
</div>
</template>
```
相关知识点:
- [isRef()](https://staging-cn.vuejs.org/api/reactivity-utilities.html#isref)
- [unref()](https://staging-cn.vuejs.org/api/reactivity-utilities.html#unref)
- [toRef](https://staging-cn.vuejs.org/api/reactivity-utilities.html#toref)
### 响应性丢失
保证解构/扩展不丢失响应性
```vue
<script setup lang="ts">
import { reactive, toRefs } from 'vue'
function useCount() {
const state = reactive({
count: 0
})
function update(value: number) {
state.count = value
}
return {
state: toRefs(state),
update
}
}
// Ensure the destructured properties don't lose their reactivity
const {
state: { count },
update
} = useCount()
</script>
<template>
<div>
<p>
<span @click="update(count - 1)">-</span>
{{ count }}
<span @click="update(count + 1)">+</span>
</p>
</div>
</template>
```
相关知识点:[toRefs](https://staging-cn.vuejs.org/api/reactivity-utilities.html#torefs)
### 可写的计算属性
```vue
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(1)
const plusOne = computed({
get() {
return count.value + 1
},
set(newValue) {
count.value = newValue - 1
}
})
/**
* Make the `plusOne` writable.
* So that we can get the result `plusOne` to be 3, and `count` to be 2.
*/
plusOne.value++
</script>
<template>
<div>
<p>{{ count }}</p>
<p>{{ plusOne }}</p>
</div>
</template>
```
相关知识点:[可写的计算属性 ](https://staging-cn.vuejs.org/guide/essentials/computed.html#writable-computed)
### watch 全家桶
```vue
<script setup lang="ts">
import { ref, watch } from 'vue'
const count = ref(0)
/**
* Challenge 1: Watch once
* Make sure the watch callback only triggers once
*/
const watchOnce = watch(count, () => {
console.log('Only triggered once')
watchOnce()
})
count.value = 1
setTimeout(() => (count.value = 2))
/**
* Challenge 2: Watch object
* Make sure the watch callback is triggered
*/
const state = ref({
count: 0
})
watch(
state,
() => {
console.log('The state.count updated')
},
{ deep: true }
)
state.value.count = 2
/**
* Challenge 3: Callback Flush Timing
* Make sure visited the updated eleRef
*/
const eleRef = ref()
const age = ref(2)
watch(
age,
() => {
console.log(eleRef.value)
},
{
flush: 'post'
}
)
age.value = 18
</script>
<template>
<div>
<p>
{{ count }}
</p>
<p ref="eleRef">
{{ age }}
</p>
</div>
</template>
```
相关知识点:[侦听器 | Vue.js](https://staging-cn.vuejs.org/guide/essentials/watchers.html)
### 浅层 ref
响应式 API: shallowRef
```vue
<script setup lang="ts">
import { shallowRef, watch } from 'vue'
const state = shallowRef({ count: 1 })
// Does NOT trigger
watch(
state,
() => {
console.log('State.count Updated')
},
{ deep: true }
)
/**
* Modify the code so that we can make the watch callback trigger.
*/
state.value = { count: 2 }
</script>
<template>
<div>
<p>
{{ state.count }}
</p>
</div>
</template>
```
相关知识点:[shallowRef()](https://staging-cn.vuejs.org/api/reactivity-advanced.html#shallowref)
### 依赖注入
child.vue
```vue
<script setup lang="ts">
import { inject } from 'vue'
const count = inject('count')
</script>
<template>
{{ count }}
</template>
```
相关知识点:[组合式 API依赖注入 | Vue.js](https://staging-cn.vuejs.org/api/composition-api-dependency-injection.html)
### Effect 作用域 API
```vue
<script setup lang="ts">
import { ref, computed, watch, watchEffect, effectScope } from 'vue'
const counter = ref(1)
const doubled = computed(() => counter.value * 2)
// use the `effectScope` API to make these effects stop together after being triggered once
const scope = effectScope()
scope.run(() => {
watch(doubled, () => console.log(doubled.value))
watchEffect(() => console.log(`Count: ${doubled.value}`))
counter.value = 2
})
setTimeout(() => {
counter.value = 4
scope.stop()
})
</script>
<template>
<div>
<p>
{{ doubled }}
</p>
</div>
</template>
```
相关知识点:[effectScope](https://staging-cn.vuejs.org/api/reactivity-advanced.html#effectscope)
### 自定义 ref
```vue
<script setup>
import { watch, customRef } from 'vue'
/**
* Implement the function
*/
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
const text = useDebouncedRef('hello')
/**
* Make sure the callback only gets triggered once when entered multiple times in a certain timeout
*/
watch(text, value => {
console.log(value)
})
</script>
<template>
<input v-model="text" />
</template>
```
相关知识点:[customRef](https://staging-cn.vuejs.org/api/reactivity-advanced.html#customref)
## Directives
### 大写
创建一个自定义的修饰符  `capitalize`,它会自动将  `v-model`  绑定输入的字符串值首字母转为大写:
App.vue
```vue
<script setup>
import { ref } from 'vue'
import Input from './Input.vue'
const value = ref('')
</script>
<template>
<Input type="text" v-model.capitalize="value" />
</template>
```
Input.vue
```vue
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
modelValue: String,
modelModifiers: {
default: () => ({})
}
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
```
相关知识点:[处理 v-model 修饰符](https://staging-cn.vuejs.org/guide/components/events.html#usage-with-v-model)
### 优化性能的指令
见上面。v-once
### 切换焦点指令
```vue
<script setup lang="ts">
import { ref } from 'vue'
const state = ref(false)
/**
* Implement the custom directive
* Make sure the input element focuses/blurs when the 'state' is toggled
*
*/
const VFocus = {
updated: (el, state) => (state.value ? el.focus() : el.blur())
}
setInterval(() => {
state.value = !state.value
}, 2000)
</script>
<template>
<input v-focus="state" type="text" />
</template>
```
相关知识点:[自定义指令 | Vue.js](https://staging-cn.vuejs.org/guide/reusability/custom-directives.html)
### 防抖点击指令
尝试实现一个防抖点击指令
```vue
<script setup lang="ts">
/**
* Implement the custom directive
* Make sure the `onClick` method only gets triggered once when clicked many times quickly
* And you also need to support the debounce delay time option. e.g `v-debounce-click:ms`
*
*/
function debounce(fn, delay) {
let timeout
let count = 0
return (...args) => {
if (count === 0) {
count++
fn(...args)
}
clearTimeout(timeout)
timeout = setTimeout(() => {
fn(...args)
}, delay)
}
}
const VDebounceClick = {
mounted: (el, binding) => {
const { value, arg } = binding
el.addEventListener('click', debounce(value, arg))
}
}
function onClick() {
console.log('Only triggered once when clicked many times quickly')
}
</script>
<template>
<button v-debounce-click:200="onClick">Click on it many times quickly</button>
</template>
```
相关知识点:[指令钩子](https://staging-cn.vuejs.org/guide/reusability/custom-directives.html#introduce)
### 激活的样式-指令
```vue
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
/**
* Implement the custom directive
* Make sure the list item text color changes to red when the `toggleTab` is toggled
*
*/
const VActiveStyle = {
mounted: (el, binding) => {
const [styles, fn] = binding.value
watchEffect(() => {
Object.keys(styles).map(key => (el.style[key] = fn() ? styles[key] : ''))
})
}
}
const list = [1, 2, 3, 4, 5, 6, 7, 8]
const activeTab = ref(0)
function toggleTab(index: number) {
activeTab.value = index
}
</script>
<template>
<ul>
<li
v-for="(item, index) in list"
:key="index"
v-active-style="[{ color: 'red' }, () => activeTab === index]"
@click="toggleTab(index)">
{{ item }}
</li>
</ul>
</template>
```
### 实现简易版`v-model`指令
```vue
<script setup lang="ts">
import { ref } from 'vue'
/**
* Implement a custom directive
* Create a two-way binding on a form input element
*
*/
const VOhModel = {
mounted: (el, binding) => {
el.value = binding.value
el.addEventListener('input', () => {
value.value = el.value
})
}
}
const value = ref('Hello Vue.js')
</script>
<template>
<input v-oh-model="value" type="text" />
<p>{{ value }}</p>
</template>
```
## Event Handling
### 阻止事件冒泡
```vue
<script setup lang="ts">
const click1 = () => {
console.log('click1')
}
const click2 = e => {
console.log('click2')
}
</script>
<template>
<div @click="click1()">
<div @click.stop="click2()">click me</div>
</div>
</template>
```
相关知识点:[事件修饰符](https://staging-cn.vuejs.org/guide/essentials/event-handling.html#event-modifiers)
### 按键修饰符
```vue
<template>
<!-- Add key modifiers made this will fire even if Alt or Shift is also pressed -->
<button @click.alt="onClick1" @click.shift="onClick1">A</button>
<!-- Add key modifiers made this will only fire when Shift and no other keys are pressed -->
<button @click.shift.exact="onCtrlClick">A</button>
<!-- Add key modifiers made this will only fire when no system modifiers are pressed -->
<button @click.exact="onClick2">A</button>
</template>
<script setup>
function onClick1() {
console.log('onClick1')
}
function onCtrlClick() {
console.log('onCtrlClick')
}
function onClick2() {
console.log('onClick2')
}
</script>
```
相关知识点:[按键修饰符](https://staging-cn.vuejs.org/guide/essentials/event-handling.html#key-modifiers)
## Global API:General
### 下一次 DOM 更新
在`Vue.js`中改变响应式状态时DOM 不会同步更新。 `Vue.js`  提供了一个用于等待下一次 DOM 更新的方法
```vue
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
const counter = ref(null)
async function increment() {
count.value++
/**
* DOM is not yet updated, how can we make sure that the DOM gets updated
* Make the output be true
*/
await nextTick()
console.log(+counter.value.textContent === 1)
}
</script>
<template>
<button ref="counter" @click="increment">
{{ count }}
</button>
</template>
```
相关知识点:[nextTick()](https://staging-cn.vuejs.org/api/general.html#nexttick)
## Lifecycle
### 生命周期钩子
[同上:生命周期钩子](#生命周期钩子)
## Reactivity:Advanced
### 浅层 ref
[同上:浅层 ref](#浅层-ref)
### 原始值 API
```vue
<script setup lang="ts">
import { reactive, isReactive, toRaw, markRaw } from 'vue'
const state = { count: 1 }
const reactiveState = toRaw(reactive(state))
/**
* Modify the code so that we can make the output be true.
*/
console.log(reactiveState === state)
/**
* Modify the code so that we can make the output be false.
*/
const info = markRaw({ count: 1 })
const reactiveInfo = reactive(info)
console.log(isReactive(reactiveInfo))
</script>
<template>
<div>
<p>
{{ reactiveState.count }}
</p>
</div>
</template>
```
相关知识点:
- [toRaw](https://staging-cn.vuejs.org/api/reactivity-advanced.html#toraw)
- [markRaw](https://staging-cn.vuejs.org/api/reactivity-advanced.html#markraw)
### Effect 作用域 API
[同上Effect 作用域 API](#effect-作用域-api)
### 自定义 ref
[同上:自定义 ref](#自定义-ref)
## Reactivity:Core
### ref 全家桶
[同上ref 全家桶](#ref-全家桶)
### 可写的计算属性
[同上:可写的计算属性](#可写的计算属性)
### watch 全家桶
[同上watch 全家桶](#watch-全家桶)
## Reactivity:Utilities
### 响应性丟失
[同上:响应性丟失](#响应性丟失)
## Utility Function
### until
```vue
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
/**
* Implement the until function
*/
function until(initial) {
function toBe(value) {
return new Promise(resolve => {
initial.value = value
resolve(initial.value)
})
}
return {
toBe
}
}
async function increase() {
count.value = 0
setInterval(() => {
count.value++
}, 1000)
await until(count).toBe(3)
console.log(count.value === 3) // Make sure the output is true
}
</script>
<template>
<p @click="increase">Increase</p>
</template>
```
## Web Components
### 自定义元素
```vue
<script setup lang="ts">
import { onMounted, defineCustomElement } from 'vue'
/**
* Implement the code to create a custom element.
* Make the output of page show "Hello Vue.js".
*/
const VueJs = defineCustomElement({
props: { message: String },
template: '<span>{{message}}</span>'
})
customElements.define('vue-js', VueJs)
onMounted(() => {
document.getElementById('app')!.innerHTML = '<vue-js message="Hello Vue.js"></vue-js>'
})
</script>
<template>
<div id="app"></div>
</template>
```
并且 vite.config.js 里要做相关设置
相关知识点:[Vue 与 Web Components | Vue.js](https://staging-cn.vuejs.org/guide/extras/web-components.html)