首页 > 其他分享 >Vue3 Composition API

Vue3 Composition API

时间:2022-10-27 17:13:21浏览次数:49  
标签:const watch value reactive state API Vue3 ref Composition

一、响应式基础

前提:你会使用 setup 函数或 <script setup>语法

1.reactive

我们可以使用 reactive() 函数创建一个响应式对象数组

import { reactive } from 'vue'

const state = reactive({ count: 0 })
  • 当我们使用 reactive 函数处理我们的数据之后,数据被使用时就会进行依赖收集;
  • 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面);
  • 事实上,在使用Options API时编写的 data 选项,也是在内部交给了 reactive 函数将其变成响应式对象的

注意:

reactive 函数只能包裹对象或数组,包裹基本数据类型会丧失响应式,并且控制会打印出警告

2. ref

reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。为此,Vue 提供了一个 ref()方法来允许我们创建可以使用任何值类型的响应式 ref

import { ref } from 'vue'

const countRef = ref(0)
// 使用
console.log(countRef.value)

使用ref()函数定义的响应式数据,是一个带有.value的Ref对象,在<script>中使用该值时,需要.value赋值或取值。

而在template模板中,则会自动解包,取出.value的值。

实例代码:

<template>
  <h2>composition API</h2>
  <p>ref-simple: {{ simple }}</p>
  <p>ref-obj: {{ obj }}</p>
  <div>
    <p><button @click="simple++">更改simple</button></p>
    <p><button @click="obj.name += '1'">更改obj.name</button></p>
    <p><button @click="editObjName">更改obj.name</button></p>
  </div>
  <hr />
  <p>reactive-state: {{ state }}</p>
  <p>reactive-count: {{ count }}</p>
  <div>
    <p><button @click="state.firstName += '1'">更改reactive-state</button></p>
    <p><button @click="count++">更改reactive-count</button></p>
  </div>
</template>

<script>
import { reactive, ref } from 'vue';
export default {
  name: 'App',
  setup() {
    const simple = ref(0);
    const obj = ref({ name: 'fct' });
    // reactive
    const state = reactive({ firstName: 'Steven', secordName: 'Diff' });
    const count = reactive(0);	// 报警告
    function editObjName() {
      obj.value.name += '1';
    }
    return {
      simple,
      obj,
      state,
      count,
      editObjName
    };
  }
};
</script>

2.1 获取HTML元素或组件实例

使用ref()函数,要声明一个同名的 ref。

<template>
  <input ref="inputRef" />
</template>

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

// 声明一个 ref 来存放该元素的引用
// 必须和模板里的 ref 同名
const inputRef = ref(null)

onMounted(() => {
  inputRef.value.focus()
})
// 如果不使用 <script setup>,需确保从 setup() 返回 ref:
// export default {
//   setup() {
//     const inputRef = ref(null)
//     // ...
//     return {
//       inputRef
//     }
//   }
// }
</script>

3. readonly

我们通过reactive()或者ref()可以获取到一个响应式的对象,但是某些情况下,我们传入响应式对象给其他地方(组件),希望在另外一个地方(组件)被使用,但是不能被修改,这个时候可以使用readonly()

  • readonly()接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

实例代码:

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)  // 1.打印 0
})

// 更改源属性会触发其依赖的侦听器
original.count++    // 2. 侦听器打印 1

// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!

二、reactive、ref、readonly相关API

1. reactive判断的API

1.1 isProxy

检查对象是否是由 reactive 或 readonly创建的 proxy

1.2 isReactive

  • 检查对象是否是由 reactive 创建的响应式代理
  • 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true。

1.3 isReadonly

检查对象是否是由 readonly 创建的只读代理

1.4 toRaw

返回 reactive 或 readonly 代理的原始对象建议保留对原始对象的持久引用。请谨慎使用)。

1.5 shallowReactive

创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。

1.6 shallowReadonly

创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。

2.toRefs

如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive 返回的state对象数据都不再是响应式的:

import { reactive } from 'vue';
const state = reactive({
  msg: 'fct',
  tips: 'vue3'
});
const { msg, tips } = state;
// msg 为非响应式的
  • Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref

  • 那么我们再次进行结构出来的 msg 本身都是 ref的;

import { reactive, toRefs } from 'vue';

const state = reactive({
  msg: 'fct',
  tips: 'vue3'
});
const { msg, tips } = toRefs(state);

