Vue3入门到精通教程
2022-05-24 17:54:49
2024-11-19 15:49:47
起初 Vue3.0 暴露变量必须 return 出来,template中才能使用;Vue3.2 中 只需要在 script 标签上加上setup 属性,组件在编译的过程中代码运行的上下文是在setup()函数中,无需return,template可直接使用。都上了Vue3就直接学setup语法糖吧
一、文件结构
vue
<template>
// Vue2中,template标签中只能有一个根元素,在Vue3中没有此限制
// ...
<div class="box">hello linge</div>
</template>
<script setup>
import { ref } from "vue";
const color = ref("red");
// ...
</script>
<style lang="scss" scoped>
// 支持CSS变量注入v-bind(color)
.box {
width: 100px;
height: 100px;
color: v-bind(color);
}
</style>
二、data
vue
<script setup>
import { reactive, ref, toRefs } from "vue";
// ref声明响应式数据,用于声明基本数据类型
const name = ref("小白龙");
// 修改
name.value = "小龙龙";
// reactive声明响应式数据,用于声明引用数据类型
const state = reactive({
name: "小白龙",
sex: "男",
});
// 修改
state.name = "小龙龙";
// 使用toRefs解构
const { name, sex } = toRefs(state);
// template可直接使用{{name}}、{{sex}}
// toRef 是对定义的响应对象的某个属性进行引用,使用一个函数返回一个响应式对象,依旧保持响应式
const nameRef = toRef(state, 'name')
</script>
获取dom元素
language
<div ref="myRef">获取单个DOM元素</div>
<div>获取多个DOM元素</div>
<ul>
<li v-for="(item, index) in 3" :key="index" :ref="setRef">
{{ item }}
</li>
</ul>
//获取单个DOM元素
const myRef = ref(null);
//获取多个DOM元素
// 存储dom数组
const myRefs = ref([]);
const setRef = (el) => {
myRefs.value.push(el);
};
三、method
vue
<template>
// 调用方法
<button @click="changeName">按钮</button>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({
name: '小白龙'
})
// 声明method方法
const changeName = () => {
state.name = '小龙龙'
}
</script>
四、computed
vue
<script setup>
import { computed, ref } from 'vue'
const count = ref(1)
// 通过computed获得doubleCount
const doubleCount = computed(() => {
return count.value * 2
})
</script>
五、watch
属性
- watch 具备一定的惰性
- 可以拿到原始值和当前值
- 可以侦听多个数据的变换,用一个侦听器承载
- 默认开启深度监听
传参
- 第一个参数,需要传递一个需要监听的 function 、ref 、reactive object
- 第二个参数,用来接受数据原始值和当前值
- 第三个参数,传递 watch 的高级配置项 例如 immediate: true
vue
<script setup>
import { watch, reactive } from 'vue'
### 监听基础类型
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log('watch' + newValue, oldValue)
})
### 监听复杂类型
const boy = reactive({
name: '小白龙',
friend: {
friend1 : '小小龙',
friend2 : '小小小龙'
}
})
#### 监听整个对象
watch(boy, (newValue, oldValue) => {
console.log('boy发生了变化')
console.log(newValue);
console.log(newValue.friend);
})
boy.name = 'Little children'
boy.friend.friend3 = '小王'
> 第一个参数传入我们要监听的对象,当监听的对象里面的任意一个属性发生变化,watch 方法便会触发。
#### 监听对象中的某个属性
// 如果我们直接写 boy.name
watch(boy.name, (newValue, oldValue) => {
console.log('boy发生了变化')
console.log(newValue)
})
// vue会提示我们,监听的对象需要是一个 function 或者 ref 或者是一个 reactive object
// 正确的写法是:
watch(() => boy.name, (newValue, oldValue) => {
console.log('boy发生了变化')
console.log(newValue)
})
boy.name = 'Little children'
#### 监听对象的所有属性
watch(() => boy, (newValue, oldValue) => {
console.log('boy发生了变化')
console.log(newValue)
}, {
immediate: true//立即执行
})
boy.name = 'Little children'
#### 监听多个数据
- watch 里面可以接收一个数组
- 无论数组里面的哪一个数据发生变化,都会执行侦听器
watch([()=> boy.name, count], ([newName, newCount], [oldName, oldCount]) => {
console.log(newName + '---' + oldName)
console.log(newCount + '---' + oldCount)
})
boy.name = 'Little children'
</script>
watchEffect
属性
- 立即执行的,没有惰性
- 不需要传递要侦听的内容,会自动感知代码依赖,不需要传递很多参数,只需要一个回调函数
- 不能获取之前数据的值
监听基础类型
language
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
count.value ++
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
count.value ++
六、props父传子
子组件
vue
<template>
<span>{{ props.name }}</span>
// 可省略【props.】
<span>{{ name }}</span>
</template>
<script setup>
// import { defineProps } from 'vue'
// defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineProps: true】
// 声明props
const props = defineProps({
name: {
type: String,
default: ''
}
})
</script>
父组件
vue
<template>
<child name="Jerry" />
</template>
<script setup>
// 引入子组件(组件自动注册)
import child from './child.vue'
</script>
七、emit子传父
子组件
vue
<template>
<span>{{ props.name }}</span>
// 可省略【props.】
<span>{{ name }}</span>
<button @click="changeName">更名</button>
</template>
<script setup>
// import { defineEmits, defineProps } from 'vue'
// defineEmits和defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】
// 声明props
const props = defineProps({
name: {
type: String,
default: ''
}
})
// 声明事件
const emit = defineEmits(['updateName'])
//ts写法
//const emit = defineEmits<{
// (e: 'updateName', name: string): void
// }>()
const changeName = () => {
// 执行
emit('updateName', '小龙龙')
}
</script>
父组件
vue
<template>
<child :name="state.name" @updateName="updateName" />
</template>
<script setup>
import { reactive } from 'vue'
// 引入子组件
import child from './child.vue'
const state = reactive({
name: '小白龙'
})
// 接收子组件触发的方法
const updateName = (name) => {
state.name = name
}
</script>
八、v-model
子组件
vue
<template>
<span @click="changeInfo">我叫{{ modelValue }},今年{{ age }}岁</span>
</template>
<script setup>
// import { defineEmits, defineProps } from 'vue'
// defineEmits和defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】
defineProps({
modelValue: String,
age: Number
})
const emit = defineEmits(['update:modelValue', 'update:age'])
const changeInfo = () => {
// 触发父组件值更新
emit('update:modelValue', '小龙龙')
emit('update:age', 30)
}
</script>
父组件
vue
<template>
// v-model:modelValue简写为v-model
// 可绑定多个v-model
<child v-model="state.name" v-model:age="state.age" />
</template>
<script setup>
import { reactive } from 'vue'
// 引入子组件
import child from './child.vue'
const state = reactive({
name: '小白龙',
age: 20
})
</script>
九、nextTick
vue
<script setup>
import { nextTick } from 'vue'
nextTick(() => {
// ...
})
</script>
十、子组件ref变量和defineExpose
- 在标准组件写法里,子组件的数据都是默认隐式暴露给父组件的,但在 script-setup 模式下,所有数据只是默认 return 给 template 使用,不会暴露到组件外,所以父组件是无法直接通过挂载 ref 变量获取子组件的数据。
- 如果要调用子组件的数据,需要先在子组件显示的暴露出来,才能够正确的拿到,这个操作,就是由 defineExpose 来完成。
子组件
vue
<template>
<span>{{ state.name }}</span>
</template>
<script setup>
import { reactive, toRefs } from 'vue'
// defineExpose无需引入
// import { defineExpose, reactive, toRefs } from 'vue'
// 声明state
const state = reactive({
name: 'Jerry'
})
// 将方法、变量暴露给父组件使用,父组件才可通过ref API拿到子组件暴露的数据
defineExpose({
// 解构state
...toRefs(state),
// 声明方法
changeName() {
state.name = '小白龙'
}
})
</script>
父组件
vue
<template>
<child ref="childRef" />
</template>
<script setup>
import { ref, nextTick } from 'vue'
// 引入子组件
import child from './child.vue'
// 子组件ref
const childRef = ref('childRef')
// nextTick
nextTick(() => {
// 获取子组件name
console.log(childRef.value.name)
// 执行子组件方法
childRef.value.changeName()
})
</script>
十、插槽slot
子组件
vue
<template>
// 匿名插槽
<slot/>
// 具名插槽
<slot name='title'/>
// 作用域插槽
<slot name="footer" :scope="state" />
</template>
<script setup>
import { useSlots, reactive } from 'vue'
const state = reactive({
name: '小白龙',
age: '18岁'
})
const slots = useSlots()
// 匿名插槽使用情况
const defaultSlot = reactive(slots.default && slots.default().length)
console.log(defaultSlot) // 1
// 具名插槽使用情况
const titleSlot = reactive(slots.title && slots.title().length)
console.log(titleSlot) // 3
</script>
父组件
vue
<template>
<child>
// 匿名插槽
<span>我是默认插槽</span>
// 具名插槽
<template #title>
<h1>我是具名插槽</h1>
<h1>我是具名插槽</h1>
<h1>我是具名插槽</h1>
</template>
// 作用域插槽
<template #footer="{ scope }">
<footer>作用域插槽——姓名:{{ scope.name }},年龄{{ scope.age }}</footer>
</template>
</child>
</template>
<script setup>
// 引入子组件
import child from './child.vue'
</script>
将某个.vue组件挂载到根节点
vue
<teleport to="#app">
<ChannelProgress :show="isShow" @updateShow="updateShow" />
</teleport>
KeepAlive
是一个内置组件,允许我们在多个组件之间动态切换时,有条件地缓存组件实例。
js
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
属性
- include 排查
include="a,b"
:include="/a|b/"
:include="['a', 'b']"
- max 最大缓存实例数
:max="10"
- 缓存实例的生命周期
js
//创建
activated() {
},
//销毁
deactivated() {
// called when removed from the DOM into the cache
// and also when unmounted
}
vue全局方法/变量(不推荐这样子使用)
只要创建一个createApp(App),然后用app变量接受就好
main.ts
ts
const app = createApp(App);
//全局方法
app.config.globalProperties.$name1 = '小白龙';
使用
ts
const { proxy } = getCurrentInstance() as any;
console.log(111, proxy.$name1);//小白龙
十二、路由useRoute和useRouter
vue
<script setup>
import { useRoute, useRouter } from 'vue-router'
// 必须先声明调用
const route = useRoute()
const router = useRouter()
// 路由信息
console.log(route.query)
// 路由跳转
router.push('/newPage')
</script>
十三、路由导航守卫
vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 添加一个导航守卫,在当前组件将要离开时触发。
onBeforeRouteLeave((to, from, next) => {
next()
})
// 添加一个导航守卫,在当前组件更新时触发。
// 在当前路由改变,但是该组件被复用时调用。
onBeforeRouteUpdate((to, from, next) => {
next()
})
</script>
十四、store
*Vue3 中的Vuex不再提供辅助函数写法
vue
<script setup>
import { useStore } from 'vuex'
import { key } from '../store/index'
// 必须先声明调用
const store = useStore(key)
// 获取Vuex的state
store.state.xxx
// 触发mutations的方法
store.commit('fnName')
// 触发actions的方法
store.dispatch('fnName')
// 获取Getters
store.getters.xxx
</script>
十五、生命周期
通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
下表包含如何在 Option API 和 setup() 内部调用生命周期钩子
Option API | setup中 |
---|---|
beforeCreate | 不需要 |
created | 不需要 |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
十六、CSS变量注入
vue
<template>
<span>Jerry</span>
</template>
<script setup>
import { reactive } from 'vue'
const state = reactive({
color: 'red'
})
</script>
<style scoped>
span {
// 使用v-bind绑定state中的变量
color: v-bind('state.color');
}
</style>
十七、原型绑定与组件内使用
main.js
js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// 获取原型
const prototype = app.config.globalProperties
// 绑定参数
prototype.name = 'Jerry'
组件内使用
vue
<script setup>
import { getCurrentInstance } from 'vue'
// 获取原型
const { proxy } = getCurrentInstance()
// 输出
console.log(proxy.name)
</script>
十八、对 await 的支持
不必再配合 async 就可以直接使用 await 了,这种情况下,组件的 setup 会自动变成 async setup 。
vue
<script setup>
const post = await fetch('/api').then(() => {})
</script>
十九、定义组件的name
用单独的<script>
块来定义
vue
<script>
export default {
name: 'ComponentName',
}
</script>
二十、provide和inject
父组件
vue
<template>
<child/>
</template>
<script setup>
import { provide } from 'vue'
import { ref, watch } from 'vue'
// 引入子组件
import child from './child.vue'
let name = ref('Jerry')
// 声明provide
provide('provideState', {
name,
changeName: () => {
name.value = 'Tom'
}
})
// 监听name改变
watch(name, () => {
console.log(`name变成了${name}`)
setTimeout(() => {
console.log(name.value) // Tom
}, 1000)
})
</script>
子组件
vue
<script setup>
import { inject } from 'vue'
// 注入
const provideState = inject('provideState')
// 子组件触发name改变
provideState.changeName()
</script>
二十一、Vue3中使用echarts
js
// 安装
npm i echarts --save
// 组件内引入
import * as echarts from 'echarts'
自定义指令实现按钮防抖
监听按钮的点击事件,给点击事件设置防抖,如果特定时间段内多次提交,则每以最后一次重新计算时间。
代码实现
src/directives/preReClick.ts
js
export default (app) => {
app.directive('preReClick', {
mounted(el, binding) {
el.addEventListener('click', () => {
if (!el.disabled) {
el.disabled = true;
setTimeout(() => {
el.disabled = false;
}, binding.value || 2000)
}
})
}
})
}
在main.js中引入指令文件
language
import preReClick from '@/directives/preReClick';
const app = createApp(App);
app.use(ElementPlus).use(preReClick).mount('#app');
组件中使用
language
<button @click="confirm" v-preReClick>点我点我</button>
全局消息弹框封装
Tips.vue
language
<template>
<div class="tips">
<div class="box" :class="type">
<svg class="iconpark-icon"><use :href="icontype"></use></svg>
<span>{{ title }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, computed } from 'vue';
interface Props {
type: string;
title: string;
}
const props: Props = defineProps({
type: {
type: String,
default: 'success'
},
title: {
type: String,
default: ''
}
});
const icontype = computed(() => {
const type = {
success: '#gou',
warning: '#zu2141',
error: '#4',
info: '#gou',
hint: 'icon-tishi'
};
return type[props.type];
});
</script>
<style lang="scss" scoped>
.tips {
z-index: 9999;
position: fixed;
top: 100px;
left: 50%;
transform: translateX(-50%);
animation: tipsTop 1s;
overflow: hidden;
box-shadow: 0 1px 20px 0 rgba(153, 153, 153, 0.5);
.box {
width: fit-content;
height: 80px;
display: flex;
align-items: center;
background-color: #ffffff;
box-shadow: 0 1px 20px 0 rgba(153, 153, 153, 0.5);
border-radius: 4px;
padding: 0 42px;
.iconpark-icon {
width: 30px;
height: 30px;
margin-right: 10px;
}
span {
max-width: 1000px;
font-size: 18px;
font-weight: normal;
font-stretch: normal;
letter-spacing: 2px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.success {
color: #0173cd;
}
.warning {
color: #ff9800;
}
.error {
color: #c73741;
}
.info {
color: #777777;
}
.hint {
color: #fd4d4d;
}
}
// 动画
@keyframes tipsTop {
0% {
top: -10px;
}
100% {
top: 100px;
}
}
</style>
Tips.ts
ts
import { createVNode, render } from 'vue';
import Tips from './Tips.vue';
const div = document.createElement('div');
div.setAttribute('class', 'tips');
document.body.appendChild(div);
let timer: number;
export default ({ type, title }: { type: string; title: string }) => {
const vnode = createVNode(Tips, { type, title });
render(vnode, div);
timer && clearTimeout(timer);
timer = setTimeout(() => {
render(null, div);
}, 2000);
};
使用
language
import Tips from '@/components/Tips/Tips';
Tips({ type: 'success', title: '新建素材成功' });
目录