Vue3.0 | 快速上手Compisition API新特性(上)

245 阅读5分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

⏳ 前言

Vue3自2020年发布以来就广受开发者热捧,其中最值得我们学习的就是新推出的Composition API,这也是在前端面试中必问的内容,这篇文章用于记录学习Vue3.0 Composition API的过程

🔧 创建vue3项目

npm install -g @vue/cli
vue create my-app
cd my-app
npm run serve

🚗 Compisition API新特性

Vue2.0升级到Vue3.0后,对开发者来说比较明显的一个改变是Vue从选项式API(Options API)变成了Composition API框架的设计模式,也就是说Vue将很多API拆分成了一个一个的hook,组合起来形成了Composition API。我们在Vue2中的methodscomputedwatchmounted等...都变成了Vue3中的每一个钩子函数hook,并提供给开发者按需导入并在setup函数作用域中使用

// Vue2.0   Options API
<script>
import { watch, computed, mounted } from 'vue';
export default {
    name: "App",
    data() {
      return {}
    },
    mounted() {
      // ...
    },
    computed() {
      // ...
    }
}
</script>

// Vue3.0  Composition API
<script>
import { onUpdated, watch, computed, onUnmounted } from 'vue';
export default {
    name: "App",
    setup() {
      onUpdated(() => {
          // ...
      });
      onUnmounted(() => {
          // ...
      });
      watch(() => {}, () => {});
      return {
        // ...
      };
    }
}
</script>

Setup