这种做法相当于已经在state.msgref.value之间建立了链接,任何一个修改都会引起另外一个变化;

3. toRef

如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法

import { reactive, toRef } from 'vue';

const state = reactive({
  msg: 'fct',
  tips: 'vue3'
});
const tips = toRef(state, 'tips');
// tips 为响应式数据

4. ref其他的API

4.1 unref

如果我们想要获取一个ref引用中的value,那么也可以通过unref方法

  • 如果参数是一个 ref,则返回内部值,否则返回参数本身

  • 这是 val = isRef(val) ? val.value : val 的语法糖函数;

    import { ref, unref } from 'vue';
    
    const fct = ref(999);
    console.log(fct.value, unref(fct));
    

4.2 isRef

判断值是否是一个ref对象

4.3 shallowRef

创建一个浅层的ref对象

// 不能监听 info.name 发生的改变,因为是浅层Ref对象
const info = shallowRef({ name: 'fct' });

// 能监听 num 发生的改变
const num = shallowRef(32);

4.4 triggerRef

手动触发和 shallowRef 相关联的副作用

<template>
  <div>
    <p>info: {{ info }}</p>
    <p>num: {{ num }}</p>
    <button @click="info.name += '1'">更改shallowRef-info中的name</button>
    <button @click="editInfoName">triggerRef-更改shallowRef-info中的name</button>
    <button @click="num++">shallowRef-num++</button>
  </div>
</template>

<script setup>
import { shallowRef, triggerRef } from 'vue';

const info = shallowRef({ name: 'fct' });
const num = shallowRef(32);

function editInfoName(params) {
  info.value.name += '1';
  // 手动触发更新
  triggerRef(info);
}
</script>

三、其他API

1. computed计算属性

1.1 基本使用

与Vue2相同,推荐使用计算属性来描述依赖响应式状态的复杂逻辑。

computed() 方法期望接收一个 getter 函数,返回值为一个计算属性 ref。和其他一般的 ref 类似,你可以通过 publishedBooksMessage.value 访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value

<template>
  <div>
    <p>computed: {{ fullName }}</p>
  </div>
</template>

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

export default {
  setup() {
    const state = reactive({
      fName: 'Steven',
      sName: 'Jiff'
    });
    const fullName = computed(() => {
      return state.fName + '--' + state.sName;
    });

    return {
      fullName
    };
  }
};
</script>

1.2 可写可读计算属性

计算属性默认是只读的。当你尝试修改一个计算属性时,你会收到一个运行时警告。只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 gettersetter 来创建:

<template>
  <div>
    <p>computed-fullName: {{ fullName }}</p>
    <p>computed-fullName-2: {{ fullName2 }}</p>
    <button @click="editFullName2">更改fullname2</button>
  </div>
</template>

<script setup>
import { computed, reactive } from 'vue';
const state = reactive({
  fName: 'Steven',
  sName: 'Jiff'
});
const fullName = computed(() => {
  return state.fName + '--' + state.sName;
});
const fullName2 = computed({
  get() {
    return state.fName + '--' + state.sName;
  },
  set(value) {
    const arr = value.split('--');
    state.fName = arr[0];
    state.sName = arr[1];
  }
});
function editFullName2() {
  fullName2.value = 'go--back';
}
</script>

2. 生命周期钩子

在setup函数中,不再需要beforeCreatecreated两个生命周期函数。

使用前,需要在setup函数中注册生命周期钩子函数。

举例:onMounted钩子,在组件完成初始渲染并创建 DOM 节点后运行代码,

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

onMounted(() => {
  console.log(`the component is now mounted.`)
})
</script>

Options API 和 Composition API的生命周期钩子对比:

Options API Composition API
beforeCreate not need
created not need
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onUnmounted
unmounted onUnmounted
activated onActivated
deactivated onDeactivated

3. provide/inject

3.1 provide

使用provide(),提供一个值,可以被后代组件注入。

provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。

与注册生命周期钩子的 API 类似,provide() 必须在组件的 setup() 阶段同步调用。

<script>
import { reactive, readonly, ref, watchEffect, onMounted, provide } from 'vue';
import Son from './components/Son.vue';
export default {
  name: 'App',
  components: {
    Son
  },
  setup() {
    // provide/inject
    const good = reactive({
      id: '001',
      name: '小米手机'
    });
    const goodNum = ref(100);
    provide('good', good);
    provide('goodNum', goodNum);
    return {
      good,
      goodNum
    };
  }
};
</script>

