Vue: 讨论:创建 HOC 的最佳方式

创建于 2017-07-25  ·  40评论  ·  资料来源: vuejs/vue

版本

2.4.2

转载链接

https://jsfiddle.net/o7yvL2jd/

重现步骤

我一直在寻找用 vue.js 实现 HoC 的正确方法。 但找不到任何合适的例子。
下面的链接是已知的 HoC 实现。 但没有达到预期。
https://jsfiddle.net/o7yvL2jd/

如何使用 vue.js 实现 HoC?
我只想知道如何以 react.js 的方式实现 HoC。

期望什么?

它们是简单渲染作为参数传入的组件的 HoC。
包含插槽和事件的 HoC 将正常渲染。

实际发生了什么?

缺少要渲染的元素,或者渲染顺序与 baseComponent 不同。
某些 HoC 实现不适用于事件处理程序。

discussion

最有用的评论

好的,所以我尝试了一种使这更容易的方法。

看看这里: https ://jsfiddle.net/Linusborg/j3wyz4d6/

我对 API 不满意,因为它是一个非常粗略的想法草图,但它能够做任何它应该能够做的事情。

import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
  props: ['message']
  template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)

createHOC将处理:

  • 从组件复制道具
  • $createElement与父级交换以进行正确的插槽解析。
  • 添加名称
  • 如果没有提供第二个参数(如上例所示),它会渲染组件并传递:

    • 道具

    • 属性

    • 听众

    • 普通插槽

    • 作用域插槽

当然,这本身并不是很有用。 所以乐趣发生在第二个参数中,它是一个简单的 Component 对象。

如果你想自己写渲染函数,当然可以。 如果你只是想扩展 props、attrs 或 listeners,你可以使用createRenderFn助手。 它将创建一个与上述默认函数类似的渲染函数,但会将您传递给它的任何attrspropslisteners与来自父级的函数合并。

const HOC = createHOC(Component, {
  mounted() { 
    console.log(`lifecycle hooks work, it's simply component options`) 
  },
  render: createRenderFn(Component, {
    props: {
      message: 'Hello from your HOC!'
    }
  }
})

如果要编写自己的渲染函数,可以使用normalizeSlots帮助器将对象从this.$slots转换为适当的数组以传递:

const HOC = createHOC(Component, {
  render(h) {
    return h(Component, {
      props: { message: 'hi from HOC'}, // nothing from parent will be passed on
    }, normalizeSlots(this.$slots))
  }
})

想要评论:)

所有40条评论

你好@eu81273

感谢您对这个项目的兴趣。

但是,您的问题是一个使用/支持问题,并且问题跟踪器专门用于错误报告和功能请求(如我们的贡献指南中所述)。

我们鼓励您在论坛Stack Overflow或我们的不和谐聊天中提问,我们很乐意为您提供帮助。

FWIW,我出于个人兴趣看了一下 - 这应该在 100% 的情况下有效。

const HOC = WrappedComponent => ({
  props: typeof WrappedComponent === 'function' // accept both a construtor and an options object
    ? WrappedComponent.options.props 
    : WrappedComponent.props,
  render (h) {
    // reduce all slots to a single array again.
    const slots = Object.keys(this.$slots).reduce((arr, key) => arr.concat(this.$slots[key]), []);
    return h(WrappedComponent, {
      attrs: this.$attrs,
      props: this.$props,
      on: this.$listeners,
    }, slots);
 }
});

我在您的示例中编辑了 HOC04,因为它最接近解决方案:

https://jsfiddle.net/Linusborg/o7yvL2jd/22/

编辑:仍然是插槽的问题,正在调查......

我可能已经解决了: https://jsfiddle.net/BogdanL/ucpz8ph4/。 插槽现在只是硬编码,但这很容易解决。

似乎解决方案是沿着@lbogdan的方法,但 createElement 应该有一种获取插槽的方法,就像它可以获取 scopedSlots 一样。

但是,创建一个 HoC 仍然需要付出很多努力。 有很多事情要记住,而通过 react 你只需用道具渲染 WrappedComponent。

我只是想到了一个非常简单的解决方案......如果我在这里遗漏了什么,请告诉我:

const HOC06 = WrappedComponent => Vue.extend({
   mounted () {
      console.log('mounted 6')
   },
   ...WrappedComponent
})

