欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue 3.0 function-based API尝鲜(六):组件间通信

程序员文章站 2022-05-17 07:50:35
...

关键词:state、Vuex、context.root

从广义上来说,我觉得组件间通信的应用场景,无非就是父子组件间的通信和非父子组件间(兄弟、祖孙……)的通信。其实说白了,所谓的通信,就是控制数据的流动,也就是控制“状态”;我倒是挺认可有限状态机模型的。如果进一步细分,大致是这样几种:

  1. 父子组件间通信,这个是最简单也是最常用的场景。父子组件间的通信可以直接通过props$emit完成,如果放在3.0,就是propscontext.emit。从直观感受上说,父子组件之间的关系,有点CSS那种一层层叠加的感觉;如果从面向对象理论的角度说,有点类似于组合委托,拥有共同的生命周期。
  2. 总线通信,我觉得store模式也算总线的一种。这个适用于比较简单的组件间通信。有时候需要兄弟组件传值,或者一个父组件需要管理多个子组件(这个时候用props就会臃肿,而且不好维护),并且整个项目规模不大,完全可以考虑用一个总线代替Vuex来管理整个项目的状态。当然缺陷也是很明显的,扩展性明显不足,同时对没有办法很好地追踪数据的流动(至少很不优雅),给调试带来了困难。说到底是一个工程问题,要在成本和效率上进行权衡。
  3. Vuex通信,这个单独拿出来,适用于比较复杂的项目。有时候会有一些全局共享的数据,比如token和一些登录信息。虽然这些数据也可以存在sessionStorage、localStorage或者cookie里,但刷新后丢失这个特性有时候还是很有用的,比如我可以检测这个来更新token。我很喜欢Dan Abramov那句话:“Flux libraries are like glasses: you’ll know when you need them.”需要的时候,你自然就会想到它。Vuex可以更好地在组件外部管理状态,而且是那种受控的全局变量,避免了无法维护的公共耦合。
  4. router通信。不要忘了,router也是可以携带数据的。有时候,我们需要在两个页面间传递数据(比如id),但是这个数据只在这里出现,并不值得放进Vuex里。这个时候,我们就可以让router来传递数据;可能还需要activateddeactivated钩子的配合。router的params和query都可以承担这个职责。

说起总线,要么就是创建一个空的Vue实例,然后用$emit$on进行传递:

// bus.js
import Vue from 'vue'
export const bus = new Vue()
// parent.vue
created () {
    bus.$on('foo', (bar) => {// do sth...})
}
// child.vue
bus.$emit('foo', bar)

但是这种写法的限制比较多,要求监听的对象(在这里是父组件)一定要先于被监听的对象创建;而且中途销毁子组件也会影响监听。

或者像官方文档上写的那样,创建一个store:

var store = {
  debug: true,
  state: {
    message: 'Hello!'
  },
  setMessageAction (newValue) {
    if (this.debug) console.log('setMessageAction triggered with', newValue)
    this.state.message = newValue
  },
  clearMessageAction () {
    if (this.debug) console.log('clearMessageAction triggered')
    this.state.message = ''
  }
}

不过在这种简单的应用场景下,还有更简单的方案;我们只是需要一个状态的容器而已,不是吗?所以……还记得2.6的新API,Vue.observable吗?

让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。

返回的对象可以直接用于渲染函数和计算属性内,并且会在发生改变时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景。

在 Vue 2.x 中,被传入的对象会直接被 Vue.observable 改变,它和被返回的对象是同一个对象。在 Vue 3.x 中,则会返回一个可响应的代理,而对源对象直接进行修改仍然是不可响应的。因此,为了向前兼容,我们推荐始终操作使用 Vue.observable 返回的对象,而不是传入源对象。

我们先设想一个最简单的非父子组件间通信的场景。这个时候,我们可以用Vue.observable创建一个容器,然后就可以代替之前的bus了:

import Vue from 'vue'
export const bus = Vue.observable({
  foo: 'bar'
})

在3.0里,state,从某种意义上,是可以用来取代Vue.observable的:

import Vue from 'vue'
import { plugin, state } from 'vue-function-api'

Vue.use(plugin)

export const bus = state({
  foo: 'bar'
})

需要注意的是,这里一定要有一个Vue.use的过程。这个不同于函数式的组件。不过,在3.0里,state的作用并不仅止于这些。它的真正意义,大概是作为包装对象的另一面,作为共享状态的容器只是其中一个应用场景罢了:

如果你依然想创建一个没有包装的响应式对象,可以使用 stateAPI(和 2.x 的 Vue.observable()等同)。

说完这个,说说Vuex的用法。我们都知道,Vuex和router是挂载在根实例上的,所以我们可以用$store$router$route来调用它们。这一点在main里体现得很明显:

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

但是到了3.0,$已经被setup代替了。那么,我们应该怎么使用呢?可以看一个例子,这个是来自vue-function-api的issue的写法:

setup (props, { root }) {
  const fields = computed(() => root.$store.state.fields)
}

不过,为了支持3.0,Vuex和router的API显然是会有变动的。按照作者的说法,未来的API可能会类似于这样:

import  { useStore, useComputed } from 'vuex';

setup (props, { root }) {
  const store = useStore();
  const filed = useComputed('state.field');
}

目前为了达到这个效果(也就是所谓的FOP,面向未来编程),可以按照作者的说法,搞一个tricky的适配;这个我也在demo里写了:

const store = new Vuex.Store({//...})

export function userStore() {
  return store;
}

当然,也有dalao自己实现了一个版本。虽然我并不认识他……但是我觉得很有意思,就分享给大家:传送门

目录

Vue 3.0 function-based API尝鲜(一):前言

Vue 3.0 function-based API尝鲜(二):配置与启动

Vue 3.0 function-based API尝鲜(三):包装对象

Vue 3.0 function-based API尝鲜(四):值得一提的watch

Vue 3.0 function-based API尝鲜(五):生命周期

Vue 3.0 function-based API尝鲜(七):This与Refs