vue3.0之组合API有哪些(详解)

vue3.0之组合API有哪些

  • 一、setup函数
  • 二、生命周期
  • 三、reactive函数
  • 四、toRef函数
  • 五、toRefs函数
  • 六、ref函数
  • 七、知识运用案例
  • 八、computed函数
  • 九、watch函数
  • 十、ref属性
  • 十一、父子通讯
    • 1.父传子
    • 2.子传父
  • 十二、依赖注入
  • 十三、补充 v-model语法糖(简写)
  • 十四、补充 mixins语法

一、setup函数

  • setup 是一个新的组件选项,作为组件中使用组合API的起点
  • 从组件生命周期来看,它的执行在组件实例创建之前vue2.x的beforeCreate执行
  • 这就意味着在setup函数中 this 还不是组件实例,this 此时是 undefined
  • 在模版中需要使用的数据和函数,需要在 setup 返回

演示代码

<template>
  <div class="container">
    <h1 @click="say()">{{msg}}</h1>
  </div>
</template>
<script>
export default {
  setup () {
    console.log('setup执行了')
    console.log(this) //undefined
    // 定义数据和函数
    const msg = 'hi vue3'
    const say = () => {
      console.log(msg)
    }

    return { msg , say}  //必须以对象形式返回
  },
  beforeCreate() {
    console.log('beforeCreate执行了')
    console.log(this)
  }
}
</script>

二、生命周期

vue2.0 vue3.0
beforeCreate setup 创建实例前
created
beforeMount onBeforeMount 挂载DOM前
mounted onMounted 挂载DOM后
beforeUpdate onBeforeUpdate 更新组件前
updated onUpdated 更新组件后
beforeDestroy onBeforeUnmount 卸载销毁前
destroyed onUnmounted 卸载销毁后
  • 注意
    • 1.页面中钩子函数需要先从vue引入
    • 2.需要写在setup函数内部
    • 3.可以多次使用同一个钩子,执行顺序和书写顺序相同

实例代码

<template>
  <div class="container">
    container
  </div>
</template>
<script>
import { onBeforeMount, onMounted } from 'vue'
export default {
  setup () {
    onBeforeMount(()=>{
      console.log('DOM渲染前',document.querySelector('.container'))
    })
    onMounted(()=>{
      console.log('DOM渲染后1',document.querySelector('.container'))
    })
    onMounted(()=>{
      console.log('DOM渲染后2',document.querySelector('.container'))
    })
  },
}
</script>

三、reactive函数

  • reactive是一个函数,它可以定义一个复杂数据类型,成为响应式数据。
  • 通常是用来定义响应式对象数据

实例代码

<template>
    <div>
        <div>名字{{ obj.name }}</div>
        <div>年龄{{ obj.age }}</div>
        <button @click="updateName()">更改名字</button>
    </div>
</template>
  
<script>
import { reactive } from 'vue'
export default {
    name: 'App',
    setup() {
        //定义对象响应式数据
        const obj = reactive({
            age: 18,
            name: '李四'
        })
        //修改名字
        const updateName = () => {
            obj.name = '张三'
            console.log('更改了', obj.name)
        }
        return { obj, updateName }
    }
}
</script>

四、toRef函数

  • toRef是函数,转换响应式对象中某个属性为单独响应式数据,并且值是关联的
  • 使用场景:有一个响应式对象数据,但是模版中只需要使用其中一项数据

示例代码

<template>
  <div class="container">
    {{name}} <button @click="updateName">修改数据</button>
  </div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
  name: 'App',
  setup () {
    // 1. 响应式数据对象
    const obj = reactive({
      name: 'ls',
      age: 10
    })
    console.log(obj)
    // 2. 模板中只需要使用name数据
    // 注意:从响应式数据对象中解构出的属性数据,不再是响应式数据
    // let { name } = obj 不能直接解构,出来的是一个普通数据
    const name = toRef(obj, 'name')
    // console.log(name)
    const updateName = () => {
      console.log('updateName')
      // toRef转换响应式数据包装成对象,value存放值的位置
      name.value = 'zs'
    }

    return {name, updateName}
  }
}
</script>

