Composition API
Vue2 模板随着组件功能越来越多,代码量越来越大,每个功能模块的代码会散落分布在各个位置,让整个项目的内容难以阅读和维护
Vue3 根据逻辑功能来进行组织,提高可维护性,可读性,重用代码,vue2 用 mixins 重用逻辑代码,容易产生冲突,所以 Composition API 又被称为基于函数组合的 API
全局 API
2.x 全局 API(Vue) | 3.x 实例 API (app) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
hooks 函数
Vue3 的 hook 函数 相当于 vue2 的 mixin, 不同在与 hooks 是函数
Vue3 的 hook 函数 可以帮助我们提高代码的复用性, 让我们能在不同的组件中都利用 hooks 函数
//一般都是建一个hooks文件夹,都写在里面
import {reactive,onMounted,onBeforeUnmount} from 'vue'
export default function (){
//鼠标点击坐标
let point = reactive({
x:0,
y:0
})
//实现鼠标点击获取坐标的方法
function savePoint(event){
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX,event.pageY)
}
//实现鼠标点击获取坐标的方法的生命周期钩子
onMounted(()=>{
window.addEventListener('click',savePoint)
})
onBeforeUnmount(()=>{
window.removeEventListener('click',savePoint)
})
return point
}
//在其他地方调用
import useMousePosition from './hooks/useMousePosition'
let point = useMousePosition()
setup
组合式 api 的入口,在 beforeCreate 前调用,setup 不能使用 this,
setup(props,context){}
props 父组件传递给子组件的所有数据,不能使用解构
context:(attrs,slots,emit)
props: ["parent_data"],
emits:['subClick'],
setup (props, context) {
let { attrs, slots, emit } = context;
// vue3.x 获取组件上的属性
console.log(attrs.subData); // 'some other data'
// vue3.x 获取slot插槽的节点
console.log(slots.default());
// vue3.x emit方法(子组件向父组件传递数据)
function handleClick () {
emit('subClick', '发送给父组件的值');
}
return { handleClick }
}
setup语法糖
//Father.vue
<template>
<div >
<h2 >我是父组件!</h2>
<Child :msg="hello" @child-click="childCtx" />
</div>
</template>
<script setup>
//无需注册components
import Child from './Child.vue';
const childCtx = (ctx) => {
console.log(ctx);
}
</script>
//Child.vue
<template>
<span @click="handleClick">我是子组件! -- msg: props.msg </span>
</template>
<script setup>
// 模板中可以通过 $slots 和 $attrs
import { useSlots,useAttrs } from 'vue'
const emit = defineEmit(['child-click', 'update:changeProp'])
const props = defineProps({
msg: String
})
const handleClick = () => emit('child-click', 'x')
// 自定义指令
const vMyDirective = {beforeMount,mounted}
<!-- vue3.3语法糖 -->
根据接受 defineModel 返回值的变量名,这里是 newName,会自动定义 props 名为 newName,emit 事件为 update:newName
const newName = defineModel()
defineOptions()
defineExpose({a,b})
defineSlots()
</script>
TSX&h
import { h } from 'vue'
// 除了类型必填以外,其他的参数都是可选的
h('div')
h('div', { id: 'foo' })
// attribute 和 property 都能在 prop 中书写
// Vue 会自动将它们分配到正确的位置
h('div', { class: 'bar', innerHTML: 'hello' })
// 像 `.prop` 和 `.attr` 这样的的属性修饰符
// 可以分别通过 `.` 和 `^` 前缀来添加
h('div', { '.name': 'some-name', '^width': '100' })
// 类与样式可以像在模板中一样
// 用数组或对象的形式书写
h('div', { class: [foo, { bar }], style: { color: 'red' } })
// 事件监听器应以 onXxx 的形式书写
h('div', { onClick: () => {} })
// children 可以是一个字符串
h('div', { id: 'foo' }, 'hello')
// 没有 props 时可以省略不写
h('div', 'hello')
h('div', [h('span', 'hello')])
// children 数组可以同时包含 vnodes 与字符串
h('div', ['hello', h('span', 'hello')])
// 重复的 vnodes 是无效的
const p = h('p', 'hi')
return h('div', [
p,
p])
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "vue"
// ...
}
}
// 事件修饰符
<input
onClickCapture=
onKeyupOnce=
onMouseoverOnceCapture=
/>
// 渲染插槽(子组件)
export default {
props: ['message'],
setup(props, { slots }) {
return () => [
// 默认插槽:
// <div><slot /></div>
h('div', slots.default()),
// 具名插槽:
// <div><slot name="footer" :text="message" /></div>
h(
'div',
slots.footer({
text: props.message
})
),
]
}
}
// 作用域插槽
export default {
setup(props, { slots }) {
const text = ref('hi')
return () => h('div', null, slots.default({ text: text.value }))
}
}
// 默认插槽
<div>{slots.default()}</div>
// 具名插槽
<div>{slots.footer({ text: props.message })}</div>
// 传递插槽(父组件)
export default defineComponent({
name: 'SideMenu',
props: {},
async setup() {
// 插槽,并按组件名称引入
return () => <>
icon: () => h(resolveComponent(item.icon)),
default: (val) => <>{val}</>
</>
}
})
h(MyComponent, null, {
default: () => 'default slot',
foo: () => h('div', 'foo'),
bar: () => [h('span', 'one'), h('span', 'two')]
})
// 作用域插槽
h(MyComp, null, {
default: ({ text }) => h('p', text)
})
ref、reactive
ref:任意类型(建议基本类型)数据的响应式引用(设置、获取值时需要加.value)
reactive:只能是复杂类型数据的响应式引用,浅拷贝
import { ref, reactive } from 'vue'
let name=1
let a=ref(name)
a.value
let nameObj=reactive({a:1})
let readonlyObj = readonly(nameObj);
nameObj.a
template中引用 ref:attr reactive:state.attr
toRef 与 toRefs
toRef:用来给抽离响应式对象中的某一个属性,并把该属性包裹成 ref 对象,使其和原对象产生链接。
toRef 的本质是引用,修改响应式数据会影响原始数据。
toRefs:用来把响应式对象转换成普通对象,把对象中的每一个属性,包裹成 ref 对象。
import { toRef, toRefs, reactive } from 'vue'
let names=reactive({
name:'老谭',
age:23,
job:{
salary:10
}
})
return {
name:toRef(names,'name'),
age:toRef(names,'age'),
salary:toRef(names.job,'salary')
...toRefs(names)
}
template中引用 reactive:attr
computed、watch、watchEffect
//computed
const addCount = computed(() => {
return num.value * 2;
})
// 设置计算属性
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
//watch
watch(name, (newVal, oldVal) => {
console.log(newVal, oldVal);
}, { immediate: true }) // 立即执行
//监听复杂数据
watch(() => nameObj, (newVal, oldVal) => {
console.log(newVal, oldVal); // newVal、oldVal具有响应式
}, { deep: true,immediate: true})
// 同时监听多个对象
watch([() => nameObj.name, () => nameObj.lastName], ([newName, newLastName], [oldName, oldLastName]) => {
console.log(newName, oldName, newLastName, oldLastName);
})
//1.立即执行、立即监听(immediate)
//2.自动会感知代码依赖(自动收集依赖),不需要传递监听的内容(不需要像 watch 一样手动传入依赖)
//3.无法获得变化前的值(oldVal)
const stop = watchEffect(() => {
console.log(name);
console.log(nameObj.name);
})
stop()//停止监听
获得 dom 节点
子组件需要将数据暴露出去,父组件才能用ref获得数据defineExpose
//子组件
import { reactive, toRefs } from 'vue'
defineExpose({
// 解构state
...toRefs(state)
})
//父组件
<template>
<div ref="myRef">获取单个DOM元素</xdiv>
</template>
<script>
import { ref, onMounted } from 'vue';
export default {
setup() {
const myRef = ref(null);//单个
const myRefs = ref([null]);//多个
onMounted(() => {
console.log(myRef.value);
});
return {
myRef
};
}
};
</script>
provide 与 inject
==react context
父组件
setup () {
const name = ref('张三');
// provide(别名, 要传递的数据和方法)
provide('myName', name)
provide('handleClick', () => {
name.value = 'zhangsan';
})
},
子组件
setup () {
//调用 inject 方法,通过指定的别名,接收到父级共享的数据和方法
const name = inject('myName', defaultValue);
const handleClick = inject('handleClick');
return { name, handleClick }
}
router
定义 vue-router 然后引入的 useRoute,useRouter == vue2 的 this.$route,this.$router
import {useRouter,useRoute} from "vue-router";
setup(){
const router= useRouter()
const route= useRoute()
function fn(){
router.push('/about')
}
onMounted(()=>{
console.log(route.query.code)
})
return{
fn
}
}
生命周期改变
beforeCreate ===> Not needed*
created ===> Not needed*
beforeMount ===> onBeforeMount
mounted ===> onMounted
beforeUpdate ===> onBeforeUpdate
updated ===> onUpdated
beforeDestroy ===> onBeforeUnmount
destroyed ===> onUnmounted
vue2局限
this.$set({} || [],key,value)
不能检测数组变化
this.arr[i] = newValue
解决:
Vue.set(example1.items, indexOfItem, newValue)
//vue2重写方法
example1.items.splice(indexOfItem, 1, newValue)
this.arr.length = newlength
Vue 不能检测对象属性的添加或删除
let obj = {a:213,b:322}
obj.c = 3242 //非响应
解决:
this.$delete(obj,key)
this.obj = Object.assign({}, this.obj, {
age: 27,
favoriteColor: 'Vue Green'
})
this.$set(this.obj,age,27)
this.$set(this.obj,favoriteColor,'Vue Green')