Vue中使用高阶组件实现依赖注入

公司里面接手一个前端老项目,采用的是vuex状态管理方案,Store目录结构是按照vuex官方的module方案实现的,不熟悉Vuex的小伙伴,可以点击此处查阅。整个代码的实现,在我看来是比较符合规范的。奈何,同事们认为现有Store架构,太繁琐了,查找一个问题,需要从action发起的源头一直追到mutation。大家认为首先查找问题比较麻烦(这个我是不赞同的,如果合理且充分使用好vue-devtools效率还是很高的。其次,vuex这种架构模式写起来太繁琐了,有很多模版代码。我看了一下其他同事没有采用soter方案的代码,数据请求逻辑和数据处理逻辑基本上是直接放在vue单文件组件里面的,有比较多的子组件对父组件的强依赖,这种方案确实比较直接,写代码的人很爽,维护代码的人只需要看那个组件就行了。但是,这是在一个组件不复杂的情况下,比较好的处理方式,一旦业务逻辑复杂起来,这种方式实现的代码大概率会走向腐化。为了照顾大家对代码逻辑的简洁需求以及架构稳固性,我比较vuex的架构和大部分同事的组件代码。确定了重构的方向:

  • 首先,vuex方案对于现在的项目规模来说,还是偏重,需要实现一种偏简洁的方案。根据我以前写react的经验,大家可能对mobx那种轻量化的状态管理方案比较喜欢,所以我只需要实现一个极简响应式状态管理。我心想,vue这种响应式原理不是和mobx一样么。按照这个思路,我重新看了以下vue官方文档。刚好我们使用的vue版本是2.6.11,vue在这个版本已经在暴露出它的响应式编程核心能力,也就是reactiveapi,通过这个api可以实现一个极简的store方案。

  • 其次,改善vuexstore方案的强侵入性。例如需要mapState将状态侵入型的注入到组件中,我知道vuex有通过顶层vue实例注入的能力,但这样也会形成子组件对顶层组件的强依赖。我希望实现的是,一个vue组件对外部store是无感知的,我们只需要关注它需要的数据prop,以及它对外作出的状态变更请求emit事件。这里我参考了react里面使用mobx通过高阶组件inject注入stateaction的思路。

当我在实现向vue组件注入状态的高阶组件时,一切变得不是那么容易了。vue不像react那样,所有的对外依赖都是通过prop传入,总之vue的组件不够函数式,更像是一个webpack配置文档,通过对象的形式去描述一个组件的渲染逻辑以及对外依赖。就在我一筹莫展之际,我又翻阅起了官方文档。在[渲染函数](https://cn.vuejs.org/v2/guide/render-function.html)这一节,我看到了曙光。以下是官方文档:

向子元素或子组件传递 attribute 和事件
在普通组件中,没有被定义为 prop 的 attribute 会自动添加到组件的根元素上,将已有的同名 attribute 进行替换或与其进行智能合并。

然而函数式组件要求你显式定义该行为:

Vue.component('my-functional-button', {
    functional: true,
    render: function (createElement, context) {
       // 完全透传任何 attribute、事件监听器、子节点等。
        return createElement('button', context.data, context.children)
    }
})

通过向 createElement 传入 context.data 作为第二个参数,我们就把 my-functional-button 上面所有的 attribute 和事件监听器都传递下> 去了。事实上这是非常透明的,以至于那些事件甚至并不要求 .native 修饰符。

看了以上官方文档大家应该知道我要干什么了,首先我需要实现一个高阶函数,它接受一个组件对象作为参数,然后返回一个经过篡改的函数式组件。有人可能要问了为什么要篡改了,因为,这个函数式组件不仅需要injectstore,还需要透传父组件传递的prop和设置的listeners,这样就可以实现被包装组件的无感知了。以下是实现的为代码:

//  这里store可以不传入,而使用闭包下的store
const closureStore = Vue.observable({ count: 1 })

const actions = {
    add() {
        store.count += 1
    },
    sub() {
        if (store.count === 1) return
        store.count -= 1
    }
}

function withInject(component, config = { state = closureStore, actions  = actions}) {
    return Vue.component(`hoc-${component.name}`, {
        functional: true,
        render(createElement, context) {
            const data = context.data
            Object.assign(data.attrs, { ...state })
            Object.assign(data.on, actions)
            return createElement(component, data, context.children)
        }
    })
}

//  在需要使用某个组件的位置,对其进行包装,然后这个组件就可以通过注册到任意vue组件中进行使用了
const SomeComponetWithInject = withInject(SomeComponent)

以上代码,不是全部,但核心逻辑都在上面。从`react`到开始写`vue`已接近两个多月,说实话,`vue`对开发者更友好,不需要思考太多代码架构,就像要是做菜,材料和菜谱都给你准备好,就等你去煎炒炖煮了。然而,以上优点也是`vue`的弊端,在实现一些复杂设计模式时,不够灵活,没有`react`提供那么多供你设计的可能性,不过,以上需求都在于代码书写者的执行能力和思维方式。也许我们不用那么折腾,少思考一点,把代码逻辑写的大家都能接受就OK了。看到这里的人,应该是认为我的文章有价值,感谢你们。近期加入了新公司,比较忙,有很多想写的技术文章都没时间写,2020年后期得加油了。
posted @ 2020-08-16 20:54  snicker  阅读(1071)  评论(0编辑  收藏  举报