五、toRefs函数

  • toRefs是函数,转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的
  • 剥离响应式对象(解构|展开),想使用响应式对象中的多个或者所有属性做为响应式数据
<template>
    <div>
        <div>名字{{ name }}</div>
        <button @click="updateName()">更改名字</button>
    </div>
</template>
  
<script>
import { reactive, toRef, toRefs } from 'vue'
export default {
    name: 'App',
    setup() {
        //1.定义响应式数据对象
        const obj = reactive({
            age: 18,
            name: '李四'
        })
        //obj1 具有响应式
        const obj1 = toRefs(obj)
        console.log('打印obj1', obj1)
        const updateName = () => {
            //更改方式1
            // obj1.name.value = "张三"
            //更改方式2 (直接更改响应式数据)
            obj.name = "张三"
        }
        return { ...obj1, updateName }
    }
}
</script>

六、ref函数

  • ref函数,常用于简单数据类型定义为响应式数据
  • 在修改值,获取值的时候,需要.value
  • 在模板中使用ref申明的响应式数据,可以省略.value
  • 注意
    • 当你明确知道需要的是一个响应式数据 对象 那么就使用 reactive 即可
    • 其他情况使用ref

示例代码

<template>
    <div>
        <div>名字{{ name }}</div>
        <button @click="updateName()">更改名字</button>
    </div>
</template>
  
<script>
import { ref } from 'vue';
export default {
    name: 'App',
    setup() {
        const name = ref('李四')
        const updateName = () => {
            name.value = '张三'
        }
        return { name, updateName }
    }
}
</script>

七、知识运用案例

vue3.0之组合API有哪些(详解)_第1张图片

<template>
  <div class="container">
    <div>坐标</div>
    <div>x: {{x}}</div>
    <div>y: {{y}}</div>
    <hr>
    <div>{{count}} <button @click="add">累加1</button></div>
  </div>
</template>
<script>
import { onMounted, onUnmounted, reactive , ref, toRefs} from 'vue'
const useMouse = () => {
    // 1. 记录鼠标坐标
    // 1.1 申明一个响应式数据,他是一个对象,包含x y
    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', move)
    })

    return mouse
}
export default {
  name: 'App',
  setup () {

    const mouse = useMouse()

    // 2. 数字累加
    const count = ref(0) 
    const add = () => {
      count.value ++
    }
    return { ...toRefs(mouse), count, add }
  }
}
</script>

八、computed函数

  • computed函数,是用来定义计算属性的,计算属性不能修改。
  • 总结
    • 给computed传入函数,返回值就是计算属性的值
    • 给computed传入对象,get获取计算属性的值,set监听计算属性改变。
<template>
    <div>
        <div>今年{{ age }}</div>
        <div>后年{{ newAge }}</div>
        <input v-model="newAge">
    </div>
</template>
  
<script>
import { computed, ref } from 'vue'
export default {
    name: 'App',
    setup() {
        //计算属性使用场景 需要依赖响应式数据得到一个新的数据
        const age = ref(18)
        // const newAge = computed(() => {

        //   return age.value + 2
        // })
        //计算属性高级用法
        const newAge = computed({
            //get方法 取值
            get() {
                return age.value + 2
            },
            //set方法 赋值
            set(val) {
                age.value = val - 2
                console.log('打印value', val)
            }
        })
        return { age, newAge }
    }
}
</script>

九、watch函数

  • 1.监听ref定义的响应式数据
  • 2.监听多个响应式数据
  • 3.监听reactive定义的响应式数据
  • 4.监听reactive定义的响应式数据,某一个属性
  • 5.深度监听和默认执行
<template>
  <div>
    <div>count的数量{{ count }}</div>
    <button @click="watchCount">监听ref</button>
    <hr>
    <div>名字:{{ obj.name }}</div>
    <div>年龄:{{ obj.age }}</div>
    <button @click="watchMore">监听多个响应式</button>
    <hr>
    <button @click="watchDeep">深度监听</button>
  </div>
</template>

