vue3笔记-脚手架篇
vue2与vue3的区别 vue项目脚手架 父子通信(props,属性透传,子传父) 跨级通信 动态组件 异步组件 插槽(slot,具名插槽,作用域插槽)生命周期 自定义指令
第二章 脚手架篇
vue2与vue3的一些区别
- 响应式系统:
- Vue 2 使用 Object.defineProperty 进行响应式数据的劫持和监听,它对数据监听是一项项的进行监听,因此,当新增属性发生变化时,它无法监测到,并且响应性能不是很好。
- Vue 3 使用 Proxy 来实现响应式系统,它用一个中间代理来管理所有的数据响应,这使得它可以监听新增和删除的属性,并且提供了更好的性能和扩展性。
- 组件实例:
-
Vue 2 的组件实例使用
选项式 API
,将组件的逻辑拆分为不同的生命周期钩子函数,随着组件复杂度的增加,代码结构变得臃肿。<template> <div> </div> </template> <script> export default { data() { return { }; }, methods: { }, }; </script>
-
Vue 3 引入了
组合式 API
,通过 setup 函数来组织逻辑,可以根据功能进行逻辑的组合,提高了代码的可维护性和重用性。<template> <div> </div> </template> <script setup> </script> <style lang="scss" scoped> </style>
- 编译器:
-
Vue 2 使用基于字符串的模板编译方式,模板中的指令和插值表达式在编译阶段被转换成对应的 JavaScript 代码。
-
Vue 3 引入了编译器的静态标记,利用更先进的编译技术(如基于 AST 的编译),提供了更好的性能和错误检查。
基于 AST(抽象语法树)的编译是一种常见的编译技术,它将源代码转换为一个表示代码结构的树状数据结构。在编译过程中,AST 用于分析、转换和生成代码。
- 性能优化:
- Vue 3 在编译和运行时都进行了一系列的性能优化,比如更细粒度的更新跟踪、更高效的虚拟 DOM 算法等,提供了更高的性能和更小的包体积。
- TypeScript 支持:
- Vue 3 对 TypeScript 的支持更加友好,它在编译器和 API 设计上进行了改进,提供了更好的类型推导和类型检查。
Vue CLI
Vue CLI(Command Line Interface)是一个用于快速开发 Vue.js 项目的官方脚手架工具。它提供了一套完整的项目脚手架,包括构建、开发服务器、代码规范等,帮助你快速搭建和管理 Vue.js 项目。
下载安装Vue CLI
npm install -g @vue/cli
安装完成以后,使用vue
命令创建你的vue项目
vue create ny-project
运行完命令以后需要选择项目的一些配置依赖,选择完毕以后,回车Vue CLI就会帮我们创建一个vue项目,cd到项目文件下,运行 npm run server启动项目。
如果是第一次使用vueCLI创建项目,可以先去B站找几个项目视频看一下,会教你怎么选择依赖,对项目文件进行解读。
Vite
Vite 是一个现代化的前端构建工具,用于快速开发现代化的 Web 应用程序。它与传统的打包工具(如Webpack)不同,使用了基于原生 ES 模块的开发服务器,在开发过程中具有更快的冷启动时间和更高的性能。
Vite 针对现代浏览器进行了优化,利用原生 ES 模块的特性,可以直接在浏览器中执行模块,而无需像传统方式那样将它们打包成一个或多个文件。这使得在开发过程中,代码变更后的热重载速度非常快,加快了开发者的反馈循环。
使用vite创建你的vue项目
npm create vite@latest
这个命令会检查本地是否有vite,如果没有vite会提示您先下载vite
脚手架,然后输入项目名称,选择项目依赖,创建好项目文件目录后,在项目目录下运行npm install
下载依赖包,即可创建好一个vue项目。
vite目录
组件基础
父子通信
父传子props
将属性(props)从父组件传递给子组件来实现父子之间的通信
示例:
父组件
<script setup>
import HelloWorld from './components/HelloWorld.vue'
const myName = 'qingtian'
</script>
<template>
<div>
{{ myName }}
<HelloWorld :myName="myName" />
</div>
</template>
<style scoped>
</style>
子组件
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
myName: String
})
</script>
<template>
<div>
名字:{{ myName }}
</div>
</template>
<style lang="scss" scoped>
</style>
tip
所有的props都遵循单向数据流
,即props会因父组件的更新而变化,自然地将新的状态向下传递到子组件,而不会再由子组件传递到父组件。因此要避免子组件意外的修改父组件的状况,不然应用的数据流将很容易变得混乱。
props 属性验证与默认属性
子组件的需要接收的参数有类型要求,但是父组件有可能并不知需要的类型,错误属性类型传入会导致组件无法正常工作,因此要对属性进行验证。属性验证是一种在组件中对传入的属性进行类型检查和有效性验证的机制。它可以确保组件在接收到正确类型和有效值的属性时能够正常工作,并且能够及早地捕获和处理属性错误。
类型要求
const props = defineProps({
myName: String,
age:Number
})
validator验证器
test:{
validator(value){
return ['String','Number'....]
}
}
默认属性
通过设置default
属性来为属性提供默认值。当父组件没有向子组件传递该属性时,子组件将使用默认值作为属性的初始值。
const props= defineProps ({
name: {
type: String,
default: 'Qing Tian' // 设置默认值为'Qing Tian'
}
})
required
要求必须传递属性
const props= defineProps ({
name: {
type: String,
required:true //
}
})
属性透传
透传的现象
<!--父组件-->
<template>
<div>
<children class="test" id="testId" style="background-color: aqua;" />
</div>
</template>
<!--子组件-->
<template>
<div>
测试透传现象
</div>
</template>
浏览器查看
属性透传
(Attribute Inheritance)是一种机制,它允许你在一个组件上定义的属性被传递到该组件的子组件上,而无需在父组件和子组件之间显式声明这些属性。
优点:
-
简化代码: 属性透传可以减少在父组件和子组件之间重复声明相同属性的代码量。父组件传递的属性会自动应用到子组件,减少了代码的冗余。
-
更灵活的组合: 属性透传使得组件的组合更加灵活。父组件可以通过属性透传将自己的属性传递给子组件,而不需要了解子组件内部的具体实现细节,降低了组件之间的耦合性。
-
保持HTML特性: 如果父组件上使用了一些HTML特性,例如class、style等,属性透传可以确保这些特性传递到子组件,使得子组件能够正确地渲染这些特性。
缺点:
-
潜在的属性冲突: 属性透传可能导致属性冲突的问题。如果父组件和子组件都定义了相同的属性,那么子组件将无法接收到父组件相应的属性值。这时需要通过适当的命名约定或其他方法来避免属性冲突。
-
隐藏了子组件的API: 属性透传可能会隐藏子组件的API。父组件可以将任意属性传递给子组件,但这些属性对于子组件来说可能没有意义或不应该暴露给外部使用。这种情况下,开发者需要做好文档和注释的工作,以确保其他开发者正确使用组件。
-
增加了组件间的依赖性: 当父组件通过属性透传将属性传递给子组件时,父组件和子组件之间会建立更紧密的依赖关系。这可能会导致代码难以维护和理解,特别是当组件结构复杂、嵌套层级深时。
因此,在开发过程中,我们可能需要阻止掉这种行为,以保证我们封装的子组件的纯洁。此时我们需要用到v-bind="$attrs"
和inheritAttrs: false
来帮助我们。一般来说,父组件透传的属性都会集中到$attrs
上面,因此我们使用inheritAttrs: false
,阻止掉默认属性继承。在使用v-bind绑定到特定的元素上。
<template>
<div>
测试透传现象
<button v-bind="$attrs">按钮</button>
</div>
</template>
<script setup>
inheritAttrs=false,
</script>
子传父
通俗来讲,子传父就是子组件需要向父组件传递某个参数,或者调用父组件中的方法。因为单向数据流的存在我们不能让子组件直接修改父组件传递过来的数据,因此需要有子传父的方法。需要明白的是,父传子是不需要触发事件的,但是子传父需要事件的触发才能开始。
子传父有一个固定的写法:父组件中有一个自定义事件:event:"handletest"
,子组件接收这个自定义事件this.$emit("event")
<!--父组件-->
<template>
<div>
<Child @event="handTest">
</div>
</template>
<script setup>
const handTest = (data)=>{
console.log(data)
}
</script>
<!--子组件-->
<template>
<div>
<button @click="childClick">
</div>
</template>
<script setup>
import { defineEmits } from 'vue';
const emits = defineEmits(['event']) ;
const data = "test";
const childClick = () => {
emits('event', data);
};
</script>
Tip
在 Vue父子通信中:props down
event up
React父子通信只有:props down
props up
React中父传子与Vue一样,但是子传父是通过将父组件的方法传给子组件来模拟实现的。其实也很好理解,子传父就是子组件传递一个数据到父组件,然后再把数据放到父组件的方法中使用,所以React的处理就是直接把方法给到子组件。
一些不再被推荐或禁用的通信方法
$refs
:父组件直接对子组件进行操作的方法
在vue3中已经改变为,使用ref来创建响应式的对象
<template>
<div>
<button ref="myButton" @click="handleClick">点击按钮</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const myButton = ref(null);
const handleClick = () => {
console.log(myButton.value); // 访问引用
};
</script>
$parent
:是指向父组件实例的引用,不再推荐使用
$root
:是指向根组件实列的引用,不再推荐使用
跨级通信
provide&inject
中间人模式
传递
provide&inject模式(提供注入)
使用选项式API书写,
//提供数据的祖宗组件
provide(){
return {
testdata
}
}
//接收数据的子孙组件
inject:["testdata"]
需要注意的是,provide
和 inject
并不是响应式的。也就是说,如果提供的值发生变化,并不会触发子组件的重新渲染。如果需要在子组件中实现响应式,可以结合使用 props
和自定义事件来实现更灵活的通信方式。
使用组合式API书写
<!--提供数据的祖宗组件-->
<script setup>
import { ref, provide } from 'vue'
const testdata = ref('testdata')
provide('testdata',testdata)
</script>
<!--接收数据的子孙组件-->
<script setup>
import { inject } from 'vue'
const message = inject("testdata")
</script>
组合式API配合ref,数据就是响应式的啦。
订阅发布模式
订阅发布模式是一种解耦组件之间通信的方法,在项目中创建发布方法和订阅方法,并暴露出来。当组件调用发布方法,该组件就是一个发布者,负责发布消息,调用订阅方法的组件则成为订阅者,订阅并接收感兴趣的消息。
在生态篇,分享VueX和Pinia时会再重点描述此模式。
动态组件
动态组件可以根据不同的条件渲染不同的组件,动态组件的关键是通过特殊的标签 <component>
来指定动态组件的渲染目标。可以将动态组件的名称绑定到一个变量或表达式上,然后根据变量或表达式的值来决定要渲染哪个组件。
<template>
<div>
<component :is="components"></component>
<button @click="change">切换组件</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
const components = ref('ComponentA')
const change = ()=>{
components.value = components.value==='ComponentA'?'ComponentB':'ComponentA'
}
</script>
keep-alive
keepAlive
在组件切换以后,保持组件存活
<template>
<div>
<keepAlive>
<component :is="components"></component>
</keepAlive>
<keep-alive>
<component :is="components"></component>
</keep-alive>
</div>
</template>
使用include
属性来选择让那些组件保持存活。示例<keepAlive include="home,list"></keepAlive>
使用exclude
属性来选择让那些组件保持存活。示例<keepAlive exclude="home,list"></keepAlive>
组件中的v-model
在页面中,v-model
通常使用在input中做数据双向绑定,它是v-bind:value=""
和change
的语法糖。如果我们要使得封装的组件能够实现既能读取父组件的参数,又能够读取到子组件对数据的改变,则需要同时使用父传子和子传父两种方法。
示例(setup语法糖写法)
<template>
<div>
{{myValue}}
<Filed :value="myValue" @myevent="handleEvent"/>
<button @click="handleRegister">注册</button>
<button @click="handleReset">重置</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
import {Filed} from './Filed.vue'
const myValue = ref('aaaa')
const handleEvent = (value)=>{
myValue.value = value
}
const handleRegister = ()=>{
console.log(myValue.value)
}
const handleReset = ()=>{
myValue.value = ''
}
<script>
子组件
<template>
<div>
<input :type="type" :value="value" @input="handleInput" />
</div>
</template>
<script setup>
import {defineProps} from 'vue'
import { defineEmits } from 'vue';
const props = defineProps({
value:{
type:String,
default:''
}
})
const emits = defineEmits(['myevent'])
const handleInput = (evt)=>{
emits('myevent', evt.target.value);
}
</script>
假设要求我们封装的组件满足v-model的功能
<template>
<div>
{{myValue}}
<Filed v-model="myValue"/>
<button @click="handleRegister">注册</button>
<button @click="handleReset">重置</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
import {Filed} from './Filed.vue'
const myValue = ref('aaaa')
const handleEvent = (value)=>{
myValue.value = value
}
const handleRegister = ()=>{
console.log(myValue.value)
}
const handleReset = ()=>{
myValue.value = ''
}
<script>
<template>
<div>
<input :type="type" :value="modelValue" @input="handleInput" />
</div>
</template>
<script setup>
import {defineProps} from 'vue'
import { defineEmits } from 'vue';
const props = defineProps({
modelValue:{
type:String,
default:''
}
})
const emits = defineEmits(['update:modelValue'])
const handleInput = (evt)=>{
emits('update:modelValue', evt.target.value);
}
</script>
异步组件
异步组件
用来来延迟加载和渲染组件,以优化初始页面加载时间和减轻首屏压力。通过异步加载组件,你可以将组件的 JavaScript 代码和模板按需加载。
vue.component
提供了一个方法可以异步加载组件,叫做defineAsyncComponent
,示例:
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => import('./MyComponent.vue'))
为了防止异步加载时出现一些不可预料的错误,我们需要对异步加载方法进行错误配置
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => {
loader:()=>import('./MyComponent.vue'),
//正在加载时显示
loadingComponent:loadingComponent, //loadingComponent组件需要提前引入
delay:500, //延时时间
//加载失败或超时等错误
errorComponent:errorComponent,
timeout:3000,
})
插槽 - solt
插槽(slot)是一种用于组件之间内容分发的机制。它允许你在组件的模板中定义一个或多个插槽,然后在组件的使用者处插入内容。
<!--父组件-->
<template>
<div>
<Child>
<h1>这是一个插槽位置</h1>
</Child>
</div>
</template>
<script setup>
import Child from './Child.vue'
<script>
<!--子组件-->
<template>
<div>
child
<solt></slot>
</div>
</template>
具名插槽
在插槽中允许使用v-slot:
或#
搭配着子组件中的name
更细粒度地控制组件之间的内容分发,我们称之为具名插槽
,与默认插槽不同的是,插入时可以根据name插入到具体的位置。
<!--父组件-->
<template>
<div>
<Child>
<template v-slot:slot1>
<h1>内容插入到slot1</h1>
<template>
<template #slot2>
<h1>内容插入到slot2</h1>
<template>
</Child>
</div>
</template>
<script setup>
import Child from './Child.vue'
<script>
<!--子组件-->
<template>
<div>
child
<solt name='slot1'></slot>
<solt name='slot2'></slot>
</div>
</template>
作用域插槽
作用域插槽
允许你在使用组件时更灵活地传递数据和自定义内容的渲染。通过它,父组件可以向子组件传递数据,并且子组件可以根据这些数据自定义渲染逻辑。这提供了一种强大而灵活的方式来进行组件交互。
<!--父组件-->
<template>
<div>
<Child>
<template v-slot:"suibian">
<ul>
<li v-for="item in suibian.mylist" :key="item.id" >{{item}}</li>
</ul>
<template>
</Child>
</div>
</template>
<script setup>
import Child from './Child.vue'
<script>
<!--子组件-->
<template>
<div>
child
<solt :mylist="mydata">
<!--插槽没有传入的时候显示下面片段-->
<ul>
<li v-for="item in mydata" :key="item.id" >{{item}}</li>
</ul>
</slot>
</div>
</template>
<script setup>
import {ref} from 'vue'
let mydata = ref([testArr])
<script>
生命周期
生命周期勾子用于在组件 的不同阶段执行特定操作,下面是vue2中的写法:
-
beforeCreate
:在实例初始化之后、数据观测 (data observer) 和事件配置之前被调用。 -
created
:在实例创建完成后被调用,这时候实例已经完成了数据观测、属性和方法的运算,但是尚未挂载到 DOM 上。 -
beforeMount
:在挂载开始之前被调用,相关的 render 函数首次被调用。 -
mounted
:在挂载完成后被调用,此时组件已经被放入 DOM 中。 -
beforeUpdate
:在组件更新之前被调用,发生在重新渲染之前。 -
updated
:在组件更新之后被调用,发生在重新渲染之后。 -
beforeDestroy
:在实例销毁之前调用。在这一步,实例仍然完全可用。 -
destroyed
:在实例销毁之后调用。此时,所有的指令都已解绑,所有的事件监听器都已移除。
在vue3中,因为引入了组合式API,将组件逻辑转移到了setup
函数中,setup
函数就替代了beforeCreate和created勾子函数,因此在vue3中生命周期钩子发生了一些变化:
-
onBeforeMount
:在挂载之前执行的钩子函数。 -
onMounted
:在组件挂载到 DOM 后执行的钩子函数。 -
onBeforeUpdate
:在组件更新之前执行的钩子函数。 -
onUpdated
:在组件更新后执行的钩子函数。 -
onBeforeUnmount
:在组件卸载之前执行的钩子函数。 -
onUnmounted
:在组件卸载后执行的钩子函数。
自定义指令
除了Vue内置的一系列指令之外,Vue还允许用户注册自己的指令,自定义指令可以通过 directive
函数来创建。自定义指令可以用于在 DOM 元素上添加特定的行为或操作。
下面是自定义的一个图片懒加载指令,使用了vueuse
中的一个API方法useIntersectionObserver
来监测元素是否出现在视口中。
//定义图片懒加载指令
app.directive('img-lazy', {
/**
*
* @param {*} el 指令绑定的元素 (这里是img)
* @param {*} binding binding.value 指令等于号后面绑定的表达式的值 (这里是图片的url)
*/
mounted(el, binding) {
// console.log(el, binding.value);
const { stop } = useIntersectionObserver(
el,
([{ isIntersecting }]) => {
// console.log(isIntersecting);
if (isIntersecting) {
el.src = binding.value
stop()
}
},
)
}
})
过渡效果
在 Vue 3 中,使用过渡效果可以给组件在进入、离开或更新时添加动画效果。Vue 3 提供了 <Transition>
组件和 <TransitionGroup>
组件来实现过渡效果。
结尾
Vue3脚手架篇到此结束啦,下一篇一起来看看Vue的生态系统,在此之前会详解一下组合式API,然后写写VueRouter,VueX,Pinia,TS,UI组件库。
更多推荐
所有评论(0)