Vue3 脚手架分析
/* createApp 创建对应的应用,产生应用的实例 */
import { createApp } from 'vue'
import App from './App.vue'
/* 创建 APP 返回实例对象,mount 方法挂载到 #app */
createApp(App).mount('#app')
js
注意
vscode 设置搜索 Validate vue-html in <template> using eslint-plugin-vue 关闭,否则会提示 eslint 报错
APP.vue
- template 不需要有一个根节点
- <script lang=“ts”> 代表编写ts文件
- import { defineComponent } from ‘vue’; // 引入函数,定义一个组件,传入配置对象
export default defineComponent({
name: 'App',
components: {
HelloWorld
}
});
js
Vue Ref、reactive、toRefs、toRef、customRef 创建响应式数据
Ref
import { ref } from 'vue';
setup(){
const a = ref(0); // 一般用于定义基本类型响应式, 返回 Ref 对象
function add(){
a.value ++; // js 中操作数据: xxx.value,模板中则不需要
}
return{
a,add
}
}
js
reactive
let num = ref(0);
let obj = reactive({}) // 用于创建引用类型响应式
let obj = reactive({num}) // ref 会被解包 obj.num === num.value
obj.num = num // ref 赋值给 reactive 属性也会被解包,obj.num === num.value
js
响应式原理
let obj = {
name: 'name',
arr: []
}
js
- target 被代理的对象
- handler 处理器对象,监视数据操作
let pro = new Proxy(obj,{
get(target,prop){
console.log('get方法');
return Reflect.get(target,prop); // 读取对象的值
},
set(target,prop,value){
console.log('set方法调用');
return Reflect.set(target,prop,value); // 设置对象的值
},
deleteProperty(target,prop){
console.log('delete方法调用了');
return Reflect.deleteProperty(target,prop ); // 设置对象的值
}
})
js
toRefs
const reactiveObj = reactive({
num: 1,
})
// 将 reactive 对象转成 ref 对象,可用于解构
const toRefObj = toRefs(reactiveObj);
setInterval(() => {
// reactiveObj.num ++ ;
toRefObj.num.value++
},1000)
return {
reactiveObj,
...toRefObj,
}
js
toRef
const user = reactive({
age: 18,
money: 20,
});
// toRef age 与 user 互通
const age = toRef(user, "age");
// ref money 与 user 不互通
const money = ref(user.money);
js
组件中的应用
<!-- 这里传入的是 age.value -->
<child :age='age'/>
<!-- child组件 -->
<template>
<div>{{ age }}</div>
<div>{{ length }}</div>
</template>
html
// 别人写的 hook 函数
const getLength = (age: Ref) => computed(() => age.value.toString().length);
export default defineComponent({
name: "child",
props: {
age: {
type: number,
required: true,
},
},
setup(props) {
// 传入 props 把 age 变为 ref 对象并且同步响应
const length = getLength(toRef(props, "age"));
return { length };
},
});
js
customRef
创建自定义的响应式
<input v-model="text" />
<div>{{text}}</div>
html
import {customRef} from 'vue'
const useDebouncedRef = (value, delay = 200) => {
let timeout
return customRef((track, trigger) => {
get() {
track() // 跟踪 return 出去的 value
return value
},
set(newValue) {
// 延迟后设置新的值
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 通知 vue 出发模板更新
}, delay)
}
})
}
export default {
setup() {
return {
text: useDebouncedRef('hello')
}
}
}
js
Vue setup 组合式 API
组合式 API
import {ref,onMounted,watch,toRefs,computed} from 'vue'
setup(props) {
console.log(props); // 父传子接收的参数
let { user } = toRefs(props); // 创建对 user 的响应式引用
let arr = ref([]); // 创建响应式引
let getArr = async () => { // 异步调用 api
arr.value = await Promise.resolve([user.value.name,Math.random() + '','asdf','qwesax','ervhf','dfdaw']);
}
onMounted(getArr); // 在mounted时期调用此方
watch(user,getArr); // 当user变化调用方法
let search = ref(''); // 创建响应式引用
let arrSearch = computed(() =>// 计算属性 过滤
arr.value.filter(item => item.includes(search.value))
);
return { // 返回的对象可以在模板当中直接使用
arr:arrSearch, // 只需要搜索过滤的结果
getArr,
search,
}
}
js
setup 细节
- 执行的时机
- 在 beforeCreate 之前执行(一次)此时组件对象还没有创建
- this 是 undefined,不能通过 this 来访问 data/computed/methods / props
- 所有的 composition API 相关回调函数中也都不可以
- 返回值
- 返回一个对象:属性会与 data 合并,方法会与 methods 合并
- 如果有重名,setup 优先
- 注意:
- methods 中可以访问 setup 提供的属性和方法,但在 setup 方法中不能访问 data 和 methods
- setup 不能是一个 async 函数:因为返回值不再是 return 的对象,而是 promise
- 参数
- setup(props, {attrs, slots, emit})
- props:接收的传值
- attrs:相当于 this.$attrs,包含 props没有接的参数
- slots:所有传入的插槽内容的对象,相当于 this.$slots
- emit:用来分发自定义事件的函数,相当于 this.$emit
vue computed 计算属性与 watch 监听
computed
- 只有 get,使用回调
const name = computed(() => obj.name + obj.age)
js
- get,set方法 name1 执行 set 时调用
const name1 = computed({
get: () => obj.name + obj.age,
set: val => {obj.name = val}
})
js
watch 监听对象
watch(obj,newVal => {
console.log(newVal);
}, {
immediate: true, // 是否在初始化的时候就执行一次,默认 false
deep: true, // 深监听,默认 false
})
const one = ref('')
/* 监听一个数据 */
watch(one, value => {
console.log(value)
})
js
- watch多个数据:
使用数组来指定
ref 对象, 直接指定
reactive 对象必须通过函数返回
watch([() => obj.name, () => obj.age, one], (values) => {
console.log('监视多个数据', values)
})
js
回调中使用到的数据会自动监视
默认初始化执行一次,不需要设置 immediate
watchEffect(() => {
let a = obj.name + obj.age;
})
js
Vue3 生命周期
- vue3 中只有 beforeDestroy 和 destroyed 名字变了
- vue2 中生命周期使用的是对象上 option,vue3 使用的是 api
- setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,这两个周期里面的代码都可以在 setup 中编写
- vue2 的声明周期可以在3中写
- beforeCreate -> 使用 setup()
- created -> 使用 setup()
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeDestroy -> onBeforeUnmount
- destroyed -> onUnmounted
- errorCaptured -> onErrorCaptured
- activated -> onActivated
- deactivated -> onDeactivated
- 新增
- renderTracked -> onRenderTracked
- renderTriggered -> onRenderTriggered
- 父子生命周期加载顺序
- parent-beforeCreate
- parent-created
- parent-onBeforeMount
- parent-beforeMount
- child-beforeCreate
- child-created
- child-onBeforeMount
- child-beforeMount
- child-onMounted
- child-mounted
- parent-onMounted
- parent-mounted
- 更新
- child-onBeforeUpdate
- child-beforeUpdate
- child-onUpdated
- child-updated
Vue Provide/Inject 祖孙传值
setup 中使用,需要 import 引入。
- 祖先组件
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
js
- 使用 ref,reactive 是为了响应性
provide('location', location)
provide('geolocation', geolocation)
/* readonly 可以保证不被孙组件修改属性 */
provide('location', readonly(location))
/* 孙组件引入 inject */
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
js
Vue+ts ref 获取 DOM 元素
- template
<input type="text" ref="inputRef" />
<!-- 加冒号传入 divs 方法 -->
<div v-for="i of 3" :key="i" :ref="divs"></div>
html
- setup
const inputRef = ref<HTMLElement | null>(null);
const arr = ref([]);
const divs = (el: HTMLElement) => {
// 断言为 HTMLElement 类型的数组
(arr.value as Array<HTMLElement>).push(el);
// 这样写编译器会抛出错误 --> Argument of type 'HTMLElement' is not assignable to parameter of type 'never'.
// arr.value.push(el);
};
onMounted(() => {
// 加载完成获取 input 焦点
inputRef.value && inputRef.value.focus();
// 打印多个ref DOM
console.log(arr);
});
return {
inputRef,
divs,
};
js
注意
dom 标签上 :ref 传入的是方法名,如果 :ref='变量 + ‘字符串拼接’'则不能获取指定的 dom 元素了。官网也没有说明如何获取,解决办法还是在 methods 方法中使用 vue2 语法 this.$refs[‘xxx’] 来获取
vue 更改响应式方法、只读属性、标记响应式数据
shallowReactive & shallowRef
// shallowReactive 页面渲染只响应外层属性
// shallowRef 只响应 value 级别属性
/*
m1.name += '==';
m1.a.name += '==';
同时更改页面会响应,因为 m1.name 是外层的
*/
const m1 = shallowReactive({name:'1',a:{name:'4'}})
const m2 = shallowRef({name:'1',a:{name:'2'}})
js
readonly & shallowReadonly
// 深度只读 所有都不能更改
const m1 = readonly({name:'1',a:{name:'2'}})
// 浅只读, 深层次属性可以更改
const m2 = shallowReadonly({name:'1',a:{name:'4'}})
js
toRaw & markRaw
interface UserInfo {
name: string;
age: number;
likes?: number[];
}
// 页面显示 user
const user = reactive<UserInfo>({
name: "小明",
age: 18,
});
const arr = [1, 2];
user.likes = markRaw(arr) // 标记并之后再也无法响应
const upData = () => {
const raw = toRaw(user); // 将一个响应式数据变为普通对象
raw.name += "=="; // 界面不会更新
user.likes && (user.likes[0] += 1) // 不会更新响应
}
js
vue3 单文件 style 特性
scope
只对当前组件的样式生效,这在 vue2 中经常使用。
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
html
深度选择器
选择子组件的类名
<style scoped>
.a :deep(.b) {
/* ... */
}
/* 或 */
.a /deep/ .b{
}
/* 或 */
.a >>> .b{
}
</style>
html
插槽选择器
子组件的插槽的内容属于父组件,子组件要选择需要用 :slotted
父组件
<template>
<HelloWorld>
<div>122</div>
</HelloWorld>
</template>
html
子组件
<template>
<div class="children">
<slot></slot>
</div>
</template>
<style scoped>
.children :slotted(div){
color: red;
}
</style>
html
全局选择器
选择全局的 .red class
<style scoped>
:global(.red) {
color: red;
}
</style>
html
moudle
module 属性会将css类放在 $style 对象上
<template>
<p :class="$style.red"> 文本 </p>
</template>
<style module>
.red {
color: red;
}
</style>
html
自定义名称
<template>
<p :class="classes.red">red</p>
</template>
<style module="classes">
.red {
color: red;
}
</style>
html
API 中获取 style module
// 默认, 返回 <style module> 中的类
useCssModule()
// 命名, 返回 <style module="classes"> 中的类
useCssModule('classes')
js
<script setup>中一样
<script setup>
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
html
vue3 新组件
Fragment(片段)
- vue2 中组件必须要一个根表情
- vue3 不需要
- vue3 中没有根标签,内部会将其包裹在Fragment虚拟元素中(在新版vue.js devtools 审查元素能够看到)
<template>
<h2>aaaa</h2>
<h2>aaaa</h2>
</template>
html
Teleport(瞬移)
- to:表示teleport包裹的元素会插入body标签下。
- 即使在不同的元素下渲染,但组件的父子关系不会变。
<template>
<teleport to="body">
<div class="modal">
这是一个弹窗
</div>
</teleport>
</template>
html
也可以使用选择器
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
<!-- 输出 -->
<div id="modals">
<div>A</div>
<div>B</div>
</div>
html
Suspense(不确定)
- 在子组件异步加载之前,可以先显示后背内容
父组件
<template>
<Suspense>
<template #default>
<son />
</template>
<template #fallback>
<h1>加载中...</h1>
</template>
</Suspense>
</template>
html
son 子组件
<template>
<h2>
{{data}}
</h2>
</template>
<script>
export default {
setup () {
// 注意此处 setup 返回的是 Promise 对象
return new Promise(res => {
setTimeout(() => {
res({data: '加载完成'})
}, 2000);
})
}
}
</script>
html