<script>
import { reactive, ref, watch } from 'vue';
export default {
  name: 'App',
  setup() {
    //1.监听ref定义的响应式数据
    const count = ref(18)
    const watchCount = () => {
      count.value++
    }
    //第一个参数是监听的目标 
    //第二个参数为改变后触发的函数
    //第三个参数为配置项
    watch(count, (newval, oldval) => {
      console.log('ref变化了', newval, oldval)
    })

    //2.监听多个响应式数据
    const obj = reactive({
      name: '张三',
      age: '18',
      brand: {
        id: 1,
        name: '宝马'
      }
    })
    const watchMore = () => {
      obj.name = '李四'
    }
    watch(obj, (newVal, oldVal) => {
      console.log('多个响应式改变了', newVal, oldVal)
    })
    //3.监听多个响应式数据的变化
    watch([obj, count], (newVal, oldVal) => {
      console.log('监听多个响应式数据的变化', newVal, oldVal)
    })
    //4.监听reactive定义的某一个属性 例如obj.name
    watch(obj.name, (newVal, oldVal) => {
      console.log('监听reactive定义的某一个属性', newVal, oldVal)
    })
    //5.深度监听 和默认执行
    //()=>obj.brand return返回
    watch(() => obj.brand, (newVal, oldVal) => {
      console.log('深度监听并且默认执行', newVal, oldVal)
    }, {
      deep: true, //深度监听
      immediate: true //默认执行
    })
    const watchDeep = () => {
      obj.brand.id = 2
    }
    return { count, obj, watchCount, watchMore, watchDeep }
  }
}
</script>

十、ref属性

  • 单个元素:先申明ref响应式数据,返回给模版使用,通过ref绑定数据
  • 遍历的元素:先定义一个空数组,定一个函数获取元素,返回给模版使用,通过ref绑定这个函数
    • 有一个边界问题:组件更新的时候(增加li标签)会重复的设置dom元素给数组
// ref获取v-for遍历的DOM元素,需要在组件更新的时候重置接受dom元素的数组。
    onBeforeUpdate(()=>{
      list = []
    })

实例代码

<template>
  <div>
    <!-- vue2.0获取div元素 -->
    <!-- 1. 通过ref属性绑定该元素 -->
    <!-- 2. 通过this.$refs.box获取元素 -->
    <!-- <div ref="box">vue2.0获取div元素</div> -->
    <!-- vue2.0 获取v-for遍历的多个元素 -->
    <!-- 1. 通过ref属性绑定被遍历元素 -->
    <!-- 2. 通过this.$refs.li 获取所有遍历元素  -->
    <!-- <ul>
      <li v-for="i in 4" :key="i" ref="li">{{i}}</li>
    </ul> -->
    
    <!-- vue3.0获取div元素 -->
    <!-- 单个元素 -->
    <div ref="onebox">单个元素</div>
    <hr>
    
    <!-- 获取v-for遍历的DOM或者组件 -->
    <ul>
      <li v-for="i in 4" :key="i" :ref="setDom">{{ i }}</li>
    </ul>
  </div>
</template>

<script>
import { ref } from 'vue';
export default {
  name: 'App',
  setup() {
    //获取单个dom
    //1.先定义一个空的响应式数据ref定义的 并返回
    //2.想获取那个dom元素,在该元素使用ref绑定即可
    const onebox = ref(null)
    console.log('onebox', onebox);

    // 获取多个 v-for遍历
    //1.定义一个空数组 接收所有的li标签
    const domList = []
    //2.定义一个函数,往数组中插入dom
    const setDom = (li) => {
      console.log('setDom', li)
      domList.push(li)
      console.log('domList', domList)
    }
    return { onebox, setDom }
  }
}
</script>

十一、父子通讯

1.父传子

父组件

<template>
  <div class="container">
    <h1>父组件</h1>
    <p>{{money}}</p>
    <hr>
    <Son :money="money" />
  </div>
</template>
<script>
import { ref } from 'vue'
import Son from './Son.vue'
export default {
  name: 'App',
  components: {
    Son
  },
  // 父组件的数据传递给子组件
  setup () {
    const money = ref(100)
    return { money }
  }
}
</script>

子组件

<template>
    <div>
        <h1>子组件</h1>
        <p>money{{ money }}</p>
    </div>
</template>

<script>
import { onMounted } from 'vue';

