Vue high-level components
Definition of higher-order components
- A higher-order component is a function that takes a component as an argument and returns the newly created component. The returned component is typically augmented with functionality provided by HOC (high-level component).
- Higher-order components are not software that can be grabbed and installed, but rather a technical solution that helps you write reusable and maintainable code.
The sample
- For example, BlogPost, CommentsList:
# App.vue
<template>
<div id="app">
<blog-post/>
<comments-list/>
</div>
</template>
<script>
import CommentsList from './components/CommentsList'
import BlogPost from './components/BlogPost'
export default {
name: 'app',
components: {
'blog-post': BlogPost,
'comments-list': CommentsList
}
}
</script>
# components/CommentsList.vue
<template>
<ul>
<li
v-for="(comment, index) in comments"
:key="index"
>{{comment}}</li>
</ul>
</template>
<script>
import DataSource from '.. /store/source.js'
export default {
name: 'comments-list'.data() {
return {
comments: DataSource.getComments()
}
},
methods: {
handleChange() {
this.comments = DataSource.getComments()
}
},
mounted() {
DataSource.addChangeListener(this.handleChange)
},
beforeDestroy() {
DataSource.removeChangeListener(this.handleChange)
}
}
</script>
# components/BlogPost.vue
<template>
<div>
{{blogPost}}
</div>
</template>
# source.js
<script>
import DataSource from '.. /store/source.js'
export default {
data() {
return {
blogPost: DataSource.getBlogPost()
}
},
methods: {
handleChange() {
this.blogPost = DataSource.getBlogPost()
}
},
mounted() {
DataSource.addChangeListener(this.handleChange)
},
beforeDestroy() {
DataSource.removeChangeListener(this.handleChange)
}
}
</script>
const listeners = {};
const comments = ['comment one'.'comment two'.'comment three'.'comment four'.'comment five'];
const blogPosts = {
1: `Lorem ipsum dolor sit amet, utinam scripta splendide ei cum.
Mediocrem dissentiet est ut, nec tale ullum no, has putent scaevola mediocrem an.
Ex quot latine denique vim, ne quot quaeque sea.
In pri habeo diceret, an ius tale voluptatum, ad liber facilis minimum vis.
Eos iriure concludaturque id, sed inani nulla interesset in, labores adipiscing dissentiet vel ut.`,
2: 'Peek-A-Boo! '
}
setInterval(() => {
comments.push(`fresh comment ${comments.length + 1}`)
Object.keys(blogPosts).forEach(id => {
blogPosts[id] = `${blogPosts[id]} ${comments.length}`})}, 5000)export default {
getComments() {
return comments;
},
getBlogPost(id) {
return blogPosts[id];
},
addChangeListener(listener) {
const intervalId = setInterval(() => {
listener()
}, 1000)
listeners[listener] = intervalId
},
removeChangeListener(listener) {
clearInterval(listeners[listener])
}
}
Copy the code
- As you can see from the BlogPost and CommentsList components, they are very similar in structure, as described below:
- Get data from a DataSource (e.g. DataSource.getComments(), DataSource.getblogpost ()))
- Update each update in the external data source (handleChange method)
- Add change listeners to the data source (Mounted method)
- Remove change listeners from the data source (beforeDestroy method)
To avoid code duplication, B can be addedlogThe shared logic between Post and CommentsList is extracted into higher-order components. Here are the steps.Copy the code
- Higher-order components evolve step by step
- At this point, higher-order components don’t do much. It just takes one component and creates a new component that renders the delivery component. The code is as follows:
# hocs/withSubscription.js
import Vue from 'vue'
import CommentsList from '~/components/CommentsList.vue'
const withSubscription = (component) => {
return Vue.component('withSubscription', {
render(createElement) {
return createElement(component)
}
}
}
const CommentsListWithSubscription = withSubscription(CommentsList)
Copy the code
- Implement shared logic by adding mount, beforeDestroy hooks and handleChange methods. The code is as follows:
# hocs/withSubscription.js
import DataSource from '.. /store/source'
import Vue from 'vue'
const withSubscription = (component) => {
return Vue.component('withSubscription', {
render(createElement) {
return createElement(component)
},
methods: {
handleChange() {}},mounted() {
DataSource.addChangeListener(this.handleChange)
},
beforeDestroy() {
DataSource.removeChangeListener(this.handleChange)
}
})
}
export default withSubscription
Copy the code
- New components returned by higher-order components now require lifecycle hooks. The handleChange method is left empty. Both components have a handleChange method, but this method is implemented slightly differently in each component - higher-order components can accept multiple parameters. Currently, withSubscription accepts only components as arguments. In order to invoke custom logic in handleChange, you need a second argument. The second parameter is the method that should be invoked each time the data source changes. The following codeCopy the code
# hocs/withSubscription.js
import DataSource from '.. /store/source'
import Vue from 'vue'
const withSubscription = (component, selectData) => {
return Vue.component('withSubscription', {
render(createElement, context) {
return createElement(component, {
props: {
content: this.fetchedData
}
})
},
data() {
return {
fetchedData: null
}
},
methods: {
handleChange() {
this.fetchedData = selectData(DataSource)
}
},
mounted() {
DataSource.addChangeListener(this.handleChange)
},
beforeDestroy() {
DataSource.removeChangeListener(this.handleChange)
}
})
}
export default withSubscription
Copy the code
- The code for using higher-order components in app.vue is as follows
# App.vue
<template>
<div id="app">
<blog-post/>
<comments-list/>
</div>
</template>
<script>
import CommentsList from './components/CommentsList'
import BlogPost from './components/BlogPost'
import withSubscription from './hocs/withSubscription'
const BlogPostWithSubscription = withSubscription(BlogPost, (DataSource) => {
return DataSource.getBlogPost()
})
const CommentsListWithSubscription = withSubscription(CommentsList, (DataSource) => DataSource.getComments())
export default {
name: 'app',
components: {
'blog-post': BlogPostWithSubscription,
'comments-list': CommentsListWithSubscription
}
}
</script>
Copy the code
- The code for BlogPost and CommentsList is as follows
# components/BlogPost.vue
<template>
<div>
{{content}}
</div>
</template>
<script>
export default {
props: ['content']
}
</script>
----
# components/CommentsList.vue
<template>
<ul>
<li v-for="(comment, index) in content" :key="index">{{comment}}</li>
</ul>
</template>
<script>
export default {
name: 'comments-list',
props: ['content']
}
</script>
Copy the code
- So far, the component rendering is working well, give yourself a pat on the back, some work has been done, but it's not perfect and needs to be improved. Suppose I need to pass a blog post ID to BlogWhat about Post? Or if I need to go from BlogWhat if Post posts an event to an App component?Copy the code
- Handles props in higher-order components
# App.vue
<template>
<div id="app">
<blog-post :id="1"/>
</div>
</template>
<script>
import BlogPost from './components/BlogPost'
import withSubscription from './hocs/withSubscription'
const BlogPostWithSubscription = withSubscription(BlogPost, (DataSource, props) => {
return DataSource.getBlogPost(props.id)
})
export default {
name: 'app',
components: {
'blog-post': BlogPostWithSubscription
}
}
</script>
---
# components/BlogPost.vue
<template>
<div>
{{content}}
</div>
</template>
<script>
export default {
props: ['content'.'id']
}
</script>
Copy the code
- Adjustment of higher-order components
# hocs/withSubscription.js
import DataSource from '.. /store/source'
import Vue from 'vue'
const withSubscription = (component, selectData) => {
const originalProps = component.props || [];
return Vue.component('withSubscription', {
render(createElement) {
returncreateElement(component, { props: { ... originalProps, content: this.fetchedData } }) }, props: [...originalProps],data() {
return {
fetchedData: null
}
},
methods: {
handleChange() {
this.fetchedData = selectData(DataSource, this.$props)}},mounted() {
DataSource.addChangeListener(this.handleChange)
},
beforeDestroy() {
DataSource.removeChangeListener(this.handleChange)
}
})
}
export default withSubscription
Copy the code
- From the original component BlogPost gets props and saves it in originalProps, withSubscription higher-order component props receives the values of the originalProps so that they can be passed to B laterlogPost componentsCopy the code
- Handles processing events in higher-order components
# App.vue
<template>
<div id="app">
<blog-post :id="1" @click="onClick"/>
</div>
</template>
---
# components/BlogPost.vue
<template>
<div>
<button @click="$emit('click', 'aloha')">CLICK ME! </button> {{data}} </div> </template> <script>export default {
props: ['data'.'id']
}
</script>
Copy the code
- It is important to remember that we do not render blogPosts directly in the App, but have an intermediate component – withSubscription HOC. To pass the event listener to the rendering component, I need to add a line of code to the higher-order component. The withSubscription code is adjusted as follows
# hocs/withSubscription.js
return Vue.component('withSubscription', {
...
on: {...this.$listeners} # <= this line,
})
Copy the code
Refer to the link
- Vue advanced components – need to climb the wall
- Do we need higher-order components in vue.js? – need to over the wall
- The source address