前端框架Vue(四)


组合式API

组合式API(Vue-Composition-API)简称VCA,是Vue3新增加的特性。

前面三篇文章都是基于选项式API(Vue-Options-API,VOA)进行介绍的。是兼容Vue2的写法,使用包含多个选项的对象来描述组件的逻辑,例如datamethodsmounted等。选项所定义的属性都会暴露在函数内部的this上,它会指向当前的组件实例。

而通过组合式API,可以使用导入的API函数来描述组件逻辑。在单文件组件中,组合式API通常会与<script setup>搭配使用。这个setup是一个标识,告诉Vue需要在编译时进行一些处理,让我们可以更简洁地使用组合式API。

例子:

选项式API写法:

<template>
  <div @click="changeMsg">{{msg}}</div>
</template>

<script>
  export default{
    name:"App",
    data(){
      return{
        msg:"hello vue3"
      }
    },
    methods:{
      changeMsg(){
        this.msg = "hello AiLynn"
      }
    }
  }
</script>

组合式API写法:

<template>
  <div @click="changeMsg">{{msg}}</div>
</template>

<script>
  import {ref, defineComponent} from "vue"
  export default defineComponent({
    setup() {
      const msg = ref("hello vue3");
      const changeMsg = ()=> {
        msg.value = "hello PLScript";
      }
      return {
        msg,
        changeMsg
      }
    }
  })
</script>

图示:VCA通过组合的方式,把零散在各个data,methods等代码,重新组合,一个功能的代码都放在一起维护,并且这些代码可以单独拆分成函数 。(VCA最早被命名为Vue-Function-API)

setup

在Vue组合式API中,每个组件都必须包含一个setup()函数。这个函数是在组件初始化时调用,其目的是为组件创建并设置响应式状态,并返回需要再模板中使用的数据和方法。setup()函数只在组件实例化时执行一次,并返回一个对象。setup()中不能使用thisthis指向的是undefined

reactive 和 ref

在Vue组合式API中,使用reactive()函数接收一个普通的对象传入,把对象数据转化为响应式对象并返回。响应式对象意味着当有数据发生变化时,会自动触发组件重新渲染。

<template>
  <div>
    <input type="text" v-model="state.mytext">
    <button @click="plClickAdd">add</button>
    <ul>
      <li v-for="(data,index) in state.datalist" :key="data">
        {{data}}
        <button @click="plClickDel(index)">del</button>
      </li>
    </ul>
  </div>
</template>

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

export default{
  setup(){
    // 可以有多个reactive
    const state = reactive({
      mytext:"",
      datalist:["pyscript","ailynn","laobai"]
    })
    const plClickAdd = ()=>{
      state.datalist.push(state.mytext)
      // 增加成功后清空输入框
      state.mytext=""
    }
    const plClickDel = (index)=>{
      state.datalist.splice(index,1)
    }
    return{
      state,
      plClickAdd,
      plClickDel
    }
  }
}
</script>

ref()是创建一个包装式对象,含有一个响应式属性value,它和reactive()的差别是,reactive()没有包装属性value

ref()是在reactive()基础上进行的二次封装。reative()是将引用类型数据转换为响应式数据,而ref()不仅能够将引用类型数据转换为响应式数据,还可以将基本类型转换为响应式数据,并可以获取DOM元素。

JavaScript的基本数据类型:NumberStringBooleanNullUndefinedSymbol;引用数据类型:Object(除了基本数据类型以外的都是对象)

<template>
  <div>
    app
    {{myname}}
    <input type="text" ref="myinput">
    <button @click="plClick">click</button>
  </div>
</template>

<script>
import {ref} from "vue"
export default {
  setup(){
    // ref(基本类型)
    const myname = ref("AiLynn") // new Proxy({value:"AiLynn"})
    const myinput = ref(null)
    const plClick = ()=>{
      myname.value = "PLScript"
      console.log(myinput.value)
      console.log(myinput.value.value)
    }

    return {
      myname,
      myinput,
      plClick
    }
  }
}
</script>

将上面reactive()的代码改写为ref()代码,如下:

