Vue3入门
vue项目目录
main.js 入门文件
App.vue App根组件
index.html 模版文件
运行流程
main.js中的核心代码
加载main.js执行项目
import Vue from 'vue'
//导入vue
import App from './App.vue'
//导入App.vue
new Vue({
render:h=>h(App),
}).$mount('app')
//实例化Vue,将App.vue渲染到index.html容器中
启动项目命令
yarn serve或npm run serve
组件化开发
组件化:页面按照独立的结构,样式行为等拆分成一个个组件
便于维护,利于复用
组件分类:普通组件,根组件.
根组件:整个应用最上层的组件,包裹所有普通的小组件
组件构成
template:结构,写HTML元素
script:js逻辑,写js代码
style:样式,写CSS样式
<template>
</template>
<script>
export default{
导出组件
}
</script>
<style lang="less">
</style>
//less是CSS预处理器,扩展了CSS语法,增强了CSS功能
//使用npm i less less-loader -D进行安装
组件注册
局部注册
在一个特点的Vue组件(通常为单文件组件.vue文件)内部定义和注册一个组件,作用域仅限于定义它的组本身和其子组件.局部注册通常在组件的components选项中完成
在使用的组件中导入文件并通过component:{组件名:组件对象}进行
全局注册
使用Vue.component()方法来注册全局组件.全局组件在整个Vue实例化的过程中都是可用的,无论他们位于哪个组件树中.所以可以在任何其他Vue组件的模版中直接使用全局注册的组件.
在main.js中导入并通过Vue.component(组件名,组件对象)进行全局注册
局部注册
一般使用局部注册,只有确定是通用组件才有必要抽离到全局
在App.vue中进行组件注册(局部注册)
<template>
<div class="App">
<!-- 头部组件 -->
<HmHeader></HmHeader>
<!-- 主体组件 -->
<HmMain></HmMain>
<!-- 底部组件 -->
<HmFooter></HmFooter>
</div>
</template>
<script>
import HmHeader from './components/HmHeader.vue'//引入文件
import HmMain from './components/HmMain.vue'
import HmFooter from './components/HmFooter.vue'
export default {//组件导出
components: {
// '组件名': 组件对象
HmHeader: HmHeader,
HmMain,
HmFooter
}
}
</script>
<style>
.App {
width: 600px;
height: 700px;
background-color: #87ceeb;
margin: 0 auto;
padding: 20px;
}
</style>
全局组件注册(全局注册)
<template>
<button class="hm-button">我是按钮</button>
</template>
<script>
export default {
}
</script>
<style>
.hm-button{
background-color: green;
}
</style>
在main.js中进行全局注册
import Vue from 'vue'
import App from './App.vue'
import './styles/base.css' // css 样式重置
import './styles/common.css' // 公共全局样式
import './assets/iconfont/iconfont.css' // 字体图标的样式
import './styles/index.css' // 页面样式
//全局注册Vue组件
//1. 导入
import HmButton from './components/HmButton.vue'
//2. 全局注册vue组件
Vue.component("HmButton",HmButton)
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
注册后就可以进行全局使用
Vue3入门
引入组合式API
<script setup>
import { ref } from 'vue'
const count = ref(0)
const addCount = ()=> count.value++
</script>
Vue3优势
组合式API,更好的TypeScript支持
更容易维护
重写diff算法,模版编译优化,更高效的组件初始化
更快的速度
良好的TreeShaking,按需引入
更小的体积
更优秀的数据响应式
Proxy
create-vue
Vue3的脚手架工具,底层切换到了vite
使用
npm init vue@latest
命令进行安装并执行
项目结构
setup
在beforeCreate钩子之前执行
定义数据+函数 以对象方式return
在setup(){
}中定义,使用return返回后可以在组件中直接使用
可以通过语法糖的封装更加简单地使用组合式API
setup中无法使用this,this指向undefined
<script>
export default {
setup(){
let name = "张三";
let age = 18;
function sayHello(){
alert(`大家好,我叫${name},今年${age}岁。`);
}
return {
name,
age,
sayHello
}
},
beforeCreate(){
}
}
</script>
创建组件
<template>
<div>
数据:{{ message }}
<br />
<button @click="logMessage">打印消息</button>
</div>
</template>
<script>
// setup
// 执行时机,比beforeCreate还要早
// setup函数中,获取不到this (this是undefined)
export default {
/* setup()和beforeCreate()的执行机制 */
setup() {
console.log("setup....");
//console.log('setup函数', this)
},
beforeCreate() {
console.log("beforeCreate....");
},
/* setup()的基本写法 */
}
</script>
<style></style>
可以通过在script标签上指定setup属性进行简化代码编写,这种方式称为语法糖
使用
<script setup>
// setup
const message = "this is a message2";
const logMessage = () => {
console.log(message);
};
</script>
reactive和ref函数
reactive():接受对象类型的参数传入并返回一个响应式的对象
响应式对象:页面中的数据和数据模型的数据绑定
代码写法如下:
<script setup>
import { reactive } from 'vue'//导入
const state = reactive(对象类型的数据)//传入参数,执行函数,变量接收
</script>
<template>
<div>
<div>{{ state.count }}</div>
<button @click="setCount">加1</button>
</div>
</template>
<script setup>
import { reactive } from "vue";
//申明响应式对象state,对象的属性count值为1
const state = reactive({count:1});
//申明函数,使上面的state对象的count属性值做自增长操作
const setCount = () => {
state.count++;
}
</script>
<style></style>
ref()
接受简单类型或者对象类型的数据传入并返回一个响应式的对象
<script setup>
import { ref } from 'vue'//导入
const count = ref(简单类型或者复杂类型数据)//传入参数,执行函数,变量接收
</script>
<template>
<div>
<div>{{ count }}</div>
<button @click="setCount">加1</button>
</div>
</template>
<script setup>
// ref: 接收简单类型 或 复杂类型,返回一个响应式的对象
// 本质:是在原有传入数据的基础上,外层包了一层对象,包成了复杂类型
// 底层,包成复杂类型之后,再借助 reactive 实现的响应式
// 注意点:
// 1. 脚本中访问数据,需要通过 .value
// 2. 在template中,.value不需要加(vue自动加了.value)
// 推荐:以后声明数据,统一用 ref => 统一了编码规范
import { ref } from "vue";
//申明响应式对象
const count = ref(100);
const setCount = () => {
count.value++;
}
</script>
<style>
</style>
ref和reactive的区别
ref:
用于封装单个值
封装值,任何类型都可以
通过.value访问或修改
在模板中自动解包
reactive:
用于复杂数据结构
创建响应式对象/数组
直接操作属性/数组项
内部属性/数组项响应式
Computed()函数
用于计算属性,返回一个值,这个值基于响应式状态的其他属性计算得出,并且自动保持与这些依赖状态同步.
具备以下特点
依赖追踪:
函数内定义的计算逻辑会追踪其访问的响应式状态,计算属性会在依赖状态变化时重新计算
惰性求值:
只会在原数据发生变化或者首次访问时更新新数据的内容,不会出现非必要的计算
只读性:
计算后的属性默认只读,不能被直接修改
在模版中进行使用时:
可以按照普通的数据一样使用,会被vue自动识别和读取
可以和ref,reactive一起使用,实现响应式数据的动态处理
<template>
<div>
list : {{ list }}
<br>
computedList : {{ computedFilter }}
<br>
<button @click="setList">list++</button>
<button @click="deleteList">list--</button>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9]);
const computedFilter = computed(() => {
return list.value.filter((item) =>
item > 50
)
})
const setList = () => {
list.value.push(Math.floor(Math.random() * 100));
}
const deleteList = () => {
list.value.pop();
}
</script>
<style lang='stylus' scoped>
</style>
watch()函数
用于观察特定响应式数据的变化,在数据变化时执行指定的回调函数
watch(count,(new,old)=>{
执行函数...
})
immediate:true 立即执行一次回调函数,新值为代码中设置的值,旧值为undefined
deep:true 执行深层监听,可以监听对象中的属性的变化
案例
<template>
<div>
<div>{{ count }}</div>
<button @click="changeCount">改数字</button>
<div>-----------------------</div>
<div>{{ nickname }}</div>
<div>{{ oldnickname }}</div>
<div>{{ newnickname }}</div>
<!-- <button @click="changeNickname">改昵称</button> -->
<input type="text" v-model="nickname">
<!-- 绑定数据模型 -->
<div>-----------------------</div>
<div>{{ state.count }}</div>
<button @click="changeStateByCount">deep测试</button>
<div>-----------------------</div>
<div>{{ userInfo }}</div>
<div>{{ olduserInfo }}</div>
<div>{{ newuserInfo }}</div>
<button @click="setUserInfo">修改userInfo</button>
<div>{{ olduserInfo }}</div>
<div>{{ newuserInfo }}</div>
</div>
</template>
<script setup>
import { watch, ref } from 'vue';
const count = ref(0);
const nickname = ref('张三');
var oldnickname = "";
var newnickname = "";
var olduserInfo = 0;
var newuserInfo = 0;
var olduserInfo1 = 0;
var newuserInfo1 = 0;
const changeCount = () => {
count.value = Math.random() * 100;
}
watch(count, (newvalue, oldvalue) => {
alert(`oldvalue = ${oldvalue},newvalue = ${newvalue}`);
},{immediate:true})
watch(nickname, (oldvalue, newvalue) => {
console.log(`oldvalue = ${oldvalue},newvalue = ${newvalue}`);
oldnickname = oldvalue;
newnickname = newvalue;
})
const state = ref({ count: 0 });
const userInfo = ref({
a: 0,
b: 1
})
watch(userInfo.a,(newvalue,oldvalue)=>{
olduserInfo1 = oldvalue,
newuserInfo1 = newvalue
},{deep:true})
watch(()=>userInfo.value.a,(newvalue,oldvalue)=>{
olduserInfo = oldvalue,
newuserInfo = newvalue
})
const setUserInfo = () => {
userInfo.value.a = Math.floor(Math.random()*10);
}
</script>
<style></style>
总结
1,在watch的参数中,如果读取的是ref对象,无需添加value会自动读取
2,watch可以侦听多个数据
3,可以通过开启deep方式进行深层侦听,来检测嵌套属性是否被修改
4,不开启deep的情况下可以在第一个参数中写一个返回需要监听属性的函数,这样也能对这个属性进行监听
生命周期函数
vue2和vue3对比
实现方式的变化
vue2使用选项式API,生命周期钩子作为组件选项直接定义在组件对象中
export default{
created(){}
}
vue3引入了组合式API,使用setup()函数,将生命周期作为setup的返回对象的一部分或者作为Onxxx形式使用
<script setup>
onMounted(() => {
})
</script>
-
Vue 2 的生命周期钩子包括:
-
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed
-
Vue 3 对部分生命周期钩子进行了重命名,并新增了一些钩子:
-
setup()
: 新增的顶级生命周期函数,用于替代 Vue 2 中的beforeCreate
和created
部分功能,用于设置响应式状态和计算属性等。onBeforeMount()
: 替代beforeMount
onMounted()
: 保持不变onBeforeUpdate()
: 保持不变onUpdated()
: 保持不变onBeforeUnmount()
: 替代beforeDestroy
onUnmounted()
: 保持不变onActivated()
: Vue 3 为<keep-alive>
缓存的组件新增的钩子,在组件被激活时触发。onDeactivated()
: 同样为缓存组件新增,当组件从活跃状态变为非活跃状态时触发。onErrorCaptured()
: 保留此钩子,用于捕获子孙组件抛出的错误。
3. 响应式系统的变化:
- Vue 2 使用
Object.defineProperty()
实现基于 getter/setter 的响应式系统。 - Vue 3 引入了更高效且灵活的 Composition API,采用 Proxy 实现响应式。数据状态现在通过
ref()
和reactive()
创建,而不是在data()
函数中返回一个对象。
4. 作用域与访问限制:
- Vue 2 中的生命周期钩子可以直接访问
this
上下文,通过this.data
、this.methods
等访问组件的各种属性和方法。 - Vue 3 的
setup()
函数没有this
上下文,所有状态和方法应通过返回的对象暴露给模板或其他组合式函数。同时,由于setup()
在beforeCreate
和created
之间执行,它不能直接访问data
、props
、computed
等选项的原始值,而需要通过相应的 Composition API 函数(如props
、setupContext.attrs
、computed()
等)来获取。
5. 异步更新队列:
- Vue 2 和 Vue 3 都有异步更新队列机制,确保同一事件循环中对状态的多次修改只触发一次更新。但在 Vue 3 中,
nextTick()
等相关 API 有所调整,以适应 Composition API 的使用。
总结来说,Vue 3 的生命周期函数主要围绕着 Composition API 进行了重构和扩展,提供了更为精细的组件生命周期控制,并优化了响应式系统的实现。开发者需要适应新的编程模式,如使用 setup()
函数、ref()
和 reactive()
创建响应式状态,以及通过返回对象暴露状态和方法给模板。同时,需要注意一些钩子名称的变化和新加入的特定于 <keep-alive>
组件的生命周期钩子。
基本使用
import { onMounted } from 'vue'
onMounted(()=>{
})
组件间通信
父子组件通信
<template>
<div class="pc-a">
<B msg="A->B" :count = "count" @test = "test"></B>
//直接传递数据 变量名="数据"
//传递响应式对象 :接受的对象名="对象名"
//传递方法 @接受的方法名="方法名"
</div>
</template>
<script setup>
import { ref } from 'vue';
import B from './B.vue';
const count = ref(0);
const test = ()=>{
count.value++;
console.log('test');
}
</script>
<style>
.pc-a{
width: 300px;
height: 400px;
border: 1px red solid;
text-align: end;
}
</style>
<template>
<div class="pc-b">
<E></E>
{{ msg }}
{{ count }}
<button @click="test">count++</button>
</div>
</template>
<script setup>
import E from './E.vue';
const props = defineProps({
msg: String,
count: Number
}
);
const emit = defineEmits(
['test']
);
const test = () =>{
emit('test')
}
</script>
<style>
.pc-b {
width: 250px;
height: 100px;
border: 1px green solid;
margin-left: 25px;
margin-top: 20px;
}
</style>
子父组件通信
传值:
通过父组件传递给子组件的方法,子组件传入一个参数,父组件根据这个参数进行操作
传方法:
在子组件中对方法进行定义和暴露,这样在父组件中就可以通过ref属性进行访问
传值:
<template>
<div class="pc-a">
<B msg="A->B" :count = "count" @test = "test"></B>
{{ valueRe }}
</div>
</template>
<script setup>
import { ref } from 'vue';
import B from './B.vue';
const count = ref(0);
let valueRe = ref(0);
const test = (value)=>{//value:子组件传入的参数
count.value++;
console.log('test');
valueRe.value = value;//在父组件中接受,相当于子传父
}
</script>
<style>
.pc-a{
width: 300px;
height: 400px;
border: 1px red solid;
text-align: end;
}
</style>
<template>
<div class="pc-b">
<E></E>
{{ msg }}
{{ count }}
<button @click="test">count++</button>
</div>
</template>
<script setup>
import E from './E.vue';
const props = defineProps({
msg: String,
count: Number
}
);
const emit = defineEmits(
['test']
);
const test = () =>{//给父组件的方法传入参数
emit('test',1)
}
</script>
<style>
.pc-b {
width: 250px;
height: 100px;
border: 1px green solid;
margin-left: 25px;
margin-top: 20px;
}
</style>
传方法:
const funb = () => {
alert('funb')
}
defineExpose({//暴露方法
funb
})
<template>
<div class="pc-a">
<B msg="A->B" :count = "count" @test = "test" ref = "bref"></B>
//使用ref属性接受对象实例
{{ valueRe }}
<button @click = "fun1">fun1</button>
</div>
</template>
<script setup>
import { provide, ref } from 'vue';
import B from './B.vue';
const count = ref(0);
let valueRe = ref(0);
const test = (value)=>{
count.value++;
console.log('test');
valueRe.value = value;
}
const bref = ref({})//初始化
const fun1 = () => {//从实例中调用对应方法
bref.value.funb();
}
provide("msg","A's msg")
provide("count",count)
provide("fun",test)
</script>
<style>
.pc-a{
width: 300px;
height: 400px;
border: 1px red solid;
text-align: end;
}
</style>
跨层之间通信
通过provide和inject函数可以实现跨层级的通信,无论隔着多少层都可以完成通信
provide("msg","A's msg")
//传普通类型
provide("count",count)
//传响应式对象
provide("fun",test)
//传方法
<template>
<div class="pc-e">
{{ msg }}
{{ count }}
<button @click="fun"></button>
</div>
</template>
<script setup>
import { inject } from "vue";
const msg = inject('msg');
//接受普通类型
const count = inject('count');
//接受响应式对象
const fun = inject('fun');
//接受方法
</script>
<style>
.pc-e{
width: 100px;
height: 50px;
border: 1px blue solid;
margin-left: 20px;
margin-top: 20px;
}
</style>
无法进行跨层级的向上传递,后代数据想要传递给父组件只能通过接受父组件的方法传入参数的方式.
总结:
子组件通过
defineProps
接受数据
defineEmits
接受函数
跨层组件通过
provide/inject
实现跨层级的通信