前言
提示:本文基于vue3
一、DOM传送门
Vue.js提供了一个内置组件,将其插槽内容渲染到另一个DOM,成为该DOM的一部分。
// Teleport 的使用
<script setup>
const msg = "Hello World"
</script>
<template>
<Teleport to="body">
<span>{{ msg }}</span>
</Teleport>
</template>
二、优化性能的指令
Vue.js 提供了一个指令,以便只渲染一次元素和组件,并且跳过以后的更新。
// v-once指令
<script setup>
import { ref } from "vue"
const count = ref(0)
setInterval(() => {
count.value++
}, 1000)
</script>
<template>
<span v-once>Make it never change: {{ count }}</span>
</template>
三、动态CSS
Vue单文件组件
// css中使用v-bind
<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>
p {
color: v-bind(theme )
}
</style>
四、全局CSS
有些时候,我们想在具有CSS作用域的Vue单文件组件设置全局CSS样式
// css中:global的使用
template>
<p>Hello Vue.js</p>
</template>
<style scoped>
p {
font-size:20px;
color:red;
text-align: center;
line-height: 50px;
}
:global(body) {
width: 100vw;
height: 100vh;
background-color: burlywood;
}
</style>
五、Prop验证
验证Button组件的Prop类型 ,使它只接收: primary | ghost | dashed | link | text | default ,且默认值为default
<script setup>
defineProps({
type: {
type: String,
default: 'default',
validator(value){
return ['primary', 'ghost', 'dashed' ,'link' ,'text','default'].includes(value)
}
},
})
</script>
<template>
<button>Button</button>
</template>
六、函数式组件
尝试实现一个函数式组件
// h函数来实现
<template>
<div>
<h2>函数组件</h2>
<list-component
:list="list"
:active-index="activeIndex"
@toggle="toggle"
/>
</div>
</template>
<script setup lang="ts">
import {ref, h} from "vue"
const ListComponent = (props, context)=>{
const {list} = props
const activeIndex = props['active-index']
const {emit} = context
const lis = list.map((item:{name:string}, index:number)=>{
return h('li',{
style:{
color: activeIndex===index?'red':''
},
onClick:()=>{
emit('toggle', index)
}
},item.name)
})
return h('ul',lis)
}
const list = [{
name:"JSON"
},{
name:"Doe"
},
{
name:"smith"
}]
const activeIndex = ref(0)
function toggle (index:number){
activeIndex.value = index
}
</script>
七、渲染函数[h()]
使用h渲染函数来实现一个组件。请注意: 你应该确保参数被正确传递、事件被正常触发和插槽内容正常渲染
<script setup lang="ts">
import {FunctionalComponent, h} from 'vue'
const MyButton: FunctionalComponent<{}, {customClick(): void}> = (props, context)=> {
console.log(props, context)
return h('button', {
...context.attrs,
onClick: () => context.emit("customClick")
}, context.slots)
}
const onClick = () => {
console.log('onClick')
}
</script>
<template>
<MyButton type="submit" class="om" :disabled="false" @custom-click="onClick">
my button
</MyButton>
</template>
八、树组件
需要实现一个树组件
<script setup lang="ts">
interface TreeData {
key: string
title: string
children: TreeData[]
}
defineProps<{data: TreeData[]}>()
</script>
<template>
<ul>
<template v-for="item in data" :key="item.key">
<li >{{ item.title }}</li>
<ul v-if="item.children && Array(item.children)">
<TreeComponent :data="item.children" />
</ul>
</template>
</ul>
</template>
九、切换器
尝试编写可组合函数
import { ref } from 'vue';
function useToggle(state: boolean): [boolean, () => void] {
const status = ref(state);
const toggle = () => (status.value = !status.value);
return [status, toggle];
}
const [state, toggle] = useToggle(false);
十、计数器
实现一个计数器
<script setup lang="ts">
import { ref } from 'vue';
interface UseCounterOptions {
min?: number;
max?: number;
}
function useCounter(initialValue = 0, options: UseCounterOptions = {}) {
const { min, max } = options;
const count = ref(initialValue);
const inc = () => max > count.value && count.value++;
const dec = () => min < count.value && count.value--;
const reset = () => (count.value = initialValue);
return { count, inc, dec, reset };
}
const { count, inc, dec, reset } = useCounter(0, { min: 0, max: 10 });
</script>
<template>
<p>Count: {{ count }}</p>
<button @click="inc">inc</button>
<button @click="dec">dec</button>
<button @click="reset">reset</button>
</template>
十一、实现本地存储函数
我们经常需要使用localStorageAPI,一个好用的可组合函数封装将帮助我们更好地使用它
<script setup lang='ts'>
import { ref, watchEffect } from "vue"
function useLocalStorage(key: string, initialValue: any) {
const value = ref(window.localStorage.getItem(key) || initialValue)
watchEffect(() => {
window.localStorage.setItem(key, value.value)
})
return value
}
const counter = useLocalStorage("counter", 0)
console.log(counter.value)
const update = () => counter.value++
</script>
<template>
<p>Counter: {{ counter }}</p>
<button @click="update">
Update
</button>
</template>
十二、鼠标坐标
使用Vue.js时,我们应该关注可复用性,可组合函数是一个很好的方式
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
function useEventListener(
target: Window,
event: string,
callback: (e: any) => void
) {
onMounted(() => {
target.addEventListener(event, callback);
});
onUnmounted(() => {
target.removeEventListener(event, callback);
});
}
function useMouse() {
const x = ref(0);
const y = ref(0);
useEventListener(window, 'mousemove', (event) => {
x.value = event.pagex || document.body.scrollLeft + event.clientX;
y.value = event.pagey || document.body.scrollTop + event.clientY;
});
return {
x,
y,
};
}
const { x, y } = useMouse();
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
十三、生命周期钩子
使用 组合式 API: 生命周期钩子 来完成
<script setup lang="ts">
import { onMounted, onBeforeUnmount, inject } from "vue"
const timer = inject("timer")
const count = inject("count")
onMounted(() => {
timer.value = window.setInterval(() => {
count.value++
}, 1000)
})
onBeforeUnmount(()=>{
window.clearInterval(timer.value)
})
</script>
<template>
<div>
<p>
Child Component: {{ count }}
</p>
</div>
</template>
十四、ref 全家桶
使用 响应式 API: ref 来完成
<template>
<div>
<p>
<span @click="update(count - 1)">-</span>
{{ count }}
<span @click="update(count + 1)">+</span>
</p>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, isRef, unref, toRef } from "vue"
import type { Ref } from "vue"
const initial = ref(10)
const count = ref(0)
// 挑战 1: 更新 ref
function update(value) {
// 实现...
count.value = value
}
/**
* 挑战 2: 检查`count`是否为一个 ref 对象
* 确保以下输出为1
*/
console.log(
// impl ? 1 : 0
isRef(count) ? 1 : 0
)
/**
* 挑战 3: 如果参数是一个 ref,则返回内部值,否则返回参数本身
* 确保以下输出为true
*/
function initialCount(value: number | Ref<number>) {
// 确保以下输出为true
console.log(unref(value) === 10)
}
initialCount(initial)
/**
* 挑战 4:
* 为源响应式对象上的某个 `property` 新创建一个 `ref`。
* 然后,`ref` 可以被传递,它会保持对其源`property`的响应式连接。
* 确保以下输出为true
*/
const state = reactive({
foo: 1,
bar: 2,
})
const fooRef = toRef(state, 'foo') // 修改这里的实现...
// 修改引用将更新原引用
fooRef.value++
console.log(state.foo === 2)
// 修改原引用也会更新`ref`
state.foo++
console.log(fooRef.value === 3)
</script>
十五、响应性丟失
在 JavaScript 中,我们经常解构/扩展对象。在Vue.js中,我们同样解构/扩展“响应式”对象,但它会失去响应性。如何保证解构/扩展不丢失响应性 ?
// toRefs 让所有属性具备响应式。 就可以解构
<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,
}
}
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>
// 另一种方式,对一个属性toRef
<script setup lang="ts">
import { reactive,toRef } from "vue"
function useCount() {
const state = reactive({
count: 0,
})
function update(value: number) {
state.count = value
}
return {
state,
update,
}
}
const { state, update } = useCount()
const countRef = toRef(state,'count')
</script>
<template>
<div>
<p>
<span @click="update(countRef-1)">-</span>
{{ countRef }}
<span @click="update(countRef+1)">+</span>
</p>
</div>
</template>
<style>
body {
user-select: none;
}
</style>
十六、可写的计算属性
创建一个可写的计算属性
<template>
<div>
<p>{{ count }}</p>
<p>{{ plusOne }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue"
const count = ref(1)
/**
* 确保 `plusOne` 可以被写入。
* 最终我们得到的结果应该是 `plusOne` 等于 3 和 `count` 等于 2。
*/
const plusOne = computed({
get() {
return count.value + 1
},
set(newValue) {
return count.value++
}
})
plusOne.value++
</script>
十七、watch 全家桶
将使用 响应式 API: watch 来完成
// watch 是可以取消的
// watch的配置项含义
//{
//'immediate': true,
//'deep': true,
//'flush': post
//}
<script setup lang="ts">
import { ref, watch,nextTick } from "vue"
const count = ref(0)
/**
* 挑战 1: Watch 一次
* 确保副作用函数只执行一次
*/
const onceWatch = watch(count, () => {
console.log("Only triggered once")
// 执行本身可以取消监听
onceWatch()
})
count.value = 1
setTimeout(() => count.value = 2)
/**
* 挑战 2: Watch 对象
* 确保副作用函数被正确触发
*/
const state = ref({
count: 0,
})
// 深度监听 或者 watch(()=> state.value.count,...)
watch(state, () => {
console.log("The state.count updated")
},{deep: true})
state.value.count = 2
/**
* 挑战 3: 副作用函数刷新时机
* 确保正确访问到更新后的`eleRef`值
*/
const eleRef = ref()
const age = ref(2)
watch(age, () => {
console.log(eleRef.value)
},{flush: 'post'})
// another solution: use nextTick to solve it
// watch(age, async () => {
// await nextTick()
// console.log(eleRef.value)
// })
age.value = 18
</script>
<template>
<div>
<p>
{{ count }}
</p>
<p ref="eleRef">
{{ age }}
</p>
</div>
</template>
十八、浅层 ref
将使用 响应式 API: shallowRef 来完成
<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 })
/**
* 请注意,虽然在视图上能够看到count已经被修改了,但是并不会触发Wath,这是执行同步代码后,直接先于watch进入 到 effect 去了,导致在视图上起来已经赋值成功了
然后如果在异步代码中执行一下的代码,并不会触发视图的更新
所以要考虑同步和异步的问题
*/
// 不会更新
//Promise.resolve().then(() => {
// state.value.count = 2; // 不会被触发更改
//});
// 同步代码会更新
// state.value.count = 2
// 由于shallowRef只对value层进行响应代理,不会进行深度deep响应,所以应该采用重新赋值的方式,触发watch
state.value = {
count: 2
}
</script>
<template>
<div>
<p>
{{ state.count }}
</p>
</div>
</template>
十九、依赖注入
组合式 API: 依赖注入 来完成它
<script setup lang="ts">
import { inject } from 'vue'
const count = inject('count')
</script>
<template>
{{ count }}
</template>