<template>
  <div>
    <input type="text" v-model="mytext">
    <button @click="plClickAdd">add</button>
    <ul>
      <li v-for="(data,index) in datalist" :key="data">
        {{data}}
        <button @click="plClickDel(index)">del</button>
      </li>
    </ul>
  </div>
</template>

<script>
import { reactive } from 'vue';
import { ref } from 'vue'

export default{
  setup(){
    // 可以有多个reactive
    // const state = reactive({
    //   mytext:"",
    //   datalist:["pyscript","ailynn","laobai"]
    // })
    const mytext = ref("")
    const datalist = ref(["pyscript","ailynn","laobai"])

    const plClickAdd = ()=>{
      // state.datalist.push(state.mytext)
      datalist.value.push(mytext.value)
      // 增加成功后清空输入框
      // state.mytext=""
      mytext.value=""
    }
    const plClickDel = (index)=>{
      // state.datalist.splice(index,1)
      datalist.value.splice(index,1)
    }
    return{
      mytext,
      datalist,
      plClickAdd,
      plClickDel
    }
  }
}
</script>

toRefs 和 toRef

toRef函数的作用是将对象中的某个属性转换成响应式数据;

toRefs函数的作用是将对象中的所有属性转换成响应式数据;

<template>
  <div>
    <input type="text" v-model="mytext">
    <button @click="plClickAdd">add</button>
    <ul>
      <li v-for="(data,index) in datalist" :key="data">
        {{data}}
        <button @click="plClickDel(index)">del</button>
      </li>
    </ul>
  </div>
</template>

<script>
import { reactive,toRef,toRefs } from 'vue';

export default{
  setup(){
    // 可以有多个reactive
    const state = reactive({
      mytext:"",
      datalist:["pyscript","ailynn","laobai"]
    })
    const plClickAdd = ()=>{
      state.datalist.push(state.mytext)
      // 增加成功后清空输入框
      state.mytext=""
    }
    const plClickDel = (index)=>{
      state.datalist.splice(index,1)
    }
    return{
      // mytext:toRef(state,"mytext"),
      ...toRefs(state),
      state,
      plClickAdd,
      plClickDel
    }
  }
}
</script>

Computed

使用reactive()组合式API书写Computed()

<template>
  <div>
    <input type="text" v-model="mytext">

    <ul>
      <li v-for="data in computedList" :key="data">
        {{data}}
      </li>
    </ul>
  </div>
</template>

<script>
import { reactive,toRefs,computed } from "vue"