setup是Composition API的入口,它的存在是为了能让开发者能够使用Compisition API。(It serves as the entry point for composition APIs.

setup函数在组件示例被创建时且props被初始化后被调用。从生命周期的角度上说,它在beforeCreate前被调用

setup函数接收两个参数:propscontext。setup函数中的props是响应式的,当传入新的prop时,它将被更新。

注意: 不要对props解构,否则会丢失响应式的特性

However, do NOT destructure the props object, as it will lose reactivity

我们都知道,在Vue2中要想在模板中使用数据,必须在data中返回,到了Vue3中,这个函数被替换成了setup,也就是说我们需要在setup函数中返回一个对象,对象中的每一个属性会被合并到render函数上下文中,这样一来模板就可以获取到render函数中的数据,从而渲染到模板中去

<template>
  <div>{{ readersNumber }} {{ book.title }}</div>
</template>

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

  export default {
    setup() {
      const readersNumber = ref(0)
      const book = reactive({ title: 'Vue 3 Guide' })

      // expose to template
      return {
        readersNumber,
        book
      }
    }
  }
</script>

reactive

reactive接收一个对象作为参数,并将它转化为proxy响应式对象,当数据更新时,UI界面也会更新

<template>
  <div id="app">
    {{data.count}}
    <button @click="data.increase">👍+1</button>
  </div>
</template>

<script>
import { computed, reactive, ref } from '@vue/reactivity';
export default {
  name: 'App',
  setup() {
    const data = reactive({
      count: 0,
      increase: () => {
        data.count ++
      }
    })
    return {
      ...data
    }
  }
}
</script>

这个时候如果我们尝试将reactive对象data解构,直接得到countincrease 运行项目后我们会发现点击按钮时页面数据并没有更新,这是因为上文提到的:将响应式对象解构会使它失去响应式

如果你觉得每次需要data.来获取数据有些繁琐,执意要对data解构,但又不想丢失数据的响应式,那么你可以使用toRefs将reactive对象中的每一个属性都转化为ref对象,toRefs会很好解决这个问题,这个会在后面提及

ref

ref函数接收一个原始值inner value,返回一个响应式的ref对象,它的作用是将原始类型转化为响应式的对象。ref对象只有一个属性value,通过.value的方式可以获取ref对象的inner value

<template>
  <div id="app">
    {{count}}
    <button @click="increase">👍+1</button>
  </div>
</template>

<script>
import { ref } from '@vue/reactivity';
export default {
  name: 'App',
  setup() {
    const count = ref(0)
    const increase = () => {
      count.value ++
    }
    return {
      count,
      increase
    }
  }
}
</script>

<style>
</style>

toRef

我们知道用ref可以用于创建一个响应式数据,而toRef也可以创建一个响应式数据,那他们之间有什么区别呢? 事实上,如果利用ref函数将某个对象中的属性变成响应式数据,修改响应式数据是不会影响到原始数据。 例如:

<template>
  <div id="app">
    {{obj.name}}
    {{newObj}}
    <button @click="change">change</button>
  </div>
</template>

<script>
import { computed, reactive, ref, toRefs } from '@vue/reactivity';
export default {
  name: "App",
  setup() {
    let obj = {
      name: "张三", 
      age: 34 
    };
    let newObj = ref(obj.name);
    function change() {
      newObj.value = "李四";
      console.log(obj, newObj);
    }
    return { obj, newObj, change };
  }
};
</script>

image.png

执行change函数后,可以看到,ref对象newObj发生改变,但原始数据obj没有改变,原因是ref的本质是拷贝,与原始数据没有引用关系

image.png

而如果使用toRef将某个对象中的属性变成响应式数据,修改响应式数据是会影响到原始数据的。但是需要注意,如果修改通过toRef创建的响应式数据,并不会触发UI界面的更新。 所以,toRef的本质是引用,与原始数据有关联

<script>
import { reactive, ref, toRefs } from '@vue/reactivity';
export default {
  name: "App",
  setup() {
    let obj = {
      name: "张三", 
      age: 34 
    };
    let newObj = ref(obj.name);
    function change() {
      newObj.value = "李四";
      console.log(obj, newObj);
    }
    return { obj, newObj, change };
  }
};
</script>

当change执行时,响应式数据发生改变,原始数据obj也随之改变,但页面并不会更新。

ps:如果希望原始数据跟随toRef改变的话,可以将obj转化为reactive响应式对象

ref和toRef的区别:

  1. ref的本质是拷贝,ref数据改变不会影响原始数据。toRef的本质是引用,它的数据改变会影响原始数据
  2. ref数据修改后UI界面会更新,toRef数据修改后UI界面不会更新
  3. ref接受一个原始数据作为参数,而toRef接收两个参数,第一个参数是对象,第二个参数是对象的属性,表明需要将对象的哪个属性转化为ref响应式对象

toRefs

有的时候,我们希望将对象的多个属性都变成响应式数据,并且要求响应式数据和原始数据关联,并且更新响应式数据的能够同时更新UI界面,就可以使用toRefs将对象上的多个数据转化为为响应式数据。

toRefs接收一个对象作为参数,它会遍历对象身上的所有属性,然后将对象上的每一个属性都转化为ref对象。

<template>
  <div id="app">
    {{count}}
    <button @click="increase">👍+1</button>
  </div>
</template>

<script>
import { computed, reactive, ref, toRefs } from '@vue/reactivity';
export default {
  name: 'App',
  setup() {
    const data = reactive({
      count: 0,
      increase: () => data.count ++
    })
    const refData = toRefs(data)
    return {
      ...refData
    }
  }
}
</script>

刚才我们提到可以使用toRefs将reactive对象中的每一个属性都转化为ref对象,从而保留了数据的响应式

<template>
  <div id="app">
    {{data.count}}
    <button @click="data.increase">👍+1</button>
  </div>
</template>

<script>
import { computed, reactive, ref } from '@vue/reactivity';
export default {
  name: 'App',
  setup() {
    const data = reactive({
      count: 0,
      increase: () => {
        data.count ++
      }
    })
    return {
      ...toRefs(data)
    }
  }
}
</script>

isRef

isRef用于判断一个值是否是ref对象

<script>
import { reactive, ref, toRef, toRefs, isRef } from "@vue/reactivity";
export default {
  name: "App",
  setup() {
    const state = reactive({ count:1 })
    const stateRefs = toRefs(state)
    
    console.log(isRef(stateRefs))    // false
    console.log(isRef(stateRefs.count))    // true
  }
};
</script>