IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 06.Vue3 中的 Composition API -> 正文阅读

[JavaScript知识库]06.Vue3 中的 Composition API

Vue3 中的 Composition API

为什么要有 Composition API?

当一个组价越来越大的时候,data、methods、computed、directives、mixin 等混杂在一起。里面的功能是混杂的,在一堆混杂的逻辑里面查找如 name 相关的逻辑的话,就会有一定的困难。这样的话会造成可维护性降低。

Composition API 就是解决这个问题的有效方案。

1. setup 函数的使用

setup 函数是在 created 时候(实例被初始化前)执行的。里边返回数据或者方法,插值函数可以检测到。

const app = Vue.createApp({
  template: `
    <div>
      <div @click="handleClick">hello world</div>
      <div>name: {{ name }}, age: {{ age }}</div>
    </div>
  `,
  setup(props, context) {
    return {
      name: "sjh",
      age: 28,
      handleClick: () => {
        alert(123)
      }
    }
  }
})

app.mount("#root")

因为是实例初始化前执行,因此 setup 里不能使用 this 访问到实例。

setup 函数里有两个参数,第一个是 props,指组件从父组件那儿接收到的参数,第二个是 context,暴露了 attrs、slot、emit 等实用方法。

上面的数据

2. ref,reactive 响应式引用的用法和原理

上面看着很合理,但是 name 和 age 都是非响应式的,即数据改变并不会触发渲染。

如何解决问题呢?现在需要响应式的引用:通过 proxy 对数据进行封装。当数据变化时,触发模板等内容的更新。

后面的例子都是通过一秒后修改 name 的值来查看是否有响应式。如果页面数据没有改变,说明没有响应式。

2.1 ref:响应式封装基础类型数据

使用 ref 函数,如 ref("sjh"),对基础类型的数据进行封装,变成 proxy({value: "sjh"}) 这样的一个响应式引用。

const { ref } = Vue;

const app = Vue.createApp({
  template: `
  <div>
    <div>name: {{ name }}, age: {{ age }}</div>
  </div>
`,
  setup(props, context) {
    const name = ref("sjh")
    const age = ref(28)
    // 一秒后,name 的值改变
    setTimeout(() => {
      name.value = "hhhhh"
    }, 1000)
    return { name, age }
  }
})

app.mount("#root")

改成响应式引用后,数据变化 ,页面上的内容也会相应改变。

2.2 reactive:响应式封装非基础类型数据

使用 reactive 函数,将非基础类型数据转化为响应式的。一般就是对象或者数组。

原理: reactive({ name: "sjh" }) 转化成 proxy({ name: "sjh" })

const { ref, reactive } = Vue;

const app = Vue.createApp({
  template: `
    <div>
      <div>name: {{ obj.name }}, age: {{ obj.age }}</div>
    </div>
  `,
  setup(props, context) {
    const obj = reactive({ name: "sjh", age: 20 })
    setTimeout(() => {
      obj.name = "hhhhh"
    }, 1000)
    return { obj }
  }
})

app.mount("#root")

2.3 readonly:数据变成只读形式

用 readonly 包裹响应式引用,该响应式数据变成只读,即不可修改该数据。

2.4 toRefs:解构时不丢失响应

使用过对象解构的同学会觉得插值函数里的写法好麻烦啊,所以会用对象解构。但是这里需要注意的一点,如果直接使用结构后的值,这些值不是响应式的。

解构会将对象或数组解构成普通的数据,丢失了响应。因此解构之前需要用 toRefs 包裹一下,使解构后所有值变成响应式的。

原理:toRefs({ name: "sjh", age: 20 }) 转化成 { name: proxy({ value: "sjh" }), age: proxy({ value: 20 }) },将内部的所有数据变成响应式的。现在使用解构后就不会丢失响应式了。

const { ref, reactive, toRefs } = Vue;

const app = Vue.createApp({
  template: `
    <div>
      <div>name: {{ name }}, age: {{ age }}</div>
    </div>
  `,
  setup(props, context) {
    const obj = reactive({ name: "sjh", age: 20 })
    setTimeout(() => {
      obj.name = "hhhhh"
    }, 1000)
    const { name, age } = toRefs(obj)
    return { name, age }
  }
})

app.mount("#root")

3. toRef 以及 context 参数

3.1 toRef:从响应式对象中获取单个响应式数据

有一种使用场景,当解构的时候并非解构响应式对象里所拥有的 key,则解构出来的数据没有响应式,值为 undefined。

如下代码:data 里边只有 name,但是想解构 age 出来,此时前端页面会因为 age 是 undefined 而报错。

const { ref, reactive, toRefs } = Vue;

const app = Vue.createApp({
  template: `
    <div>
      <div>{{ age }}</div>
    </div>
  `,
  setup(props, context) {
    const data = reactive({ name: "sjh" })
    const { age } = toRefs(data)
    setTimeout(() => {
      age.value = 20
    }, 1000)
    return { age }
  }
})

app.mount("#root")

toRef 就可以有效地解决问题。