3.2 inject

使用inject(),注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。

参数:

  • 第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。
  • 第二个参数是可选的,即在没有匹配到 key 时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。
  • 第三个参数,用来指定默认值就是一个函数,而不是工厂函数。
<template>
  <div>
    <p>{{ good }}</p>
    <p>{{ goodNum }}</p>
    <button @click="good.name = 'huawei'">更改good.name</button>
    <button @click="goodNum++">更改goodNum</button>
  </div>
</template>

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

const good = inject('good');
const goodNum = inject('goodNum');
watch(
  good,
  (newValue, oldValue) => {
    console.log(newValue?.name, oldValue?.name);
    console.log(newValue, oldValue);
  },
  { immediate: true }
);
watch(goodNum, (newValue, oldValue) => {
  console.log(newValue, oldValue);
});
</script>

4.侦听器

4.1 watch

在组合式 API 中,我们可以使用 watch() 函数在每次响应式状态发生变化时触发回调函数。

  • watch第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)一个响应式对象一个 getter 函数、或多个数据源组成的数组;
  • 第二个参数是回调函数,回调有两个参数(newValue新值,oldValue旧值);
  • 第三个参数是配置对象
    • immediate 是否立即执行一次
    • deep 深层侦听
<script setup>
import { ref, watch, watchEffect } from 'vue';
const x = ref(0)
const y = ref(0)

// 单个 ref
watch(x, (newX) => {
  console.log(`x is ${newX}`)
})

// getter 函数
watch(
  () => x.value + y.value,
  (sum) => {
    console.log(`sum of x + y is: ${sum}`)
  }
)

// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
  console.log(`x is ${newX} and y is ${newY}`)
})

</script>
  • 直接给 watch() 传入一个响应式对象作为第一个参数,会隐式地创建一个深层侦听器——该回调函数在所有嵌套的变更时都会被触发。
const obj = reactive({ count: 0 })

watch(obj, (newValue, oldValue) => {
  // 在嵌套的属性变更时触发
  // 注意:`newValue` 此处和 `oldValue` 是相等的
  // 因为它们是同一个对象!
})

obj.count++

4.1.1 注意:

你不能直接侦听响应式对象的属性值:

const person = ref({ name: 'fct', age: 18 });

// 错误,因为 watch() 得到的第一个参数是一个 number
watch(person.value.age, () => {
      console.log('age1');
});

// 正确做法:
watch(
  // 提供一个 getter 函数
  () => person.value.age,
  () => {
    console.log('age2');
  }
);

4.2 watchEffect

watch() 是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。就可以使用watchEffect()函数。

watchEffect() 会立即执行一遍回调函数,如果这时函数产生了副作用,Vue 会自动追踪副作用的依赖关系,自动分析出响应源。

<template>
  <div>
    <p>watch-person: {{ person }}</p>
    <p>watch-dog: {{ dog }}</p>
    <button @click="person.age++">person.age++</button>
    <button @click="dog.age++">dog.age++</button>
  </div>
</template>

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

export default {
  setup() {
    const person = ref({ name: 'fct', age: 18 });
    const dog = ref({ name: 'lele', age: 3 });
    watchEffect(() => {
      console.log(person.value, person.value.age);
    });
    return {
      person,
      dog
    };
  }
};
</script>
  • 这个例子中,回调会立即执行。在执行期间,它会自动追踪 person.value 作为依赖(和计算属性的行为类似)。每当 person.value 变化时,回调会再次执行。
  • 当改变dog.value中的值时,watchEffect中的回调函数不会执行。

4.2.1 停止侦听

setup()<script setup> 中用同步语句创建的侦听器,会自动绑定到宿主组件实例上,并且会在宿主组件卸载时自动停止。

但一个关键点是,侦听器必须用同步语句创建:如果用异步回调创建一个侦听器,那么它不会绑定到当前组件上,你必须手动停止它,以防内存泄漏。

<script setup>
import { watchEffect } from 'vue'

// 它会自动停止
watchEffect(() => {})

// ...这个则不会!
setTimeout(() => {
  watchEffect(() => {})
}, 100)
</script>

手动停止侦听器:

要手动停止一个侦听器,请调用 watchwatchEffect 返回的函数:

const num = ref(0)
const unwatch1 = watch(num, () => {})

const unwatch = watchEffect(() => {})

// ...当该侦听器不再需要时
unwatch()
unwatch1()

4.3 回调触发的时机

