-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discussion: Best way to create a HOC #6201
Comments
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:
|
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. |
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. |
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 |
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 😄 ). |
@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 ***@***.***> 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
<#6201 (comment)>, 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)
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 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 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): vuejs/v2.vuejs.org#658). |
Your link is wrong (unless you really wanted to link to an issue from 2014) But yes, technically the |
@AlexandreBonaventure That issue is from vue 0.x days. 😄 |
The function is documented as an argument of the render function, but not that it is available via |
@LinusBorg But it's basically the same function that gets sent to the |
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:
|
Hey folks, I've been looking at writing an implementation of this. |
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 ***@***.***> 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
<#6201 (comment)>, 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:
I also thought about naming. I don't like render(h) {
return h('DIV', {staticClass: 'some-styling' }, renderComponentWith({ props: {... } }))
} |
const hoc = createHOC(
Component,
{
created(){
/* ... */
}
},
{
props: (props) => ({
...props,
someProp: 'foo'
})
}
); 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 |
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. |
@LinusBorg React have already abandoned 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 |
I love the information listed here! Does anyone have a possible example of how this could be implemented in v3.2+? Or maybe a better/different way to implement this using Composition API? |
There is zero reason to mess around with HoCs when you have the power of the composition API at your disposal |
So we have a bigger project that my company is working on. We've split up portions of the project into separate packages. What I'm trying to do right now is make some of the components that we're using from our packages extendable. We want to be able to use the component and potentially make changes to the component, without having to constantly edit the original component. Those original components use Options API, with a setup function for some Composition API specific code. Any suggestions on how to go about handling that use case? If you'd prefer not to answer this question in the context of this discussion, I'd be happy to continue it somewhere else. Just point me to the right place. Thanks!! |
Version
2.4.2
Reproduction link
https://jsfiddle.net/o7yvL2jd/
Steps to reproduce
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.
What is expected?
They are HoCs that simply renders components passed in as parameters.
HoCs containing slots and events will render normally.
What is actually happening?
The element to be rendered is missing, or the rendered order differs from the baseComponent.
Some HoC implementations do not work with event handlers.
The text was updated successfully, but these errors were encountered: