# Vue3 笔记
# composition-api
为什么会有 composition-api
,想一下有一个这样的场景,一个页面的功能有很多个,这些功能都是由一个又一个的组件拼凑起来的,这个时候代码的复用性非常重要,如果按照常规写法一定是一个功能都维护一份自己独有的 data
,method
,computed
,watch
但是这样写下去的后果就是会将整个逻辑部分撑得很大,导致组件难以阅读和理解,尤其是那些没有编写过这些组件的人。或许你会想到使用 vue 的 mixin
,没错它一定意义地解决了复用抽离的问题,但是通常情况下我们混入整个 mixin
,不管是变量还是 method
,都无法直接读出整个变量或者函数的真正作用和意义,很难读懂源码。composition-api
就是用来解决这种问题的。
# setup
setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API
的入口。
setup 执行时机:beforeCreate
之前
setup 参数:
- props: 传入的属性
- context:
context
中就提供了this
中最常用的三个属性:attrs
、slot
和emit
,分别对应 Vue2.x 中的$attr
属性、slot
插槽 和$emit
发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。
export default defineComponent({
name: 'App',
props: {
name: String
},
// 接受两个参数 props 和 context
setup(props,context) {
// props 属性
console.log(props.name)
// Attribute (非响应式对象)
console.log(context.attrs)
// 插槽 (非响应式对象)
console.log(context.slots)
// 触发事件 (方法)
console.log(context.emit)
}
})
# reactive、ref 与 toRefs
看以下代码就可以知晓这三个 API
的作用
<template>
<div class="homePage">
<p>第 {{ year }} 年</p>
<p>姓名: {{ nickname }}</p>
<p>年龄: {{ age }}</p>
</div>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
setup() {
// 声明响应式变量
const year = ref(0);
// 声明响应式对象
const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
setInterval(() => {
// ref声明的变量,在非template处需要用.value取值
year.value++;
user.age++;
}, 1000);
return {
year,
// 使用reRefs可以将普通对象转为ref对象,因为直接用解构的话会消除响应式
...toRefs(user),
};
},
});
</script>
# 生命周期
新增的钩子,监听响应式数据
// 新增的钩子用来监听响应式数据变化 setup(props) { // debug监听响应式对象更新的console onRenderTriggered((event) => { console.log(event) }) }
# v-model
# watch
**监听 ref **数据
import { ref, defineComponent, reactive, toRefs, watch } from "vue";
export default defineComponent({
name: "HelloWorld",
props: {
msg: {
type: String,
required: true,
},
},
setup: () => {
const state = reactive({
count: 0,
nickName: "tom",
age: 12,
});
const num = ref(0)
setTimeout(() => {
state.count++;
}, 100);
watch(()=>num,(newVal,oldVal) => {
console.log(newVal,oldVal)
})
return {
...toRefs(state),
};
},
});
监听 reactive 定义的数据
import { ref, defineComponent, reactive, toRefs, watch } from "vue";
export default defineComponent({
name: "HelloWorld",
props: {
msg: {
type: String,
required: true,
},
},
setup: () => {
const state = reactive({
count: 0,
nickName: "tom",
age: 12,
});
const num = ref(0)
setTimeout(() => {
state.count++;
}, 100);
watch(
() => state.count,
(curCount,preCount) => {
console.log(curCount,preCount)
},
{
// options
deep: true,
immediate: true
}
)
return {
...toRefs(state),
};
},
});
监听多个数据
import { ref, defineComponent, reactive, toRefs, watch } from "vue";
export default defineComponent({
name: "HelloWorld",
props: {
msg: {
type: String,
required: true,
},
},
setup: () => {
const state = reactive({
count: 0,
nickName: "tom",
age: 12,
});
const num = ref(0)
setTimeout(() => {
state.count++;
}, 100);
watch(
[() => state.count,num],
([curCount,newNum],[preCount,oldNum]) => {
console.log(curCount,preCount)
console.log(newNum,oldNum)
}
)
return {
...toRefs(state),
};
},
});
//watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
# 获取 ref
<template>
<div ref="domRef"></div>
</template>
export default defineComponent ({
setup() {
// 1. 还是常规写法
const domRef = ref(null)
onMounted(() => {
console.log(domRef.value)
})
//
}
})
# computed
import { computed } from 'vue';
const user = reactive({
firstName: '韩',
lastName: '志伟',
});
const fullName1 = computed(() => {
return user.firstName + user.lastName;
});
return {
user,
fullName1,
};
另外支持传入一个对象,可以进行读取和修改操作
const fullName2 = computed({
get() {
return user.firstName + '_' + user.lastName;
},
set(val: string) {
const names = val.split('_');
user.firstName = names[0];
user.lastName = names[1];
},
});
return {
user,
fullName2,
};
# 自定义Hooks
在 vue3
中我们想去定义一个可以服用的逻辑代码时通常采用 hooks
的方式,然后再引入。这种方式可以将某个需求功能拆分出来,看起来更舒服,并且可以扩展更多功能。
在 hooks
下新建 useMousePosition.ts
import { ref, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
const x = ref(0)
const y = ref(0)
const updateMouse = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
document.addEventListener('click',updateMouse)
})
onUnmounted(() => {
document.removeEventListener('click',updateMouse)
})
return { x,y }
}
export default useMousePosition
接着在需要使用的组件上引用即可
// 自定义hooks
const { x, y } = useMousePosition()
return {
x,
y
}
# 新增组件 Suspence和teleport
Suspence:异步组件
定义一个需要进行异步加载的组件 DogShow.vue
<template>
<img :src = "result && result.message">
</template>
<script lang="ts">
import 'axios' from 'axios'
import {defineComponent} from 'vue'
export default {
name: 'DogShow',
async setup(props) {
const data = axios.get(url)
return {
result: data.data
}
}
}
</script>
然后在需要引入的页面中引入
<template>
<Suspence>
<!--异步加载完成展示-->
<template #default>
<async-show />
<dog-show />
</template>
<template #fallback>
<h1>Loading......</h1>
</template>
</Suspence>
</template>
teleport:瞬移组件
<template>
<!-- teleport标签作用:将包裹组件隔离传送至指定选择器 -->
<teleport to='#extra'>
<img alt="Vue logo" src="./assets/logo.png" />
</teleport>
<p>{{ count }}</p>
<p>{{ double }}</p>
<p>x: {{ x }}</p>
<p>y: {{ y }}</p>
<h1 v-if="loading">Loading .....</h1>
<h1 v-if="loaded">
<div>
<img :src="result.message" />
</div>
</h1>
<button @click="increase">+1</button>
</template>
效果图
# 插槽
# slot 具名插槽语法
在 Vue2.x 中, 具名插槽的写法:
<!-- 子组件中:-->
<slot name="title"></slot>
在父组件中使用:
<template slot="title">
<h1>歌曲:成都</h1>
<template>
如果我们要在 slot 上面绑定数据,可以使用作用域插槽,实现如下:
// 子组件
<slot name="content" :data="data"></slot>
export default {
data(){
return{
data:["走过来人来人往","不喜欢也得欣赏","陪伴是最长情的告白"]
}
}
}
<!-- 父组件中使用 -->
<template slot="content" slot-scope="scoped">
<div v-for="item in scoped.data">{{item}}</div>
<template>
在 Vue2.x 中具名插槽和作用域插槽分别使用slot
和slot-scope
来实现, 在 Vue3.0 中将slot
和slot-scope
进行了合并同意使用。
Vue3.0 中v-slot
:
<!-- 父组件中使用 -->
<template v-slot:content="scoped">
<div v-for="item in scoped.data">{{item}}</div>
</template>
<!-- 也可以简写成: -->
<template #content="{data}">
<div v-for="item in data">{{item}}</div>
</template>
# 自定义指令
Vue2.x 写法实例
Vue2.x 自定义指令钩子
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
Vue使用自定义指令实现按钮级别的权限控制,需要设置两个值,一个是该按钮需要的权限,一个是当前用户用户角色
- 首先定义Vue组件,这里可以使用Vuex来保存角色信息
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
userInfo: {
name: 'Suk',
roles: ['add', 'delete'] // 管理员有哪些权限
}
},
mutations: {},
actions: {},
getters: {},
})
export default store
- 编写
directives
实现自定义指令
directives: {
// 指令名
'permission':{
//dom被插入元素时执行的钩子,el获取dom,binding.value拿到指令绑定的值,vnode.context可以拿到实例
inserted:(el,binding,vnode)=>{
// 获取保定的值
const userRoles = bing.value
// 获取按钮需要的权限
const btnRole = el.getAttribute('data-rule')
// 判断是否该角色是否有权限,无权限移除元素
if(!userRoles.includes(btnRole)){
el.parentNode.removeChild(el)
}
}
}
}
- 应用指令
<template>
<div class="test">
{{ userInfo.name }}拥有的按钮权限:
<el-button data-rule="add" v-permission="userInfo.roles">新增</el-button>
<el-button data-rule="delete" v-permission="userInfo.roles">删除</el-button>
<el-button data-rule="update" v-permission="userInfo.roles">修改</el-button>
</div>
</template>
Vue3.x 写法
钩子变更
const { createApp } from "vue"
const app = createApp({})
app.directive('focus', {
mounted(el) {
el.focus()
}
})
# 父组件保留子组件能力和方法
<el-cascader
v-model="defaultData"
:options="calcData"
clearable
v-bind="$attrs"
v-on="$listeners"
></el-cascader>
# Vue3-json-schema-form 项目笔记
# 前置目标
- [ ] 开源项目的创建发布流程
- [ ] 如何更合理的设计广泛设计型 API
- [ ] 如何保证代码质量
- [ ] Vue3 实现原理
# 项目初始化,集成 TSX
# Vue3-Compoonents
# DropDown
第一步、考虑需要传什么参数