2.4.2
https://jsfiddle.net/o7yvL2jd/
I've searching for the right way to implement HoC with vue.js. But couldn't found any suitable example.
The link below is known HoC implementations. But didn't work expected.
https://jsfiddle.net/o7yvL2jd/
How can I implement HoC with vue.js?
I just want to know how to implement HoC in the react.js way.
They are HoCs that simply renders components passed in as parameters.
HoCs containing slots and events will render normally.
The element to be rendered is missing, or the rendered order differs from the baseComponent.
Some HoC implementations do not work with event handlers.
Hello @eu81273
Thank your for your interest in this project.
However, your issue is a usage/support question, and the issue tracker is reserved exclusively for bug reports and feature requests (as outlined in our Contributing Guide).
We encourage you to ask it on the forum , Stack Overflow or on our discord chat and are happy to help you out there.
FWIW, I took a look out of personal interest - this should work 100% of cases.
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);
}
});
I edited HOC04 in your example since it was the closest to the solution:
https://jsfiddle.net/Linusborg/o7yvL2jd/22/
Edit: still an issue with slots, investigating ...
I might have solved it: https://jsfiddle.net/BogdanL/ucpz8ph4/. Slots are just hardcoded now, but that's trivial to solve.
Seems the solution is along the method of @lbogdan but createElement should have a way of taking slots, just like it can take scopedSlots.
However, it is _still_ a lot of effort to create an HoC. There's a lot to remember to pass through, while with react you just render the WrappedComponent with props.
I just thought of a very simple solution...let me know if I'm missing something here:
const HOC06 = WrappedComponent => Vue.extend({
mounted () {
console.log('mounted 6')
},
...WrappedComponent
})
Based on the examples given by @LinusBorg and @lbogdan, the most minimal HoC implementation that can handle components with slots is:
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,
});
}
});
As @blocka mentioned, it is still a lot of effort to create an HoC with vue.js.
@eu81273
const HOC06 = WrappedComponent => Vue.extend({
mounted () {
console.log('mounted 6')
},
...WrappedComponent
})
This seems to pass your test, no? Of course, you would have to adjust it depending on whether WrappedComponent is a constructor or object, but no need to pass slots, events or props.
it is still a lot of effort to create an HoC with vue.js.
Apart from the issue with slots, this is just due to the fact that Vue does have a more complex API than React, which in this scenario is a disadvantage. I admire Reacts minimal API in these kinds of use cases - Vue was just designed with slightly different deign goals, so HOCs don't come as easily as in React.
But it should be fairly trivial to create a createHOC()
helper function that wraps this initial setup for you, shouldn't it?
Well, it really depends what the end goal is. From what I understand, the goal of HoC is to somehow change (decorate) the original component (WrappedComponent) to add (or inject) props, methods, event listeners etc. (much like a mixin, really :smile: ). HOC06
variant only changes the component definition, it doesn't change the way in which it's instantiated.
@blocka The goal of HOCs often is to get state (e.g. from redux / vuex) and inject it into the wrapped component's props - that would not work with your approach.
@LinusBorg right. I knew it was too good to be true and that I was forgetting something obvious.
I think this a good example of implementing a real use case HoC in Vue: https://github.com/ktsn/vuex-connect.
Vue Hocs would be an awesome plus (since it's almost always brought up in any vue vs react debate). Perhaps an official repo could be created to develop a vue-hoc-creator package? That way we could work on a robust, supported, implementation
Btw, got a better way: use $createElement from the parent component instead of the HOC's own - this make the child resolve the slots correctly:
Cute, but all the more reason for there to be some official tool so we
don't all keep on reinventing this code.
On Sun, Jul 30, 2017 at 4:33 PM, Thorsten Lünborg notifications@github.com
wrote:
Btw, got a better way: use $createElement from the parent component
instead of the HOC's own - this make the child resolve the slots correctly:https://jsfiddle.net/o7yvL2jd/23/
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-318927628, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AACoury4Fix2jsX_lyTsS6CYOiHJaOuVks5sTOiugaJpZM4Oh0Ij
.
I'm sorry for not coming up with an official solution yet. Glad you think it's cute though.
My solution is not perfect either, there are other problems to sort out - i.e. scoped slots won't work with my latest trick.
edit: oh, never mind, they work
An official solution will probably be done, at least I would expect so - but it has to be thorougly thought through and tested.
Okay, so I played around with a way to make this easier.
Have a look here: https://jsfiddle.net/Linusborg/j3wyz4d6/
I'm not happy with the API as it's a very rough idea sketch, but it's able to do anything it should be able to do.
import { createHOC, createRenderFn, normalizeSlots } from 'vue-hoc'
const Component = {
props: ['message']
template: `<p>{{message}}</p>`
}
const HOC = createHOC(Component)
createHOC
will take care of:
$createElement
witht that from the parent for proper slots resolving.That on it's own isn't very useful, of course. So the fun happens in second argument which is a simple Component object.
If you want to write the render function on your own, you of course can. If you just want to extend props, attrs or listeners, you can use the createRenderFn
helper. it will create a render function like the default one described above but will merge any attrs
, props
or listeners
you pass to it with those from the parent.
const HOC = createHOC(Component, {
mounted() {
console.log(`lifecycle hooks work, it's simply component options`)
},
render: createRenderFn(Component, {
props: {
message: 'Hello from your HOC!'
}
}
})
If you want to write your own render function, you can use the normalizeSlots
helper to transform the object from this.$slots
into a proper array to pass on:
const HOC = createHOC(Component, {
render(h) {
return h(Component, {
props: { message: 'hi from HOC'}, // nothing from parent will be passed on
}, normalizeSlots(this.$slots))
}
})
Comments wanted :)
@LinusBorg Very nice!
What I think would help is coming up with real life HoC use cases and solving them using these primitives.
Absolutely.
I'm mentioning this issue (EDIT (mybad): https://github.com/vuejs/vuejs.org/issues/658).
Since you're using the undocumented $createElement API, It would be worth documenting it for plugin developers.
Your link is wrong (unless you really wanted to link to an issue from 2014)
But yes, technically the $ceateElement
API is still missing on the API page.
@AlexandreBonaventure That issue is from vue 0.x days. :smile:
Also, createElement
is documented here: https://vuejs.org/v2/guide/render-function.html#createElement-Arguments .
The function is documented as an argument of the render function, but not that it is available via this.$createElement
. There's an open issue for that on vuejs/vuejs.org/issues/658
@LinusBorg But it's basically the same function that gets sent to the render()
function, right?
exactly the same. It's just not documented clearly that it's also available outside of the render function via this instance method.
I'm just having a play with the above example and there are a couple of issues when using it in a more complex scenario, hence the need for an official repo. A couple of notes:
withDefaultProps(withProps(component, {}), {})
the second hoc does not have access to the parent's props exampleHey folks, I've been looking at writing an implementation of this.
https://github.com/jackmellis/vue-hoc/tree/develop
Let me know what you think and I'll look to publish it soon. I'm also thinking about writing a recompose-style package.
A recompose style package would be great. Really could have used something
like withState recently.
On Sat, Aug 19, 2017 at 5:50 AM, Jack notifications@github.com wrote:
Hey folks, I've been looking at writing an implementation of this.
https://github.com/jackmellis/vue-hoc/tree/develop
Let me know what you think and I'll look to publish it soon. I'm also
thinking about writing a recompose-style package.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/vuejs/vue/issues/6201#issuecomment-323513287, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AACoumQWQMgpVeIvzJ3Ti8A4kLBD04Hhks5sZq_zgaJpZM4Oh0Ij
.
@jackmellis Great that you take a lead on this :)
A couple of thoughts on your feedback you gave previously:
Vue.config.optionsMergeStrategies
I also thought about naming. I don't like createRenderFn
, something like renderComponentWith
would more meaningful and make more sense in a scenario where we embed it in some other nodes:
render(h) {
return h('DIV', {staticClass: 'some-styling' }, renderComponentWith({ props: {... } }))
}
createHOC
is component-first non-curried, and then there's a curried variant createHOCc
. The React ecosystem is very functional-focused, unlike Vue, which acts more like an OOP framework. So I think it's best to keep consistent with the rest of Vue, but still offer a functional alternative.createRenderFn
since that's exactly what it's doing. But the more I put this together, the less I think people will use it directly. The general usage will be something like:const hoc = createHOC(
Component,
{
created(){
/* ... */
}
},
{
props: (props) => ({
...props,
someProp: 'foo'
})
}
);
Perhaps I don't quite understand your example?
Perhaps I don't quite understand your example?
No, I think you are on the right track, my thoughts were going in a similar direction when I thought some more about it after my last reply.
In my initial POC I included it so people could add additional markup around the rendered component, but that would mean it's not really a HOC anymore, as it would bring in UI as well...
Yes I think you are attempting to render additional content, you've gone outside of HOC territory and might as well create an SFC to handle it.
I've just published vue-hoc on npm!
I've also been working on vue-compose which is a quick documentation-session-away from being ready as well. Although it's similar to recompose, Vue handles a lot of clever stuff (like caching computations and it encourages the use of this
) so it actually doesn't need quite as complex (or functional-style) syntax.
extends
works differently, but can be used to achieve similar results, that's true.
I will close this since I think @jackmellis project does provide a solid foundation. We don't intend to include a way to create HOC's into core, mainly because we don't see a large benefit over mixins / Extends.
mainly because we don't see a large benefit over mixins / Extends.
@LinusBorg React have already abandoned mixin
, HOC brings lots of benefit.
This article have analyzed the benefits:
I think Vue team should consider to support HoC more carefully....although it is not easy(It seems Vue is not designed in this way).
I'm not convinced that HoCs are such a superior concept. The "implicit contract" potential clashes can happen with HoCs just as well, for example.
See this talk By the maintainer of React-router about it: https://www.youtube.com/watch?v=BcVAq3YFiuc
That being said I don't think they are bad either, they are useful in many situations. They are just not the magic bullet they are praised as in the React world, though, they have their own downsides.
As being evident from the discussion above, implementing HoCs in Vue isn't as trivial as in React because Vue's API and functionality is broader and more edge cases have to be taken care of.
We can surely talk about how to improve this as long as it doesn't require breaking anything in Vue - HoC's aren't worh a breaking change in my view.
666
the most minimal HoC implementation that can handle components with slots is:
function hoc(WrappedComponent) {
return {
render(h) {
return h(WrappedComponent, {
on: this.$listeners,
attrs:this.$attrs,
scopedSlots: this.$scopedSlots,
});
},
};
}
comparing to replys above,
i writed a example to check
Most helpful comment
Okay, so I played around with a way to make this easier.
Have a look here: https://jsfiddle.net/Linusborg/j3wyz4d6/
I'm not happy with the API as it's a very rough idea sketch, but it's able to do anything it should be able to do.
createHOC
will take care of:$createElement
witht that from the parent for proper slots resolving.That on it's own isn't very useful, of course. So the fun happens in second argument which is a simple Component object.
If you want to write the render function on your own, you of course can. If you just want to extend props, attrs or listeners, you can use the
createRenderFn
helper. it will create a render function like the default one described above but will merge anyattrs
,props
orlisteners
you pass to it with those from the parent.If you want to write your own render function, you can use the
normalizeSlots
helper to transform the object fromthis.$slots
into a proper array to pass on:Comments wanted :)