Vue3-01 组合式API

本文介绍了Vue3的升级过程,包括VueCLI的更新、项目创建、Vue实例、Router和Store的创建。重点讨论了组合式API的调用时机、返回值、参数以及在模板中的使用。同时,对比了Vue3的生命周期钩子和Vue2的变化,探讨了响应式修改、依赖注入、模板Refs的使用,并与React Hooks进行了比较。文章指出,Vue3的组合式API旨在提供更灵活的代码复用和逻辑提取,但需要开发者保持良好的代码组织。

升级到Vue3

升级VueCLI

VueCLI需要在4.3.1以上才可以支持Vue3

npm update -g @vue/cli

vue -V
@vue/cli 4.4.6

创建项目

vue create vue3-learning
vue add vue-next # 添加 vue3 插件升级为 vue3

在创建项目时选择手动添加配置,选择vue-router和Vuex,这样创建完的项目各个插件也都会升级为支持Vue3的版本

{
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^3.0.0-beta.1",
    "vue-router": "^4.0.0-alpha.6",
    "vuex": "^4.0.0-alpha.1"
  }
}

创建Vue实例

import {createApp} from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

createApp(App)
  .use(router)
  .use(store)
  .mount('#app');

创建Router

import {createRouter, createWebHistory} from 'vue-router';

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
});

export default router;