当你更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调。

默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着你在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。

如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: 'post' 选项:

watch(source, callback, {
  flush: 'post'
})

watchEffect(callback, {
  flush: 'post'
})

其中后置刷新的 watchEffect() 有个更方便的别名 watchPostEffect()

import { watchPostEffect } from 'vue'

watchPostEffect(() => {
  /* 在 Vue 更新后执行 */
})

4.3.1 实例

<template>
  <div>
    <p>watch-person: {{ person }}</p>
    <p>watch-dog: {{ dog }}</p>
    <input ref="inputRef" type="text" v-model.number="person.age" />
    <button @click="person.age++">person.age++</button>
    <button @click="dog.age++">dog.age++</button>
  </div>
</template>

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

export default {
  setup() {
    const person = ref({ name: 'fct', age: 18 });
    const dog = ref({ name: 'lele', age: 3 });
    const inputRef = ref(null);
    // 回调触发的时机
    // 错误侦听
    // watch(person.value.age, () => {
    //   console.log('age1');
    // });
    watch(
      () => person.value.age,
      (newValue, oldValue) => {
        // if (inputRef.value) {
        console.log('person.value.age--new', newValue);
        console.log('person.value.age--old', oldValue);
        console.log('input.target.value-组件更新前', inputRef.value.value);
        // }
      }
      // {
      //   flush: 'post'
      // }
    );
    watchEffect(
      () => {
        if (person.value.age) {
          console.log('watchEffect-flush:post-组件更新后', inputRef.value.value);
        }
      },
      { flush: 'post' }
    );
    watchPostEffect(() => {
      if (person.value.age) {
        console.log('watchPostEffect-组件更新后', inputRef.value.value);
      }
    });
    return {
      person,
      dog,
      inputRef
    };
  }
};
</script>

标签:const,watch,value,reactive,state,API,Vue3,ref,Composition
From: https://www.cnblogs.com/fuct/p/16832911.html

相关文章

  • #打卡不停更#三方库移植之NAPI开发[3]通过IDE开发NAPI工程
    在三方库移植之NAPI开发[1]—HelloOpenHarmonyNAPI一文中,笔者开发的是一个rom包的napi工程。该工程需要编译烧录固件,C++的动态库会集成到开发板的ROM中。在本篇文章中,......
  • 【计算机视觉(CV)】基于高层API实现宝石分类
    【计算机视觉(CV)】基于高层API实现宝石分类作者简介:在校大学生一枚,华为云享专家,阿里云专家博主,腾云先锋(TDP)成员,云曦智划项目总负责人,全国高等学校计算机教学与产业实践资......
  • 090_索引和文档的API操作
    目录注入RestHighLevelClient关于索引的API操作创建索引判断索引是否存在删除索引关于文档的API操作创建实体类添加文档判断文档是否存在获取文档更新文档删除文档大批量文......
  • .NET Core WebApi 快速切换开发/生产环境
    项目结构中有三个配置文件  appsettings.json:通用配置文件appsettings.Development.json:开发环境配置文件appsettings.Production.json:生产环境配置文......
  • vue3-code-diff
    vue3-code-diffVue2/Vue3可用的codediff插件是vue-code-diff安装#WithNPMnpmiv-code-diff#WithYarnyarnaddv-code-diff全局注册import{createA......
  • 微信小程序之获取定位api忽然失效解决方法
    这个问题困扰了我一整天才解决,一定要写篇文章记录下,下面先讲讲遇到问题后的思路。测试提出bug:小程序获取位置信息失败了,也不会弹出授权弹框,然后我在想之前明明是好的,......
  • 一、认识Vue-API 风格
    Vue的组件可以按两种不同的风格书写:选项式API 和组合式API。(一)、选项式API(OptionsAPI)使用选项式API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data......
  • fs01 FreeSWITCH中APP和API
    PART1APP和API的区别 简单来说,一个APP是一个程序,它作为一个Channel一端与另一端的UA进行通信,相当于它工作在Channel内部;而一个API则是独立于一个Channel之外的,它只能通......
  • 【Spark 3.0-JavaAPI-pom】体验JavaRDD函数封装变化
    一、pom<properties><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><scala.version>2.......
  • .Net Core WebApi AutoFac用法
    1.安装Autofac.Extensions.DependencyInjection管理包UI层安装 2.在Program里面配置服务提供工厂  3.在Startup里面添加一个配置容器的方法使用基于扫描程序集......