根据@LinusBorg@lbogdan给出的示例,可以处理带有插槽的组件的最小HoC 实现是:

const HoC = WrappedComponent => ({
    props: typeof WrappedComponent === 'function' 
        ? WrappedComponent.options.props 
        : WrappedComponent.props,
    render (h) {
        const slots = this.$slots;
        const scopedSlots = {};
        Object.keys(slots).map(key => (scopedSlots[key] = () => slots[key]));

        return h(WrappedComponent, {
            attrs: this.$attrs,
            props: this.$props,
            on: this.$listeners,
            scopedSlots,
        });
     }
});

正如@blocka提到的,使用 vue.js 创建一个 HoC 仍然需要付出很多努力。

@eu81273

const HOC06 = WrappedComponent => Vue.extend({
   mounted () {
      console.log('mounted 6')
   },
   ...WrappedComponent
})

这似乎通过了你的测试,不是吗? 当然,你必须根据 WrappedComponent 是构造函数还是对象来调整它,但不需要传递槽、事件或道具。

用 vue.js 创建一个 HoC 还是很费劲的。

除了插槽的问题之外,这只是因为 Vue确实有一个比 React 更复杂的 API,这在这种情况下是一个缺点。 我很欣赏 React 在这些用例中的最小 API - Vue 的设计目标略有不同,因此 HOC 不像 React 那样容易。

但是创建一个createHOC()帮助函数来为你包装这个初始设置应该是相当简单的,不是吗?

嗯,这真的取决于最终目标是什么。 据我了解,HoC 的目标是以某种方式更改(装饰)原始组件(WrappedComponent)以添加(或注入)道具、方法、事件侦听器等(很像 mixin,真的 :smile: )。 HOC06变体只会改变组件定义,它不会改变它的实例化方式。

@blocka HOC的目标通常是获取状态(例如来自 redux / vuex)并将其注入到包装组件的 props 中 - 这不适用于您的方法。

@LinusBorg对。 我知道这太好了,不可能是真的,而且我忘记了一些明显的事情。

我认为这是在 Vue 中实现真实用例 HoC 的一个很好的例子: https://github.com/ktsn/vuex-connect。

Vue Hocs 将是一个很棒的加分项(因为它几乎总是在任何 vue 与 react 的辩论中提出)。 也许可以创建一个官方仓库来开发一个 vue-hoc-creator 包? 这样我们就可以进行强大的、受支持的实施

顺便说一句,有一个更好的方法:使用父组件中的 $createElement 而不是 HOC 自己的 - 这使子组件正确解析插槽:

https://jsfiddle.net/o7yvL2jd/23/

可爱,但更有理由有一些官方工具,所以我们
不要都继续重新发明这段代码。

2017 年 7 月 30 日星期日下午 4:33,Thorsten Lünborg [email protected]
写道:

顺便说一句,有一个更好的方法:使用父组件中的 $createElement
而不是 HOC 自己的 - 这使孩子正确解析插槽:

https://jsfiddle.net/o7yvL2jd/23/


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/vuejs/vue/issues/6201#issuecomment-318927628或静音
线程
https://github.com/notifications/unsubscribe-auth/AACoury4Fix2jsX_lyTsS6CYOiHJaOuVks5sTOiugaJpZM4Oh0Ij
.

很抱歉还没有提出正式的解决方案。 很高兴你觉得它很可爱。

我的解决方案也不完美,还有其他问题需要解决 - 即,作用域插槽不适用于我的最新技巧。

编辑:哦,没关系,他们工作

一个官方的解决方案可能会完成,至少我希望如此——但它必须经过深思熟虑和测试。

好的,所以我尝试了一种使这更容易的方法。

看看这里: https ://jsfiddle.net/Linusborg/j3wyz4d6/

我对 API 不满意,因为它是一个非常粗略的想法草图,但它能够做任何它应该能够做的事情。

import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
  props: ['message']
  template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)

createHOC将处理:

  • 从组件复制道具
  • $createElement与父级交换以进行正确的插槽解析。
  • 添加名称
  • 如果没有提供第二个参数(如上例所示),它会渲染组件并传递:

    • 道具

    • 属性

    • 听众

    • 普通插槽

    • 作用域插槽

