Skip to content

Latest commit

 

History

History
750 lines (604 loc) · 15.1 KB

setup.md

File metadata and controls

750 lines (604 loc) · 15.1 KB

Vue3 中的 Composition API

options api的弊端

代码结构分散,对同一个数据的不同操作分散到了不同的options选项中。

composition api的优势

对同一个数据的操作在同一个函数中完成。

开启使用setup

setup()

<script setup><script>

参数:

props:父组件传递过来的属性

context

返回值:

  1. 可以在template模板中使用
  2. 通过setup函数返回值替代data

注意:

setup里的数据不是默认响应式的,需要引入响应式函数对目标数据进行包裹

将数据包装为响应式数据的两种方式

  1. ref函数,主要对简单类型数据进行包裹,也可以处理复杂类型数据。需要通过ref.value获取到数据,但是在template下不需要,会进行自动解包。
<template>
  <div class="app">
    <h2>{{ info.age }}</h2>
  </div>
</template>

<script>
  import {ref} from 'vue'
  export default{
    setup(){
      let info = ref({'age':18})
      return {
        info
      }
    }
}
  1. 引入reactive函数,将复杂类型的数据包装为响应式数据

使用refreactive处理数据之后,数据再次使用就会进行依赖收集,数据改变时所有收集的依赖都是对应进行对应的响应式操作。

const state = reactive({...})

简单的实例:计数器的实现

<template>
  <div class="app">
    <!-- template中的数据会自动解包 -->
    <h2>计数器{{ counter }}</h2>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
  </div>
</template>

<script>
  import {ref} from 'vue'
  export default{
    setup(){
      // 默认定义的数据不是响应式数据,需要用 ref 包裹
      let counter = ref(10)
      const increment = ()=>{
        counter.value++
      }
      const decrement = ()=>{
        counter.value--
      }
      return {
        counter,
        increment,
        decrement,
      }
    }

  }
</script>

抽离为单独的hook

hooks/useCounter.js

import {ref} from 'vue'
export default function useCounter(){
    let counter = ref(10)
    const increment = ()=>{
      counter.value++
    }
    const decrement = ()=>{
      counter.value--
    }
    return {
      counter,
      increment,
      decrement,
    }
}