export default {
    //子组件接收父组件传过来的值
    props: {
        money: {
            type: Number,
            default: 0
        }
    },
    setup(props) {
        //获取父组件的数据 money
        console.log('props传参', props.money)

        //获取失败
        onMounted(() => {
            // console.log('子组件money', this.money);
        })
    }
}
</script>

2.子传父

父组件

<template>
  <div>
    <h1>父组件</h1>
    <p>money{{ money }}</p>
    <hr>
    <Son :money="money" @changeBtn="changeBtn"></Son>
  </div>
</template>

<script>
import { ref } from 'vue';
import Son from './components/son.vue';
export default {
  components: {
    Son
  },
  name: 'App',
  setup() {
    //1.父组件数据 传递给子组件
    const money = ref(100)

    //定义子组件调用父组件的方法
    const changeBtn = (newMoney) => {
      console.log('子组件调用父组件的方法', newMoney)
      money.value = newMoney
    }
    return { money, changeBtn }
  }
}
</script>

子组件

<template>
    <div>
        <h1>子组件</h1>
        <p>money{{ money }}</p>
        <button @click="changeBtn()">花费50</button>
    </div>
</template>

<script>
import { onMounted } from 'vue';

export default {
    //子组件接收父组件传过来的值
    props: {
        money: {
            type: Number,
            default: 0
        }
    },
    //props父组件数据 
    //context 发现(包含emit方法) 触发自定义事件的函数
    //可解构 {emit}
    setup(props, context) {
        //获取父组件的数据 money
        console.log('props传参', props.money)

        //定义函数
        const changeBtn = () => {
            console.log('花费50元', context)
            //通知父组件
            context.emit('changeBtn', 50)
        }

        return { changeBtn }
    }
}
</script>

补充

  • 在vue2.x的时候 .sync 除去v-model实现双向数据绑定的另一种方式
<!-- <Son :money='money' @update:money="fn"  /> -->
<Son :money.sync='money'  />
  • 在vue3.0的时候,使用 v-model:money="money" 即可
//子组件 改值方法内
 //简写
context.emit('update:money', 50)

//父组件
<!-- 简写前 -->
    <!-- <Son :money="money" @update:money="changeBtn"></Son> -->
    <!-- 简写后 -->
    <Son v-model:money="money"></Son>

十二、依赖注入

使用场景

  • 有一个父组件,里头有子组件,有孙组件,有很多后代组件,共享父组件数据。
  • 父组件
<template>
  <div class="container">
    <h1>父组件 {{money}} <button @click="money=1000">发钱</button></h1>
    <hr>
    <Son />
  </div>
</template>
<script>
import { provide, ref } from 'vue'
import Son from './Son.vue'
export default {
  name: 'App',
  components: {
    Son
  },
  setup () {
    const money = ref(100)
    const changeMoney = (saleMoney) => {
      console.log('changeMoney',saleMoney)
      money.value = money.value - saleMoney
    }
    // 将数据提供给后代组件 provide
    provide('money', money)
    // 将函数提供给后代组件 provide
    provide('changeMoney', changeMoney)

    return { money }
  }
}
</script>
  • 子组件
<template>
    <div>
        <h2>子组件</h2>
        <p>money{{ money }}</p>
        <Sun></Sun>
    </div>
</template>

<script>
import Sun from './sun.vue';
import { inject } from 'vue'
export default {
    components: {
        Sun
    },

    setup() {
        //接收祖先传递的数据 inject
        const money = inject('money')
        return { money }
    }
}
</script>
  • 孙组件
<template>
  <div class="container">
    <h3>孙组件 {{money}} <button @click="fn">消费20</button></h3>
  </div>
</template>
<script>
import { inject } from 'vue'
export default {
  name: 'GrandSon',
  setup () {
    const money = inject('money')
    // 孙组件,消费50,通知父组件App.vue组件,进行修改
    // 不能自己修改数据,遵循单选数据流原则,大白话:数据谁定义谁修改
    const changeMoney = inject('changeMoney')
    const fn = () => {
      changeMoney(20)
    }
    return {money, fn}
  }
}
</script>

总结

  • provide函数提供数据和函数给后代组件使用
  • inject函数给当前组件注入provide提供的数据和函数

