From the looks of it, it's not possible to use HOC's in .vue files because of the way vue-loader loader works. Instead you have to apply your HOC in a .js file.
In, for instance, React+Redux containers you would usually define your component and and "connect" it to the store using a HOC from the react-redux package. It could look something like this:
class MyComponent extends React.Component {
render () {
return <div>Hello {this.props.name}</div>
}
}
export default connect(({name}) => ({name}), null)(MyComponent);
When trying to do something similar with .vue files the render function of the HOC gets replaced instead of the render function of the component you're applying your HOC to.
The following code will cause vue-loader to replace the render function of the component returned by connect instead of replacing the render function of MyComponent:
<template><div>Hello {{name}}</div></template>
<script>
const MyComponent = {
props: ['name']
}
export connect(({name}) => ({name}), null)(MyComponent)
</script>
To work around this you can, in your .vue, export MyComponent and have a .js which exports the connected MyComponent, but i find it unnecessarily complicated.
tl;dr
I want to be able to better reuse code with higher-order functions in Vue, the way you do in e.g. React.
If a practical use case for this is needed i'll try to provide one:
As a user i want to connect my form-fields to Vuex via a HOC to avoid having local state. I make a custom component called TextInput and like so:
<template>
<div class="error" v-if="input.error" v-text="error"></div>
<some-input-component type="text" :value="input.value" :name="input.name" v-else>
</template>
<script>
export default connectField({
props: ['input'],
name: 'my-textinput'
})
</script>
The connectField function returns a component which renders the passed component as a child. To the child, connectField passes input which contains relevant information for each field.
This won't work as it is today. The user would have to create a separate file where he/she uses connectField which isn't parsed by vue-loader.
Imagine if the user had several of these components:
For each of these components the user would have to create two files, even if it adds no additional value.
I personally feel that this adds unwanted complexity to do something so trivial as to create a HOC. I'm not sure how we would solve this problem and would be happy to hear some ideas!
I'm also interested in HoC's via single file components and just finished digging through vue-loader and some related code - I don't see how this could be accomplished with a sensible API.
Currently vue-loader assumes the whole component is the vue file. My understanding of what you're asking is for some way to declare in your <script> what represents your current component and what's wrapping it, and I can't think of a sensible way to accomplish that. e.g. in your example what's to say connectField isn't just a helper function to normalize your component options? How is vue-loader supposed to figure out it's an HoC?
IMHO this would be a great feature.
Most explicit but also most complicated could be to use a custom block with a custom loader, but I don't see any way so far.
From a short look into the vue-loader I would say the following would be at least a simple solution.
hoc property on the component exported from the script section.node_modules/vue-loader/lib/component-normalizer.js:// check, if hocs should be loaded
if (options.hoc) {
// allow to be used like {聽hoc: withA } as well as {聽hoc: [ withA, withB聽] }
if (!Array.isArray(options.hoc)) {
options.hoc = [options.hoc];
}
if (typeof scriptExports === "function") {
throw new Error(`Loading vue HOCs not yet supported for Vue.extend`);
}
// just inserted compose here for readability, doesn't have to be in scope of normalizeComponent
const compose = stages => Component => stages
.reverse()
.reduce((result, fn, i) => {
if (typeof fn !== "function") {
throw new Error(`Expected HOC to be function, got ${typeof fn} for element #${i}`);
}
return fn(result);
},
Component
);
const enhance = compose(options.hoc);
scriptExports = enhance(scriptExports);
options = scriptExports;
}
鉃昬asy to develop
鉃昬asy to use
鉃杤alidated at runtime
鉃杋ntroduces a new property on vue-components that can't be used on Vue.component(...)
Another great feature would be to pass a post-processing function with the webpack options. That function would be called with the parsed component and could implement logic like above.
Most helpful comment
If a practical use case for this is needed i'll try to provide one:
As a user i want to connect my form-fields to Vuex via a HOC to avoid having local state. I make a custom component called
TextInputand like so:The
connectFieldfunction returns a component which renders the passed component as a child. To the child,connectFieldpassesinputwhich contains relevant information for each field.This won't work as it is today. The user would have to create a separate file where he/she uses
connectFieldwhich isn't parsed byvue-loader.Imagine if the user had several of these components:
For each of these components the user would have to create two files, even if it adds no additional value.
I personally feel that this adds unwanted complexity to do something so trivial as to create a HOC. I'm not sure how we would solve this problem and would be happy to hear some ideas!