Vue3 Composition API

setup 函数的使用

setup 在 created 函数被完全实例化之前执行

setup 只能是同步的,不能是异步的

由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this。这意味着,除了 props 之外,将无法访问组件中声明的任何属性 —— 本地状态计算属性方法

setup 返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。

setup(props,context){
    // 第一个参数 props,响应式对象,不能被解构
    // 第二个参数 context, attrs、emit、slots
    //code
}
//ctx 和 2.x 中 this 并不完全一样,而是选择性地暴露了一些 property,主要有 `[attrs,emit,slots]`
setup(props,context){
    const {attrs,slots,emit} = context
    //attrs第一个参数是None-Props属性
    //slot是一个方法,调用default方法会返回一个VNODE
    //emit是各种事件
}

ref reactive 响应式引用

  • ref 处理基础类型的数据
  • reactive 处理数组、对象数据
  • toRefs 处理对象解构之后的数据,无默认值
  • toRef 处理对象解构之后的数据,有默认值
  • readonly 只能读的响应式数据

ref 将给定的值创建一个响应式的数据对象并赋值初始值(int 或者 string)

reactive 可以直接定义复杂响应式对象。

原理:通过 proxy 对数据进行封装,当数据变化时,触发模板等内容的更新

ref 处理基础类型的数据

proxy : ‘r’ 变成 proxy( { value : ’r’ } ) 这样的一个响应式引用

const app = Vue.createApp({
    template:`
    <div>{{name}}</div>
    `,
    setup(props,context) {
      const {ref} = Vue; 
      let name = ref('r');  //ref类似于定义变量
      setTimeout(() => {
        name.value = 'm'
      },2000)
      return {name}
    }
  })

reactive 处理非基础类型的数据

proxy : {name : ‘r’ }变成 proxy( { name: ’r’ } ) 这样的一个响应式引用

const app = Vue.createApp({
    template:`
    <div>{{nameObj[0]}}</div>
    `,
    setup(props,context) {
      const {reactive} = Vue; 
      const nameObj = reactive([123])  //定义变量
      setTimeout(() => {
        nameObj[0] = 456
      },2000)
      return {nameObj}
    }
  })

reactive 创建的响应式数据解构后不再是响应式,toRefs 可以把响应式对象的所有属性也转换成响应式的,所以可以解构 toRefs 返回的对象,解构之后还是响应式数据;

reactive 是把普通对象转化成响应式对象,而 ref 是将基本类型数据包装成响应式对象。

ref 的本质

它的本质还是 reactive,当我们给 ref 函数传递一个值时,ref 函数会自动将 ref 转换成 reactive

ref(0) -->  reactive({
value:0
})
  • 所以在 script 中修改 ref 创建的数据时,必须通过.value 来获取
  • 需要注意的是,如果是通过 ref 创建出来的数据,
    template 中使用的时候不用通过.value来获取。因为 Vue 会自动给我们添加.value 。

vue 是如何判断当前的数据是否是 ref 类型的?
通过当前数据的__v_ref 来判断的,如果有这个私有的属性,并且取值为 true,那么就代表是一个 ref 类型的数据。

开发者自己判断。isRef (数据),返回 true 或者是 false。

import {isRef} from 'vue'

ref 和 reactive 区别:

  • 如果在 template 里使用的是 ref 类型的数据,那么 Vue 会自动帮我们添加.value
    如果在 template 里使用的是 reactive 类型的数据,那么 Vue 不会自动帮我们添加.value
  • Vue 是如何决定是否需要自动添加.value 的
    Vue 在解析数据之前,会自动判断这个数据是否是 ref 类型的,
    如果是就自动添加.value, 如果不是就不自动添加.value
  • Vue 是如何判断当前的数据是否是 ref 类型的
    通过当前数据的__v_ref 来判断的
    如果有这个私有的属性,并且取值为 true, 那么就代表是一个 ref 类型的数据

toRefs 将 proxy({name:’r’}) 转化为 {name:proxy({value:’m’})}