App.vue中引入:

  import useCounter from './hooks/useCounter'
  export default{
    setup(){
      const {counter,increment,decrement} = useCounter()
      return{
        counter,
        increment,
        decrement,
      }
    }

或者还可以更简单:

  import useCounter from './hooks/useCounter'
  export default{
    setup(){
      return {
        ...useCounter()
      }
    }

单向数据流

单向数据流:由于组件传值是引用传值,所以子组件可以改变父组件的数据,从而影响其他同样使用了父组件数据的其他子组件。如果想要更改父组件数据,应该由子组件将改变数据的事情发送给父组件,让父组件去改变数据。

<template>
  <div class="app">
    <info :info="info" @changeInfoAge="changeInfoAge"></info>
  </div>
</template>

<script>
import { reactive } from 'vue';
import Info from './components/Info.vue'

export default{
  components:{
    Info
  },
  setup(){
    const info = reactive({
      name:"flten",
      age:20,
      job:"worker",
    })
    const changeInfoAge = (payload)=>{
      info.age = payload
    }
    return {
      info,
      changeInfoAge,
    }
  }

}
</script>
<template>
    <div>
        <h1>{{ info.age }}</h1>
        <button @click="changeInfo">改变按钮</button>
    </div>
</template>
<script>
export default {
    props:{
        info:{
            type: Object,
            default:()=>({})
        }
    },
    emits:['changeInfoAge'],
    setup(props, context){
        const changeInfo = ()=>{
            context.emit('changeInfoAge', 25)
        }
        return {
            changeInfo
        }
    }
}
</script>

ref 和 reactive 的使用选择

  1. 一般都使用ref,简单类型和复杂类型都可以处理;定义从服务器中获取的数据一般也使用ref,获取数据会更方便,因为可以将服务器中的数据赋给ref响应式数据的.value属性
  2. reactive一般应用于自己定义的聚合数据(多个数据之间有关联),比如收集表单数据

readonly

通过readonly可以从语法上避免违反单向数据流的原则

readonly包裹后返回的数据也是响应式数据,但是不允许对数据进行修改。

注意:readonly处理过的原对象是可以更改的。

  1. 比如const info = readonly(obj)readonly处理后得到的只读对象info是不可更改的,但如果源对象obj被修改了,那么readonly返回的info对象也会被修改。
  2. 实际情况下,多对reactive返回的数据再用readonly进行包装。如果是对reactive包裹后的数据A进行了包装,得到了只读数据BB不可被更改。但如果改变可变数据A,也会影响只读数据B

原理:readonly会返回原始对象的只读代理,是对一个proxyset方法进行了劫持,保证其不被更改。

进行判断的 API

isProxy:判断是否由reactivereadonly创建的proxy

isReactive:判断是否由reactive创建的proxy;即便是由readonly包裹了的reactive返回的另一个proxy,仍然判断为true

    const info = reactive({
      name:"flten",
      age:20,
      job:"worker",
    })
    const readonlyInfo = readonly(info)
    console.log(isReactive(readonlyInfo)) //true

isReadonly:判断对象是否由readonly 创建的只读proxy

toRaw:返回由reactivereadonly代理的原始对象

shalloReactive:不进行深层响应式转化,深层嵌套属性不被代理

shalloReadonly:不进行深层只读转化,深层嵌套属性仍然可读可写

保证解构后的数据仍然是响应式toRefs

需求:在setup中,对reactive返回的响应式对象进行解构后,得到的数据不再是响应式数据。

const info = reactive({
    name:"flten",
    age:20,
    job:"worker",
})

const {name ,age, job} = info
  1. 使用toRefs可以做到解构后得到的每一个数据都用ref进行包装后再返回,保证解构后还是响应式。

  2. 如果只想对单个属性进行结构并用ref进行包装,则可以使用toRef

<template>
  <div class="app">
    <h1>{{ name }}, {{ age }},{{ job }}</h1>
    <button @click="changeInfo">改变数据</button>
  </div>
</template>

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

export default{
  setup(){
    const info = reactive({
      name:"flten",
      age:20,
      job:"worker",
    })

    const {name ,age} = toRefs(info)
    const job = toRef(info, 'job')
     
    const changeInfo = ()=>{
      info.name = 'wall'
      info.job = 'leader'
    }
    return {
      name,
      age,
      job,
      changeInfo,
    }
  }

}
</script>

ref的其他相关 API

isRef:判断值是否是一个Ref对象

unref:如果参数是一个ref则直接返回ref的值,相当于ref.value,否则返回参数本身。在不确定接收到的参数是不是ref对象的情况下可以使用,相当于const val = isRef(obj) ? obj.value : value

shallowRef:创建一个浅层的 Ref 对象,即深层属性不是响应式。

triggerRef:手动触发更新,想要对shallowRef创建的浅层Ref 对象的某个深层嵌套属性进行响应式操作时,可以进行手动触发更新。

// 创建浅层 ref 对象
const info = shallowRef({age:20})
const changeInfo = ()=>{
    // age不是响应式数据
    info.value.age = 18
    // 手动触发更新
    triggerRef(info)
}

setup中使用computed

注意📢:computed的返回值是一个ref对象

import { reactive, computed } from 'vue';

export default{
  setup(){
    const info = reactive({
      name:"flten",
      age:20,
      job:"worker",
    })
    const fullInfo = computed(()=>{
      return `${info.name} is ${info.age} years old`
    })
    return {
      fullInfo
    }
  }

computed中的 get 和 set 使用示例

<template>
  <div class="app">
    <h1>{{ fullInfo }}</h1>
    <button @click="setFullInfo">修改值</button>
  </div>
</template>

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

export default{
  setup(){
    const info = reactive({
      name:"flten",
      age:20,
      job:"worker",
    })
    const fullInfo = computed({
      get: ()=>{
        return `${info.name} is ${info.age} years old`
      },
      set: (newValue)=>{
        info.name = newValue
      }
    })
    const setFullInfo = () => {
      fullInfo.value = 'fltenwall'
    }
    return {
      fullInfo,
      setFullInfo,
    }
  }
}
</script>

setup 中获取元素对象

  1. 使用ref对象绑定到元素的ref属性上
  2. 在生命周期函数onMounted获取到元素对象
<template>
  <div class="app">
    <h1 ref="h1Ref">Hello</h1>
    <button ref="btnRef">修改值</button>
  </div>
</template>

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

export default{
  setup(){
    const h1Ref = ref()
    const btnRef = ref()
    onMounted(()=>{
      console.log(h1Ref.value)
      console.log(btnRef.value)
    })
    return {
      h1Ref,
      btnRef,
    }
  }
}
</script>

在setup中获取组件元素对象

子组件 Info.Vue

<template>
    <div>
        <h1>This is Info.</h1>
    </div>
</template>
<script>
export default {
    setup(){
        return {

        }
    }
}
</script>

父组件

<template>
  <div class="app">
    <Info ref="infoRef"></Info>
  </div>
</template>

<script>
import Info from './components/Info.vue';
import { ref, onMounted } from 'vue';

export default{
  components:{
    Info,
  },
  setup(){
    const infoRef = ref()
    onMounted(()=>{
      console.log(infoRef.value)
    })
    return {
      infoRef
    }
  }
}
</script>

在setup中使用声明周期函数

用法:onName

注意:没有createdbeforeCreated对应的onCreatedonBeforeCreated。原本在createdbeforeCreated进行的操作可以在setup中执行。

import { onBeforeMount, onMounted } from 'vue';

export default{
  setup(){
    onBeforeMount(()=>{})
    onMounted(()=>{})
    return {
      onBeforeMount,
      onMounted,
    }
  }
}

也可以注册多个同名生命周期函数,并会依次执行

    onMounted(()=>{})
    onMounted(()=>{})
    onMounted(()=>{})

setup中使用provideinject

用途:组件间的数据共享,父子组件、非父子组件。

provide提供数据,inject注入数据

父组件

<template>
  <div class="app">
    <Info></Info>
    <button @click="changeName">改变数据</button>
  </div>
</template>

<script>
import Info from './components/Info.vue';
import { provide, ref } from 'vue';

export default{
  components:{
    Info
  },
  setup(){
    const name = ref('fltenwall')
    provide('name', name)
    const changeName = ()=>{
      name.value = 'flten'
    }
    return {
      name,
      changeName,
    }
  }
}
</script>

子组件

<template>
    <div>
        <h1>This is {{name}}.</h1>
    </div>
</template>
<script>
import { inject } from 'vue'
export default {
    setup(){
        // 第二个参数为默认值
        const name = inject('name', 'fltenwal')
        return {
            name
        }
    }
}
</script>

setup中使用watch监听函数

  1. 如果监听的是reactive数据,默认深度监听
const info = reactive({
    name : 'fltenwall',
    city: {
    name: 'ShenZhen'
    }
})
watch(info, (newValue, oldValue)=>{
    console.log(newValue, oldValue)
},{
    immediate: true
})

但如果监听获取到的是普通对象,则需要手动开启深度监听。

    const message = ref('good')
const info = reactive({
    city: {
    name: 'ShenZhen'
    }
})
watch(()=>({...info}), (newValue, oldValue)=>{
    console.log(newValue, oldValue)
},{
    immediate:true,
    deep:true,
})

同时监听多个数据:

    const name = ref('flten')
    const age = ref(20)
    watch([name ,age], (newValue, oldValue)=>{
      console.log(newValue, oldValue)
    })

setup中使用watchEffect

  1. 传入的函数第一次会自动执行
  2. 自动收集函数内部所有依赖过的响应式数据,无须向watch指定
  3. 可以收集多个依赖,其中一个依赖数据改变时,就会自动执行回调
<template>
  <div class="app">
    <h1>{{counter}}</h1>
    <button @click="counter++">增加计数</button>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue'

export default{
  setup(){
    const counter = ref(10)
    watchEffect(()=>{
      console.log(counter.value)
    })
    return {
      counter,
    }
  }
}
</script>

设置停止监听:达到某个阈值停止监听

watchEffect会返回一个停止监听对象,调用它就可以停止监听

const stopWatchEffect= watchEffect(()=>{
  console.log(counter.value)
  if(counter.value > 15){
    stopWatchEffect()
  }
})

上面的函数在counter的值达到 15 后就会停止监听

setup语法糖

基本特点

  1. 不再需要 components 选项,导入的组件直接引入就可以使用
  2. 不需要将声明的变量通过return出去,顶层声明的变量都会直接暴露给模板
  3. 建议将<script setup>放在模板前面
<script setup>
import { ref, watchEffect } from 'vue'

const counter = ref(10)
const stopWatchEffect= watchEffect(()=>{
  console.log(counter.value)
  if(counter.value > 15){
    stopWatchEffect()
  }
})
</script>

<template>
  <div class="app">
    <h1>{{counter}}</h1>
    <button @click="counter++">增加计数</button>
  </div>
</template>

通过函数defineProps定义接收的数据类型

无须引用,可以直接在组件中使用

父组件传递必须按照子组件定义的类型进行传递

const props = defineProps({
    name : {
        type: String,
        default: '',
    },
    age : {
        type: Number,
        default: 0, 
    }
})

需要使用defineExpose暴露子组件的对象/函数给父组件

必须通过defineExpose暴露子组件的对象/函数给父组件,父组件才能通过ref获取子组件的元素对象,然后调用这些暴露的函数和对象

子组件

const info = {
    age : 19,
}
function childFn(){
    console.log('childFn')
}
defineExpose({
    childFn,
    info,
})

父组件

<template>
  <div class="app">
    <Info :name="name" :age="age" ref="infoRef"></Info>
  </div>
</template>

<script setup>
import Info from './components/Info.vue'
import { onMounted, ref } from 'vue'

const name = ref('flten1')
const age = ref(17)

const infoRef = ref()
onMounted(()=>{
  infoRef.value.childFn()
  console.log(infoRef.value.info)
})
</script>