组合式API-1-入门

493 阅读5分钟

组合式API

前置知识

SFC组件化简单原理

vue-loader处理*.vue文件内部的信息,比如template被vue-complier进行编译成渲染函数注入到模块中,最后整个文件被处理成js模块。

这个过程中还有一些很有趣的点,比如data为什么要设置成函数返回对象的形式?

并且我们也可以在文件中写我们自己的标签,比如可以用来生成对应组件的文档信息。

为什么需要组合式API

由于vue2的SFC组件天写在script标签中分为data method 各种api。

一个组件的功能的代码实现分散在各个api中,导致代码组件增大后,难以维护的问题。

并且有时候组件的某些功能需要被复用(功能复用对于大型的项目是必要的,你也不想一段相同的模块,重复出现在你的代码中吧,同时还有IOC的思想在里面),vue2的解决方案是mixin,但是mixin有许多的缺点,不够完善。

于是推出了组合式API这种关注点分析的组件编写方式。

还有一点就是组合式API能够提供更好的ts支持。

个人感觉组合式api是mixin的强化版本。

mixin的问题:

  • Mixin 很容易发生冲突:因为每个 mixin 的 property 都被合并到同一个组件中,所以为了避免 property 名冲突,你仍然需要了解其他每个特性。
  • 可重用性是有限的:我们不能向 mixin 传递任何参数来改变它的逻辑,这降低了它们在抽象逻辑方面的灵活性。

为了解决这些问题提出了组合式Api

组合式API和组件的区别

组合式API(setup函数)会在每一个组件实例化的时候调用,而传统SFC编写的组件就是将信息注册到prototype上,生成一个组件类。从这一点看来,你可以将组合式API理解为构造函数,传统SFC编写的组件信息理解为prototype。组合式API可以用来编写组件也可以编写一些公共的接口,但是不意味着我们应该滥用它,具体原理我还没有进行深究。待到之后深入了解了再说。

setup的执行流程

新的 setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口。

执行结果

setup run beforeCreated created

我们可以看出setup Api是先于组件创建的,或者说先于new组件之间?

这也很符合setup这个名字....

对于一个组件来说 父组件传入的props被解析完毕后就会,就会以setup作为组件的入口。

在setup中是不能使用this的,原因如上面分析。

setup的返回值会和mixin的效果一样返回给组件。

 export default {
     props:{
         a: {
             type: String,
         },
         b: {
             type: String,
         }
     },
     setup(props) {
         console.log(props);
         console.log('setup run');
     },
     beforeCreate() {
         console.log('beforeCreated');
     },
     created() {
         console.log('created');
     }
 }

ref

在setup中注册的变量只是单纯的js的变量并不是响应式的,我们可以用vue3提供的ref API来”包裹“其变为响应式变量(回想一下Observe)

没有使用ref

 <template>
     <div>
         <p>demo0</p>
         <div>{{ans}}</div>
         <button style="width:50px;height:20px;background:blue" @click="add"></button>
     </div>
 </template>
 ​
 <script>
 export default {
     props:{
         a: {
             type: String,
         },
         b: {
             type: String,
         }
     },
     setup(props) {
         console.log('setup run');
         let ans = parseInt(props.a,10) + parseInt(props.b,10);
         let add = function() {
             console.log(ans);
             ans++;
         }
         console.log(ans);
         return {
             ans,
             add
         }
     },
 ​
 }

可以发现setup ans值增加了但是显示没有改变。

这是因为js是值复制的语言,我们传递的又是一个普通参数所以组件实例和setup函数运行时内部的不是一个地址的number

但是如果我们将其改造成对象传递出去呢?

很失望,发现值还是不能改变,这是为什么呢?我们探究一下

判断是实现内部的和setup传出去的到底是不是一个对象--通过下面的例子可以看出是一个对象

这时候官网提示我们要用ref封装值,才能让他们变为响应式。

 <template>
     <div>
         <p>demo0</p>
         <div>{{out}}</div>
         <button style="width:50px;height:20px;background:blue" @click="add"></button>
     </div>
 </template>
 <script>
 import { ref } from '@vue/reactivity';
 export default {
     props:{
         a: {
             type: String,
         },
         b: {
             type: String,
         }
     },
     data() {
         return {
             test:{}
         }
     },
     setup(props) {
         console.log('setup run');
         let ans = parseInt(props.a,10) + parseInt(props.b,10);
         let add = function() {
             out.value++;
             console.log(out);
         }
         const out = ref(ans);
         return {
             out,
             add
         }
     },
     created() {
         window.inner = this.out;
         console.log(window.inner);
     }
 }
 </script>

使用ref

使用ref后我们可以发现,原本的对象变成了一个Proxy对象,这也正好证明了,调用ref后setup传回的值才会变成响应式,否则不会变为响应式。

  const out = ref({ans:ans});

同时直接使用data创建的对象本身就是响应式。

 data() {
     return {
         test:{}
     }
 },
 created() {
     console.log(this.test);
 }
 //Proxy{}

生命周期函数

某些功能模块可能需要使用组件实例的生命周期函数来完成特定动作,vue也提供给我们对应的api。总结就是在原本生命周期函数前+on。

原理猜想:不再是一个回调函数,而是对于每一个时间点设置一个回调队列,调用onMounted的时候会将回调函数加入到组件实例的对应回调函数时间点队列中。

队列是先入先出的,setup先于实例创建所以setup的生命周期函数先执行。

有趣的一点,我想试试destory的时候他告诉我以及废弃了 我们应该使用unmounted,难道vue3的生命周期内部修改了嘛。 由于setup的执行时间和created和beforeCreated执行时间差不多(都是先于组件实例产生),于是你如果要需要这两个hook的话,就可以直接在setup里面写对应需要执行的代码就好了。

 export default {
     setup() {
         onMounted(()=>{
             console.log('setup mounted');
         })
     },
     mounted() {
         console.log('mounted');
     }
 }

watch和computed方法

使用这两个方法之前我们要确保被处理的属性是可被监听的(也就是响应式对象) vue3中可以使用 reactive 和 ref来让对象变成响应式,ref主要是为了解决基本变量而提出来的,封装之后reactive多一层 可以用ref(xx).value 取出具体对象Proxy。如果需要解构那么就需要用到toRefs 以及 toRef 这两个API。

 watch(counter, (newValue, oldValue) => {
   console.log('The new counter value is: ' + counter.value)
 })
 const twiceTheCounter = computed(() => counter.value * 2)
 console.log(twiceTheCounter.value)

优化代码

我们可以将各个功能模块在不同的文件中实现,最后引入组件的setup函数中。

   setup(props) {
     const { user } = toRefs(props)
 ​
     const { repositories, getUserRepositories } = useUserRepositories(user)
 ​
     const {
       searchQuery,
       repositoriesMatchingSearchQuery
     } = useRepositoryNameSearch(repositories)
 ​
     const {
       filters,
       updateFilters,
       filteredRepositories
     } = useRepositoryFilters(repositoriesMatchingSearchQuery)
 ​
     return {
       // 因为我们并不关心未经过滤的仓库
       // 我们可以在 `repositories` 名称下暴露过滤后的结果
       repositories: filteredRepositories,
       getUserRepositories,
       searchQuery,
       filters,
       updateFilters
     }
   }

总结

组合式API即实现了复用又可以让关注点集中,真的很棒!

但是它的缺点是什么呢?

有没有性能损失?

在什么场景下运用最好?

这些问题我们还要继续探讨!

参考:

vue文档