目录
一、初识vue3.0
现在主流组件库都已经发布了支持vue3.0的版本,其他生态也在不断地完善中,这是趋势。
- element-plus (opens new window) 基于 Vue 3.0 的桌面端组件库
- vant (opens new window) vant3.0版本,有赞前端团队开源移动端组件库
- ant-design-vue (opens new window) Ant Design Vue 2.0版本,社区根据蚂蚁 ant design 开发
1.1 vue3.0 的优点
- 最火框架,它是国内最火的前端框架之一,官方文档 (opens new window)中文文档(opens new window)
- 性能提升,运行速度事vue2.x的1.5倍左右
- 体积更小,按需编译体积比vue2.x要更小
- 类型推断,更好的支持Ts(typescript)这个也是趋势
- 高级给予,暴露了更底层的API和提供更先进的内置组件
- ★组合API (composition api) ,能够更好的组织逻辑,封装逻辑,复用逻辑
- 重写了虚拟Dom的实现(且保证了兼容性,脱离模版的渲染需求旺盛)
- 编译模板的优化
- 更高效的组件初始化
- update性能提高1.3~2倍
- SSR速度提高了2~3倍
- 可以将无用模块“剪辑”,仅打包需要的(比如v-model,
<transition>
,用不到就不会打包)。 - 一个简单“HelloWorld”大小仅为:13.5kb
- 包含运行时完整功能:22.5kb,拥有更多的功能,却比Vue 2更迷你。 很多时候,我们并不需要 vue提供的所有功能,在 vue 2 并没有方式排除掉,但是 3.0 都可能做成了按需引入
- ......
1.2 vite 的基本使用
vite是什么:
官方文档(opens new window)https://cn.vitejs.dev/
- 它是一个更加轻量(热更新速度快,打包构建速度快)的vue项目脚手架工具
- 相对于vue-cli它默认安装的插件非常少,随着开发过程依赖增多,需要自己额外配置
- 所以: 在单纯学习vue3语法会使用它,后面做项目的时候我们还是使用vue-cli
vite基本使用:
- 创建项目
npm init vite-app 项目名称
或者yarn create vite-app 项目名称
- 安装依赖
npm i
或者yarn
- 启动项目
npm run dev
或者yarn dev
总结: vite是什么?
- 使用vite创建项目学习vue3语法,使用vue-cli创建项目正式开发。
1.3 如何创建一个Vue3.0的项目
基本步骤:
- 在main.js中导入createApp函数
- 定义App.vue组件,导入main.js
- 使用createApp函数基于App.vue组件创建应用实例
- 挂载至index.html的#app容器
1.安装最新版本的Vue-cli工具,最新版本工具已经提供Vue3-preview
npm install -g @vue/cli
# OR
yarn global add @vue/cli
2.创建项目选择
3.根实例初始化:
在2.x中,通过new Vue()
的方法来初始化
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
在3.x中Vue不再是一个构造函数,通过createApp方法初始化
createApp(App).use(store).use(router).mount('#app')
二、组合API
2.1 什么是选项API 和组合PAI
选项API:
在 Vue2.x 中使用的都是选项API,比如要定义数据就应该在 data 选项下、要定义方法就应该在 methods 选项下、要定义计算属性就应该在 computed 选项下...,这其实就是选项API,也叫 Options API
优点:易于学习和使用,写代码的位置已经约定好
缺点:代码组织性差,相似的逻辑代码不便于复用,逻辑复杂代码多了不好阅读
补充:虽然提供mixins用来封装逻辑,但是出现数据函数覆盖的概率很大,不好维护
组合API:
在 Vue3.x 中使用的就是组合API
代码风格:一个功能逻辑的代码组织在一起(包含数据,函数...)
优点:功能逻辑复杂繁多情况下,各个功能逻辑代码组织再一起,便于阅读和维护
缺点:需要有良好的代码组织能力和拆分逻辑能力
补充:vue3.0也支持vue2.x选项API写法
2.2 setup 函数
使用细节:
setup
是一个新的组件选项,作为组件中使用组合API的起点
从组件生命周期来看,它的执行在组件实例创建之前(vue2.x的beforeCreate
)执行
这就意味着在setup
函数中 this
还不是组件实例,this
此时是 undefined,因此在setup中不能使用this
在模版中需要使用的数据和函数,需要在 setup
返回
App.vue,
<template>
<div class="container">
<h3>{{msg}}</h3>
<button @click="say">点击</button>
</div>
</template>
<script>
export default {
name: 'App',
// 组合API的起点
setup () {
console.log(this); // undefined
// 数据
const msg = 'hello vue3'
// 函数
const say = () => {
console.log('hi, i am vue3.0');
}
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { msg, say }
}
}
</script>
2.3 生命周期函数
回顾vue2.x生命周期钩子函数:
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
认识vue3.0生命周期钩子函数:
setup
创建实例前onBeforeMount
挂载DOM前onMounted
挂载DOM后onBeforeUpdate
更新组件前onUpdated
更新组件后onBeforeUnmount
卸载销毁前onUnmounted
卸载销毁后
App.vue,
<template>
<div class="container">
根组件
</div>
</template>
<script>
import { onBeforeMount, onMounted } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
// 1. DOM 渲染前的钩子函数
onBeforeMount(() => {
console.log('DOM 渲染前的钩子函数', document.querySelector('.container'));
})
// 2. DOM 渲染后的钩子函数
onMounted(() => {
console.log('DOM 渲染后的钩子函数1', document.querySelector('.container'));
})
// 可以定义多个相同的钩子函数,从而去实现不同的逻辑
onMounted(() => {
console.log('DOM 渲染后的钩子函数2', document.querySelector('.container'));
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { }
}
}
</script>
总结: 组合API的生命周期钩子有7个,可以多次使用同一个钩子,执行顺序和书写顺序相同
2.4 reactive 函数
定义响应式数据
reactive是一个函数,它可以定义一个复杂数据类型(对象、数组),成为响应式数据
总结: 通常是用来定义响应式对象数据
App.vue,
<template>
<div class="container">
<p>{{obj.name}}</p>
<p>{{obj.age}}</p>
<button @click="updatedName">修改名字</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
const obj = reactive({
name: 'ls',
age: 18
})
// 修改名字
const updatedName = () => {
obj.name = 'zs'
}
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { obj, updatedName }
}
}
</script>
2.5 toRef 函数
定义响应式数据
toRef是函数,转换响应式对象中某个属性为单独响应式数据,并且值是关联的
使用场景:有一个响应式对象数据,但是模版中只需要使用其中一项数据
App.vue,
<template>
<div class="container">
<p>{{obj.name}}</p>
<p>{{obj.age}}</p>
<button @click="updatedName">修改名字</button>
</div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
// 响应式数据对象
const obj = reactive({
name: 'ls',
age: 18
})
// 2.模板中只想使用 name 属性
// 注意:从响应式数据对象中解构出来的属性数据,不再是响应式数据
// let { name } = obj // 解构出来的是一个普通数据,并不是响应式的数据
const name = toRef(obj, 'name')
const updatedName = () => {
// toRef 转换响应式数据包装成对象,value 是存放值的位置
name.value = 'zs'
}
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { obj, updatedName }
}
}
</script>
2.6 toRefs 函数
定义响应式数据
toRefs是函数,转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的
使用场景:剥离响应式对象(解构|展开),想使用响应式对象中的多个或者所有属性做为响应式数据
App.vue,
<template>
<div class="container">
<p>{{name}}</p>
<p>{{age}}</p>
<button @click="updateName">修改数据</button>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
// 响应式数据对象
const obj = reactive({
name: 'ls',
age: 18
})
// 解构或者展开响应式数据对象,后不再是响应式数据
// const { name, age} = obj
// const obj2 = {...obj}
const obj2 = toRefs(obj)
console.log(obj2);
const updateName = () => {
// obj2.name.value = 'zs' // 或如下写法
obj.name = 'zs'
}
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { ...obj2, updateName }
}
}
</script>
2.7 ref 函数
定义响应式数据
ref函数,常用于将简单数据类型定义为响应式数据,但是也可以定义复杂数据类型的响应式数据
- 在修改值,获取值的时候,需要.value
- 在模板中使用ref申明的响应式数据,可以省略.value
当得到的数据类型未知时,可以用 ref
const data = ref(null)
setTimeout(() => {
data.value = res.data
}, 1000)
使用场景:
- 当你明确知道需要的是一个响应式数据 对象 那么就使用 reactive 即可
- 其他情况使用ref
App.vue,
<template>
<div class="container">
<p>{{name}}</p>
<p>{{age}}</p>
<button @click="updateName">修改数据</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
const name = ref('ls')
const age = ref(20)
const updateName = () => {
name.value = 'zs'
}
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { name, age, updateName }
}
}
</script>
2.8 demo
App.vue,
<template>
<div class="container">
<p>x:{{x}}</p>
<p>y:{{y}}</p>
<hr>
<p>{{count}}</p>
<button @click="add">+1</button>
</div>
</template>
<script>
import { onMounted, onUnmounted, reactive, toRefs, ref } from 'vue'
const useMouse = () => {
// 1.记录鼠标坐标
// 1.1
const mouse = reactive({
x: 0,
y: 0
})
// 1.3 修改响应式数据
const move = (e) => {
mouse.x = e.pageX
mouse.y = e.pageY
}
// 1.2 等 DOM 渲染完毕,去监听事件
onMounted (() => {
document.addEventListener('mousemove', move)
})
// 1.4 组件销毁时,删除事件
onUnmounted(() => {
document.removeEventListener('mousemove')
})
return mouse
}
export default {
name: 'App',
// 组合API的起点
setup () {
const mouse = useMouse()
// 2.累加
const count = ref(0)
const add = () => {
count.value ++
}
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { ...toRefs(mouse), count, add }
}
}
</script>
2.9 computed 函数
定义计算属性:当需要依赖现有的响应式数据,根据一定逻辑得到一个新的数据
computed函数,是用来定义计算属性的,计算属性不能修改
目的:让计算属性支持双向数据绑定
总结:计算属性两种用法:
- 给computed传入函数,返回值就是计算属性的值
- 给computed传入对象,get获取计算属性的值,set监听计算属性改变
i. 基本用法:
App.vue,
<template>
<div class="container">
<p>今年:{{age}}岁</p>
<p>后年:{{newAge}}岁</p>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
const age = ref(18)
const newAge= computed(() => {
// 得到后年的值
// 该函数的返回值就是计算属性的值
return age.value + 2
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { age, newAge }
}
}
</script>
ii. 高级用法:
App.vue,
<template>
<div class="container">
<p>今年:{{age}}岁</p>
<p>后年:{{newAge}}岁</p>
<hr>
<p>使用 v-model 绑定计算属性</p>
<input type="text" v-model="newAge">
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
const age = ref(18)
// 2.计算属性高级用法
const newAge = computed({
// get 获取计算属性的值
get(){
return age.value + 2
},
// set 给计算属性设置值
set(value){
age.value = value - 2
}
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { age, newAge }
}
}
</script>
2.10 watch 函数
定义计算属性
watch函数,是用来定义侦听器的:
- 监听ref定义的响应式数据
App.vue
<template>
<div class="container">
<p>count的值:{{count}}</p>
<button @click="add">修改数据</button>
</div>
</template>
<script>
import { ref, watch } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
const count = ref(8)
const add = () => {
count.value++
}
// 需要监听数据的变化
/**
* 第一个参数:需要监听的目标
* 第二个参数:改变后触发的函数
*/
watch(count, (newValue, oldValue) => {
console.log('newvalue' + newValue);
console.log('oldValue' + oldValue);
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { count, add }
}
}
</script>
- 监听多个响应式数据数据
App.vue
<template>
<div class="container">
<p>count的值:{{count}}</p>
<button @click="add">修改数据</button>
<hr>
<p>{{obj.name}}</p>
<p>{{obj.age}}</p>
<button @click="updataName">修改数据</button>
</div>
</template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
const count = ref(8)
const add = () => {
count.value++
}
const obj = reactive({
name: 'zs',
age: 15
})
const updataName = () => {
obj.name = 'ls'
}
// 监听多个数据的变化
/**
* 第一个参数:需要监听的目标
* 第二个参数:改变后触发的函数
*/
watch([count, obj], () => {
console.log('监听多个数据的改变');
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { count, add, obj, updataName }
}
}
</script>
- 监听reactive定义的响应式数据
App.vue
<template>
<div class="container">
<p>{{obj.name}}</p>
<p>{{obj.age}}</p>
<button @click="updataName">修改数据</button>
</div>
</template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
// 需要监听数据的变化
/**
* 第一个参数:需要监听的目标
* 第二个参数:改变后触发的函数
*/
const obj = reactive({
name: 'zs',
age: 15
})
const updataName = () => {
obj.name = 'ls'
}
// 监听 reactive 数据
watch(obj, (newValue, oldValue) => {
console.log('newvalue' + newValue);
console.log('oldValue' + oldValue);
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { obj, updataName }
}
}
</script>
- 监听reactive定义的响应式数据,某一个属性
需要写成函数返回该属性的方式才能监听到
App.vue
<template>
<div class="container">
<p>count的值:{{count}}</p>
<button @click="add">修改数据</button>
<hr>
<p>{{obj.name}}</p>
<p>{{obj.age}}</p>
<button @click="updataName">修改数据</button>
</div>
</template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
const count = ref(8)
const add = () => {
count.value++
}
const obj = reactive({
name: 'zs',
age: 15
})
const updataName = () => {
obj.name = 'ls'
}
// 只监听 obj 里的 name -- 监听对象中某一个属性的变化
/**
* 第一个参数:需要监听的目标
* 第二个参数:改变后触发的函数
*/
watch(() => obj.name, () => {
console.log('监听到了 name 的变化');
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { count, add, obj, updataName }
}
}
</script>
- 深度监听
- 默认执行
App.vue
<template>
<div class="container">
<p>count的值:{{count}}</p>
<button @click="add">修改数据</button>
<hr>
<p>{{obj.name}}</p>
<p>{{obj.age}}</p>
<button @click="updataName">修改数据</button>
<hr>
<p>{{obj.brand.name}}</p>
<button @click="updataBrandName">修改名字</button>
</div>
</template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
const count = ref(8)
const add = () => {
count.value++
}
const obj = reactive({
name: 'zs',
age: 15,
brand: {
id: 1,
name: 'lv'
}
})
const updataName = () => {
obj.name = 'ls'
}
const updataBrandName = () => {
obj.brand.name = 'll'
}
// 需要监听数据的变化
/**
* 第一个参数:需要监听的目标
* 第二个参数:改变后触发的函数
*/
watch(() => obj.brand, () => {
console.log('brand数据改变了');
},{
// 深度监听
deep: true,
// 默认执行
immediate: true
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { count, add, obj, updataName, updataBrandName }
}
}
</script>
2.11 ref 属性
获取DOM或者组件实例可以使用ref属性,写法和vue2.0需要区分开
i. 获取单个DOM或者组件:
App.vue,
<template>
<div class="container">
<!-- vue2.x 获取div元素:
1. 通过 ref 属性绑定该元素
<div ref="div">我是box</div>
再由 this.$refs.div 获取元素
-->
<!-- <div>我是box</div> -->
<!-- vue2.x 获取 v-for 遍历的多个元素
2. 通过 ref 属性绑定被遍历的元素
<li v-for="i in 4" :key="i" ref="li">{{i}}</li>
通过 this.$refs.li 获取所有遍历的元素
-->
<!-- <ul>
<li v-for="i in 4" :key="i" ref="li">{{i}}</li>
</ul> -->
<!-- 单个元素或组件 -->
<div ref="box">我是box</div>
<!-- 被遍历的元素或组件 -->
</div>
</template>
<script>
import { onMounted, ref } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
/**
* 步骤:
* 1.先定义一个空的响应式数据ref定义
* 2.在 setup 中返回该数据,然后在需要获取的 DOM 元素上通过 ref 绑定该数据
*/
const box = ref(null)
console.log(box);
onMounted(() => {
console.log(box.value);
})
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { box }
}
}
</script>
ii. 获取v-for遍历的DOM或者组件:
App.vue,
<template>
<div class="container">
<!-- vue2.x 获取div元素:
1. 通过 ref 属性绑定该元素
<div ref="div">我是box</div>
再由 this.$refs.div 获取元素
-->
<!-- <div>我是box</div> -->
<!-- vue2.x 获取 v-for 遍历的多个元素
2. 通过 ref 属性绑定被遍历的元素
<li v-for="i in 4" :key="i" ref="li">{{i}}</li>
通过 this.$refs.li 获取所有遍历的元素
-->
<!-- <ul>
<li v-for="i in 4" :key="i" ref="li">{{i}}</li>
</ul> -->
<!-- 被遍历的元素或组件 -->
<ul>
<li v-for="i in 4" :key="i" :ref="setDom">第{{i}}个li</li>
</ul>
</div>
</template>
<script>
import { onMounted, ref } from 'vue'
export default {
name: 'App',
// 组合API的起点
setup () {
/**
* 2.获取 v-for 遍历的元素或组件
* 步骤:
* 1.定义一个空数组,接收 所有的li
* 2.定义一个函数,往空数组里 push DOM
*/
const domList = []
const setDom = (el) => {
console.log(el);
domList.push(el)
}
console.log(domList);
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { box, domList, setDom }
}
}
</script>
总结:
-
单个元素:先申明ref响应式数据,返回给模版使用,通过ref绑定数据
-
遍历的元素:先定义一个空数组,定一个函数获取元素,返回给模版使用,通过ref绑定这个函数
- 有一个边界问题:组件更新的时候会重复的设置dom元素给数组:
// ref获取v-for遍历的DOM元素,需要在组件更新的时候重置接受dom元素的数组。 onBeforeUpdate(()=>{ list = [] })
2.12 父子通讯
i. 父传子:
App.vue
<template>
<div class="container">
<h3>父组件</h3>
<p>{{money}}</p>
<hr>
<Son :money="money"></Son>
</div>
</template>
<script>
import Son from './Son'
import { ref } from 'vue'
export default {
name: 'App',
components: {
Son
},
// 组合API的起点
setup () {
const money = ref(100)
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { money }
}
}
</script>
Son.vue
<template>
<div>
<h3>子组件</h3>
<p>{{money}}</p>
</div>
</template>
<script>
import { onMounted } from '@vue/runtime-core'
export default {
name: 'Son',
// 子组件接收父组件数据使用 props 即可
props: {
money: {
type: Number,
default: 0
}
},
/**
* 1.props:外部组件传递过来的内容,响应式的,当传入新的 prop 时,它将被更新
*
* 2.
*/
setup (props) {
// 若想在这里获取父组件的数据
onMounted(() => {
// console.log(this.money); // 报错
})
onMounted(() => {
console.log(props.money); // 100
})
console.log(props.money);
}
}
</script>
注意点:
不能使用 ES6 解构,它会消除 prop 的响应性;如果需要解构 prop,可以在 setup
函数中使用toRefs函数来完成此操作:例如,
// MyBook.vue
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
如果 title
是可选的 prop,则传入的 props
中可能没有 title
。在这种情况下,toRefs
将不会为 title
创建一个 ref 。你需要使用 toRef
替代它:例如,
// MyBook.vue
import { toRef } from 'vue'
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
ii. 子传父:
App.vue
<template>
<div class="container">
<h3>父组件</h3>
<p>{{money}}</p>
<hr>
<Son :money="money" @changeMoney="updateMoney"></Son>
</div>
</template>
<script>
import Son from './Son'
import { ref } from 'vue'
export default {
name: 'App',
components: {
Son
},
// 组合API的起点
setup () {
const money = ref(100)
const updateMoney = (newMoney) => {
// console.log(newMoney);
money.value = newMoney
}
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { money, updateMoney }
}
}
</script>
Son.vue
<template>
<div>
<h3>子组件</h3>
<p>{{money}}</p>
<button @click="changeMoney">花50元</button>
</div>
</template>
<script>
import { onMounted } from '@vue/runtime-core'
export default {
name: 'Son',
// 子组件接收父组件数据使用 props 即可
props: {
money: {
type: Number,
default: 0
}
},
/**
* 1.props:外部组件传递过来的内容,响应式的,当传入新的 prop 时,它将被更新
*
* 2.context:一个普通 JavaScript 对象,暴露了其它可能在 setup 中有用的值
* context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,
* 这意味着你可以安全地对 context 使用 ES6 解构
*/
setup (props, {emit}) {
// 若想在这里获取父组件的数据
onMounted(() => {
// console.log(this.money); // 报错
})
onMounted(() => {
//console.log(props.money); // 100
})
//console.log(props.money);
// 子传父
const changeMoney = () => {
// 消费了50
// 通知父组件,money 需要变成 50
emit('changeMoney', 50)
}
return { changeMoney }
}
}
</script>
注意:
context
是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context
使用 ES6 解构。例如:
// MyBook.vue
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
iii. 扩展:
- 在vue2.x的时候
.sync
除去v-model实现双向数据绑定的另一种方式
<!-- <Son :money='money' @update:money="fn" /> -->
<Son :money.sync='money' />
- 在vue3.0的时候,使用
v-model:money="money"
即可
<!-- <Son :money="money" @update:money="updateMoney" /> -->
<Son v-model:money="money" />
总结:
- 父传子:在setup种使用props数据
setup(props){ // props就是父组件数据 }
- 子传父:触发自定义事件的时候emit来自
setup(props,{emit}){ // emit 就是触发事件函数 }
- 在vue3.0中
v-model
和.sync
已经合并成v-model
指令
2.13 依赖注入
使用provide函数和inject函数完成后代组件数据通讯
使用场景:有一个父组件,里头有子组件,有孙组件,有很多后代组件,共享父组件数据
Provide / Inject的应用,它们在3.x中得到了增强:
- provide() 和 inject() 可以实现嵌套组件之间的数据传递。
- 这两个函数只能在 setup() 函数中使用。
- 父级组件中使用 provide() 函数向下传递数据。
- 子级组件中使用 inject() 获取上层传递过来的数据。
- 不限层级
App.vue,
<template>
<div class="container">
<p>我是父组件</p>
<p>{{money}}</p>
<button @click="money=1000">发钱</button>
<hr>
<Son1></Son1>
</div>
</template>
<script>
import Son1 from "./Son1"
import { ref, provide } from 'vue'
export default {
name: 'App',
components: {
Son1
},
// 组合API的起点
setup () {
const money = ref(100)
const changeMoney = (saleMoney) => {
console.log('changeMoney');
money.value = saleMoney
}
// 将数据提供给后代组件 provide
provide('money', money)
// 将函数提供给后代组件 provide
provide('changeMoney', changeMoney)
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { money, changeMoney }
}
}
</script>
Son1.vue,
<template>
<div>
<p>我是儿子组件</p>
<p>{{money}}</p>
<hr>
<GrandSon></GrandSon>
</div>
</template>
<script>
import { inject } from '@vue/runtime-core'
import GrandSon from './GrandSon'
export default {
name: 'Son',
components: {
GrandSon
},
setup () {
// 接收祖先组件提供的数据
const money = inject('money')
return { money }
}
}
</script>
GrandSon.vue,
<template>
<div>
<p>我是孙子组件</p>
<p>{{money}}</p>
<button @click="changeMoneyFn">花费了50</button>
</div>
</template>
<script>
import { inject } from '@vue/runtime-core'
export default {
name: 'GrandSon',
setup () {
// 接收祖先组件传递过来的数据
const money = inject('money')
// 孙组件消费50,通知祖先组件 App.vue进行修改
// 不能直接在这里修改,需要遵循单项数据流原则--谁定义谁修改
const changeMoney = inject('changeMoney')
const changeMoneyFn = () => {
changeMoney(50)
}
return { money, changeMoney, changeMoneyFn }
}
}
</script>
总结:
- provide函数提供数据和函数给后代组件使用
- inject函数给当前组件注入provide提供的数据和函数
2.14 v-model 语法糖
在vue2.0中v-mode语法糖简写的代码:
<Son :value="msg" @input="msg=$event" />
在vue3.0中v-model语法糖有所调整:
<Son :modelValue="msg" @update:modelValue="msg=$event" />
App.vue
<template>
<div class="container">
<!--
如果想获取原生事件的事件对象
如果绑定的是函数 fn fn(e) { // e 就是事件对象 }
如果绑定的是js表达式,此时提供一个默认的变量 $event
-->
<h3 @click="$event.target.style.color='red'">父组件</h3>
<p>{{count}}</p>
<hr>
<!--
如果想获取自定义事件的事件对象
如果绑定的是函数 fn fn(data) { // data 触发自定义事件的传参 }
如果绑定的是js表达式,此时 $event 代表的是触发自定义事件的传参
-->
<Son2 :modeValue="count" @update:modeValue="count=$event"></Son2>
<!-- 可以简写成如下形式 -->
<!-- <Son2 v-model="count"></Son2> -->
</div>
</template>
<script>
import Son2 from './Son2'
import { ref } from 'vue'
export default {
name: 'App',
components: {
Son2
},
// 组合API的起点
setup () {
const count = ref(10)
// 模版中需要使用的数据和函数,需要在 setup 中返回
return { count }
}
}
</script>
Son2.vue
<template>
<div>
<h3>子组件</h3>
<p>{{modeValue}}</p>
<button @click="fn">改变数据</button>
</div>
</template>
<script>
export default {
name: 'Son2',
props: {
modeValue: {
type: Number,
default: 0
}
},
setup (props, {emit}) {
const fn = () => {
// 改变数据
emit('update:modeValue', 100)
}
return { fn }
}
}
</script>
总结: vue3.0封装组件支持v-model的时候,父传子:modelValue
子传父 @update:modelValue
补充: vue2.0的 xxx.sync
语法糖解析 父传子 :xxx
子传父 @update:xxx
在vue3.0 使用 v-model:xxx
代替
三、EventBus
在 vue3.x 中无法使用EventBus
在 src 目录下新建一个名为 utils 的文件夹,并在该文件夹里新建一个名为 mitt.js 的文件
并通过 npm install --save mitt 安装 mitt
在 main.js 里引入(import "./utils/mitt")
在 src/components 目录下新建一个名为 Child 的文件夹,并在该文件夹里新建一个名为 index.vue 的文件
mitt.js,
import mitt from "mitt"
const emitter = mitt();
export default emitter;
src/components/Child/index.vue,
<template>
<h3>Child页面</h3>
<p>{{message}}</p>
</template>
<script>
import emitter from "../../utils/mitt"
export default {
data () {
return {
message:""
}
},
mounted () {
//接收数据
emitter.on("onCustomEvent",data=>{
// console.log(data);
this.message = data;
});
}
}
</script>
<style>
</style>
在2.x中通过EventBus的方法来实现组件通信:(Vue2Demo.vue)
var EventBus = new Vue()
Vue.prototype.$EventBus = EventBus
...
this.$EventBus.$on() this.$EventBus.$emit()
在3.x中移除了$on, $off等方法(参考rfc),而是推荐使用mitt方案来代替(Vue3Demo.vue)
<template>
<h3>vue3版本</h3>
<button @click="sendMessageHandle">发送数据给Child</button>
<Child />
</template>
<script>
import Child from "./Child"
import emitter from "../utils/mitt"
export default {
setup(){
const sendMessageHandle = () => {
//发送数据
emitter.emit("onCustomEvent",'456123');
};
return {
sendMessageHandle
}
},
components: {
Child
}
}
</script>
<style>
</style>
四、代码组织能力
关于代码组织能力,其实很简单,就是将多个组件都需要用到的一些方法等提取出来,单独放在一个文件里,然后分别引用即可
五、Teleport
关于这一部分其实没什么好说的,都比较简单
举个例子来说就是,你在书写代码时可以指定将其加到某一个class/id...属性为xxx的结构里:
<telepoer to="#app">
<h3>这是一个独立的结构</h3>
</telepoer>
类似于上面的代码,就是将里面书写的 h3 放到一个属性为id=app的结构里
六、VueX中如何使用
6.1 同步方法改变数据
Home.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<h1>{{name}}</h1>
</div>
</template>
<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';
export default {
name: 'Home',
setup(){
const store = useStore();
const {name}=toRefs(store.state);
return{
name
}
}
}
</script>
About.vue
<template>
<div class="about">
<h1 @click="handleClick">This is an about page</h1>
<h1>{{name}}</h1>
</div>
</template>
<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';
export default {
name: 'About',
setup(){
const store = useStore();
const {name}=toRefs(store.state);
const handleClick=()=>{
store.commit('changeName','hello');
}
return{
name,
handleClick
}
}
}
</script>
store/index.js
import { createStore } from 'vuex'
//VueX 数据管理框架
//VueX 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
// 存放全局的数据
state: {
name: 'dell'
},
//mutation 里面只允许写同步代码,不允许写异步代码
//commit和mutation作关联
mutations: {
//第四步,对应的 mutation 被执行
changeName(state, str) {
//第五步 在 mutation 里修改数据
// this.state.name = 'lee';
state.name = str;
}
},
modules: {}
})
6.2 异步方法改变数据
About.vue
<template>
<div class="about">
<h1 @click="handleClick">This is an about page</h1>
<h1>{{name}}</h1>
</div>
</template>
<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';
export default {
name: 'About',
setup(){
const store = useStore();
const {name}=toRefs(store.state);
const handleClick=()=>{
store.dispatch('getData');
}
return{
name,
handleClick
}
}
}
</script>
Home.vue
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<h1>{{name}}</h1>
</div>
</template>
<script>
import {toRefs} from 'vue';
import {useStore} from 'vuex';
export default {
name: 'Home',
setup(){
const store = useStore();
const {name}=toRefs(store.state);
return{
name
}
}
}
</script>
index.js
import { createStore } from 'vuex'
//VueX 数据管理框架
//VueX 创建了一个全局唯一的仓库,用来存放全局的数据
export default createStore({
// 存放全局的数据
state: {
name: 'dell'
},
//mutation 里面只允许写同步代码,不允许写异步代码
//commit和mutation作关联
mutations: {
//第四步,对应的 mutation 被执行
changeName(state, str) {
//第五步 在 mutation 里修改数据
// this.state.name = 'lee';
state.name = str;
}
},
// dispatch 和 actions 作关联
actions: {
//第二步,store 感知到了你触发了一个叫做 change 的 action
//执行 change()方法
getData(store) {
setTimeout(() => {
//第三步,提交一个 commit,触发一个 mutation
store.commit('changeName', 'hello');
}, 2000)
}
},
modules: {}
})