当然,这本身并不是很有用。 所以乐趣发生在第二个参数中,它是一个简单的 Component 对象。

如果你想自己写渲染函数,当然可以。 如果你只是想扩展 props、attrs 或 listeners,你可以使用createRenderFn助手。 它将创建一个与上述默认函数类似的渲染函数,但会将您传递给它的任何attrspropslisteners与来自父级的函数合并。

const HOC = createHOC(Component, {
  mounted() { 
    console.log(`lifecycle hooks work, it's simply component options`) 
  },
  render: createRenderFn(Component, {
    props: {
      message: 'Hello from your HOC!'
    }
  }
})

如果要编写自己的渲染函数,可以使用normalizeSlots帮助器将对象从this.$slots转换为适当的数组以传递:

const HOC = createHOC(Component, {
  render(h) {
    return h(Component, {
      props: { message: 'hi from HOC'}, // nothing from parent will be passed on
    }, normalizeSlots(this.$slots))
  }
})

想要评论:)

@LinusBorg非常好!

我认为会有所帮助的是提出现实生活中的 HoC 用例并使用这些原语解决它们。

绝对地。

我提到了这个问题(编辑(mybad):https://github.com/vuejs/vuejs.org/issues/658)。
由于您使用的是未记录的 $createElement API,因此值得为插件开发人员记录它。

您的链接错误(除非您真的想链接到 2014 年的问题)

但是是的,从技术上讲,API 页面上仍然缺少$ceateElement API。

@AlexandreBonaventure这个问题来自 vue 0.x 天。 :微笑:
此外,此处记录了createElementhttps: //vuejs.org/v2/guide/render-function.html#createElement -Arguments。

该函数被记录为渲染函数的参数,但不是通过this.$createElement可用。 在 vuejs/vuejs.org/issues/658 上有一个未解决的问题

@LinusBorg但它基本上与发送到render()函数的函数相同,对吧?

一模一样。 只是没有清楚地记录它也可以通过这个实例方法在渲染函数之外使用。

我只是在玩上面的例子,在更复杂的场景中使用它时会出现一些问题,因此需要官方回购。 几点注意事项:

  • createRenderFn 应该检查 attrs/props/listeners 是否是函数并评估它们,这将允许您根据现有道具动态设置道具等。
  • 对于可组合性,组件应该是第二个参数,如果整个 createHOC 方法是柯里化的,我们可以轻松地将多个 hoc 创建者链接在一起。
  • 因为 hoc 是作为 mixin 添加的,如果您尝试将 2 个 hoc 链接在一起(即withDefaultProps(withProps(component, {}), {})第二个 hoc 无法访问父级的 props示例

嘿伙计们,我一直在考虑编写一个实现。
https://github.com/jackmellis/vue-hoc/tree/develop
让我知道您的想法,我会尽快发布。 我也在考虑写一个重组风格的包。

一个重组风格的包会很棒。 真的可以用一些东西
最近喜欢withState。

2017 年 8 月 19 日星期六上午 5:50,Jack [email protected]写道:

嘿伙计们,我一直在考虑编写一个实现。
https://github.com/jackmellis/vue-hoc/tree/develop
让我知道您的想法,我会尽快发布。 我也是
正在考虑编写一个 recompose 风格的包。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/vuejs/vue/issues/6201#issuecomment-323513287或静音
线程
https://github.com/notifications/unsubscribe-auth/AACoumQWQMgpVeIvzJ3Ti8A4kLBD04Hhks5sZq_zgaJpZM4Oh0Ij
.

@jackmellis很高兴您在这方面带头:)

关于您之前提供的反馈的一些想法:

  • 我认为咖喱版本是一个很好的观点,在某种程度上应该是“默认”的,因为这就是 HOC 通常是如何完成的,不是吗?
  • 关于mixins问题的好点。 您是否已经知道如何缓解这种情况? 我现在没有,但我的直觉是,这应该可以通过您创建的咖喱变体和使用Vue.config.optionsMergeStrategies的组合来缓解

我也想过起名。 我不喜欢createRenderFn ,像renderComponentWith这样的东西在我们将它嵌入到其他一些节点的场景中会更有意义和更有意义:

render(h) {
  return h('DIV', {staticClass: 'some-styling' }, renderComponentWith({ props: {... } }))
}

  • 最后我两个都选了。 所以createHOC是组件优先的非柯里化,然后是柯里化变体createHOCc 。 React 生态系统非常注重功能,与 Vue 不同,后者更像是一个 OOP 框架。 所以我认为最好与 Vue 的其余部分保持一致,但仍然提供功能性替代方案。
  • 我刚刚添加了一些代码来处理这个问题。 我没有将整个 hoc 作为 mixin,而是使用 optionsMergeStrategies 手动将 hoc 和选项合并在一起。 唯一的问题是 optionsMergeStrategies 需要一个 vm 作为最后一个参数,但我在实例化之前进行了合并,所以没有 vm。
  • 我对createRenderFn很满意,因为这正是它正在做的事情。 但我越是把它放在一起,我认为人们会直接使用它的可能性就越小。 一般用法如下:
const hoc = createHOC(
  Component,
  {
    created(){
      /* ... */
    }
  },
  {
    props: (props) => ({
      ...props,
      someProp: 'foo'
    })
  }
);

也许我不太明白你的例子?

也许我不太明白你的例子?

不,我认为您走在正确的轨道上,当我在上次回复后对此进行了更多思考时,我的想法也朝着类似的方向发展。

在我最初的 POC 中,我包含了它,以便人们可以在渲染组件周围添加额外的标记,但这意味着它不再是真正的 HOC,因为它也会带来 UI...

是的,我认为您正在尝试渲染其他内容,您已经超出了 HOC 领域,不妨创建一个 SFC 来处理它。

我刚刚在 npm 上发布了vue-hoc

我也一直在研究vue-compose ,它是一个快速的文档会话——远离准备就绪。 尽管它类似于 recompose,但 Vue 处理了很多聪明的东西(比如缓存计算,它鼓励使用this ),因此它实际上不需要那么复杂(或函数式)的语法。

FWIW: https ://vuejs.org/v2/api/#extends

extends的工作方式不同,但可以用于实现类似的结果,这是真的。

我将关闭它,因为我认为@jackmellis项目确实提供了坚实的基础。 我们不打算在核心中包含一种创建 HOC 的方法,主要是因为我们看不到 mixins / Extends 有很大的好处。

主要是因为我们没有看到比 mixins / Extends 有很大的好处。

@LinusBorg React 已经放弃mixin ,HOC 带来了很多好处。

这篇文章分析了好处:

  • 组件和它的 mixin 之间的契约是隐含的。
  • 当您在单个组件中使用更多 mixin 时,它们开始发生冲突。
  • Mixin 倾向于为您的组件添加更多状态,而您应该争取更少。
  • ……

我认为Vue团队应该考虑更仔细地支持HoC....虽然这并不容易(看起来Vue不是这样设计的)。

我不相信 HoC 是一个如此优越的概念。 例如,“隐式合同”的潜在冲突也可能发生在 HoC 上。

请参阅 React-router 的维护者的演讲: https ://www.youtube.com/watch?v=BcVAq3YFiuc

话虽如此,我也不认为它们不好,它们在许多情况下都很有用。 它们只是不像在 React 世界中那样被称赞的灵丹妙药,尽管它们也有自己的缺点。

从上面的讨论中可以明显看出,在 Vue 中实现 HoC 并不像在 React 中那么简单,因为 Vue 的 API 和功能更广泛,并且需要处理更多的边缘情况。

我们当然可以讨论如何改进这一点,只要它不需要破坏 Vue 中的任何东西——在我看来,HoC 并不值得改变。

666

可以处理带有插槽的组件的最小 HoC 实现是:

function hoc(WrappedComponent) {
  return {
    render(h) {
      return h(WrappedComponent, {
        on: this.$listeners,
        attrs:this.$attrs,
        scopedSlots: this.$scopedSlots,
      });
    },
  };
}

对比上面的回复,

  • 不需要 props 配置,可以从 this.$attrs 传递给 child
  • 不需要额外的插槽,this.$scopedSlots 包含自 v2.6.0+ 起的插槽

我写了一个例子来检查

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

bfis picture bfis  ·  3评论

robertleeplummerjr picture robertleeplummerjr  ·  3评论

franciscolourenco picture franciscolourenco  ·  3评论

aviggngyv picture aviggngyv  ·  3评论

paulpflug picture paulpflug  ·  3评论