二、学习笔记
1、写法
过去写Vue项目的时候在template必须有父元素,例如:
<template>
<div>
首页
</div>
</template>
那么在Vite构建的Vue3项目中,无需父元素,完全可以这样写:
<template>
首页
</template>
2、Vite解决@问题
过去写Vue项目的时候,@代表src目录,例如引入文件:
import About from '@/views/About.vue'
那么在Vite构建的Vue3项目中需要先配置一下
打开Vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
const path = require('path');
export default defineConfig({
plugins: [vue())],
resolve: {
//配置路径别名
alias:{
'@': path.resolve(_dirname, './src'),
}
}
})
3、Vue3语法糖
Vue3语法糖写法,setup组合式API
<template>
<h1>{{ str }}</h1>
<News></News>
</template>
// Vue3写法:
<script setup>
import News from '@/components/News.vue'
let str = '张小三333';
</script>
// Vue2写法:
<script type="text/javascript">
export default{
data () {
//定义数据
return{
}
},
methods:{
//定义方法
},
computed:{
//计算属性
}
}
</script>
Vue官方API说明:API 参考 | Vue.js
3.1 定义数据
1> 死数据,不可以修改之类的,但是可以展示视图层
如let str = '1';
2> 响应式数据 : ref
2>1 在使用的时候需要 : x.value
3>响应式数据 : reactive
3>1 在使用的时候需要 : 不像ref一样,不需要.value
3>2 reactive “只能” 写数组或对象,不能定义基本数据类型
(写基本数据类型,会报黄标,不能修改数据)
3.2 数据拦截方式(Vue2与Vue3)
Vue2.x ==> Object.defineProperty
Vue3.x ==> new Proxy
3.3 toRefs
解决结构化数据,无法直接修改数据的问题,如:
let { name, age } = reactive({ name:'张三', age:20 })
这里如果通过方法修改name或age的数值,是没法得到响应
用toRefs解决该问题:
let obj = reactive({ name:'张三', age:20 })
let { name, age } = toRefs(obj);
注意,转化成ref响应式数据,需要.value才是数值。
4、setup语法糖插件
解决import { ref, reactive ... } 引入的问题
4.1 下载安装
npm i unplugin-auto-import -D
4.2 配置vite.config.js
详细参考(一、项目搭建)的第3点
5、computed(计算)
5.1 第一种写法(相当于get方法,仅可同步)
let changeStr = computed({
return str.value;
})
5.2 第二种写法(可同步可修改)
let changeStr = computed({
get(){
return str.value;
}
set(val){
str.value = val;
}
})
6、watch(监听)
6.1 监听一个
watch( str , (newVal, oldVal) => {
console.log(newVal, oldVal);
})
6.2 (同时)监听多个
watch( [str, num], (newVal, oldVal) => {
// ...
})
6.3 初始化监听
watch( str , (newVal, oldVal) => {
console.log(newVal, oldVal);
},{
immediate:true, //这里是初始化监听
deep:true //这里是深度监听
})
6.4监听对象
let obj = reactive({
a: 1,
b: 2,
m:{
c:3
}
})
watch(obj, (newVal) =>{
console.log( newVal);
})
watch( ()=>obj.m, (newVal, oldVal) => {
console.log( newVal, oldVal);
},{
deep:true //当使用() =>对象,监听需要深度监听
})
6.5 立即监听
watchEffect(() => {
console.log( Str.value )
})
6.6 监听路由对象
let router = useRouter();
watch(() => router.currentRoute.value, (newVal) => {
cosole.log(newVal);
})
7、生命周期钩子
| onBeforeMount() | 在组件被挂载之前被调用。 |
| onMounted() | 在组件挂载完成后执行。 |
| onBeforeUnmount() | 在组件实例被卸载之前调用。 |
| onUnmounted() | 在组件实例被卸载之后调用。 |
| onBeforeUpdate() | 在组件即将因为响应式状态变更而更新其 DOM 树之前调用。 |
| onUpdated() | 在组件因为响应式状态变更而更新其 DOM 树之后调用。 |
| onErrorCaptured() | 在捕获了后代组件传递的错误时调用。 |
| onRenderTracked() | 当组件渲染过程中追踪到响应式依赖时调用。(Dev) |
| onRenderTriggered() | 当响应式依赖的变更触发了组件渲染时调用。(Dev) |
| onActivated() | 若组件实例是 缓存树的一部分,当组件被插入到 DOM 中时调用。 |
| onDeactivated() | 若组件实例是 缓存树的一部分,当组件从 DOM 中被移除时调用。 |
| onServerPrefetch() | 在组件实例在服务器上被渲染之前调用。(SSR) |
8、组件间数据传输
8.1 组件——父传子
// 父组件
<template>
<div>
<List :msg='msg'></List>
</div>
</template>
<script setup>
import List from '../components/List.vue'
let msg = ref('这是父的数据');
</script>
// 子组件
<template>
<div>
{{ msg }}
</div>
</template>
<script setup>
import { defineProps } from 'vue' //自动引入则忽略
defineProps({
msg:{
type:String,
default:'111'
}
})
</script>
8.2 组件——子传父
// 子组件
<template>
<div>
这是子组件 ==>{{ num }}
<button @click='changeNum'>按钮</button>
</div>
</template>
<script setup>
import { defineEmits } from 'vue' //自动引入则忽略
let num = ref(100);
const emit = defineEmits(['fn'])
const changeNum = () => {
emit('fn', num);
}
</script>
// 父组件
<template>
<div>
<List @fn='changeHome'></List>
</div>
</template>
<script setup>
import List from '../components/List.vue'
const changeHome = (n) => {
console.log(n.value)
}
</script>
8.3 组件 双向绑定v-model
// 父组件
<template>
<div>
<List v-model:msg='msg'></List>
</div>
</template>
<script setup>
import List from '../components/List.vue'
let msg = ref('这是父的数据');
</script>
// 子组件
<template>
<div>
{{ msg }}
<button @click='btn'></button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue' //自动引入则忽略
const props = defineProps({
msg:{
type:String,
default:'111'
}
const emit = defineEmits(['update:msg'])//update这里是写死的
const btn = () =>{
emit('update:msg','这是子改的数据')
}
})
</script>
8.4 子传父(通过ref绑定)
1> 子组件通过defineExpose,将属性或方法暴露出去
2> 父组件可以通过绑定组件ref的方式,获取属性或方法
// 父组件
<template>
<div>
<List ref="list"></List>
<button @click='btn'>按钮</button>
</div>
</template>
<script setup>
import List from '../components/List.vue'
let list = ref();
const btn = () =>{
console.log(list.value.a);
console.log(list.value.b);
}
</script>
// 子组件
<template>
</template>
<script setup>
let a = 'a';
let b = ref('b');
defineExpose({
a,
b
})
</script>
8.5 兄弟组件之间传值
1> 子传父,父再传子,这是一种方式
2> 可以使用类似Bus的mitt插件,直接子传子
npm install mitt -S
3> 配置plugins/Bus.js
import mitt from 'mitt';
const emitter = mitt();
export default emitter;
4> 使用例子
// 子组件A
<template>
<div>
<button @click='btn'>按钮</button>
</div>
</template>
<script setup>
import emitter from '../plugins/Bus.js'
let str = ref('这是A组件的数据')
const btn = () =>{
emitter.emit('fn', str)
}
</script>
// 子组件B
<template>
<div>
{{ msg }}
</div>
</template>
<script setup>
import emitter from '../plugins/Bus.js'
let msg = ref();
onBeforeMount(() => {
emitter.on('fn',e=>{
msg.value = e.value;
})
})
</script>
9、插槽
9.1 匿名插槽
父组件中,有一个List的子组件
<List>这是内容<List>
子组件中插入插槽才能显示内容
<slot>这是内容</slot>
9.2 具名插槽
父组件中,有一个List的子组件
<List>
<template v-slot:A>
AAAA
</template>
<template v-slot:B>
BBBB
</template>
<List>
子组件中,需要分别展示内容:
<slot name='A'></slot> //展示AAAA
<slot name='B'></slot> //展示BBBB
注意:v-slot:A可以简写成#A
9.3 作用域插槽
父组件
<template v-slot='{data}'>
{{ data }} ==> {{ data.name }}
</template>
子组件
<div v-for='item in list' :key='item.id'>
<slot :data='item'></slot>
</div>
<script setup>
let list = ref([
{id: 1, name: '张三', age: 18},
{id: 2, name: '李四', age: 19},
{id: 3, name: '赵五', age: 17}
])
注意:v-slot='{data}'可以简写成#default='{data}'
</script>
9.4 动态插槽
通过变量去定义插槽的name
父组件中,有一个List的子组件
<List>
<template #[str]>
AAAA
</template>
<List>
<script setup>
let str = ref('A');
</script>
子组件中,需要分别展示内容:
<slot name='A'></slot> //展示AAAA
10、Teleport传送
**Vue3新增的内容
***必须传送到有这个dom的内容中,需要注意顺序
父组件
<template>
<div class='main'>
这是main
</div>
<div id='container'>
这是container
</div>
</template>
<script setup>
import A from '../components/A.vue'
</script>
A组件
<template>
<h1>A组件</h1>
<teleport to='body'>
<div>这是传送内容</div>
</teleport>
</template>
to='body' //传送到body主体中
to='.main' //传送到main中,因为是class需要增加.
to='#container' //传送到container中,因为是id
11、动态组件
动态加载多个组件,减少维护成本
<template>
<ul>
<li
v-for='(item,index) in tabList'
:key='index'
@click='change(index)'
>
{{ item.name }}
</li>
</ul>
<keep-alive>
<component :is="currentComponent.com"></component>
</keep-alive>
</template>
<script setup>
import A from '../components/A.vue'
import B from '../components/B.vue'
import C from '../components/C.vue'
let tabList = reactive([
{name:'这是Atab', com:marKraw(A)},
{name:'这是Btab', com:marKraw(B)},
{name:'这是Ctab', com:marKraw(C)}
])
let currentComponent = reactive({
com:tabList[0].com
})
const change = (idx) =>{
currentComponent.com = tabList[idx].com
}
</script>
PS1:组件没必要作为响应数据,因此marKraw(A)
PS2:currentComponent.com=切换的组件
12、异步组件
***提升性能,如分步加载、js分包
12.1 如分步加载
实现分步加载,还需要添加一个插件vueuser
安装 npm install @vueuse/core -S
<template>
<A></A>
<B></B>
<div ref='target'>
<C v-if='targetIsVisible'></C>
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue' //自动导入忽略
import { useIntersectionObserver} from '@vueuse/core'
import A from '../components/A.vue'
import B from '../components/B.vue'
const C = defineAsyncComponent(() =>
import C from '../components/C.vue'
)
const target = ref(null); //绑定div块
const targetIsVisible = ref(false);
const { stop } = useIntersectionObserver(
target,
([{ isIntersecting }], observerElement) =>{
targetIsVisible.value = isIntersecting
}
)
</script>
//observerElement可以不用
12.2 异步加载(分包加载)
可以结合插槽的形式,达成分步加载,组件的包会后置加载
<template>
<A></A>
<B></B>
<Suspense>
<template #default>
<C></C>
</template>
<template #fallback>
加载中...
</template>
</Suspense>
</template>
<script setup>a
import { defineAsyncComponent } from 'vue' //自动导入忽略
import A from '../components/A.vue'
import B from '../components/B.vue'
const C = defineAsyncComponent(() =>
import C from '../components/C.vue'
)
)
</script>
13、Mixin 混入
作用:用于分发Vue组件中的可复用功能
在mixin.js中
import { ref } from 'vue'
export default function(){
let num = ref(1);
let fav = ref(false);
const favBtn = () ={
num.value++;
fav.value = true;
setTimeout(() =>{
fav.value = false;
},2000)
}
return {
num, fav, favBtn
}
}
在A组件中
<template>
<div>
{{ num }}
<button @click='favBtn'>
{{ fav ? '收藏中...' : '收藏'}}
</button>
</div>
</template>
<script setup>
import mixin from '../mixins/mixin.js'
let {num, fav, favBtn} = mixin();
</script>
在B组件中(同样写法)
但运行的时候,A组件与B组件的num等值是独立的。
14、Provide/Inject 依赖注入
作用,用于将父数据直接传递给孙子(数据可改变)
父组件:
<template>
<A></A>
</template>
<script setup>
import { provide } from 'vue' //自动导入忽略
import A from '../components/A.vue'
let num = ref(100);
provide('changeNum', num);
</script>
子组件:
<template>
</template>
<script setup>
import { inject } from 'vue' //自动导入忽略
const aNum = inject('changeNum')
</script>
15、路由
5.1 官网
5.2 路由跳转
跳转至about
注意:tag属性被取消了
5.3 路由跳转
跳转
let router = useRouter();
const goAbout = () =>{
router.push('/about');
}
5.4 导航守卫
1> 全局路由守卫
全局前置守卫,路由跳转前触发
beforeEach(to, from, next)
全局解析守卫,在所有组件内守卫和异步路由组件被解析之后触发
beforeResolve(to, from, next)
全局后置守卫,路由跳转完成后触发
afterEach(to, from)
2> 路由独享守卫
路由对象单个路由配置,单个路由进入前触发
beforeEnter(to, from, next)
3> 组件路由守卫
beforeRouteEnter(to, from, next)
beforeRouteUpdate(to, from, next)
beforeRouteLeave(to, from, next)
4>整体触发顺序
当点击切换路由时:beforeRouterLeave-->beforeEach-->beforeEnter-->beforeRouteEnter
-->beforeResolve-->afterEach-->beforeCreate-->created-->beforeMount-->mounted
-->beforeRouteEnter的next的回调
当路由更新时:beforeRouteUpdate
5>写法
beforeEnter:(to, from, next) => {
console.log('进入前触发');
}
16、Pinia 状态管理
16.1 Vuex和Pinia的区别
参考网址:[Vuex] Vuex 5 by kiaking · Pull Request #271 · vuejs/rfcs · GitHub
1. pinia没有mutations,只有:state、getters、actions
2. pinia分模块不需要modules(直接分js文件即可)
3. pinia体积更小(性能更好)
4. pinia可以直接修改数据
16.2 pinia配置
官网网址:Pinia | The intuitive store for Vue.js
1> 安装
npm install pinia
2> main.js配置
import { createPinia } from 'pinia'
app.use(createPinia())
3> 根目录新建store/index.js中写入
import { defineStore } from 'pinia'
export const useStore = defineStore('storeID',{
state: () => {
return{
counter: 0,
}
},
getters:{},
actions:{}
})
16.3 pinia使用
<template>
{{ name }}
{{ num }}
{{ changeNum }}
<button @click='btn'></button>
</template>
<script setup>
import { useStroe } from '../store'
import { storeToRefs } from 'pinia'
const store = useStore();
let { name, num, changeNum } = storeToRefs(store);//套上即可修改数据
const changeName = () ={
name.value = 'XX';
num.value = 12;
//或使用批量更新函数
store.$patch(state=>{
state.name = 'XX';
state.num++;
})
}
const btn = () ={
store.upNum( 200 );
}
</script>
import { defineStore } from 'pinia'
export const useStore = defineStore('storeID',{
state: () => {
return{
num: 0,
name: '张三'
}
},
getters:{
changeNum(){
return this.num + 1000
}
},
actions:{//支持同步异步操作
upNum(val){
this.num += val;
}
}
})
16.4 持久化存储
1> 安装依赖
npm i pinia-plugin-persist --save
2> 配置
可以针对main.js改变维护store的结构
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './stores/index.js'
const app = createApp(App)
app.use(store)
app.use(router)
app.mount('#app')
然后在store目录下新建一个index.js
import { createPinia } from 'pinia'
//引入pinia的持久化存储插件
import piniaPluginPersist from 'pinia-plugin-persist'
const store = createPinia();
//使用持久化存储插件
store.use(piniaPluginPersist)
export default store
3> 使用
在需要的store中指定模块的js文件进行配置
import { defineStore } from 'pinia'
export const useStore = defineStore('storeID',{
state: () => {
return{
num: 0,
name: '张三'
}
},
getters:{
changeNum(){
return this.num + 1000
}
},
actions:{//支持同步异步操作
upNum(val){
this.num += val;
}
},
// 开启数据缓存
persist: {
enabled: true,
strategies: [{
key: 'my_user', //存储的key
storage: localStorage //存储的位置,默认session
}]
}
})
本文介绍了在Vue3中使用Vite构建项目时模板写法的变化,不再需要父元素包裹。Vite配置中解决@别名问题,以及Vue3的setup语法糖,包括ref和reactive的使用。文章还涉及到了Vue3的计算属性、监听器、生命周期钩子、组件间数据传输的各种方式,如props、emit、v-model、ref和Bus插件。另外,讨论了插槽的使用,包括匿名、具名、作用域和动态插槽。最后提到了Teleport、动态组件和异步组件的加载优化。
2667

被折叠的 条评论
为什么被折叠?



