参考视频:https://www.bilibili.com/video/BV1484y1m7av/?spm_id_from=333.1007.tianma.16-1-59.click&vd_source=5c584bd3b474d579d0bbbffdf0437c70
1. 脚手架创建项目的区别:
- vue2: vue init webpack 项目名称
- vue3: vue create 项目名称 或者vue3一般与vite结合使用:
npm create vite@latest
yarn create vite
2. template中结构
- vue2: template下只有一个元素节点
<template>
<div>
<div></div>
<div></div>
</div>
</template>
- vue3:template下可以有多个元素节点
<template>
<div></div>
<div></div>
<div></div>
</template>
3. main.js入口文件挂载App.vue文件
- vue2:
new Vue({
app: '#app',
router,
...
})
- vue3:使用createApp
import App from './App.vue'
import router from '../src/router/index.js'
createApp(App).use(router).mount('#app')
// const app = createApp(App)
// app.use(router) // 挂载路由
// app.mount('#app')
4. router的区别
- vue2:
在这里插入代码片
- vue3: 使用createRouter,使用createRouter必须要写history
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
// history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
component: () => import('@/App.vue'),
},
{
path: '/props_pre',
component: () => import('@/components/props_test/PropsTest.vue'),
},
]
})
export default router
5. setup的使用
- vue2: 引入子组件需要使用compontents{}挂载
import xx from '..子组件...'
<script>
export default {
components: { xx }
}
</script>
- vue3:
引入子组件:
<template>
<xx />
</template>
<script setup>
import xx from '..子组件...'; // 引入子组件不需要再compontents{}挂载,在template中直接使用即可
// 使用setup就不能使用export default{}导出了
</script>
如果需要使用export default则:
<script>
export default {
setup() {
...
},
}
</script>
6. props的使用
- vue2
子组件中:
export default{
props: {
xx: {
type: String/Number/Object/Array,
default: ''/0/()=>{}/()=()=>{}
}
},
// props:['xx1', 'xx2']
}
- vue3: 如果报错 ‘defineProps’ is not defined则在package.json文件eslintConfig{env: {‘vue/setup-compiler-macros’: true }}中添加’vue/setup-compiler-macros’: true
defineProps(['xx1', 'xx2'])不需要引入直接使用,返回一个对象,数组或者对象写法都可
子组件中使用 let props = defineProps(['xx1', 'xx2']), // 在template中props可以省略{{props.xx1}} => {{xx1}},props名字可以随便取
<template>
<span>{{props.name}}</span> // 可以省略props,直接使用name <span>{{name}}</span>
</template>
<script setup>
let props = defineProps(['name', 'age']);
</script>
7. ref的使用
- vue2:
<template>
<Child ref="child1" />
</template>
// 使用
<script>
export default {
mouted() {
console.log(this.$refs.child1); // 获取child1的ref有关数据
}
}
</script>
- vue3: 需要先从vue中引入 ref, vue3中不可使用this
<template>
<Child :money = money /> // 子组件也是用defineProps(['money'])接收
</template>
<script setup>
import { ref } from 'vue'
let money = ref(10000)
</script>
8. 自定义事件
下面这个Event1组件上的@click:
- 在vue2框架当中,这种写法是自定义事件,可以通过.native修饰符变为原生DOM事件
- 在vue3框架当中,这种写法就是原生DOM事件,绑定自定义事件为<Event1 @xxx=“handlexxx” />
<template>
<Event1 @click="" />
</template>
自定义事件父子组件传值:
<Event2 @updateList=“handlexxx” />
- vue2中子组件用this.$emit(‘updateList’, 参数1, 参数2)调用
- vue3使用setup组合式APIZ没有实例不能用this.$emit,vue3中使用defineEmits方法返回函数触发自定义事件
<script setup>
// 利用defineEmits方法返回函数触发自定义事件
// defineEmits方法不需要引入直接使用
let $emit = defineEmits(['updateList']);
const handleUpdateList = () => {
$emit('updateList', 参数1, 参数2);
}
</script>
注意:
<template>
<Event2 @updateList="handle1" @click="handle2" />
</template>
<script setup>
const handle1 = (参数1, 参数2) => {
...
}
// click原生dom事件,点击就会执行
const handle2 = (参数1, 参数2) => { // 子组件调用了父组件的click类型的方法,需要接收参数, 没调用传参时不用接收参数
// alert(123) // 这是之前DOM事件时,点击就会执行
...
}
</script>
Event2子组件中
<template>
<button @click="$emit('click', 参数1, 参数2)">子组件调用父组件click自定义事件</button>
<template>
<script setup>
let $emit = defineEmits(['updateList']); // 此时在父组件点击子组件会执行alert(123)
let $emit = defineEmits(['updateList', 'click']); // 此时在父组件点击子组件不会执行alert(123),这里把click类型的DOM事件变成了自定义事件
...
$emit('updateList', 参数1, 参数2)
<script>
9. 组件通信方式之全局事件总线
- vue2:兄弟组件通信 b u s ( bus( bus(on, $emit)
- vue3:没有实例,没有this,所以不能使用$bus,使用插件mitt
1.安装mitt
npm i --save mitt
2.新建bus/index.js文件
mitt是一个方法(),方法执行会返回bus对象
import mitt from 'mitt'; // 引入mitt插件
const $bus = mitt(); // 名字随意取
export default $bus;
3.使用
在父组件中使用两个子组件
eventBus.vue中
<template>
<div class="box">
<h1>全局事件总线$bus</h1>
<div class="dis-flex">
<ChildBus1 />
<ChildBus2 />
</div>
</div>
</template>
<script setup>
import ChildBus1 from "./ChildBus1.vue";
import ChildBus2 from "./ChildBus2.vue";
</script>
在ChildBus1子组件中监听接收
<template>
<div class="child1">
<h3>我是子组件1: 曹植</h3>
</div>
</template>
<script setup>
import $bus from '../../bus/index.ts';
// 组合式API函数
import { onMounted } from "vue";
// 当组件挂载完毕时,当前组件绑定一个事件,接收将来兄弟组件传递的数据
onMounted(() => {
// 第一个参数:即为事件类型,第二个参数:即为事件回调
$bus.on('car', (car) => {
console.log(car, '在回调函数内可以做这个组件相应的操作');
})
})
</script>
在ChildBus2子组件中发送
<template>
<div class="child2">
<h2>我是子组件2:曹丕</h2>
<button @click="handleClick">点击我给我的兄弟送火箭</button>
</div>
</template>
<script setup>
// 引入$bus对象
import $bus from '../../bus/index.ts';
// 点击按钮回调
const handleClick = () => {
$bus.emit('car', {car: '火箭'});
}
</script>
10. 组件通信方式之v-model
- vue2:
- vue3: 使用v-model, v-model不仅可以用来收集表单数据,实现数据双向绑定还可以用来父子组件之间通信
<template>
<input type="text" v-model="name" />
</template>
<script setup>
// v-model指令:收集表单数据,数据双向绑定
// v-model也可以实现组件之间的通信,实现父子组件数据同步的业务
import { ref } from 'vue';
let name = ref('');
</script>
在vue的3.3版本中新加了defineModel,但是跟ref一样需要引入
import { defineModel } from 'vue';
let xx = defineModel();
v-model组件身上使用
<Child :modelValue="money" @update:moneyModel="handleMoney" /> 等价与
/**
v-model组件身上使用
第一:相当于给子组件传递props['modelValue'], 名字一定要叫modelValue
第二:相当于给子组件绑定自定义事件update:modelValue
*/
<Child v-model="money" />
// 如果是v-model:money="money"这样的话,子组件传递props['money'], 名字不一定要叫modelValue了
<Child v-model:money="money" /> // 跟多个v-model一样的,然后这样的不用再绑定自定义事件,$emit那边就能更改
父组件
<template>
<!-- <ChildModel1 :modelValue="money" @update:modelValue="handleUpdate" /> -->
<ChildModel1 v-model="money" /> // 这句就相当于上面那行代码,用了这行代码可能会有报错,handleUpdate声明了却没使用,页面上叉掉报错是能正常增加的
</template>
<script setup>
import ChildModel1 from './ChildModel1.vue';
let money = ref(100);
const handleUpdate = (num) => {
money.value = num;
}
</script>
子组件
<template>
<div>父组件的钱:{{modelValue}}</div>
<button @click="handleClick">点击更改父组件钱</button>
</template>
<script setup>
const props = defineProps(['modelValue']);
const $emit = defineEmits(['update:modelValue']);
const handleClick = () => {
$emit('update:modelValue', props.modelValue + 1);
}
</script>
设置多个v-model
<template>
<Child v-model:pageNo="pageNo" v-model:pageSize="pageSize" />
</template>
<script setup>
import { ref } from 'vue';
let pageNo = ref(1);
let pageSize = ref(10);
</script>
11. 组件通信方式之useAttrs方法
vue3框架提供一个方法useAttrs方法,它可以获取组件身上的属性与事件
- 父组件中调用子组件并传参:
<template>
<HintButton type="primy" :size="small" :icon="edit" />
</template>
<script setup>
import HintButton from './HintButton.vue';
</script>
- 在HintButton子组件中:
<template>
// 使用$attrs
<el-button :type="$attrs.type"></el-button>
// 还可以使用语法
<el-button :="$attrs"></el-button> // 这种写法相当于<h1 v-bind="{a: 1,b: 2}" /> => 可以简写为<h1 :="{a: 1,b: 2}" />
</template>
<script setup>
// 1.这样可以获取子组件上的参数
let props = defineProps(['type', 'size', 'icon']);
// 2.引入useAttrs方法:获取组件标签身上的属性与事件, 此方法执行会返回一个对象
import { useAttrs } from 'vue';
let $attrs = useAttrs();
</script>
注意:使用defineProps接收过的参数,useAttrs()就接收不到了,defineProps(['xxx'])的优先级更高,useAttrs方法不仅能接收到父组件传过来的参数,还能接收事件(DOM原生click事件和自定义事件),比如<HintButton type="primy" :size="small" :icon="edit" @click="handle1" @handleUpdate="handle2" />
12. 组件通信方式之ref与$parent
ref: 可以获取真实的DOM节点,可以获取到子组件实例VC
$parent:可以在子组件内部获取到父组件的实例
获取子组件的实例,ref需要与组件名称同名: 比如
<Son ref="son" /> // ref的名称需要与ref获取的名字一样 比如<Son ref="account" /> 下面获取时需要名字相同 let account = ref();
import { ref } from 'vue';
import Son from "./refSon.vue";
let son = ref();
console.log(son.value);
这样能拿到子组件实例,但拿不到子组件内部的数据,因为:
组件内部数据对外关闭的,别人不能访问,如果想让外部访问需要通过defineExpose方法对外暴露,这个方法不仅能暴露属性还能暴露方法
$parent,子组件必须使用这个名称可以拿到父组件的实例,父组件的属性也需要暴露出去
- 父组件代码
<template>
<div class="box">
父组件的钱:{{money}}
<button @click="handler">父组件加10</button>
<Son ref="son" />
<Daughter />
</div>
</template>
<script setup>
import Son from "./refSon.vue";
import Daughter from "./refDaughter.vue";
import { ref } from "vue";
let money = ref(10000);
let son = ref();
const handler = () => {
money.value += 10;
// 儿子减10
son.value.money -= 10;
son.value.flyFun();
}
defineExpose({
money // 暴露出去给daughter.vue组件使用
})
</script>
- son组件代码:
<template>
<div class="son">儿子组件钱:{{money}}</div>
</template>
<script setup>
import { ref } from "vue";
let money = ref(666);
// 组件内部数据对外关闭的,别人不能访问
// 如果想让外部访问需要通过defineExpose方法对外暴露,这个方法不仅能暴露属性还能暴露方法
const flyFun = () => {
console.log('想飞咯');
}
defineExpose({
money,
flyFun
})
</script>
- daughter组件代码
<template>
<div>女孩的钱:{{money}}
<button @click="handler($parent)">点击+100</button> // 把父组件$parent实例作为参数传入函数中
</div>
</template>
<script setup>
import { ref } from "vue";
let money = ref(999);
const handler = ($parent) => {
money.value += 100;
$parent.money -= 100;
}
</script>
13.组件通信方式之Provide与Inject
vue3提供provide(提供)与inject(注入),可以实现隔辈组件传递数据
在爷爷组件使用provide(传递),在孙子组件inject(获取);
- 爷爷组件:
<template>
<div class="box">provide: {{car}}
<ChildPage />
</div>
</template>
<script setup>
import ChildPage from "./childPage.vue";
import { ref, provide } from "vue";
let car = ref('豪车');
// vue3提供provide(提供)与infect(注入),可以实现隔辈组件传递数据
// 祖先组件给后代组件提供数据
// 两个参数:第一个参数就是提供的数据key
// 第二个参数:祖先组件提供数据
provide('TOKEN', car)
</script>
<style scoped>
.box {
height: 500px;
background: skyblue;
}
</style>
- 父亲组件ChildPage.vue:中间组件:只需要导入孙子组件就行了
<template>
<div class="child">子组件
<GrandChildPage />
</div>
</template>
<script setup>
import GrandChildPage from "./grandChildPage.vue";
</script>
<style scoped>
.child {
height: 300px;
background: pink;
}
</style>
- 孙子组件GrandChildPage.vue中使用inject()来获取,如果需要更改爷爷组件传递过来的属性值,直接修改 xx.value = ‘’即可。
<template>
<div class="box">孙子组件:{{car}}
<button @click="updateCar">更新数据</button>
</div>
</template>
<script setup>
import { inject } from "vue";
// 注入祖先组件提供数据
// 需要参数:即为祖先提供数据的key
let car = inject('TOKEN');
console.log(car);
const updateCar = () => {
car.value = '自行车'; // 更改爷爷组件的car属性值
}
</script>
<style scoped>
.box {
height: 100px;
background: salmon;
}
</style>
13.组件通信之pinia组合式API
使用pinia需要先安装piaia: npm i pinia -D;
在src目录下新建store目录,创建index.js文件作为大仓库,需要在main.js入口文件中引入index.js文件并用使用use挂载
/**
- vuex:集中式管理状态容器,可以实现任意组件之间的通信
- 核心概念:state, mutations, actions, getters, modules
*/
/**
- pinia:集中式管理状态容器,可以实现任意组件之间通信
- 核心概念:state, actions, getters
- pinia写法:选择器API,组合式API
*/ - 选择器API
main.js入口文件...........
import { createApp } from 'vue'
import App from './App.vue'
import router from '../src/router/index.js'
import store from "./store/index.ts";
createApp(App).use(router).use(store).mount('#app')
在store目录下创建modules目录用来装小仓库:
src
----store文件夹
-------modules文件夹用来存放小仓库
---------------info.js小仓库使用defineStore:import { defineStore } from “pinia”;
-------index.js文件用来存放大仓库, 大仓库使用createPinia:import { createPinia } from “pinia”;
修改store数据:
- 直接修改
import useInfoStore from '../store/modules/info.js';
let info = useInfoStore();
info.count ++ ; // 会改变store.count的值
- 使用$patch修改
infoStore.$patch({
count: 1111
})
- 直接调用仓库内的方法来修改值,可以传参
infoStore.updateNum();
infoStore.updateNum(66);
- 在info小仓库内:
// 创建小仓库
import { defineStore } from "pinia";
// 第一个参数: 小仓库名字, 第二个参数:小仓库配置对象
// defineStore方法会返回一个函数,函数作用就是让组件可以获取到仓库数据
let useInfoStore = defineStore('info', {
// 存储数据: state
state: () => {
return {
count: 99,
arr: [1,2,3,4,5,6,7,8,9]
}
},
actions: {
// 注意:函数没有context上下文对象,但是有this,this指向的是这个小仓库
updateNum() {
this.count++;
}
},
getters: {
total() { // 有点像computer,需要return返回值
let result = this.arr.reduce((pre, next) => {
return pre + next;
}, 0)
return result;
}
}
});
// 对外暴露方法
export default useInfoStore;
- 在组件中使用info小仓库
<template>
<div class="child1">
子组件1: {{infoStore.count}}------{{infoStore.total}}
<button @click="updateCount">点击我,修改store数据</button>
</div>
</template>
<script setup>
import useInfoStore from "../../store/modules/info.ts";
// 获取小仓库对象
let infoStore = useInfoStore();
const updateCount = () => {
// infoStore.count ++; // 这样是可以修改的
// infoStore.$patch({
// count: 1111
// }) // 这个方法也是可以修改的
// 仓库调用自身的方法去修改仓库数据
infoStore.updateNum();
}
</script>
- 组合式API
todo.js代码
// 定义组合式API仓库
import { defineStore } from "pinia";
import { ref, computed } from "vue";
// 创建小仓库
let useTodoStore = defineStore('todo', () => {
let todos = ref([
{
id: 1,
title: '吃饭'
},
{
id: 2,
title: '睡觉'
}
]);
// let arr = [1,2, 3, 4, 5];
let arr = ref([1, 2, 3, 4, 5]);
const total = computed(() => {
return arr.value.reduce((pre, next) => pre + next, 0);
})
// 务必要返回一个对象:属性与方法可以提供给组件使用
return {
todos,
arr,
total,
updateTodo(){
todos.value.push({
id: 3,
title: '打豆豆'
})
}
}
});
export default useTodoStore;
在组件中使用:
<template>
<p @click="updateTodo">{{todoStore.todos}}------{{todoStore.total}}</p>
</template>
<script setup>
import useTodoStore from "../../store/modules/todo.js";
let todoStore = useTodoStore();
// 点击p段落去修改仓库的数据
const updateTodo = () => {
todoStore.updateTodo();
}
</script>
14. 组件通信方式之插槽
默认插槽,具名插槽,作用域插槽
- 默认插槽:传递组件
内容
,接收组件
父组件内:
<template>
<Child>
<div>
<pre>想要在子组件slot插槽展现的内容</pre>
</div>
</Child>
</template>
<script setup>
import Child from './Child.vue';
</script>
子组件内:
<template>
<slot></slot>
</template>
- 具名插槽:传递组件使用或者<template #xxx>#号可以代替v-slot指令,简写。接收方使用
父组件内:
<template>
<Child>
<template v-slot:context>
<div>具名插槽内容</div>
</template>
// v-slot指令可以简写为#
<template #context>
<div>具名插槽内容</div>
</template>
</Child>
</template>
<script setup>
import Child from './Child.vue';
</script>
子组件内:
<template>
<slot name="context"></slot>
</template>
- 作用域插槽:就是可以传递数据的插槽,子组件可以将数据回传给父组件,父组件可以决定这些回传的数据是以何种结构或者外观在子组件内部去展示
父组件:
<template>
<div>
<TestPage1 :todos="todos">
<template v-slot="{$row, $index}">
<span :style="{color: $row.done ? 'green' : 'red'}">{{$row.title}}---{{$index}}</span>
</template>
</TestPage1>
</div>
</template>
<script setup>
import TestPage1 from "./testPage1.vue";
/**
* 插槽:默认插槽,具名插槽,作用域插槽
*/
import { ref } from "vue";
// todos数据
let todos = ref([
{id: 1, title: '吃饭', done: true},
{id: 2, title: '睡觉', done: false},
])
</script>
<template>
<div>
<h1>作用域插槽</h1>
<ul>
<li v-for="(item, index) in todos" :key="item.id">
<!-- 作用域插槽:可以将数据回传给父组件 -->
<slot :$row="item" :$index="index"></slot>
</li>
</ul>
</div>
</template>
<script setup>
// 通过props接收父组件传递数据
defineProps(['todos'])
</script>