项目搭建
尤大又一力作Vite
不知不觉间升级到2.0,作为风头正盛号称下一代前端构建工具,有什么理由不去体验一下。
Vite项目初始化
官方提供npm、yarn两个指令。
npm
: npm init @vitejs/app
yarn
: yarn create @vitejs/app
Vite项目初始配置
这里使用了npm的方式,新建 HelloWorld 文件夹并使用vscode打开。
-
在终端命令行输入
npm init @vitejs/app
-
配置项目名称
-
配置项目框架,这里我选用了vue
-
配置项目语言类型,这里我选用了vue
-
项目搭建完成,进入项目目录并安装依赖
- 进入项目目录
cd vite-project
- 安装项目依赖
npm install
- 进入项目目录
-
运行项目
npm run dev
项目目录调整
新建组件A src/components/ComA.vue
,在App.vue中导入ComA.vue
并挂载到模板中。
我们在ComA
中进行Vue3相关练习
Compostion-API
相比较Vue2来说,Vue3的升级主要是包含 使用proxy重写了响应式逻辑、新API风格Compostion-API 等…,这里只对 Compostion-API 进行讲解。
为什么要使用Compostion-API
它是为了实现基于函数的逻辑复用机制而产生的。以往的 options-API ,在大型项目中可能会出现一下几个痛点。
-
代码分散、可读性差
在大型组件中,实现某个功能的代码可能会分散在多个选项中(data、methods、computed…),不便于我们查找选项之间依赖关系、降低代码的可读性。
-
因代码复用导致 依赖来源不明确
options-API 我们可以使用混入、Vue原型挂载等方式,为Vue实例挂载依赖数据。例如 在某组件莫名其妙出现了几个未在该组件定义的data数据,这些数据可能某地方挂载到Vue原型上 也可能在混入的示例上等 这些混乱的依赖关系势必会增加开发的心智负担。
setup
setup是Vue3新增选项,它是使用Compostion-API的入口函数,Compostion-API相关代码都要放入到setup函数中。
// components/ComA.vue
<template>
<div></div>
</template>
<script>
export default {
// composition-API 入口函数
setup(props,context) {
// 书写Composition-API
...
},
// 其他Vue选项
data() {...},
methods:{...}
}
</script>
复制代码
setup参数
- props:对象,保存父组件向子组件传递的props数据
- context:
对象,保存上下文数据 可以使用 ES6解构 :setup(props,{attrs,slots,emit}){...}
- attrs:对象,保存了组件标签所有attrs属性
- slots:对象,保存了组件插槽传入的数据
- emit: 方法,用来触发组件标签上注册的自定义事件,相当于
this.$emit
props与attrs
父组件传入了props数据,如果子组件没有在props选项中声明要接收props字段。则该字段会保存在attrs中。
// App.vue
<template>
<ComA :msg="msg" text="这是ComA组件"></ComA>
</template>
<script >
import ComA from "./components/ComA.vue";\
export default {
setup() {
const msg = 11
return {msg}
}
}
</script>
// ComA.vue
<script>
export default {
// 在props 声明接收msg
// props:["msg"],
// composition-API 入口函数
setup(props,{attrs}) {
console.log("props",props);
console.log("attrs",attrs);
}
}
</script>
复制代码
注意:不允许对props进行解构,props为proxy对象,对其解构会让其失去响应式
例如:setup({name,age}){......}
这种用法是错误的
如果需要解构props,可以使用let { msg } = toRefs(props)
,这样就需要msg.value
进行读写所以最好还是用props.msg
的形式
emit
// App.vue 根组件
<template>
<ComA :msg="msg" @update="update"></ComA>
</template>
<script setup>
import ComA from "./components/ComA.vue";
const msg = 11
function update(value) {
console.log("update被触发",value)
}
</script>
// ComA.vue 子组件
<template>
<button @click="hClickBTN">更新</button>
</template>
<script>
import { onMounted, ref } from '@vue/runtime-core';
export default {
setup(props,{emit}) {
function hClickBTN () {
// 通过emit触发自定义事件 "update",进行子传父通讯
emit("update","张三")
}
return {
hClickBTN
}
}
}
</script>
复制代码
setup返回值
setup的返回值必须为对象,setup调用后会将返回对象的所有属性和方法挂载Vue实例上。这样模板中就能直接使用。
如果setup没有将属性放到对象中返回出来 模板中就无法使用该属性
// ComA.vue
<template>
<div>msg:{{msg}}</div>
</template>
<script>
import { onMounted, ref } from '@vue/runtime-core';
export default {
setup() {
const msg = 111
// return {msg}
}
}
</script>
复制代码
setup注意事项
setup
只会执行一次,并且setup
执行时,组件实例并未被创建,所以setup
的this指向undefined
setup
返回的对象,如果对象中的属性和方法与 options的data
、methods
返回的字段有冲突时,setup
具有更高的优先级它会覆盖掉其他选项中重复的字段。- 可以在options的
methods
等选项中,通过this
实例获取setup
返回出的数据。但是setup
的this为undefined
,setup
中无法访问data
、methods
等.
script setup
script setup 是 setup入口函数的语法糖写法。这种写法不用再配置setup选项,也不需要手动将声明的变量或者函数return出去,script setup会帮你完成这些工作
<template>
<div>姓名:{{name}},年龄:{{age}}</div>
</template>
// 普通写法
<script>
export default {
setup() {
const name = "张三"
const age = 18
return { name,age }
}
}
</script>
// script setup 写法
<script setup>
const name = "张三"
const age = 18
</script>
复制代码
在 script setup 中使用 props、emit、nextTick
// App.vue
<template>
// 传入count、注册自定义事件update
<ComA :count="count" @update="update"></ComA>
</template>
<script setup>
import { ref } from "vue";
import ComA from "./components/ComA.vue";
// 声明响应式ref数据
const count = ref(10)
function update(value) {
// 更改ref数据
count.value = value
console.log("根组件-count设置新值",count.value);
}
</script>
// ComA.vue
<script setup>
import { defineEmit, defineProps, onMounted,nextTick } from "vue";
// 声明props方式1:
// const props = defineProps({
// num: {
// type: Number,
// default: 1
// }
// })
// 声明props方式2:
const props = defineProps(["count"])
// 声明 emit
const emit = defineEmit(["update"])
// 页面DOM挂载完成
onMounted(() => {
const num = props.count
console.log("ComA-props.count初始值",props.count);
// 触发自定义事件update、传入新值
emit("update", num+1)
// Vue响应式数据更新是异步的,更改props后需要在nextTick获取最新值
nextTick().then(()=>{
console.log("nextTick-ComA-props.count修改后",props.count);
})
})
</script>
复制代码
在 script setup 中使用 attrs、slots
// App.vue
<template>
<ComA parent="app.vue"></ComA>
</template>
// ComA.vue
<script setup>
import { onMounted, useContext } from "vue";
// 使用useContext 注册attrs、slots
const { attrs, slots } = useContext()
console.log("ComA-attrs.parent",attrs.parent);
</script>
复制代码
响应式数据
ref
ref
主要是用来声明简单数据类型的响应式数据
在 setup
中, ref
数据必须通过 变量名.value 的形式进行读写,setup
在将ref数据返回时会进行解包,所以在模板可以直接使用 ref
数据而不是 变量名.value的形式。
<template>
<!-- 模板中使用ref数据 -->
<div>数量:{{amount}}</div>
<div>
<!-- 模板中修改ref数据 -->
<button @click="amount++">增加</button>
</div>
<div>
<button @click="hClickAdd">setup增加</button>
</div>
</template>
<script >
import { ref } from "vue";
export default{
setup(){
const amount = ref(0)
// 在setup中访问ref变量的值 必须用 变量名.value 的形式
function hClickAdd() {
// 在setup中修改ref变量的值 必须用 变量名.value 的形式
amount.value++
}
return {amount,hClickAdd}
}
}
</script>
复制代码
reactive
reactive
主要是用来声明复杂数据类型的响应式数据,他也可以引用 ref
数据
<template>
<div>
<p>年龄:{{person.age}}</p>
<p>姓名:{{person.name}}</p>
<p><button @click="age++">增加年龄</button></p>
<p>
<input type="text" v-model="msg">
<button @click="updateName">修改姓名</button>
</p>
<p></p>
</div>
</template>
<script setup>
import { reactive, ref } from "vue";
const age = ref(0)
const msg = ref("")
const person = reactive({
name:"张三",
age
})
const updateName = ()=>{
person.name = msg.value
}
</script>
复制代码
toRefs
他可以将一个响应式对象转换成普通对象,该普通对象的每个属性都是一个ref
数据
应用场景
某些响应式数据不能直接使用解构,否则会丢失响应式。我们可以通过toRefs来解决这个问题
对props进行解构
// App.vue
<template>
<ComA :parent="parent"></ComA>
</template>
<script setup>
import { onMounted, reactive } from "vue";
import ComA from "./components/ComA.vue";
const parent = reactive({
name:"App.vue"
})
onMounted(()=>{
setTimeout(()=>{
// 更改了name的值
console.log("parent.name is change");
parent.name = "newValue"
},1000)
})
</script>
// ComA.vue
<template>
<div class="teleport_content">props.parent.name:{{parent.name}}</div>
</template>
<script setup>
import { defineProps, reactive, toRefs } from "vue";
const props = defineProps(["parent"])
// toRefs 会将所有props的属性变为ref数据
const {parent} = toRefs(props)
</script>
复制代码
对reactive数据进行解构
// ComA.vue
<template>
<div>name1:{{name1}}</div>
<div>name2:{{name2}}</div>
</template>
<script setup>
import { reactive, toRefs } from "vue";
const person = reactive({name:"张三"})
// 直接解构 数据丢失响应式
const {name:name1} = person
const {name:name2} = toRefs(person)
// 修改数据
person.name = '李四'
</script>
复制代码
Compsition-API 生命周期
Vue3提供了一系列onXXX函数 来在setup中注册生命周期钩子函数
options-API与Compsition-API 生命周期的映射关系
beforeCreate、created ==> setup()
beforeMount ==> onBeforeMount
mounted ==> onMounted
beforeUpdate ==> onBeforeUpadte
updated ==> onUpdated
…….
生命周期的使用
<template>
<div>
<p>年龄:{{person.age}}</p>
<p>姓名:{{person.name}}</p>
<p><button @click="age++">增加年龄</button></p>
<p>
<input type="text" v-model="msg">
<button @click="updateName">修改姓名</button>
</p>
<p></p>
</div>
</template>
<script setup>
import { ref,reactive,onBeforeMount,onMounted,onUpdated } from "vue";
const age = ref(0)
const msg = ref("")
const person = reactive({
name:"张三",
age
})
const updateName = ()=>{
person.name = msg.value
}
console.log("setup run...");
onBeforeMount(()=>{
console.log("onBeforeMount run...");
})
onMounted(()=>{
console.log("onMounted run...");
})
onUpdated(()=>{
console.log("onUpdated run...");
})
...
</script>
复制代码
Computed
Vue3的Composition-API中的Computed使用方式有两种,一种是只读的computed,另外一种是可读写的computed
只读computed
const refData = computed(()=>{...})
computed传入一个 getter
函数,并返回一个不可修改的ref对象
<template>
<p v-for="(e,i) in persons" :key="i">
{{e.name}}的年龄为:{{e.age}}
<p>
<input type="number" v-model="e.editAge"/>
<button @click="e.age=e.editAge">确定更改</button>
</p>
</p>
<p>所有人年龄总和:{{totalAge}}</p>
</template>
<script setup>
import { ref,onMounted,reactive,computed } from "vue";
const persons = reactive([
{
name:"张三",
age:10,
editAge:0
},
{
name:"李四",
age:20,
editAge:0
},
{
name:"王五",
age:25,
editAge:0
},
])
// 创建一个只读的computed
const totalAge = computed(()=>{
return persons.reduce((total,cur,i)=>{
const totalNum = i===1?total.age:total
const curNum = cur.age?Number(cur.age):0
return Number(totalNum)+curNum
})
})
</script>
复制代码
可读写的 computed
computed通过传入一个 具有 get
和 set
函数的对象来创建可写的 ref 对象
<script setup>
import { ref,onMounted,reactive,computed } from "vue";
const zsAge = ref(0)
const lsAge = ref(5)
// 创建一个可读写的computed
const totalAge = computed({
set(value) {
zsAge.value = value
},
get() {
return zsAge.value+lsAge.value
}
})
console.log("computed初始",totalAge.value);
zsAge.value = 5
console.log("computed依赖被修改后",totalAge.value);
totalAge.value = 10
console.log("computed被修改后",totalAge.value);
</script>
复制代码
watch
侦听单个数据源
<script setup>
import { ref,watch, reactive,onMounted } from "vue";
const count = ref(0)
const perosn = reactive({
age:0
})
// 直接侦听 ref数据源
watch(count,(value,preValue)=>{
console.log("ref-count发生变化",value,preValue);
})
// 侦听一个 reactive数据源的getter
watch(()=>perosn.age,(value,preValue)=>{
console.log("reactive-person.age发生变化",value,preValue);
})
onMounted(()=>{
// 修改ref、reactive
count.value++
perosn.age++
})
</script>
复制代码
侦听多个数据源
<script setup>
import { onMounted, watch,ref } from "vue";
const a = ref(0)
const b = ref(1)
// 使用 注册attrs、slots
watch([a,b],([a,b],[preA,preB])=>{
console.log("a或b发生变化","a当前值:"+a,"a改变前的值:"+preA,"b当前值:"+b,"b改变前的值:"+preB);
})
onMounted(()=>{
a.value++
})
</script>
复制代码
watchEffect
watchEffect
与 wtach
有些类似,它不用主动声明观测哪个数据。函数中使用了哪些响应数据,当这些数据发生变化时该函数就会响应式的执行,该函数初始化时会先执行一次。
<script setup>
import { onMounted,ref, watchEffect } from "vue";
const a = ref(0)
const b = ref(1)
watchEffect(()=>{
b.value = a.value+10
console.log("b的值发生变化", b.value);
})
onMounted(()=>{
// 修改a的值
a.value=100
})
</script>
复制代码
getCurrentInstance
getCurrentInstance
可以在 setup
中,获取组件实例。
<script setup>
import { onMounted, ref,getCurrentInstance, } from "vue";
const a = ref(0)
const b = ref(1)
onMounted( ()=>{
// 获取组件实例
const instance = getCurrentInstance()
console.log("instance",instance);
})
</script>
复制代码
新特性
teleport
teleport
(传送门)是Vue3新增内置组件,它可以将组件模板节点内容插入到 组件内外的任意元素节点中。
属性
- to:字符串,值为DOM选择器(有效选择器)。代表teleport的内容将要移动到哪个元素节点中。
- disabled:布尔值,代表是否禁用传送门,注意该属性只决定了teleport内容的渲染位置,当disabled为false时,teleport内容会在父组件节点的位置渲染。
举个栗子
// ComA.vue
<template>
<teleport to="body">
<!-- 将teleport的内容移动到<body>中-->
<div class="teleport_content">这是teleport的内容</div>
</teleport>
</template>
复制代码
动态改变渲染位置
// App.vue
<template>
<ComA></ComA>
</template>
<script setup>
import ComA from "./components/ComA.vue";
s</script>
// ComA.vue
<template>
<!-- 向<body>中,插入内容 -->
<teleport to="body" :disabled="isDEisabled">
<div class="teleport_content">这是teleport的内容</div>
</teleport>
</template>
<script setup>
import { ref } from "vue";
// 是否禁用传送门
const isDEisabled = ref(true)
</script>
复制代码
teleport选中多个传送目标节点
teleport的to属性指定了传送的目标节点,当有多个目标节点时会使用第一个。
// App.vue
<template>
<div class="container"></div>
<div class="container"></div>
<ComA></ComA>
</template>
<script setup>
import ComA from "./components/ComA.vue";
</script>
// ComA.vue
<template>
<teleport to=".container">
<div class="teleport_content">这是teleport的内容</div>
</teleport>
</template>
复制代码