6.20
vue3 升级
1.data 初始化数据
- vue2,根组件中可以使用对象,vue3 中必须使用函数定义
data(){
return {}
}
2.vue3 初始化实例
- 在 vue3 中创建一个新的应用实例使用
Vue.createApp()
<div id="counter">Counter: {{ counter }}</div>
const Counter = {
data() {
return {
counter: 0,
};
},
};
Vue.createApp(Counter).mount("#counter");
3.组件实例
- 每个 Vue 应用都是通过用
createApp
函数创建一个新的应用实例开始的:
const app = Vue.createApp({});
- 该应用实例是用来在应用中注册全局z 组件的
const app = Vue.createApp({});
app.component("SearchInput", SearchInputComponent); //注册全局组件
app.directive("focus", FocusDirective); //自定义指令
app.use(LocalePlugin); //引入插件
应用实例暴露的大多数方法都会返回该同一实例,允许链式:
Vue.createApp({})
.component("SearchInput", SearchInputComponent)
.directive("focus", FocusDirective)
.use(LocalePlugin);
4.生命周期(beforeUnmount,unmounted)
- vue3 对于 vue2 来说生命周期改变了销毁的生命周期,不过用法还是一样
- vue3 里边多了一个销毁的方法
app.unmount()
const app = Vue.createApp({
data() {
return {
title: "hello Vue3.",
};
},
beforeUnmount() {
console.log("beforeUnmount");
},
unmounted() {
console.log("unmounted");
},
});
app.mount("#root");
setTimeout(() => {
app.unmount(); // 销毁这个实例
}, 3000);
5.Data Property 和 方法
- vue3 中的 data 只能定义为函数
- 防抖和节流,vue 中没有内置的防抖和节流,不过可以使用第三方的库 lodash 来实现防抖和节流
- lodash
- 如果某个组件仅使用一次,可以在 methods 中直接应用防抖:
<script src="https://unpkg.com/lodash@4.17.20/lodash.min.js"></script>
<script>
Vue.createApp({
methods: {
// 用 Lodash 的防抖函数
click: _.debounce(function() {
// ... 响应点击 ...
}, 500)
}
}).mount('#app')
</script>
- 但是,这种方法对于可复用组件有潜在的问题,因为它们都共享相同的防抖函数。为了使组件实例彼此独立,可以在生命周期钩子的 created 里添加该防抖函数:
app.component("save-button", {
created() {
// 使用 Lodash 实现防抖
this.debouncedClick = _.debounce(this.click, 500);
},
unmounted() {
// 移除组件时,取消定时器
this.debouncedClick.cancel();
},
methods: {
click() {
// ... 响应点击 ...
},
},
template: `
<button @click="debouncedClick">
Save
</button>
`,
});
6.v-if
和 v-for
- 在 vue2 中,当
v-if
和v-for
同时使用时,v-for
的权重高 - 在 vue3 中,
v-if
的权重更高,而且在 vue3 中v-for
也不需要我们写v-key
来使用了 - v-for 中的 Ref 数组
- 在 Vue 2 中,在
v-for
中使用的ref
attribute 会用 ref 数组填充相应的$refs
property。当存在嵌套的v-for
时,这种行为会变得不明确且效率低下。 - 在 Vue 3 中,此类用法将不再自动创建
$ref
数组。要从单个绑定获取多个 ref,请将ref
绑定到一个更灵活的函数上 (这是一个新特性):
- 在 Vue 2 中,在
7.多事件处理器
- vue3 中允许绑定多个方法,事件处理程序中可以有多个方法,这些方法由逗号运算符分隔:
<!-- 这两个 one() 和 two() 将执行按钮点击事件 -->
<button @click="one($event), two($event)">Submit</button>
// ...
methods: {
one(event) {
// 第一个事件处理器逻辑...
},
two(event) {
// 第二个事件处理器逻辑...
}
}
8.按键修饰器
-
在 vue2 中,可以通过
keyCode
来修改v-on
的方法-
使用键码
<input v-on:keyup.13="submit" />
-
使用按键别名
<input v-on:keyup.enter="submit" />
-
也可使使用
config.keyCodes
,自定义自己的别名Vue.config.keyCodes = { f1: 112, };
-
-
在 vue3 中
config.keyCodes被废弃
- 使用按键修饰符
<input v-on:keyup.page-down="nextPage">
- 同时匹配 q 和 Q
<input v-on:keypress.q="quit">
- 使用按键修饰符
9.$attrs
包含class
&style
-
vue2 中,在子组件标签中的
class
不会出现在 `` -
Vue3 中,
$attrs
现在包含了所有传递给组件的 attribute,包括class
和style
。- 当属性值
inheritAttrs: false
是 falsed 的时候 ,子组件标签上的属性不会继承到子组件内部元素上,当是true
的时候,会继承到子组件最外层元素上 - 子组件标签所有属性值都会在子组件的 props 中获取到
- 也会在
$attrs
中存在
<div id="root"> <date-picker data-status="activated" class="a"></date-picker> </div>
<script> const app = Vue.createApp({ }) app.component('date-picker', { inheritAttrs: false, template: ` <div v-bind:class="$attrs.class"> <input type="datetime-local" v-bind:class="$attrs.class"/> </div> `, created() { // console.log(this.$attrs.class) } }) app.mount('#root') </script>
- 当属性值
10.自定义事件
-
事件名
- 与组件和 prop 一样,事件名提供了自动的大小写转换。如果在子组件中触发一个以 camelCase (驼峰式命名) 命名的事件,你将可以在父组件中添加一个 kebab-case (短横线分隔命名) 的监听器。
<div id="root"> <parent-com @aaa="handleClick"></parent-com> </div>
const app = Vue.createApp({ methods: { handleClick() { console.log("click."); }, }, }); app.component("parent-com", { emits: ["aaa"], template: ` <div> <button @click="$emit('aaa')">click</button> </div> `, }); app.mount("#root");
-
验证抛出的事件
- 与 prop 类型验证类似,如果使用对象语法而不是数组语法定义发出的事件,则可以对它进行验证。
- 要添加验证,请为事件分配一个函数,该函数接收传递给
$emit
调用的参数,并返回一个布尔值以指示事件是否有效。
emits:{ aaa:(e)=>{ if(e>10){ return true }else{ return false } } },
-
v-model 参数
- 默认情况下,组件上的
v-model
使用modelValue
作为 prop 和update:modelValue
作为事件。我们可以通过向v-model
传递参数来修改这些名称:
// 理解:s 将自定义事件 aaa 的返回值做了动态绑定 <div id="root"> {{ counted }} <parent-com v-model:aaa="counted"></parent-com> </div>
// props 中的 aaa 就是 每次从父组件传下来的值 // emits 使用 update:aaa 来定义事件 // this.emit 触发事件,返回值到父组件 <script> const app = Vue.createApp({ data() { return { counted:1 } } }); app.component("parent-com", { data(){ return{ bbb:1 } }, props:['aaa'], emits:['update:aaa'], template: ` <button @click="handleClick">click</button> `, methods:{ handleClick(){ this.bbb++ this.$emit('update:aaa',this.bbb) } } }); app.mount("#root"); </script>
- 默认情况下,组件上的
11.插槽
- 插槽内容
<todo-button> Add todo </todo-button>
<!-- todo-button 组件模板 -->
<button class="btn-primary">
<slot></slot>
</button>
-
新增
v-slot
简写为#
<div id="root"> // v-slot:名字=“传递参数” <todo-list v-slot:list="{ index, item }"> <span>{{index}}</span> - <span>{{item}}</span> </todo-list> </div>
const app = Vue.createApp({}); app.component("todo-list", { data() { return { items: ["feed a cat", "Buy milk"], }; }, // 渲染插槽,这里用的是具名插槽,name 绑定插槽,item,index用来传递参数 template: ` <ul> <li v-for="(item, index) in items"> <slot name="list" :item="item" :index="index"></slot> </li> </ul> `, }); app.mount("#root");
12.Provide / Inject
-
Vue3 的开发文档中将这个提出来放到了 深入组件 这一栏。
-
基础使用
-
在根组件中使用
Provide
来声明一个变量provide: { user: 'John Doe' },
-
在需要用到改参数的子组件中使用
inject
来获取
inject: ['user'],
-
-
处理响应式
-
使用 Vue3 ,存储在实例中的 computed
const { createApp, computed } = Vue provide() { return { msg: Vue.computed(() => this.str) } },
-
获取的方法还是使用
inject
inject: ['msg'],
-
13.动态组件 && 异步组件
-
使用挂载在 vue 实例上的方法
defineAsyncComponent
,需要做异步的组件返回一个 promise 对象。const { createApp, defineAsyncComponent } = Vue; const app = createApp({}); const AsyncComp = defineAsyncComponent( () => new Promise((resolve, reject) => { setTimeout(() => { resolve({ template: "<div>I am async!</div>", }); }, 3000); }) ); app.component("async-example", AsyncComp); app.mount("#root");
-
使用
suspense
标签实现页面的呈现- 如果组件没有返回值就显示
fallback
,否则就显示default
<div id="root"> <suspense> <template #default> <async-example></async-example> </template> <template #fallback> <div>loading...</div> </template> </suspense> </div>
- 如果组件没有返回值就显示
14.$children
$children
实例 property 已从 Vue 3.0 中移除,不再支持。- 如果想访问子组件实例,vue3 中使用
$refs
15.自定义指令
<div id="root">
<h1>scroll down the page</h1>
<input type="range" min="0" max="500" v-model="pinPadding" />
<p v-pin:[direction]="pinPadding">text</p>
</div>
const app = Vue.createApp({
data() {
return {
pinPadding: 200,
direction: "right",
};
},
});
// app.directive('pin', {
// mounted(el, binding) {
// // console.log(binding)
// el.style.position = 'fixed'
// const s = binding.arg || 'top'
// el.style[s] = binding.value + 'px'
// },
// updated(el, binding) {
// const s = binding.arg || 'top'
// el.style[s] = binding.value + 'px'
// },
// })
// app.directive 第二个参数传一个方法 结果等同于上边 mouthed和updated的结合
app.directive("pin", (el, binding) => {
el.style.position = "fixed";
const s = binding.arg || "top";
el.style[s] = binding.value + "px";
});
app.mount("#root");
16.与自定义元素的互操作性
-
定制内置元素
-
在普通元素上使用时,它将作为
is
attribute 传递给createElement
调用,并作为原生 attribute 渲染。这支持了自定义内置元素的用法。<button is="plastic-button">点击我!</button>
-
2.x 的行为:渲染
plastic-button
组件。 -
3.x 的行为:通过调用以下函数渲染原生的 button
document.createElement("button", { is: "plastic-button" });
-
-
-
使用 vue: 前缀来解决 DOM 内模板解析问题,可以渲染组件
<div id="root"> <div :is="myDiv"></div> <table> <tr is="vue:mytr"></tr> </table> </div>
const app = Vue.createApp({ data() { return { myDiv: "abc", }; }, }); app.component("mytr", { template: ` <tr><td>abc111</td></tr> `, }); app.mount("#root");
17.组合式 API
-
1> setup()
-
基本使用
- setup 里边没有 this,不能访问 this
- setup 函数,可以把他理解成 生命周期函数
<template> <div>{{count}}</div> </template>
import { ref } from "vue"; export default { setup() { const count = ref(0); return { count, }; }, };
-
访问 Prop
- setup 的第一个参数 props 接收
- 直接解构 props 里的内容不是响应式的,这时候需要使用,vue 里的方法
toRef toRefs
// props: ['title', 'obj'] const x = toRef(props.obj, "x"); const title = toRef(props, "title"); const { title: title2, obj: { value }, } = toRefs(props);
-
setup 的上下文
- setup 的第二个参数 context 接收,有四个参数
slots emit attrs expose
// 不给父组件暴露任何数据 // 正常情况下父组件可以通过 $refs 获取子组件的值,这样的情况下,父组件是无法获取内容的 expose();
// attrs 类似于 $attrs ,包含通过子组件标签传来的内容 // emit 就是 $emit
- setup 的第二个参数 context 接收,有四个参数
-
与渲染函数裿使用
<template> <h1>{{count}}</h1> </template>
<script> import { ref, h, onMounted } from 'vue' export default { setup(props, { expose }) { const count = ref(0) // return { // count // } onMounted(() => { setTimeout(() => { count.value = 100 }, 2000) }) expose({count}) return () => h('div', {class: 'abc'}, h('a', {href: 'http://www.baidu.com'}, '百度')) } } </script>
-
-
2> 核心
-
ref()
- 用来申明 响应式 变量
- 需要在 vue 中引入
- 可以是任何数据,但是 Map 不可以,尽量用 ref 声明简单数据类型,复杂数据类型交给 reactive 去做
const couter = ref(0); const obj = ref({ x: 1, t: { a: 2, }, });
-
reactive()
- 用来申明 响应式 变量
- 需要在 vue 中引入
const obj = reactive({ count, }); obj.count = 200; console.log(count.value); // 200 const books = reactive([ref("JavaScript学习指南")]); books[0].value = "html学习指南"; console.log(books[0].value); // html学习指南
-
computed()
- 计算属性
- 需要在 vue 中引入
const count = ref(0); const addOne = computed(() => count.value + 1); console.log(addOne.value); // 1 count.value++; console.log(addOne.value); // 2
-
readonly()
- 设置只读属性
const original = reactive({ count: 0 }); const copy = readonly(original); original.count = 100; // copy.count = 200 console.log(copy.count);
-
watchEffect()
- 监听属性
- 加载完成时会执行一次,总要执行一次
- 有返回值,是一个函数,执行返回值会停止监听
- 多次更改会一起合并执行
const count = ref(0); const stop = watchEffect(() => console.log(count.value)); count.value = 100; // 会打印 100 stop(); // const title = ref('line1') // const stop = watchEffect(() => console.log(count.value + title.value)) // count.value = 100 // count.value = 100 // title.value = 'line2' // 只打印一次 100line2
-
watch()
- 监听属性
- 有两个参数,都是函数,第一个返回要坚挺的参数,第二个参数是新值和旧值,返回要执行的 js 代码
const state = reactive({ count: 0 }); watch( () => state.count, (count, prevCount) => { console.log(count); console.log(prevCount); } ); state.count++; // 1 ,0
-
-
3> 工具
-
isRef()
- 检查一个值是否为一个 ref 对象
const count = ref(0); console.log(isRef(count));
-
unRef()
- 如果参数是一个 ref 则返回它的 value,否则返回参数本身
- 它是 val = isRef(val) ? val.value : val 的语法糖
const count = ref(10); const value = unref(count);
-
toref()
- 可以用来为一个 reactive 对象的属性创建一个 ref。这个 ref 可以被传递并且能够保持响应性
const countObj = reactive({ x: 0, y: 100, z: 300 }); const countObj = reactive({ x: 0, y: 100, z: 300 }); const x = toRef(countObj, "x"); const { x: x1, y, z } = toRefs(countObj);
-
toRefs()
- 把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref ,和响应式对象 property 一一对应。
// 看上面
-
isProxy()
- 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
const msg = reactive({}); const msg2 = {}; console.log(isProxy(msg)); // true console.log(isProxy(msg2)); // false
-
isReactive()
- 检查一个对象是否是由 reactive 创建的响应式代理
- 如果这个代理是由 readonly 创建的,但是又被 reactive 创建的另一个代理包裹
了一层,那么同样也会返回 true
const count = ref(0); const count2 = reactive({ x: 0, y: 0 }); console.log(isReactive(count)); // false console.log(isReactive(count2)); // true
-
isReadonly()
- 检查一个对象是否是由 readonly 创建的只读代理。
const msg = readonly(reactive({})); const msg2 = ref(0); console.log(isReadonly(msg)); // true console.log(isReadonly(msg2)); // false
-
-
4> 声明周期钩子
- 用法和之前也一样只不过,与组合式 API 结合使用,js 逻辑代码是以参数的形式书写的
beforeCreate
->setup()
created
->setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
destroyed
->onUnmounted
setup() { const count = ref(0) onMounted(() => { console.log('onMounted') count.value = 100 }) onUpdated(() => { console.log('onUpdated') }) onUnmounted(() => { console.log('onUnmounted') }) onBeforeMount(() => { console.log('onBeforeMount') }) onBeforeUpdate(() => { console.log('onBeforeUpdate') }) onBeforeUnmount(() => { console.log('onBeforeUnmount') }) return { count } }
-
5> 依赖注入- provide & inject
-
和之前的 provide & inject 用法是一样的
-
provide()
- 存入内容,两个参数,第一个是这个上下文的名字,第二个为参数
const title = ref('hello111')
provide('title1',title)
- 存入内容,两个参数,第一个是这个上下文的名字,第二个为参数
-
inject()
- 第二个参数是一个可选的的默认值。如果未提供默认值,并且在 provide 上下文中未找到该属性,则 inject 返回 undefined。
const title = inject('title1')
- 第二个参数是一个可选的的默认值。如果未提供默认值,并且在 provide 上下文中未找到该属性,则 inject 返回 undefined。
-
-
6>
<script setup>
- 在 Vue3 中 script 标签中写了 setup 之后,就不需要在 里面写 setup 了
-
7> Vue Router 4.x 组合式API
-
在 vite 下安装 Vue Router 4.x
pnpm i vue-router@4
-
定义路由
import { createRouter, createWebHistory } from 'vue-router'
createWebHistory
history 模式路由
createWebHashHistory
hash 模式路由 -
引入路由
- 在 main.js 中引入
import router from './router'
app.use(router)
- 在 main.js 中引入
-
useRouter(),useRoute()
-
页面中要是用 原来的 router 和 route 需要引入
import { useRouter, useRoute } from 'vue-router'
const router = useRouter(); const route = useRoute(); router.push({ name: "home", query: { ...route.query, }, });
-
-
watch 路由变化
- 可以监听动态路由的变量
watch( () => route.params.id, (newId) => { console.log(newId); } );
-
onBeforeRouteLeave() 守卫
- 添加一个导航守卫,在当前位置的组件将要离开时触发。类似于 beforeRouteLeave,但它可以在任何组件中使用。当组件被卸载时,导航守卫将被移除。
-
onBeforeRouteUpdate() 守卫
- 添加一个导航守卫,在当前位置即将更新时触发。类似于 beforeRouteUpdate,但它可以在任何组件中使用。当组件被卸载时,导航守卫将被移除。
-
-
8> 21、Vuex 4.x 组合式API
-
在 vite 下安装 Vuex
pnpm install vuex@next --save
-
定义 store
- 在 store 中引入 Vuex
import { createStore } from "vuex"; const store = createStore({ state() { return { count: 0, }; }, mutations: { increment(state) { state.count++; }, }, }); export default store;
-
引入 store
- 在 main.js 入口文件引入
import store from "./store"; app.use(store);
-
useStore()
- 页面是用 vuex 中的数据 ,需要引入 useStore
import { useStore } from 'vuex'
- 使用
const store = useStore(); // 取值 const count = computed(() => { return store.state.count; }); // 使用方法 const increment = () => { store.commit("increment"); };
- 页面是用 vuex 中的数据 ,需要引入 useStore
-