语法:toRef(data, 'age'),意思是,尝试从 data 里取 age 数据,如果没找到,则返回一个值为 null 的响应式数据。

const { ref, reactive, toRefs, toRef } = Vue;

const app = Vue.createApp({
  template: `
    <div>{{ age }}</div>
  `,
  setup(props, context) {
    const data = reactive({ name: "sjh" })
    const age = toRef(data, "age")
    setTimeout(() => {
      age.value = 20
    }, 1000)
    return { age }
  }
})

app.mount("#root")

3.2 context 参数的意义

在上文已经有稍微讲一下 context 了,在这里深入讲一下。

之前说过,context 里边有三个值,分别为 attrs,slots,emit。

  • attrs:为父组件传过来的 non-props,即没有通过 props 接收的属性。

  • slots:父组件传递过来的插槽内容。例如,通过 slots.default() 可以获取默认插槽的虚拟 DOM 形式。

    例如,想玩些花的,用 h 函数来渲染通过父组件传过来的默认插槽:

    const app = Vue.createApp({
      template: `
        <child>parent</child>
      `,
    })
    
    app.component("child", {
      setup(props, context) {
        const data = reactive({ name: "sjh" })
        const { name } = toRefs(data)
        // return { name }
        const { attrs, slots, emit } = context
        return () => Vue.h('div', {}, slots.default())
      }
    })
    
    app.mount("#root")
    

    如果不使用 Composition API,就需要用 this.$slots.default() 来获取默认插槽

  • emit:触发自定义事件的方法。

    例如,子组件点击的时候,触发父组件的自定义事件:

    const app = Vue.createApp({
      template: `
        <child @change="handleChange">parent</child>
      `,
      setup(props) {
        const handleChange = () => alert("change")
        return { handleChange }
      }
    })
    
    app.component("child", {
      template: `
        <div @click="handleClick">child</div>
      `,
      setup(props, context) {
        const { attrs, slots, emit } = context
        const handleClick = () => emit("change")
        return { handleClick }
      }
    })
    
    app.mount("#root")
    

3. 示例:使用 Composition API 开发 todolist