创建路由的方式与以前有所变化,路由模式除了Hash模式(createWebHashHistory)和History模式(createWebHistory),还多了带缓存的History路由(createMemoryHistory

使用路由跳转的API也有所变化:

import {useRouter} from 'vue-router';

export default {
  setup() {
    const router = useRouter();
    const goHome = () => router.push('/');
    return {goHome}
  }
}

关于router的具体变化,后面再单独学习

创建Store

import {createStore} from 'vuex';

export default createStore({
  state: {
    count: 0
  },
  mutations: {
    changeCount(state, isAdd) {
      state.count = isAdd ? state.count + 1 : state.count - 1;
    }
  },
  actions: {},
  modules: {}
});

使用:

import {useStore} from 'vuex';

export default {
  setup() {
    const store = useStore();
    const storeState = store.state;
    return {storeState}
  }
}

可以看出来,Vuex和vue-router,API都有了一些变化,与React Hooks的API很类似,但是基本原理没有太大变化

setup

setup函数是新的Composition API的入口点

调用时机

setup在创建组件之前调用(在beforeCreate之前),Props初始化后就会调用setup函数,在beforeCreate钩子前被调用

返回值

setup返回的对象的属性将被合并到组件模板的渲染上下文,也可以返回一个渲染函数

参数

接受两个参数:

  • props
  • context

props

接受props作为第一个参数,使用的时候,需要首先声明props

export default {
  props: {
    name: String,
  },
  setup(props) {
    console.log(props.name)
  },
}

props是响应式的,前提是不对props进行解构,解构后会失去响应性

如果需要结构props,可以通过使用roRefs来完成解构操作,并且保持响应式:

import { toRefs } from 'vue'

setup(props) {
  const { title } = toRefs(props)

  console.log(title.value)
}

如果title是一个可选的Prop,此时toRefs不会为title创建一个ref,需要使用toRef来创建响应式变量:

import { toRef } from 'vue'

setup(props) {
  const title = toRef(props, 'title')

  console.log(title.value)
}

context

setup第二个参数是上下文对象context,从2.x中的this选择性地暴露出三个组件的属性attrsslotsemitcontext就是一个普通的JavaScript对象,它不是响应式的,所以可以对context进行解构,不需要担心失去响应性

export default {
  setup(props, context) {
    // Attribute (非响应式对象)
    console.log(context.attrs)

    // 插槽 (非响应式对象)
    console.log(context.slots)

    // 触发事件 (方法)
    console.log(context.emit)
  }
}

// 或者
export default {
  setup(props, { attrs, slots, emit }) {
    ...
  }
}

this的用法

thissetup中不可用,它并不会指向该组件实例

访问组件的Property

执行setup时,组件实例尚未被创建,因此只能访问slots/props/attrs/emit这些组件属性,无法访问computeddatamethods组件选项

结合模板使用

如果setup返回一个对象,那么可以在组件模板中直接使用,就如同使用Props一样

<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>

setup返回的refs在模板中访问是被自动解开的,不需要在模板中使用.value

生命周期

生命周期钩子函数只能在setup期间同步使用,在组件卸载时,生命周期内部创建的侦听器和计算状态也会被自动删除

与Vue2.x相比,beforeCreatedcreated被删除了,对应的逻辑在setup内部完成,其他的生命周期钩子都改为了onXxx的形式(beforeDestoryed改为了onBeforeUnmountdestroyed改为了onUnmounted

两个新增的调试钩子函数onRenderTrackedonRenderTriggered

export default {
  onRenderTriggered(e) {
    debugger
    // 检查哪个依赖性导致组件重新渲染
  },
}

函数接受一个回调函数,当钩子被组件调用时将会被执行

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

依赖注入

基本使用

使用provideinject实现依赖注入,与2.x版本中基本一致,只能在setup中使用

import { provide, inject } from 'vue'

const ThemeSymbol = Symbol()

const Ancestor = {
  setup() {
    provide(ThemeSymbol, ref('dark'))
  },
}

const Descendent = {
  setup() {
    const theme = inject(ThemeSymbol, ref('light') /* optional default value */)
    return {
      theme,
    }
  },
}

使用ref传值可以保证providedinjected之间值的响应性

响应式修改

在使用响应式provide/inject时,建议尽可能,在提供者内保持响应式Property的任何更改

如果需要在注入数据的组件内部更新inject的数据,在这种情况下,建议provide一个方法来负责改变响应式Property的方法,让注入方调用

如果要确保通过provide传递的数据不会被inject的组件更改,可以对提供者的Property使用readonly

import { provide, reactive, readonly, ref } from 'vue'

export default {
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    const updateLocation = () => {
      location.value = 'South Pole'
    }

    provide('location', readonly(location))
    provide('geolocation', readonly(geolocation))
    provide('updateLocation', updateLocation)
  }
}

getCurrentInstance

如果在setup中要访问组件实例,需要使用这个方法来访问,例如在app.config.globalProperties定义了全局变量,如果希望在setup中访问,就要用到这个方法:

// main.js
app.config.globalProperties.$http = () => {
  console.log(123);
};

// 组件中
import { getCurrentInstance } from 'vue'

const MyComponent = {
  setup() {
    const internalInstance = getCurrentInstance()

    internalInstance.appContext.config.globalProperties.$http(); // 访问 globalProperties
  }
}

注意,它只能在setup和生命周期钩子中调用,如果在其外调用(例如Event Listener中)需要在setup中调用getCurrentInstance()获取实例后传入对应的函数中使用

const MyComponent = {
  setup() {
    const internalInstance = getCurrentInstance(); // works

    // 在组合式函数中调用也可以正常执行
    const id = useComponentId();// works

    const handleClick = () => {
      getCurrentInstance(); // doesn't work
      useComponentId(); // doesn't work

      internalInstance; // works
    }

    onMounted(() => {
      getCurrentInstance() // works
    });

    return () =>
      h(
        'button',
        {
          onClick: handleClick
        },
        `uid: ${id}`
      )
  }
}


function useComponentId() {
  return getCurrentInstance().uid
}

模板Refs

常规使用

Vue2.x中的ref原本是用于获取DOM的, Vue3中ref不仅可以响应化数据,也可以实现获取DOM的功能

<template>
  <div ref="root"></div>
</template>

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

  export default {
    setup() {
      const root = ref(null)

      onMounted(() => {
        // 在渲染完成后, 这个 div DOM 会被赋值给 root ref 对象
        console.log(root.value) // <div/>
      })

      return {
        root,
      }
    },
  }
</script>

setup中声明一个ref并返回,在模板中声明ref并且值与返回的ref相同,这时在渲染初始化后(onMounted)就可以获取分配的DOM或组件实例

v-for中使用

v-for中使用时,需要使用3.0新增的函数形的ref,为ref赋值:

<template>
  <div v-for="(item, i) in list" :ref="el => { divs[i] = el }">
    {{ item }}
  </div>
</template>

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

  export default {
    setup() {
      const list = reactive([1, 2, 3])
      const divs = ref([])

      // 确保在每次变更之前重置引用
      onBeforeUpdate(() => {
        divs.value = []
      })

      return {
        list,
        divs,
      }
    },
  }
</script>

侦听模板引用

watch或者watchEffect中引用Dom Ref,需要使用flush: 'post'选项,因为默认情况下watchwatchEffect的副作用函数是在DOM被挂载或者更新之前运行的,此时模板引用还没有被更新

使用了flush: 'post' ,保证监听器在DOM更新后执行副作用,确保模板引用于DOM保持同步,引用正确元素

组合式API实现更灵活的复用

Mixin的缺点

  • 容易发生冲突
  • 可复用性优先,不能向Mixin传递参数来改变逻辑,降低了在抽象逻辑方面的灵活行

Vue3的组合式API就是为了解决这些问题而存在的

更多的灵活性来自更多的自我克制

组合式API的初衷就是为了实现更有组织的代码,实现更灵活的逻辑提取与复用,在代码中会出现更多的、零碎的函数模块,在不同的位置、不同的组件间进行重复调用

它可以避免Vue2.x时代逻辑复用的几种主要形式(Mixin/HOS/SLOT)的弊端,带来了比较明显的好处:

但是它在提到了代码质量的上限的同时,降低了下线,setup中会出现大量面条式的代码,避免这种糟糕情况的关键就是,将逻辑更合理的划分为单独的函数,将setup作为一个入口,在其中进行不同组合函数的调用。

与React Hooks比较

Vue3的基于函数的组合式API受到了React Hooks的启发,在很多思维模型方面与React Hooks很类似,提供了同等级别的逻辑组合能力,但是也有着比较明显的不同,组合式API的setup函数只会被调用一次,也就意味着使用组合式API时:

  1. 不需要考虑调用顺序,可以用在条件语句中(React Hooks不可以)
  2. 不会再每次渲染时重复执行,降低垃圾回收的压力(React Hooks每次渲染都会重复执行)
  3. 不存在内联处理函数导致子组件永远更新的问题,也不需要useCallback(React Hooks需要用useCallback进行性能优化)
  4. 不存在忘记记录依赖的问题,也不需要useEffecruseMemo并传入依赖数组以捕获过时的变量,Vue的自动以来可以确保侦听器和计算值总是准确无误的(React Hooks需要手动记录依赖)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值