export default {
  setup(){
    const state = reactive({
      mytext:"",
      datalist:["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
    })

    const computedList = computed(()=>
      state.datalist.filter(item=>item.includes(state.mytext))
    )
  
    return {
      ...toRefs(state),
      computedList
    }
  }
}
</script>

上面代码中已经实现了组合式API模式,但可以看到代码只是有选项式API重新组合了一下,代码层面上并没显得更高效。接下来将实现search功能的代码抽象成方法。

<template>
  <div>
    <input type="text" v-model="mytext">

    <ul>
      <li v-for="data in computedList" :key="data">
        {{data}}
      </li>
    </ul>
  </div>
</template>

<script>
import { reactive,ref,computed } from "vue"

function UseSearch(state) {
  const mytext = ref("")
  const computedList = computed(()=>
      state.datalist.filter(item=>item.includes(mytext.value))
  )

  return {
    mytext,
    computedList
  }
}

export default {
  setup(){
    const state = reactive({
      datalist:[]
      // mytext:"",
      // datalist:["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
    })

    setTimeout(() => {
      state.datalist=["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
    },2000)
    
    const {mytext,computedList} = UseSearch(state)
    
  
    return {
      // mytext,
      // computedList
      ...UseSearch
    }
  }
}
</script>

function UseSearch()实现了搜索的逻辑,接下来将UseSearch()单独提取到一个js文件中,这样就提高了代码的复用性,在其他用到搜索的地方可以直接引入即可。(用户自定义的函数名前通常写上Use

import { ref,computed } from "vue"

function UseSearch(state) {
  const mytext = ref("")
  const computedList = computed(()=>
      state.datalist.filter(item=>item.includes(mytext.value))
  )

  return {
    mytext,
    computedList
  }
}

export default UseSearch
<template>
  <div>
    <input type="text" v-model="mytext">

    <ul>
      <li v-for="data in computedList" :key="data">
        {{data}}
      </li>
    </ul>
  </div>
</template>

<script>
import { reactive } from "vue"
import UseSearch from "./search"

export default {
  setup(){
    const state = reactive({
      datalist:[]
      // mytext:"",
      // datalist:["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
    })

    setTimeout(() => {
      state.datalist = ["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
    }, 2000)
    
    // const {mytext,computedList} = UseSearch(state)
    
  
    return {
      // mytext,
      // computedList
      ...UseSearch(state)
    }
  }
}
</script>

上方是通过reactive()实现的,下面再使用ref()进行实现

<template>
  <div>
    <input type="text" v-model="mytext">

    <ul>
      <li v-for="data in computedList" :key="data">
        {{data}}
      </li>
    </ul>
  </div>
</template>

<script>
import { ref } from "vue"
import UseSearch from "./search"

export default {
  setup(){
    // const state = reactive({
    //   datalist:[]
    //   })
    const datalist = ref([])

    setTimeout(() => {
      datalist.value = ["aaa","bba","abb","abc","bcc","acc","aac","aab","bbc","bbb"]
    }, 2000)
    
    // const {mytext,computedList} = UseSearch(state)
    
  
    return {
      // mytext,
      // computedList
      ...UseSearch(datalist)
    }
  }
}
</script>
import { ref,computed } from "vue"

function UseSearch(datalist) {
  const mytext = ref("")
  const computedList = computed(()=>
      datalist.value.filter(item=>item.includes(mytext.value))
  )

  return {
    mytext,
    computedList
  }
}

export default UseSearch

watch

watch具有一定的惰性,第一次页面展示的时候不会执行,只有数据变化的时候才会执行。若要第一次也展示数据,需要增加{immediate:true}watch可以通过参数获取到当前值和原始值watch(mytext,(newValue,oldValue)

<template>
  <div>
    <input type="text" v-model="mytext">

    <select v-model="select">
      <option value="aaa">aaa</option>
      <option value="bbb">bbb</option>
      <option value="ccc">ccc</option>
    </select>
  </div>
</template>

<script>
import { ref, watch } from 'vue';

export default {
  setup(){
    const mytext = ref("")
    const select = ref("aaa")
    // 写法1
    // watch(mytext,(newValue,oldValue)=>{
    //   console.log("同步/异步","ajax",newValue,oldValue)
    // })
    // 写法2
    // watch(()=>mytext.value,(newValue,oldValue)=>{
    //   console.log("同步/异步","ajax",newValue,oldValue)
    // })
    // 多个数据源侦听
    // watch([mytext,select],(newValue,oldValue)=>{
    //   console.log("同步/异步","ajax",newValue,oldValue)
    // })
    watch([mytext,select],(newValue,oldValue)=>{
      console.log("同步/异步","ajax",newValue,oldValue)
    },{immediate:true})

    return {
      mytext,
      select
    }
  }
}
</script>

props 和 emit

props 子组件声明接收 props:["attr1","attr2"]

emit子组件触发自定义事件$emit("自定义事件名")

App.vue

<template>
  <div>
    <Navbar title="首页" :left="true" @leftevent="handleLeft"/>
    <Navbar title="影院" :left="false"/>
  </div>
</template>

<script>
import Navbar from './Navbar.vue';

export default {
  components:{
    Navbar
  },

  setup(){
    const handleLeft = ()=>{
      console.log("left button")
    }
    return {
      handleLeft
    }
  }
}
</script>

Navbar.vue

<template>
  <div>
    <button v-show="left" @click="handleClick">返回</button>
    {{ computedTitle }}
    <button>首页</button>
  </div>
</template>

<script>
import { computed } from 'vue';
export default {
  props:{
    title:String,
    left:Boolean
  },

  // computed:{
  //   computedTitle(){
  //     return this.title + "-AiLynn"
  //   }
  // }
  setup(props,{emit}){
    const computedTitle = computed(()=>props.title+"-AiLynn")
    const handleClick = ()=> {
      console.log("click")

      emit("leftevent")
    }

    return {
      computedTitle,
      handleClick
    }
  }
}
</script>

其中,setup(props,{emit})的第一参数是传递属性的对象集合,第二个参数是将需要用到的属性对象直接通过{}进行解构进行使用。两种方式都可以使用。

VCA生命周期

选项式API的生命周期与组合式API的生命周期对比:

VOA(Vue2/Vue3) VCA(Vue3)
befoceCreate setup
created setup
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy(beforeUnmount) onBeforeUnmount
destroyed(unmounted) onUnmounted
<template>
  <div>
    div - {{ a }}
    <button @click="plClick">click</button>
  </div>
</template>

<script>
import { ref,onBeforeMount, onBeforeUpdate, onMounted, onUpdated,nextTick } from 'vue';

export default {
  setup(){
    const a = ref("AiLynn")

    onBeforeMount(()=>{
      console.log("dom创建之前调用")
    })

    onMounted(()=>{
      console.log("订阅、ajax、dom创建后,初始化实例")
    })

    onBeforeUpdate(()=>{
      console.log("更新之前")
    })

    onUpdated(()=>{
      console.log("更新之后")
    })

    const plClick = ()=>{
      a.value = "PLScript"

      nextTick(()=>{
        console.log("nextTick")
      })
    }

    return {
      a,
      plClick
    }
  }
}

</script>

script setup语法糖

<script setup>是在单文件组件(SFC)中使用组合式API的编译时语法糖。当同时使用SFC与组合式API时该语法糖是默认推荐。相比于普通的<script>语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码;
  • 能够使用纯TypeScript声明props和自定义事件;
  • 更好的运行时性能(其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象);
  • 更好的IDE类型推导性能(减少了语言服务器从代码中抽取类型的工作);

主组件App.vue

<template>
  <div>
    app - {{message}}
    <button @click="plClick">click</button>
    <div>
      {{state.myname}}-{{state.myage}}
    </div>
    <div>
      {{myname}}___{{myage}}
    </div>
    <div>
      {{computedName}}
    </div>
    <Child title="title1111" @right="plRightClick"/>
  </div>
</template>

<script setup>
import { ref,reactive,toRefs, computed } from "vue"
import Child from "./Child.vue"

const message = ref("hello AiLynn")
const state = reactive({
  myname:"laobai",
  myage:18
})

const {myname,myage} = {...toRefs(state)}

const plClick = ()=>{
  console.log(message)
  message.value = "hello PLScript"
  myage.value=81
}

const computedName = computed(()=>myname.value.substring(0,1).toUpperCase()+myname.value.substring(1))

const plRightClick = (value)=>{
  console.log("app",value)
}
</script>

子组件Child.vue

<template>
  <div>
    Child-{{title}}
    <button @click="plClickBack">返回</button>
  </div>
</template>

<script setup>
// import { defineProps } from "vue"

// 父传子,子接收
// 接收父组件传过来的值,使用defineProps进行接收。新版本中已不用import了
const props = defineProps({
  title:{
    type:String,
    default:"title0000"
  }
})

// 子传父,子发送
// 定义要从子组件返给父组件的事件集合,使用defineEmits,同样新版本中已经不用import了
const emit = defineEmits(["right"])
const plClickBack = ()=>{
  emit("right","从子组件返回的数据")
}

console.log(props.title)
</script>

文章作者: 老百
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 老百 !
 上一篇
前端框架Vue(五) 前端框架Vue(五)
Vue 是一款用于构建用户界面的 JavaScript 框架。本小节介绍Vue Router,是Vue.js的官方路由,与Vue.js深度集成,让用Vue.js构建单页面应用变得轻而易举。
2023-08-24
下一篇 
前端框架Vue(三) 前端框架Vue(三)
Vue 是一款用于构建用户界面的 JavaScript 框架。本小节介绍了Vue的插槽及生命周期的相关介绍。
2023-08-01
  目录