现在写一个 todolist 来感受一下 Composition API 的优势:

  const { ref, reactive, toRefs, toRef } = Vue;

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

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

  const app = Vue.createApp({
    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>
  `,
    setup(props) {
      const { list, addItemToList } = listRelativeEffect()
      const { inputValue, handleInputValueChange } = inputRelativeEffect()
      return {
        list,
        addItemToList,
        inputValue,
        handleInputValueChange,
      }
    }
  })

  app.mount("#root")

使用了 Composition API 后,相关的逻辑可以分离出来,使得代码可维护性和可读性都有了很大的提升。

4. computed 方法生成计算属性

之前在 vue 基础中已经学了传统的 computed 语法了,现在讲一下 Composition API 里的计算属性。

4.1 基本使用

实现一个简单的计数器,并且有一行计算属性是计数的结果乘以 2 的结果:

const { ref, computed } = Vue;

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

app.mount("#root")

可以看到,在 Composition API 中,需要先引入 computed 函数,然后正常使用即可。

4.2 进阶使用

其实更详细的,computed 方法接收一个对象,里面接收 get 和 set 两个属性。

get 和 set 的值均为方法。get 方法返回提供的数据的值,set 方法用来处理属性设置的时候要执行的相应逻辑。

直接上代码,看的更清晰:

const app = Vue.createApp({
  template: `
    <div>
      <span @click="handleClick">{{ count }}</span> -- 
      <span>{{ countAddFive }}</span>
    </div>
  `,
  setup(props) {
    const count = ref(0)
    const handleClick = () => {
      count.value += 1
    }
    const countAddFive = computed({
      // 获取到的值时存储的 count 的值加上 5
      get: () => count.value + 5,
      // 设置 count 的值的时候把传进来的值减了 5
      set: (param) => count.value = param - 5,
    })
    return { count, countAddFive, handleClick }
  }
})

5. watch 和 watchEffect 的使用和差异性

5.1 watch 基本用法

Composition API 里提供了 watch 方法,和传统的 watch 方法类似,写在 setup 函数里。

Composition API 中使用 watch 的方式:

watch(name, (currentValue, prevValue) => {
  console.log(currentValue, prevValue)
})

可以看到 watch 方法接收两个参数,第一个参数指的是要监听的数据,第二个参数是监听到数据变化后的回调。回调函数有两个参数,第一个参数是数据改变后的值,第二个参数是数据改变前的值。

可以看出,watch 有两个特性:

  1. 具备一定的惰性,只有在数据变化的时候才会执行
  2. 参数可以拿到改变前和改变后的值

5.2 watch 监听非响应式数据

注意:监听的数据必须具有响应式,或者是个有返回值的函数

如 reactive 返回的响应式对象,里边的属性值时没有响应式的,如果要监听里面的单个属性而非整个响应式对象,就需要写成函数,或者将属性变成响应式。

  1. 解法一:写成函数

    const { ref, watch, reactive } = Vue;
    
    const app = Vue.createApp({
      template: `
      <div>
        <div>
          Name: <input v-model="nameObj.name" />
        </div>
        <div>
          Name is {{ nameObj.name }}
        </div>
      </div>
    `,
      setup(props) {
        const nameObj = reactive({ name: "sjh" })
        watch(() => nameObj.name, (currentValue, prevValue) => {
          console.log(currentValue, prevValue)
        })
        return {
          nameObj
        }
      }
    })
    
    app.mount("#root")
    
  2. 解法二:将属性变成响应式

    const { ref, watch, reactive, toRefs } = Vue;
    
    const app = Vue.createApp({
      template: `
      <div>
        <div>
          Name: <input v-model="name" />
        </div>
        <div>
          Name is {{ name }}
        </div>
      </div>
    `,
      setup(props) {
        const nameObj = reactive({name: "sjh"})
        const { name } = toRefs(nameObj)
        watch(name, (currentValue, prevValue) => {
          console.log(currentValue, prevValue)
        })
        return {
          name
        }
      }
    })
    
    app.mount("#root")
    

5.3 watch 监听多个数据

监听多个数据的时候,没必要重复写,写成数组复用即可。

const { name, age } = toRefs(data)
watch([name, age], ([curName, curAge], [prevName, prevAge]) => {
  console.log([curName, curAge], [prevName, prevAge])
})

5.4 watchEffect 基本用法

watchEffect 侦听器相比于 watch 侦听器,更偏向于 effect。

watchEffect 和 watch 的区别

  1. 只要写了 watchEffect,回调就立即执行,没有惰性
  2. watchEffect 会自己感知内部的代码数据是否因为相关依赖发生了改变,如果发生了改变就会执行,因此不需要传递很多参数。watch 需要通过参数指定监听哪个数据。
  3. watchEffect 无法获取之前的数据和当前的数据。watch 可以获取之前和当前的数据。
watchEffect(() => {
    // 当 nameObj.name 发生改变,会触发 watchEffect 的回调函数
    console.log(nameObj.name)
})

暂停 watchEffect

例如,五秒后结束监听

const stop = watchEffect(() => {
    console.log(nameObj.name)
    setTimeout(() => {
        stop()
    }, 5000)
})

watch 暂停监听的语法和 watchEffect 一模一样

watch 立即执行

设置参数 immediate: true 即可

const stop = watch(name, (currentValue, prevValue) => {
  console.log(currentValue, prevValue)
}, {
  immediate: true
})

6. 生命周期钩子

现在用 Composition API 来写生命周期钩子,直接摘官网的:

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

选项式 API 就是 vue2 的那种 API,因为 vue 组件是通过对象内的配置新建出来的。

因为 setup 函数执行的时间就在 beforeCreate 和 created 之间,因此 Composition API 没有提供响应的钩子。

export default {
  setup() {
    // mounted
    onMounted(() => {
      console.log('Component is mounted!')
    })
  }
}

7. Provide, inject, 模板 Ref 的用法

provide 和 inject

provide 提供了可跨层级的数据的提供和获取。在 Composition API 里是这样使用的:

const app = Vue.createApp({
  setup(props) {
    const { provide } = Vue
    provide("name", ref("sjh"))
  },
  template: `
    <div>
      <child />
    </div>
  `
})

app.component("child", {
  setup(props) {
    const { inject } = Vue
    // 第二个参数是未接收到 name 时的默认值
    const name = inject("name", "defaultName")
    return {
      name
    }
  },
  template: `<div>{{ name }}</div>`
})

用 provide 和 inject 进行子组件通知父组件

因为单向数据流的原则,子组件不能直接修改父组件的数据,因此是通过子组件通知父组件,然后父组件本身来修改自己的数据。原理和以前的 emit 类似,见下面的代码:

const app = Vue.createApp({
  setup(props) {
    const { provide, ref, readonly } = Vue
    const name = ref("name before change")
    provide("name", readonly(name))
    provide("changeName", (value) => {
      name.value = value
    })
  },
  template: `
    <div>
      <child />
    </div>
  `
})

app.component("child", {
  setup(props) {
    const { inject } = Vue
    const name = inject("name")
    // 获取父组件方法
    const changeName = inject("changeName")
    // 当点击的时候,通知父组件进行修改
    const handleClick = () => {
      changeName("changed name")
    }
    return {
      name,
      handleClick
    }
  },
  template: `<div @click="handleClick">{{ name }}</div>`
})

模板 ref 用法

这里的 ref 是获取 dom 节点,和之前的响应式 ref 冲突了。这里有个固定的写法:const nodeName = ref(null),此时会根据声明的变量名去 template 上寻找名称匹配的 ref 属性。

const app = Vue.createApp({
  setup(props) {
    const { ref, onMounted } = Vue
    // 获取 ref 为 hello 节点
    const hello = ref(null)
    onMounted(() => {
      console.log(hello.value)
    })
    return {
      hello
    }
  },
  template: `
    <div>
      <div ref="hello">hello world</div>
    </div>
  `
})
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-21 18:52:15  更:2022-05-21 18:55:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:18:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码