十三、补充 v-model语法糖(简写)

  • 在vue2.0中v-mode语法糖简写的代码
  • 在vue3.0中v-model语法糖有所调整:

父组件

<template>
  <div class="container">
    <!-- 如果你想获取原生事件事件对象 -->
    <!-- 如果绑定事函数 fn fn(e){ // e 就是事件对象 } -->
    <!-- 如果绑定的是js表达式  此时提供一个默认的变量 $event -->
    <h1 @click="$event.target.style.color='red'">父组件 {{count}}</h1>
    <hr>
    <!-- 如果你想获取自定义事件  -->
    <!-- 如果绑定事函数 fn fn(data){ // data 触发自定义事件的传参 } -->
    <!-- 如果绑定的是js表达式  此时 $event代表触发自定义事件的传参 -->
    <!-- <Son :modelValue="count" @update:modelValue="count=$event" /> -->
    <Son v-model="count" />
  </div>
</template>
<script>
import { ref } from 'vue'
import Son from './Son.vue'
export default {
  name: 'App',
  components: {
    Son
  },
  setup () {
    const count = ref(10)
    return { count }
  }
}
</script>

子组件

<template>
  <div class="container">
    <h2>子组件 {{modelValue}} <button @click="fn">改变数据</button></h2>
  </div>
</template>
<script>
export default {
  name: 'Son',
  props: {
    modelValue: {
      type: Number,
      default: 0
    }
  },
  setup (props, {emit}) {
    const fn = () => {
      // 改变数据
      emit('update:modelValue', 100)
    }
    return { fn }
  }
}
</script>
  • 总结: vue3.0封装组件支持v-model的时候,父传子:modelValue 子传父 @update:modelValue
  • 补充: vue2.0的 xxx.sync 语法糖解析 父传子 :xxx 子传父 @update:xxx 在vue3.0 使用 v-model:xxx 代替

十四、补充 mixins语法

  • 混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
  • 理解全局混入:所有组件混入了这些逻辑代码
// 全局混入 全局mixin
// vue2.0 写法  Vue.mixin({})
app.mixin({
  methods: {
    say () {
      console.log(this.$el,'在mounted中调用say函数')
    }
  },
  mounted () {
    this.say()
  }
})
<template>
  <div class="container1">
    <h1> 作者:周杰伦  <a href="javascript:;">关注</a> </h1>
    <hr>
    <Son />
  </div>
</template>
<script>
import Son from './Son.vue'
export default {
  name: 'App',
  components: {
    Son
  }
}
</script>
<template>
  <div class="container2">
    <h2> 作者:周杰伦  <button>关注</button> </h2>
  </div>
</template>
<script>
export default {
  name: 'Son'
}
</script>
<style scoped lang="less"></style>
  • 理解局部混入:通过mixins选项进行混入
// 配置对象
export const followMixin =  {
  data () {
    return {
      loading: false
    }
  },
  methods: {
    followFn () {
      this.loading = true
      // 模拟请求
      setTimeout(()=>{
        // 省略请求代码
        this.loading = false
      },2000)
    }
  }
}
<template>
  <div class="container1">
    <h1> 作者:周杰伦  <a href="javascript:;" @click="followFn">{{loading?'请求中...':'关注'}}</a> </h1>
    <hr>
    <Son />
  </div>
</template>
<script>
import Son from './Son.vue'
import {followMixin} from './mixins'
export default {
  name: 'App',
  components: {
    Son
  },
  mixins: [followMixin]
}
</script>
<template>
  <div class="container2">
    <h2> 作者:周杰伦  <button @click="followFn">{{loading?'loading...':'关注'}}</button> </h2>
  </div>
</template>
<script>
import {followMixin} from './mixins'
export default {
  name: 'Son',
  mixins: [followMixin]
}
</script>
<style scoped lang="less"></style>
  • 总结: 在vue2.0中一些可复用的逻辑可以使用mixins来封装,当是需要考虑逻辑代码冲突问题。vue3.0的组合API很好的解决了这个问题,就不在推荐使用mixins了。

你可能感兴趣的:(vue.js,javascript,前端)