新建vue3项目
使用 npm:
# npm 6.x
$ npm init vite@latest <project-name> --template vue
# npm 7+,需要加上额外的双短横线
$ npm init vite@latest <project-name> -- --template vue
$ cd <project-name>
$ npm install
$ npm run dev
一、动态指令
使用中比较少用
<template>
<div>
<img alt="Vue logo" src="./assets/logo.png" @click="changeBtn" />
<div :[arributeName]="di"></div>
<button @[eventName]="changeBtn">按钮</button>
</div>
</template>
<script>
export default {
data(){
return{
di:'d1',
arributeName:'class',
eventName:'click'
}
},
methods:{
changeBtn(){
this.arributeName="id"
}
}
}
</script>
<style>
.d1{
width: 100px;
height: 100px;
background: red;
}
#d1{
width: 100px;
height: 100px;
background: goldenrod;
}
</style>
Vue3的七种组件通信方式
采用<script setup>
这种组合式API写法,相对于选项式来说,组合式API这种写法更加自由,具体可以参考Vue文档对两种方式的描述。
七种组件通信方式:
-
props
-
emit
-
v-model
-
refs
-
provide/inject
-
eventBus
-
vuex/pinia(状态管理工具)
上图中,列表和输入框分别是父子组件,根据不同传值方式,可能谁是父组件谁是子组件会有所调整。
Props方式
Props
方式是Vue中最常见的一种父传子的一种方式,使用也比较简单。
我们将数据以及对数据的操作定义在父组件,子组件仅做列表的一个渲染;
父组件代码如下:
<template>
<!-- 子组件 -->
<child-components :list="list"></child-components>
<!-- 父组件 -->
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="请输入"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
添加
</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// add 触发后的事件处理函数
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
</script>
子组件只需要对父组件传递的值进行渲染即可,代码如下:
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in props.list" :key="i">{{ i }}</li>
</ul>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
</script>
emit方式
emit
方式也是Vue中最常见的组件通信方式,该方式用于子传父;
根据上面的demo,我们将列表定义在父组件,子组件只需要传递添加的值即可。
子组件代码如下:
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="请输入"
/>
<div class="input-group-append">
<button @click="handleSubmit" class="btn btn-primary" type="button">
添加
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits } from 'vue'
const value = ref('')
const emits = defineEmits(['add'])
const handleSubmit = () => {
emits('add', value.value)
value.value = ''
}
</script>
在子组件中点击【添加】按钮后,emit
一个自定义事件,并将添加的值作为参数传递。
父组件代码如下:
<template>
<!-- 父组件 -->
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
<!-- 子组件 -->
<child-components @add="handleAdd"></child-components>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
// add 触发后的事件处理函数
const handleAdd = value => {
list.value.push(value)
}
</script>
在父组件中只需要监听子组件自定义的事件,然后执行对应的添加操作。
v-model方式
v-model
是Vue中一个比较出色的语法糖,就比如下面这段代码
<ChildComponent v-model:title="pageTitle" />
就是下面这段代码的简写形式
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
v-model
确实简便了不少,现在我们就来看一下上面那个demo,如何用v-model
实现。
子组件:
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="请输入"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
添加
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineEmits, defineProps } from 'vue'
const value = ref('')
const props = defineProps({
list: {
type: Array,
default: () => [],
},
})
const emits = defineEmits(['update:list'])
// 添加操作
const handleAdd = () => {
const arr = props.list
arr.push(value.value)
emits('update:list', arr)
value.value = ''
}
</script>
在子组件中我们首先定义props
和emits
,然后添加完成之后emit
指定事件。
注:
update:*
是Vue中的固定写法,*
表示props中的某个属性名。
父组件中使用就比较简单,代码如下:
<template>
<!-- 父组件 -->
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
<!-- 子组件 -->
<child-components v-model:list="list"></child-components>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
</script>
refs方式
在使用选项式API时,我们可以通过this.$refs.name
的方式获取指定元素或者组件,但是组合式API中就无法使用哪种方式获取。如果我们想要通过ref
的方式获取组件或者元素,需要定义一个同名的Ref
对象,在组件挂载后就可以访问了。
示例代码如下:
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in childRefs?.list" :key="i">
{{ i }}
</li>
</ul>
<!-- 子组件 ref的值与<script>中的保持一致 -->
<child-components ref="childRefs"></child-components>
<!-- 父组件 -->
</template>
<script setup>
import { ref } from 'vue'
import ChildComponents from './child.vue'
const childRefs = ref(null)
</script>
子组件代码如下:
<template>
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="请输入"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
添加
</button>
</div>
</div>
</template>
<script setup>
import { ref, defineExpose } from 'vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// add 触发后的事件处理函数
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
defineExpose({ list })
</script>
setup
组件默认是关闭的,也即通过模板ref
获取到的组件的公开实例,不会暴露任何在<script setup>
中声明的绑定。如果需要公开需要通过defineExpose
API暴露。
provide/inject方式
provide
和inject
是Vue中提供的一对API,该API可以实现父组件向子组件传递数据,无论层级有多深,都可以通过这对API实现。示例代码如下所示:
父组件
<template>
<!-- 子组件 -->
<child-components></child-components>
<!-- 父组件 -->
<div class="child-wrap input-group">
<input
v-model="value"
type="text"
class="form-control"
placeholder="请输入"
/>
<div class="input-group-append">
<button @click="handleAdd" class="btn btn-primary" type="button">
添加
</button>
</div>
</div>
</template>
<script setup>
import { ref, provide } from 'vue'
import ChildComponents from './child.vue'
const list = ref(['JavaScript', 'HTML', 'CSS'])
const value = ref('')
// 向子组件提供数据
provide('list', list.value)
// add 触发后的事件处理函数
const handleAdd = () => {
list.value.push(value.value)
value.value = ''
}
</script>
子组件
<template>
<ul class="parent list-group">
<li class="list-group-item" v-for="i in list" :key="i">{{ i }}</li>
</ul>
</template>
<script setup>
import { inject } from 'vue'
// 接受父组件提供的数据
const list = inject('list')
</script>
值得注意的是使用provide
进行数据传递时,尽量readonly
进行数据的包装,避免子组件修改父级传递过去的数据。
事件总线
Vue3中移除了事件总线,但是可以借助于第三方工具来完成,Vue官方推荐mitt
或tiny-emitter
;
在大多数情况下不推荐使用全局事件总线的方式来实现组件通信,虽然比较简单粗暴,但是长久来说维护事件总线是一个大难题,所以这里就不展开讲解了,具体可以阅读具体工具的文档。
状态管理工具
Vuex和Pinia是Vue3中的状态管理工具,使用这两个工具可以轻松实现组件通信,由于这两个工具功能比较强大,这里就不做展示了,具体可以查阅文档。
事件
组件
全局组件
传参使用content-abc接受值使用驼峰接受
NON-propds
通信
插槽
报错
下面的正确
简写#header
作用域插槽
app.component("list",{
data(){
return{
list:['1','2','3']
}
},
template: `
<slot v-for="item in list" :item="item"></slot>
`
})
显示效果
具名插槽使用
显示效果
使用
显示效果
注意点:
1)组件中定义具名插槽使用<slot name='插槽名'></slot>
2)其它组件中使用具名插槽时必须使用template进行包裹并写上插槽名。
使用具名插槽时每次都要写v-slot特别麻烦,可以使用#进行简写,如下:**
<template #header>
<header>我是头部区域</header>
</template>
<template #footer>
<footer>我是底部区域</footer>
</template>
动态组件、异步组件
<!-- 动态组件和异步组件-->
<!-- <input-item v-show="currentItem==='input-item'"></input-item>-->
<!-- <world-item v-show="currentItem==='world-item'"></world-item>-->
<!-- 动态组件根据数据的变化。给合component标签来随时切换组件的显示-->
<keep-alive> <!-- 缓冲组建的内容-->
<component :is="currentItem"></component>
</keep-alive>
methods:{
changeBtn(){
if(this.currentItem==='input-item'){
this.currentItem='world-item'
}else{
this.currentItem='input-item'
}
console.log(123)
},
},
//动态组件
app.component("input-item",{
template:`<input />`
})
app.component("world-item",{
template:`<div>hello world</div>`
})
//异步组件
//异步组件,过四秒之后显示组件的内容
const asyncCommonItem=Vue.defineAsyncComponent( () => {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve({
template:`<div>This a async component</div>`
})
},4000)
})
})
//异步组件
app.component('async-common-item',asyncCommonItem)
补充语法:
v-once:只渲染一次
ref(获取dom节点):mounted中获取dom
获取组件使用:调用组件中的方法
this.$refs.commone.methedsName()
父组件传值给孙子组件 provide/inject
<h2>父组件传值给孙子组件 provide/inject</h2>
<div>
<parent-item :count="count">
</parent-item>
</div>
const Counter={
data(){
return {
num:0,
num1:'a',
currentItem:'input-item',
count:2121
}
},
//
provide: {
count:2121
},
//如果想要将data中的数据进行传值
provide(){
return{
count:this.count
}
},
}
//组件嵌套传值
app.component("child-item",{
// props:['currentItem'],
inject:['count'],
template:`<input :value="count"/>`
})
app.component("parent-item",{
// props:['count'],
template:`<div >parent-item<child-item></child-item> </div>`
})
vue动画:
添加class类名
背景色渐变
另一种实现方法
list动画、通过数据控制动画
<h2>list动画</h2>
<div>
<transition-group>
<span class="list-item" v-for="(item,index) in list" :key="item" @click="delItem(index)"> {{item}}</span>
</transition-group>
</div>
<button @click="addBtn">按钮</button>
<h2>状态动画,svg通过数据改变控制动画</h2>
<span> {{animateNumber}}</span>
<button @click="addBtn">按钮</button>
.list-item{
display: inline-block;
padding: 10px;
}
.v-enter-from{
opacity: 0;
transform: translateY(30px);
}
.v-enter-active{
transition: all .5s ease-in;
}
.v-enter-to{
opacity: 0;
transform: translateY(0);
}
/*其他列表项移动*/
.v-move{
transition: all .5s ease-in;
}
.v-leave-from{
opacity: 1;
transform: translateY(0);
}
.v-leave-active{
transition: all .5s ease-out;
}
.v-leave-to{
opacity: 0;
transform: translateY(30px);
}
data(){
return {
num1:'a',
currentItem:'input-item',
count:2121,
list:['1','2','3'],
num:1,
animateNumber:1,
}
},
methods:{
addBtn(){
this.list.unshift(this.list.length+1)
console.log(this.animateNumber)
this.num=10
if(this.animateNumber<this.num){
let animation=setInterval(()=>{
this.animateNumber+=1
if(this.animateNumber===10){
clearInterval(animation)
}
},100)
}
},
delItem(index){
console.log(index)
this.list.splice(index,1)
}
},
maxin混入
组件内部data优先级高于mixin(混入)的data的优先级
局部
const myMixin={
//data混入,会被组件内部的data覆盖
data() {
return {
number: 2,
count2: 3
};
},
// 生命周期函数混入,不会覆盖,先执行mixin里的生命周期函数,在执行组件内部的
// 组件内部的methods,高于mixin中的方法
}
const Counter={
data(){
return {
num1:'a',
currentItem:'input-item',
count:2121,
list:['1','2','3'],
num:1,
animateNumber:1,
// minix混入
number:1,
}
},
// template: `
// <child />
// `,
// mixins: [myMixin],
.......
const app =Vue.createApp(Counter)
// 子组件中使用mixin时也需要在子组件中注册后使用
app.component('child',{
// mixins: [myMixin],
template:`<span>{{count2}}</span>`
})
全局mixin
//全局mixin
app.mixin({
//data混入,会被组件内部的data覆盖
data() {
return {
number: 2,
count2: 3
};
},
// 生命周期函数混入,不会覆盖,先执行mixin里的生命周期函数,在执行组件内部的
// 组件内部的methods,高于mixin中的方法
})
自定义属性mixin
<div id="point">
<span>{{this.$options.number}}</span>
</div>
自定义属性,组件里的自定义属性优先级高于mixin里的
const myMixin={
//不在data里,的属性称为自定义属性
number:1
}
const Counter={
number: 3,
mixins: [myMixin],
methods:{
delItem(index){
}
},
}
// 设计模式: 面向数据编程 MVVM: M->model数据 v->view视图 vm->viewmodel 视图数据连接层
const app =Vue.createApp(Counter)
//修改优先级方式
app.config.optionMergeStrategies.number=(mixinVal,appVal)=>{
return mixinVal|| appVal
}
自定义指令
全局
//自定义指令
// app.directive('focus',{
// mounted(el) {
// el.focus()
// }
// })
使用时直接v-focus
局部
const directives={
focus:{
mounted(el) {
el.focus()
}
}
}
const Counter={
directives:directives,
}
使用时
<input v-focus />
动态修改值改变显示
<div v-pos="top" class="head">
<input />
</div>
const Counter={
data(){
return {
top:100
}
},
// directives:directives,
mounted(){
// this.$refs.input.focus()
}
}
// 设计模式: 面向数据编程 MVVM: M->model数据 v->view视图 vm->viewmodel 视图数据连接层
const app =Vue.createApp(Counter)
//自定义指令
app.directive('focus',{
mounted(el) {
el.focus()
},
beforeUpdate(){
},
updated(){
},
beforeUnmount(){
},
unmounted(){
}
})
//接收参数,动态改变
app.directive('pos',{
mounted(el,binding){
el.style.top=(binding.value+'px')
},
updated(el,binding){
el.style.top=(binding.value+'px')
},
})
// 等价写法
app.directive('pos',(el,binding)=>{
el.style.top=(binding.value+'px')
},)
使用arg
<div v-pos:left="distance" class="head">
<input />
</div>
app.directive('pos',(el,binding)=>{
console.log(binding)
el.style[binding.arg]=(binding.value+'px')
},)
vue3 特性
Teleport
当我们需要在真个浏览器显示遮罩层时,可使用:
下面为不使用时在组件中显示遮罩,没有显示全屏遮罩
<div class="area">
<button @click="hello=!hello">按钮mask</button>
<div class="mask" v-show="hello"></div>
</div>
<div class="area">
<button @click="hello=!hello">按钮mask</button>
<telePort to="body">
<div class="mask" v-show="hello"></div>
</telePort>
</div>
使用后,显示效果,to表示相对于那个元素显示
render-function
使用render替换template
<div id="point">
<my-title :level="1">
hello
</my-title>
</div>
<script>
const Counter={
data(){
return {
hello:false
}
}
}
// 设计模式: 面向数据编程 MVVM: M->model数据 v->view视图 vm->viewmodel 视图数据连接层
const app =Vue.createApp(Counter)
app.component('my-title',{
props:['level'],
render(){
const {h} = Vue
return h('h'+this.level,{},this.$slots.default())
}
})
const vm=app.mount('#point')
template在底层被编译会生成render函数,render调用Vue中的h方法返回一个虚拟dom
虚拟DOM:一个dom节点的js对象表述(真是dom的js映射)
// 真实DOM <div> hello </div> //转化为虚拟dom,跨平台(weex),编译快 { tagName:'div', text:'hello', attributes:{} }
render(){
const {h} = Vue
//虚拟DOM
//h(标签,属性,可写成数组[])
return h('h'+this.level,{},[this.$slots.default(),h('h4',{},'dell')])
}
插件的定义和使用
//plugin 也是把通用的功能封装起来
const myPlugin={
install(app,options){
app.provide('name','dell lee')
app.directive('focus',{
mounted(el){
el.focus()
}
})
app.mixin({
mounted(){
console.log('mixin mounted')
}
})
app.config.globalProperties.$myhello = "see hello"
console.log(app,options)
}
}
const Counter={
}
// 设计模式: 面向数据编程 MVVM: M->model数据 v->view视图 vm->viewmodel 视图数据连接层
const app =Vue.createApp(Counter)
app.use(myPlugin,{name:'dell'})
app.component('my-title',{
props:['level'],
inject:['name'],
mounted(){
console.log(this.$myhello ,'---')
},
template:`<div>{{name}}</div> <input v-focus />`
})
const vm=app.mount('#point')
数据校验实现
不封装成插件写法
const Counter={
data(){
return {
name:'li lee',
age:25
}
},
rules:{
age:{
validate:(age) => age >20,
message:'too young!!'
}
}
}
// 设计模式: 面向数据编程 MVVM: M->model数据 v->view视图 vm->viewmodel 视图数据连接层
const app =Vue.createApp(Counter)
app.mixin({
created(){
for(let key in this.$options.rules){
const item =this.$options.rules[key]
console.log('item----',item)
this.$watch(key,(value)=>{
let result=item.validate(value)
if(!result){
console.log(item.message)
}
})
}
}
})
封装成插件写法
const validatorPlugin =(app,options)=>{
app.mixin({
created(){
for(let key in this.$options.rules){
const item =this.$options.rules[key]
console.log('item----',item)
this.$watch(key,(value)=>{
let result=item.validate(value)
if(!result){
console.log(item.message)
}
})
}
}
})
}
// 设计模式: 面向数据编程 MVVM: M->model数据 v->view视图 vm->viewmodel 视图数据连接层
const app =Vue.createApp(Counter)
app.use(validatorPlugin)
setup
<div id="point">
<div @click="handleClick">name:{{name}} age:{{age}}</div>
</div>
<script>
//template->render->h函数->虚拟dom(js对象)->真实(DOM)->展示到页面
const Counter={
methods:{
test(){
console.log(this.$options)
}
},
mounted(){
this.test()
},
//created 实例被完全初始化之前、setup里不可调用外部的方法,因为拿不到app的实例,return的内容可被模板使用
setup(props,context){
return {
name:'li lee',
age:25,
handleClick:()=>{
alert(123)
}
}
}
ref、reactive响应式引用的用法和实例
//ref、reactive响应式引用
//原理通过proxy数据进行封装,待数据变化时,触发模板等内容的更新
//ref 处理基础类型的数据
const Counter={
setup(props,context){
const {ref} =Vue
// proxy,dell 变成proxy({value:'dell'})这样一个响应式应用
let name=ref('dell');
setTimeout(()=>{
name.value='lee'
},2000)
return { name }
}
}
// 设计模式: 面向数据编程 MVVM: M->model数据 v->view视图 vm->viewmodel 视图数据连接层
const app =Vue.createApp(Counter)
ref 处理基础类型的数据
reactive,处理非基础类型的数据
<div id="point">
<div> {{nameObj.name}}{{nameArry[0]}}</div>
</div>
const Counter={
setup(props,context){
const { reactive,readonly ,toRefs} =Vue
let nameObj=reactive({name:'delll'});
let nameArry=reactive([123]);
setTimeout(()=>{
nameObj.name='lee'
nameArry[0]=456
},2000)
// torefs,会把proxy({value:'dell'})转化成 {name:proxy({value:'dell'})}
const { name } =toRefs(nameObj)
return { name ,nameArry}
}
}
Composition Api开发todoList
不封装写法
<template>
<div id="point">
<input :value="inputValue" @input="inputChangeValue"/>
<div>{{inputValue}}</div>
<button @click="btn">提交</button>
<div>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}}</li>
<li>2</li>
</ul>
</div>
</div>
<script>
const Counter={
setup(props,context){
const {ref,reactive}=Vue
let inputValue=ref(123)
let list =reactive([])
const inputChange=(e)=>{
inputValue.value=e.target.value
}
function btn(){
list.push(inputValue.value)
}
return {inputValue,inputChange,btn,list}
}
}
封装后的写法
<template>
<div id="point">
<input :value="inputValue" @input="inputChangeValue"/>
<div>{{inputValue}}</div>
<button @click="()=>addItemList(inputValue)">提交</button>
<div>
<ul>
<li v-for="(item,index) in list" :key="index">{{item}}</li>
<li>2</li>
</ul>
</div>
</div>
<script>
//ref、reactive响应式引用
//原理通过proxy数据进行封装,待数据变化时,触发模板等内容的更新
//ref 处理基础类型的数据
//关于list的操作的内容进行了封装
const listRelativeEffect = () => {
const { reactive } =Vue
let list = reactive([])
//传参item
const addItemList = (item) => {
list.push(item)
}
return {list,addItemList}
}
// 封装关于inputValue的操作封装
const inputRelativeEffect=()=>{
const { ref } =Vue
let inputValue = ref('默认值')
const inputChangeValue=(e)=>{
inputValue.value=e.target.value
}
return {inputValue,inputChangeValue}
}
const Counter={
setup(props,context){
// 封装后的写法,流程调度中转
const {addItemList,list}=listRelativeEffect()
const {inputValue,inputChangeValue}=inputRelativeEffect()
return{ addItemList,list,inputValue,inputChangeValue}
}
}