只有身在更高处的你才能体验风不一样的感觉, 看不一样的风景
vue-cli
vue create [project-name]
npm run serve
vite
npm init vite@latest
npm run dev
ref
与reactive
ref
<script setup lang="ts">
// 按需导入API
import { ref } from 'vue'
const refDc = ref('dc')
</script>
使用需要通过.value
方式,但是模板中是不需要这样的,自动会解包{{ refDc }}
就可使用
如果被套在reactive
中会自动解包不用.value
基本类型的数据:响应式依然是靠Object.defineProperty()
的get
与set
方式完成
对象类型的数据:内部是通过reactive
函数完成(下面会有介绍)
shallowRef()
ref()
的浅层作用形式import { shallowRef } from "vue"
const obj = shallowRef({ name: "dc", age: 21 })
obj.value.name = "dcdc" /* 并不会触发响应式 */
// obj.value ===> { name: "dc", age: 21 }
// 即
obj.value = {name: '帝尘'} /* 这样才会触发响应式 */
obj.value = '帝尘' /* 这样才会触发响应式 */
ref
对象使用要.value
只有.value
这一项改变才会触发响应式, 即shallowRef({ name: "dc", age: 21 })
传入的参数本身改变才会触发响应式ref
一起去写会影响视图的更新( shallowRef
也会去触发更新)triggerRef()
ref
的副作用, 也就是shallowRef()
的副作用import { shallowRef, triggerRef } from "vue"
const obj = shallowRef({ name: "dc", age: 21 })
obj.value.name = "dcdc" /* 并不会触发响应式 去更新视图 */
triggerRef(obj) /* 调用之后 会强制触发依赖于所传的ref对象 的副作用 */
customRef()
ref
,显式声明对其依赖追踪和更新触发的控制方式(创建一个我们可控的ref)track
和 trigger
, 并返回一个带有 get
和 set
方法的对象。track()
应该在 get()
方法中调用,而 trigger()
应该在 set()
中调用创建防抖的
ref
import { customRef } from "vue"
const myRef = (value: any) => {
let time: number
return customRef((track, trigger) => {
return {
get() {
/* 跟踪依赖 */
track()
return value
},
set(newVal) {
clearTimeout(time)
time = setTimeout(() => {
value = newVal
/* 触发依赖 */
trigger()
}, 500);
}
}
})
}
const my = myRef(1)
const change = () => {
my.value++
}
reactive
:<script setup lang="ts">
// 按需导入API
import { reactive} from 'vue'
const reactionDc = reactive({
name: 'dc',
age: '21'
})
</script>
reactionDc
reactive
对象是用来对复杂数据类型进行响应式,并且内部是通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等let name = reactionDc.name
这种是不可取会丢失响应式 可以使用toRef | toRefs
去解构shallowReactive()
reactive()
的浅层作用形式ref
的属性不会被自动解包了<template>
<div>{{ shallowObj.c.d.e }}</div>
<button @click="change">改变</button>
</template>
<script setup lang="ts">
import { isReactive, shallowReactive } from "vue";
const shallowObj = shallowReactive({
a: 1,
b: 2,
c: {
d: {
e: 10,
},
},
})
const change = () => {
// shallowObj.a++ 不能一起写
shallowObj.c.d.e++; /* 不会触发更新 */
console.log(
isReactive(shallowObj) /* true */,
isReactive(shallowObj.c.d.e) /* false */
)
}
不能跟
reactive
一起写会向ref
和shallowRef
一样影响视图更新
readonly()
import { readonly } from "vue";
const obj = readonly({ a: 123, b: 456 });
obj.a++; /* 会提示: 无法分配到 "a" ,因为它是只读属性。 */
shallowReadonly()
readonly()
的浅层作用形式import { shallowReadonly } from "vue";
const obj = shallowReadonly({ a: 123, b: { c: 200 } });
// obj.a++ /* 会提示: 无法分配到 "a" ,因为它是只读属性。 */
obj.b.c++ /* 深层次可以修改 */
toRaw()
reactive()
、readonly()
、shallowReactive()
或者 shallowReadonly()
创建的代理对应的原始对象import { shallowReadonly, toRaw } from "vue";
const obj = shallowReadonly({ a: 123, b: { c: 200 } });
console.log(toRaw(obj)); /* 返回原始对象 { a: 123, b: { c: 200 } } */
markRaw()
shallowReactive()
和 shallowReadonly()
, shallowRef
这种类似都是浅层作用形式, 作用为: 将一个对象标记为不可被转为代理。返回该对象本身import { markRaw, reactive} from "vue";
const markRawObj = markRaw({ a: 123, b: { c: 200 } })
console.log(markRawObj); /* 添加标记属性 __v_skip: true */
const reactiveObj = reactive(markRawObj)
console.log(reactiveObj); /* 返回的还是原对象 */
import {computed, reactive} from 'vue'
const reactionDc = reactive({
name: 'dc',
age: '21'
})
// 简单用法
let computedAge = computed(() => {
return reactionDc.age
})
// 完整用法
let computedName = computed({
// 获取computedName 触发的函数
get() {
return reactionDc.name + '!'
},
// 设置computedName 所触发的函数 value 修改的值
set(value: string) {
return reactionDc.name = value
}
})
</script>
<script setup lang="ts">
import { reactive, ref} from 'vue'
const reactionDc = reactive({
name: 'dc',
age: '21'
})
const refDc = ref('dc')
const refDcAge = ref(21)
//情况一:监视ref定义的响应式数据
watch(refDc ,(newValue,oldValue)=>{
console.log('refDc ',newValue,oldValue)
// immediate:true 配置项,代表首次执行监听一次
},{immediate:true})
//情况二:监视多个ref定义的响应式数据,用数组
watch([refDcName, refDcAge ],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})
/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 (deep)
*/
watch(reactionDc ,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性
watch(()=>reactionDc.age,(newValue,oldValue)=>{
console.log('reactionDc的age变化了',newValue,oldValue)
},{immediate:true,deep:true})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>reactionDc.name,()=>reactionDc.age],(newValue,oldValue)=>{
console.log('reactionDc的name | age 变化了',newValue,oldValue)
},{immediate:true,deep:true})
//特殊情况
watch(()=>reactionDc.name,(newValue,oldValue)=>{
console.log('reactionDc的name变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
watchEffect
函数没返回值注重过程,计算属性注重结果,有返回值{ flush: 'post'}
或者引入别名为watchPostEffect
的监听器import { reactive, watchEffect} from 'vue'
const reactionDc = reactive({
name: 'dc',
age: '21'
})
watchEffect(() => {
// 用到的数据只要发生变化(即 reactionDc .age发生变化),则直接重新执行回调。
const nawValue = reactionDc .age
console.log('我执行了!')
})
watch
或者watchEffect
的返回的函数, 执行就会停止监听import { reactive, watchEffect} from 'vue'
const reactionDc = reactive({
name: 'dc',
age: '21'
})
const stop = watchEffect(() => {
// 用到的数据只要发生变化(即 reactionDc .age发生变化),则直接重新执行回调。
const nawValue = reactionDc .age
console.log('我执行了!')
})
stop() /* 执行表示终止监听 */
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
<script setup lang="ts">
// 引入
import { onBeforeMount } from 'vue'
// 使用
onBeforeMount (() =>{
....
})
</script>
props
defineProps()
<template>
<Hello-world msg="Hello dc" />
</template>
<template>
<h1>{{ msg }}</h1>
</template>
<script setup lang="ts">
interface Props {
msg: string
}
// 可在模板中直接使用
defineProps<Props>()
</script>
<template>
<h1>{{ msg }}</h1>
</template>
<script setup lang="ts">
// interface Props {
// msg: string
// }
defineProps({
msg: String
})
</script>
withDefaults(defineProps())
() =>
函数返回值形式, 防止引用<template>
<h1>{{ msg }}</h1>
</template>
<script setup lang="ts">
import { withDefaults } from 'vue';
interface Props {
msg?: string
data?: number[]
}
// 第一个参数指定defineProps并传入泛型, 第二个参数则是配置对应的默认值
withDefaults(defineProps<Props>(), {
msg: 'dc!!!',
data: ()=> [1, 2, 3]
})
</script>
但是这种方式只能在
template
标签中使用,所以如果要在script
标签中用的话要使用常量或变量去接收
provide
与 inject
provide
选项来提供数据,后代组件有一个 inject
选项来开始使用这些数据provide(key, value)
, inject(key)
// 子组件
import { provide } from 'vue'
provide('name', ‘dc’)
// 子组件
import { inject} from 'vue'
const data = inject('name')
console.log(data) // dc
可以将依赖注入看作是“长距离的 prop”
如果需要响应式,则可以在provide
传响应式数据,如ref
,reactive
或者计算属性
defineEmits('自定义事件')
// 子组件内
let arr: number[] = reactive<number[]>([1, 2, 3])
// 可以是多个自定义是事件
const emit = defineEmits(['on-click'])
const getProps = () => {
// 第一个参数为 触发的自定义事件,第二个往后都是传递数据
emit('on-click', arr)
}
// 父组件
// 绑定自定义事件 getList 去接收
<Hello @on-click="getList"></Hello>
// list 就是传递的值可以为多个参数
const getList = (list: number[]) => {
console.log(list, '子组件传递的');
}
defineExpose()
// 父组件
<Hello ref='hello '></Hello >
const hello = ref(null)
// hello 就是子组件愿意暴露出来的值
// 子组件
defineExpose({
name: 'dc'
})
mitt
npm i mitt
// 引入
import mitt from 'mitt';
// 使用
const emitter = mitt()
emitter.on(eventType,callback)
emitter.emit(eventType,value)
emitter.all.clear()
emitter.off(eventType,callback)
v-model
// 父组件
<template>
<hello-world v-model="dc"/>
</template>
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue';
import { ref } from 'vue'
const dc = ref('')
// 子组件
<template>
<h1>{{ modelValue }}</h1>
<button @click="hander">点我更新数据</button>
</template>
<script setup lang="ts">
import { withDefaults, inject } from 'vue';
interface Props {
modelValue: string
}
defineProps<Props>()
const emits = defineEmits(['update:modelValue'])
const hander = () => {
emits('update:modelValue', '更新数据了')
}
</script>
// 父组件
<template>
<!-- 传入props 和 自定义事件 -->
<hello-world :modelValue="dc" @update:modelValue="hander"/>
</template>
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue';
import { ref } from 'vue'
const dc = ref('')
const hander = (newVnalue: string) => {
// 通过自定义事件去修改dc 的值
dc.value = newVnalue
}
</script>
<template>
<h1>{{ modelValue }}</h1>
<button @click="hander">点我更新数据</button>
</template>
<script setup lang="ts">
interface Props {
modelValue: string
}
defineProps<Props>()
const emits = defineEmits(['update:modelValue'])
const hander = () => {
// 触发自定义事件
emits('update:modelValue', '更新数据了')
}
</script>
update:modelValue
自定义事件名可以是任意
// 父组件
<template>
<hello-world v-model="dc" v-model:age="age"/>
</template>
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue';
import { ref } from 'vue'
const dc = ref('dc')
const age = ref(21)
</script>
// 子组件
<template>
<h1>name:{{ modelValue }}年龄{{age}}</h1>
<button @click="hander">点我更新数据</button>
</template>
<script setup lang="ts">
interface Props {
modelValue: string
age: number
}
defineProps<Props>()
const emits = defineEmits(['update:modelValue', 'update:age'])
const hander = () => {
emits('update:modelValue', '更新数据了')
emits('update:age', 18)
}
</script>
main.ts
中通过实例的component()
属性注册import { createApp } from "vue"
import App from "./App.vue"
import Card from "./components/Card.vue";
createApp(App).component("Card", Card).mount("#app")
注意
createApp(App)
返回的是实例, 可以链式调用, 但是mount("#app")
返回的并不是实例, 不可以在后面调用
component
组件的is
属性切换组件<template>
<component :is="A"></component>
</template>
<script setup lang="ts">
import A from "./components/A.vue";
</script>
这里的
:is
不能是字符串, 与vue2不同, vue2通过实例的component
的属性去注册, 有媒介的关系可以用字符串, 但vue3的setup
语法糖内不行
<template>
<A />
</template>
<script setup lang="ts">
import A from "./components/A.vue";
</script>
/* 父组件 */
<template>
<Content>
<div>我是默认插槽</div>
</Content>
</template>
<script setup lang="ts">
import Content from "./components/Content.vue";
</script>
/* 子组件 */
<template>
<div>
<slot>
如果没有插槽进来,在这里插入代码片我是默认内容
</slot>
</div>
</template>
v-slot
简写为 #
/* 父组件 */
<template>
<Content>
<template v-slot:header>
我是插槽进来的header
</template>
<template #main>
我是插槽进来的main
</template>
</Content>
</template>
<script setup lang="ts">
import Content from "./components/Content.vue";
</script>
/* 子组件 */
<template>
<div>
<div>
<slot name="header">
我是header默认内容
</slot>
</div>
<div>
<slot name="main">
我是main默认内容
</slot>
</div>
</div>
</template>
v-bind
绑定到slot
的传送上外层, template
标签上的v-slot:main="props"
去接收, 可以解构, 简写为#main=""{info}
实现数据交互/* 父组件 */
<template>
<Content :data="data">
<template #main="{info}">
<div>{{info}}</div>
</template>
</Content>
</template>
<script setup lang="ts">
import { reactive } from "vue";
import Content from "./components/Content.vue";
const data = reactive({
name: '帝尘',
age: 21
})
</script>
/* 子组件 */
<template>
<div>
<slot name="main" :info="data">
<div>
{{`name: ${data.name}--age: ${data.age}`}}
</div>
</slot>
</div>
</template>
<script setup lang="ts">
type Props = {
data: {
name: string
age: number
}
}
defineProps<Props>()
</script>
.js
文件, 文件过大会出现白屏情况, 这时就需要异步组件,进行分包, 进行性能优化/* 异步组件 A */
<template>
<div>{{result}}</div>
</template>
/* setup 语法糖会有一个顶层的 async 所以可以直接使用 await */
<script setup lang="ts">
const request = () => {
return new Promise((resolve)=>{
setTimeout(() => {
resolve({name: '帝尘'})
}, 2000);
})
}
const result = await request()
</script>
defineAsyncComponent()
进行导入异步组件, 要配合内置的Suspense
组件Suspense
组件接收两个插槽#default
: 表示要渲染真正的组件, #fallback
: 表示异步组件渲染之前的的内容/* 父组件 */
<template>
<Suspense>
<template #default>
<A></A>
</template>
<template #fallback>
加载中
</template>
</Suspense>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
const A = defineAsyncComponent(() => import("./components/A.vue"))
</script>
打包之后异步组件会生成独立的
.js
文件, 渲染顺序是先渲染同步.js
之后渲染异步的
v-if
v-for
这种指令<template>
<A v-move></A>
</template>
<script setup lang="ts">
/* 导入类型 */
import type { Directive, DirectiveBinding } from 'vue';
import A from './components/A.vue'
const vMove: Directive = (el: HTMLElement, binding: DirectiveBinding): void => {
console.log(binding);
const down = (e: MouseEvent) => {
// 获取点击坐标到元素的距离
let X = e.clientX - el.offsetLeft
let Y = e.clientY - el.offsetTop
const move = (e: MouseEvent) => {
// 限制移动范围 点击坐标 减去 距离元素的坐标
let _x =Math.min( Math.max(0, e.pageX - X), window.innerWidth - el.offsetWidth)
let _y = Math.min( Math.max(0, e.pageY - Y), window.innerHeight - el.offsetHeight)
el.style.top = _y + 'px'
el.style.left = _x + 'px'
}
document.addEventListener('mousemove', move, false)
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', move)
})
}
// 绑定 mousedown 事件
el.addEventListener('mousedown', down, false)
}
</script>
<style>
html,
body {
height: 100%;
}
* {
padding: 0;
margin: 0;
}
</style>
install
方法, use
时会调用这个方法, 回传一个主要参数: . app
(实例), 还有use
的第一个之后的参数use
时会直接调用/* 使用时 */
<template>
<button @click="show">点击</button>
</template>
<script setup lang="ts">
import { getCurrentInstance } from 'vue';
// 获取当前组件实例
let instance = getCurrentInstance()
const show = () => {
// instance?.proxy 获取 全局变量
instance?.proxy?.$load.trigger()
}
</script>
/* load 配置文件 */
// 导入实例的类型
import { createVNode, render, type App, type VNode } from "vue";
import A from './A.vue'
// export default {
// // app.use() 时会调用 install 方法, 并且回传app 实例
// install(app:App){
// // 创建一个 vNode 节点
// let vNode:VNode = createVNode(A)
// // render 渲染方法 => 参数: vNode, 渲染到的节点上
// render(vNode, document.body )
// /* exposed 是 组件 defineExpose() 暴露出来的数据 */
// // 往全局挂载属性
// app.config.globalProperties.$load = {
// ...vNode.component?.exposed
// }
// }
// }
// app.use() 时会调用 install 方法, 并且回传app 实例
export default (app: App) => {
// 创建一个 vNode 节点
let vNode: VNode = createVNode(A)
// render 渲染方法 => 参数: vNode, 渲染到的节点上
render(vNode, document.body)
/* exposed 是 组件 defineExpose() 暴露出来的数据 */
// 往全局挂载属性
app.config.globalProperties.$load = {
...vNode.component?.exposed
}
}
/* main.ts */
import { createApp, } from "vue"
import App from "./App.vue"
import load from './components/A'
// 声明全局变量类型
declare module 'vue' {
export interface ComponentCustomProperties {
$load: { trigger: Function }
}
}
createApp(App)
.use(load)
.mount("#app")
/* load.vue */
<template>
<div v-if="isShow" class="load-wrap">
loading........
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const isShow = ref(true)
const trigger = () => {
isShow.value = !isShow.value
}
// 需要暴露出去
defineExpose({
trigger
})
</script>
待更新!!!!