个人使用React6年多了,vue使用还是大于6年前,因为公司项目需要使用vue,所以做个笔记。个人感觉虽然vue3引入的组合式写法更简单了,但是对于快速开发或者可维护性来说,没有选项式写法好,这点和React是一样的,class写法更好维护、更易于阅读、更易于复用,这点vue不明显,react非常明显,useEffect
如果多了、依赖多了,逻辑是非常难以维护的。
小结
- 组件库PC用 element-plus,移动端用vant
- 可以把逻辑写在多个script里
- vue3可以多个根标签,不需要
Fragment
vite-plugin-vue-setup-extend
可以指定组件的名字,组件名字默认就是文件名defineExpose
定义可以暴露的方法给父组件- 如果页面代码比较多,样式代码可抽离为单独文件引入
<style scoped lang="less"> @import "./index.less"; </style>
基本使用
<div
class="static"
:class="{ active: isActive, 'text-danger': hasError }"
></div>
<div :style="{ 'font-size': fontSize + 'px' }"></div>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
全局方法
const app = createApp(App)
app.config.globalProperties.$myGlobalMethod = function () {
return 'Hello World!'
}
this.$myGlobalMethod()
组件
<template>
<div id="demo1">
<div>{{title}}</div>
<child />
</div>
</template>
<script setup lang="ts">
import {defineProps} from "vue"
// 在vue3.2 中当我们导入子组件时,setup语法糖会自动去注册该组件,所以注册语句不需要写了
import child from "./children.vue"
// 接收父组件的props,可以传数组和对象
defineProps(["a"])
const data = defineProps({
title: {
type: String,
default: ""
}
})
// 拿到父组件的数据
console.log(data);
</script>
生命周期
<script lang="ts" setup name="Person">
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum() {
sum.value += 1
}
console.log('setup')
// 生命周期钩子
onBeforeMount(()=>{
console.log('挂载之前')
})
onMounted(()=>{
console.log('挂载完毕')
})
onBeforeUpdate(()=>{
console.log('更新之前')
})
onUpdated(()=>{
console.log('更新完毕')
})
onBeforeUnmount(()=>{
console.log('卸载之前')
})
onUnmounted(()=>{
console.log('卸载完毕')
})
</script>
事件
.prevent:.prevent用于阻止事件的默认行为,比如阻止表单提交时的页面刷新。 .stop:.stop用于停止事件冒泡,使得事件不向父级元素传播。 .once:.once表示事件只触发一次,之后就不再响应。 .capture:.capture指明事件应该在捕获阶段被触发,而不是在冒泡阶段。 .self:.self限定只有当事件在该元素本身(而不是子元素)触发时才执行。 .passive:.passive用于改善滚动性能,指明事件的默认行为不会影响到浏览器的滚动。
nextTick
在 Vue 中,当我们修改数据时,Vue 会自动更新视图。但是,由于 JavaScript 的事件循环机制,我们无法立即得知视图更新完成的时机。这时候,我们就需要使用 nextTick 来获取视图更新完成后的 DOM 状态。
自定义hook
import {ref,onMounted} from 'vue'
export default function(){
let sum = ref(0)
const increment = ()=>{
sum.value += 1
}
const decrement = ()=>{
sum.value -= 1
}
onMounted(()=>{
increment()
})
//向外部暴露数据
return {sum,increment,decrement}
}
动态组件渲染
import tab1 from "./tab1.vue"
import tab2 from "./tab2.vue"
export const tabList = [
{
title: "销售项目进展情况",
component: tab1,
},
{
title: "订单与选型情况",
component: tab2,
}
]
<van-collapse v-model="tab">
<van-collapse-item
v-for="(item, index) in tabList"
:title="item.title"
:key="index"
:name="index"
>
<component :is="item.component"></component>
</van-collapse-item>
</van-collapse>
插槽
// 默认插槽使用
<demo title="title1">
<span>haha</span>
</demo>
// 组件 demo.vue
<template>
<div>{{title}}</div>
<slot>默认内容</slot>
</template>
// 具名插槽使用
<template>
<div>
<demo title="title1" v-slot:s1 >
<span>content1</span>
</demo>
<demo title="title1">
// 推荐这么用
<template v-slot:s2 >
<span>content2</span>
</template>
// 也可以这么写啊
<template #s2 >
<span>content2</span>
</template>
</demo>
</div>
</template>
// demo.vue
<template>
<div>{{ title }}</div>
<slot name="s1"></slot>
<slot name="s2"></slot>
</template>
<template>
<div>
<demo title="title1">
<template v-slot:name1="params" >
<div v-for="a in params.data">{{ a }}</div>
</template>
</demo>
</div>
</template>
// demo.vue 插槽可以传递数据
<template>
<div>{{ title }}</div>
<slot :data="data" name="name1"></slot>
</template>
<script setup>
import {defineProps} from "vue"
const data = [1,2,3]
defineProps(['title'])
</script>
数据的跨层级传递
<script setup>
import { provide, ref } from 'vue'
const location = ref('North Pole')
function updateLocation() {
location.value = 'South Pole'
}
provide('location', {
location,
updateLocation
})
</script>
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'
const { location, updateLocation } = inject('location')
</script>
<template>
<button @click="updateLocation">{{ location }}</button>
</template>
setup
- setup里可以直接声明变量使用,而不用 ref
- setup返回值,可以返回一个函数来渲染内容
- setup写法和data、methods一起存在,data可以读取setup里的数据
ref
- ref在组件上就是组件实例,用在dom上就是获取dom
<div id="hua" ref="hua">huahuadavids</div>
const hua = ref()
console.log(hua.value)
reactive
和ref
的区别,前者在脚本中直接改变,不需要.value
,ref处理对象,其实也是reactive
reactive
修改整个对象,会失去响应式,使用object.assign 就可以- toRefs用来解构
reactive
const obj = reactive({name: "david", age: 22})
let {name,age } = toRefs(obj)
watch
- watch是可以解除监视
const stopWatch = watch(name, (nv, ov) => {
console.log(nv)
if(nv === "stop"){
stopWatch()
}
})
- watch一个 ref对象,整个对象都变了才行,如果想深度监视,第三个参数加true
const obj = ref({name: "david"})
watch(obj, (newValue, oldValue) => {
console.log(newValue)
}, {
// 深度监视
deep: true,
// 立即执行
immediate: true
})
- watch 监视 reactive默认开启深度
- watch 监视 getter函数,就是返回一个值的方法,最佳写法是写函数式,并且开启deep
const obj = reactive({name: "david", age: 8})
watch(() => obj.name, () => {
console.log("name 改变")
})
watchEffect
和 react的一样,但是不需要指定依赖
通信
Vue3组件通信和Vue2的区别:
移出事件总线,使用mitt代替。
vuex换成了pinia。
把.sync优化到了v-model里面了。
把
l
i
s
t
e
n
e
r
s
所有的东西,合并到
listeners所有的东西,合并到
listeners所有的东西,合并到attrs中了。
$children被砍掉了。
props通信
缺点是只能一层一层的传递
// 父组件
<template>
<Demo1 title="aaa" :getValue="getValue">
</Demo1>
</template>
<script lang="ts" setup>
import Demo1 from "./Demo1.vue"
function getValue(a){
console.log(a);
}
</script>
// 子组件
<template>
<button @click="getValue('child')">send to parent</button>
</template>
<script setup lang="ts">
import {defineProps} from "vue"
const data = defineProps({
getValue: {
type: Function
}
})
</script>
自定义事件通信
官方推荐肉串写法
//父组件
<template>
<div>
<h1>{{ title }}</h1>
<Demo1 @change-route="clickEven"/>
</div>
</template>
<script setup>
import Demo1 from "./Demo1.vue"
import {ref} from "vue"
const title = ref("parent-title")
const clickEven = (val) => {
title.value = val
}
</script>
// 子组件
<template>
<button @click=" emit('change-route', '1111')">2222</button>
</template>
<script setup>
import {defineEmits} from "vue";
const emit = defineEmits(['change-route'])
</script>
v-model
// 下边是等价的
<input v-model="a">
<input :value="a" @input="a = $event.target.value">
attr
$attr
用来获取父组件传了,但是子组件没有声明接受的数据。
// index1.vue
<template>
<div id="index1">
index1
<div>a = {{a}}</div>
<div>b = {{b}}</div>
<index2 v-bind="{a,b,c}"></index2>
<!--等价的 -->
<!-- <index2 :a="a" :b="b" :c="c" ></index2>-->
</div>
</template>
// index2.vue
<template>
<div id="index2">
index2
<div>a = {{a}}</div>
<div>b = {{b}}</div>
<index3 v-bind="$attrs"></index3>
</div>
</template>
// index3.vue
<template>
<div id="index3">
index3
<br>
a = {{a}}
</div>
</template>
<script setup>
import {defineProps} from "vue"
defineProps(['a'])
</script>
$refs
获取所有子组件的实例对象,首先子组件得有ref才行。
vue-router
// 1. 简单使用
import {createRouter,createWebHistory} from 'vue-router'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import About from '@/pages/About.vue'
const router = createRouter({
history:createWebHistory(),
routes:[
{
path:'/home',
component:Home
},
{
path:'/about',
component:About
}
]
})
export default router
import router from './router/index'
app.use(router)
app.mount('#app')
// 2. link
<router-link active-class="active" to="/home">主页</router-link>
<router-link active-class="active" :to="{path:'/home'}">Home</router-link>
// 3. api使用
import {useRouter, useRoute} from "vue-router";
const router = useRouter();
router.replace('/processControl');
const route = useRoute()
// 打印query参数
console.log(route.query)
Teleport
<teleport to='body' >
<div class="modal" v-show="isShow">
<h2>我是一个弹窗</h2>
<p>我是弹窗中的一些内容</p>
<button @click="isShow = false">关闭弹窗</button>
</div>
</teleport>
Suspense
import { defineAsyncComponent,Suspense } from "vue";
const Child = defineAsyncComponent(()=>import('./Child.vue'))
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中.......</h3>
</template>
</Suspense>
</div>
</template>
tsx写法
<script lang="tsx">
import {defineComponent} from 'vue';
export default defineComponent({
props: {
params: {
type: Object,
default: () => {
}
}
},
data: () => {
return {
data: {
name: 'data-name'
}
}
},
methods: {
renderFooter() {
return (
<div>
<van-button>确定</van-button>
<van-button>取消</van-button>
</div>
)
},
setName(){
this.data.name = "set-name"
}
},
render() {
const buttons = this.renderFooter()
return (
<div class="demo">
<div>内容</div>
{buttons}
<div>
{this.data.name}
</div>
<van-button onClick={this.setName}></van-button>
</div>
)
}
})
</script>
<style scoped lang="less">
.demo {
color: red;
}
</style>
pinia
// index.ts
import { createPinia } from 'pinia';
import { createApp } from 'vue';
import App from './App.vue';
// 创建 Pinia 实例
const pinia = createPinia();
// 创建 Vue 应用
const app = createApp(App);
// 使用 Pinia 实例
app.use(pinia);
app.mount('#app');
// stores/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
},
});
// components/Counter.vue
import { useCounterStore } from '../stores/counter';
export default {
setup() {
const counter = useCounterStore();
return {
count: counter.count,
increment: counter.increment,
};
},
};