const {toRefs = Vue;
const nameObj = reactive({name:'r'})
setTimeout(() => {
    nameObj.name = 'm'
},2000)
const {name} = toRefs(nameObj)  //可以看成赋值,这里是解构赋值
return {name}

toRefs 需要数据存在,toRef 不用

const {toRef} = Vue
const age = toRef(data,’age’)

Composition API Demo

  // 关于 list 操作的内容进行了封装
  const listRelativeEffect = () => {
    const { reactive } = Vue;
    const list = reactive([]);
    const addItemToList = (item) => {
      list.push(item);
    }
    return { list, addItemToList }
  }

  // 关于 inputValue 操作的内容进行了封装
  const inputRelativeEffect = () => {
    const { ref } = Vue;
    const inputValue = ref('');
    const handleInputValueChange = (e) => {
      inputValue.value = e.target.value
    }
    return { inputValue, handleInputValueChange}
  }

  const app = Vue.createApp({
    setup() {
      // 流程调度中转
      const { list, addItemToList } = listRelativeEffect();
      const { inputValue, handleInputValueChange} = inputRelativeEffect();
      return {
        list, addItemToList,
        inputValue, handleInputValueChange
      }
    },
    template: `
      <div>
        <div>
          <input :value="inputValue" @input="handleInputValueChange" />
          <button @click="() => addItemToList(inputValue)">提交</button>
        </div>
        <ul>
          <li v-for="(item, index) in list" :key="index">{{item}}</li>
        </ul>
      </div>
    `,
  });

computed方法生成计算属性

  const app = Vue.createApp({
    setup() {
      const {ref,computed} = Vue
      const count = ref(0)
      const handleClick = () => {
        count.value += 1
      }
      const countAddFive = computed(() => {
        return count.value + 5
      })
      return {count,handleClick,countAddFive}
    },
    template: `
      <div>
        <span @click = 'handleClick'>{{count}}</span> -- {{countAddFive}}
      </div>
    `,
  });

watch 和 watchEffect 的使用

  • 第一个参数:要监听的数据,可以是 ref 或者是 reactive 响应式数据
  • 第二个参数:监听到数据变化后执行的函数,这个函数有两个参数分别是新值和旧值
  • 第三个参数:选项对象,deep 和 immediate

watch 具备一定的惰性 lazy

参数可以拿到原始和当前值

setup(){
    const {ref,watch} = Vue
    const name = ref('rmm')
    watch(name,(currentValue,prevValue) => {
        console.log(currentValue,prevValue)
    })
    return {name}
}

当用 reactive 时,第一个参数变成箭头函数

setup(){
    const {reactive,watch,toRefs} = Vue
    const nameObj = reactive({name:'rmm'})
    watch(() => nameObj.name,(currentValue,prevValue) => {
        console.log(currentValue,prevValue)
    })
    const {name} = toRefs(nameObj)
    return {name}
}

可以侦听多个数据的变化,用一个侦听器承载

将第一个参数变成一个数组

watch([() => nameObj.name,() => nameObj.englishName] , () => {
    //code
})

​ watchEffect 侦听器,偏向于 effect

  • 是 Watch 函数的简化版本,也用来监视数据的变化,不同的是没有第二个参数
  • 接受一个函数作为参数,监听函数内响应式数据的变化,当函数内响应式数据发生变化时函数会立即再调用一次
  • 也是返回一个取消监听的函数

立即执行,没有惰性, immediate

不需要传递要侦听的内容,自动会感知代码依赖,不需要传递很多参数,只要传递一个回调函数

不能获取之前数据的值

const {watchEffect} = Vue
watchEffect(() => {
    console.log(nameObj.name)
})

生命周期函数

在 setup 中可以使用组件生命周期的钩子函数,但是要在函数名称前面加 on 并且函数名称首字母大写,例如:mounted 在 setup 中要写成 onMounted,还要在 import 中导入使用的钩子函数

setup 在 beforeCreate 和 created 之间执行,对应的代码都可以在 setup 中执行,所以不需要再在 setup 中注册 beforeCreate 和 created

onUnmounted 相当于 2.x 版本中的 destoryed

新增钩子函数:onRenderTracked onRenderTriggered(都是在 render 函数被重新调用的时候触发,区别在于 onRenderTracked 在首次调用 render 时也会触发,而 onRenderTriggered 首次调用 render 时不会触发)

setup(){
    const {  OnBeforeMount,OnMounted,OnBeforeUpdate,OnUpdated,OnUnmounted,OnRenderTracked,OnRenderTriggered} = Vue
    OnBeforeMount(() => {
        console.log('OnBeforeMount')
    })
    OnMounted(() => {
        console.log('OnMounted')
    })
    OnBeforeUpdate(() => {
        console.log('OnBeforeUpdate')
    })
    OnUpdated(() => {
        console.log('OnUpdated')
    })
    OnUnmounted(() => {
        console.log('OnUnmounted')
    })
    // 每次渲染后重新收集响应式依赖
    OnRenderTracked(() => {
        console.log('OnRenderTracked')
    })
    // 每次触发页面重新渲染时自动执行
    OnRenderTriggered(() => {
        console.log('OnRenderTriggered')
    })
}

provide,inject 的用法

父子组件传值,父组件用 provide ,子组件用 inject 接受

const app = Vue.createApp({
    setup(){
        const {provide} = Vue
        provide('name','rmm')  //如果将此行注释,则显示 hello ,不注释显示 rmm
        return {}
    },
    templete:`
		<div>
			<child />	
		</div>
	`
})

app.component('child',{
    setup(){
        const {inject} =Vue
        const name = inject('name','hello')  //hello 作为默认值
        return {name}
    },
    templete;`<div=>{{name}}</div>`
})

改进一下,如果子组件想修改父组件的数据怎么办

const app = Vue.createApp({
    setup(){
        const {provide,ref,readonly} = Vue
        const name = ref('rmm')
        provide('name',readonly(name)) //加 readonly 是为了防止 子组件里直接name.value = 'sb'
        provide('changeName',(value)=>{
            name.value = value
        })
        return {}
    },
    templete:`
		<div>
			<child />	
		</div>
	`
})

app.component('child',{
    setup(){
        const {inject} =Vue
        const name = inject('name')
        const changeName = inject('changeName')
        const handleClick = () => {
            changeName('lee')
        }
        return { name , handleClick }
    },
    templete;`<div @click="handleClick">{{name}}</div>`
})

模板 ref 的用法

Composition API 的语法下,获取真实的 DOM 元素节点

const app = Vue.createApp({
    setup(){
        const {ref,onMounted} = Vue
        const hello = ref(null)  //这里的a一定是和标签中的ref值对应,然后给ref传null
        onMounted(() => {
            console.log(hello.value)
        })
        return { hello }
    },
    templete:`
		<div>
			<div ref="hello">hello world</div>
		</div>
	`
})
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