Skip to content

Commit 995b72d

Browse files
author
ntepluhina
committed
Added dynamic components
1 parent d1569f0 commit 995b72d

File tree

5 files changed

+359
-1
lines changed

5 files changed

+359
-1
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<template>
2+
<div id="dynamic-component-demo" class="demo">
3+
<button
4+
v-for="tab in tabs"
5+
v-bind:key="tab"
6+
v-bind:class="[
7+
'dynamic-component-demo-tab-button',
8+
{ 'dynamic-component-demo-active': currentTab === tab }
9+
]"
10+
v-on:click="currentTab = tab"
11+
>
12+
{{ tab }}
13+
</button>
14+
<component
15+
v-bind:is="currentTabComponent"
16+
class="dynamic-component-demo-tab"
17+
></component>
18+
</div>
19+
</template>
20+
21+
<script>
22+
import TabPosts from './tab-posts-dynamic'
23+
import TabArchive from './tab-archive'
24+
export default {
25+
components: {
26+
TabPosts,
27+
TabArchive
28+
},
29+
data() {
30+
return {
31+
currentTab: 'Posts',
32+
tabs: ['Posts', 'Archive']
33+
}
34+
},
35+
computed: {
36+
currentTabComponent() {
37+
return 'tab-' + this.currentTab.toLowerCase()
38+
}
39+
}
40+
}
41+
</script>
42+
43+
<style>
44+
.dynamic-component-demo-tab-button {
45+
padding: 6px 10px;
46+
border-top-left-radius: 3px;
47+
border-top-right-radius: 3px;
48+
border: 1px solid #ccc;
49+
cursor: pointer;
50+
background: #f0f0f0;
51+
margin-bottom: -1px;
52+
margin-right: -1px;
53+
}
54+
.dynamic-component-demo-tab-button:hover {
55+
background: #e0e0e0;
56+
}
57+
.dynamic-component-demo-tab-button.dynamic-component-demo-active {
58+
background: #e0e0e0;
59+
}
60+
.dynamic-component-demo-tab {
61+
border: 1px solid #ccc;
62+
padding: 10px;
63+
}
64+
.dynamic-component-demo-posts-tab {
65+
display: flex;
66+
}
67+
.dynamic-component-demo-posts-sidebar {
68+
max-width: 40vw;
69+
margin: 0 !important;
70+
padding: 0 10px 0 0 !important;
71+
list-style-type: none;
72+
border-right: 1px solid #ccc;
73+
}
74+
.dynamic-component-demo-posts-sidebar li {
75+
white-space: nowrap;
76+
text-overflow: ellipsis;
77+
overflow: hidden;
78+
cursor: pointer;
79+
}
80+
.dynamic-component-demo-posts-sidebar li:hover {
81+
background: #eee;
82+
}
83+
.dynamic-component-demo-posts-sidebar li.dynamic-component-demo-active {
84+
background: lightblue;
85+
}
86+
.dynamic-component-demo-post-container {
87+
padding-left: 10px;
88+
}
89+
.dynamic-component-demo-post > :first-child {
90+
margin-top: 0 !important;
91+
padding-top: 0 !important;
92+
}
93+
</style>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<template>
2+
<div id="dynamic-component-demo" class="demo">
3+
<button
4+
v-for="tab in tabs"
5+
v-bind:key="tab"
6+
v-bind:class="[
7+
'dynamic-component-demo-tab-button',
8+
{ 'dynamic-component-demo-active': currentTab === tab }
9+
]"
10+
v-on:click="currentTab = tab"
11+
>
12+
{{ tab }}
13+
</button>
14+
<keep-alive>
15+
<component
16+
v-bind:is="currentTabComponent"
17+
class="dynamic-component-demo-tab"
18+
></component>
19+
</keep-alive>
20+
</div>
21+
</template>
22+
23+
<script>
24+
import TabPosts from './tab-posts-dynamic'
25+
import TabArchive from './tab-archive'
26+
export default {
27+
components: {
28+
TabPosts,
29+
TabArchive
30+
},
31+
data() {
32+
return {
33+
currentTab: 'Posts',
34+
tabs: ['Posts', 'Archive']
35+
}
36+
},
37+
computed: {
38+
currentTabComponent() {
39+
return 'tab-' + this.currentTab.toLowerCase()
40+
}
41+
}
42+
}
43+
</script>
44+
45+
<style>
46+
.dynamic-component-demo-tab-button {
47+
padding: 6px 10px;
48+
border-top-left-radius: 3px;
49+
border-top-right-radius: 3px;
50+
border: 1px solid #ccc;
51+
cursor: pointer;
52+
background: #f0f0f0;
53+
margin-bottom: -1px;
54+
margin-right: -1px;
55+
}
56+
.dynamic-component-demo-tab-button:hover {
57+
background: #e0e0e0;
58+
}
59+
.dynamic-component-demo-tab-button.dynamic-component-demo-active {
60+
background: #e0e0e0;
61+
}
62+
.dynamic-component-demo-tab {
63+
border: 1px solid #ccc;
64+
padding: 10px;
65+
}
66+
.dynamic-component-demo-posts-tab {
67+
display: flex;
68+
}
69+
.dynamic-component-demo-posts-sidebar {
70+
max-width: 40vw;
71+
margin: 0 !important;
72+
padding: 0 10px 0 0 !important;
73+
list-style-type: none;
74+
border-right: 1px solid #ccc;
75+
}
76+
.dynamic-component-demo-posts-sidebar li {
77+
white-space: nowrap;
78+
text-overflow: ellipsis;
79+
overflow: hidden;
80+
cursor: pointer;
81+
}
82+
.dynamic-component-demo-posts-sidebar li:hover {
83+
background: #eee;
84+
}
85+
.dynamic-component-demo-posts-sidebar li.dynamic-component-demo-active {
86+
background: lightblue;
87+
}
88+
.dynamic-component-demo-post-container {
89+
padding-left: 10px;
90+
}
91+
.dynamic-component-demo-post > :first-child {
92+
margin-top: 0 !important;
93+
padding-top: 0 !important;
94+
}
95+
</style>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<template>
2+
<div class="dynamic-component-demo-posts-tab">
3+
<ul class="dynamic-component-demo-posts-sidebar">
4+
<li
5+
v-for="post in posts"
6+
v-bind:key="post.id"
7+
v-bind:class="{
8+
'dynamic-component-demo-active': post === selectedPost
9+
}"
10+
v-on:click="selectedPost = post"
11+
>
12+
{{ post.title }}
13+
</li>
14+
</ul>
15+
<div class="dynamic-component-demo-post-container">
16+
<div v-if="selectedPost" class="dynamic-component-demo-post">
17+
<h3>{{ selectedPost.title }}</h3>
18+
<div v-html="selectedPost.content"></div>
19+
</div>
20+
<strong v-else>
21+
Click on a blog title to the left to view it.
22+
</strong>
23+
</div>
24+
</div>
25+
</template>
26+
27+
<script>
28+
export default {
29+
data() {
30+
return {
31+
posts: [
32+
{
33+
id: 1,
34+
title: 'Cat Ipsum',
35+
content:
36+
'<p>Dont wait for the storm to pass, dance in the rain kick up litter decide to want nothing to do with my owner today demand to be let outside at once, and expect owner to wait for me as i think about it cat cat moo moo lick ears lick paws so make meme, make cute face but lick the other cats. Kitty poochy chase imaginary bugs, but stand in front of the computer screen. Sweet beast cat dog hate mouse eat string barf pillow no baths hate everything stare at guinea pigs. My left donut is missing, as is my right loved it, hated it, loved it, hated it scoot butt on the rug cat not kitten around</p>'
37+
},
38+
{
39+
id: 2,
40+
title: 'Hipster Ipsum',
41+
content:
42+
'<p>Bushwick blue bottle scenester helvetica ugh, meh four loko. Put a bird on it lumbersexual franzen shabby chic, street art knausgaard trust fund shaman scenester live-edge mixtape taxidermy viral yuccie succulents. Keytar poke bicycle rights, crucifix street art neutra air plant PBR&B hoodie plaid venmo. Tilde swag art party fanny pack vinyl letterpress venmo jean shorts offal mumblecore. Vice blog gentrify mlkshk tattooed occupy snackwave, hoodie craft beer next level migas 8-bit chartreuse. Trust fund food truck drinking vinegar gochujang.</p>'
43+
},
44+
{
45+
id: 3,
46+
title: 'Cupcake Ipsum',
47+
content:
48+
'<p>Icing dessert soufflé lollipop chocolate bar sweet tart cake chupa chups. Soufflé marzipan jelly beans croissant toffee marzipan cupcake icing fruitcake. Muffin cake pudding soufflé wafer jelly bear claw sesame snaps marshmallow. Marzipan soufflé croissant lemon drops gingerbread sugar plum lemon drops apple pie gummies. Sweet roll donut oat cake toffee cake. Liquorice candy macaroon toffee cookie marzipan.</p>'
49+
}
50+
],
51+
selectedPost: null
52+
}
53+
}
54+
}
55+
</script>
56+
57+
<style lang="scss" scoped></style>

src/.vuepress/config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ module.exports = {
4949
children: [
5050
'component-registration',
5151
'component-props',
52-
'component-custom-events'
52+
'component-custom-events',
53+
// 'component-slots',
54+
'component-dynamic-async'
5355
]
5456
}
5557
]

src/guide/component-dynamic-async.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Dynamic & Async Components
2+
3+
> This page assumes you've already read the [Components Basics](components.md). Read that first if you are new to components.
4+
5+
## `keep-alive` with Dynamic Components
6+
7+
Earlier, we used the `is` attribute to switch between components in a tabbed interface:
8+
9+
```html
10+
<component v-bind:is="currentTabComponent"></component>
11+
```
12+
13+
When switching between these components though, you'll sometimes want to maintain their state or avoid re-rendering for performance reasons. For example, when expanding our tabbed interface a little:
14+
15+
<dynamic-1/>
16+
17+
You'll notice that if you select a post, switch to the _Archive_ tab, then switch back to _Posts_, it's no longer showing the post you selected. That's because each time you switch to a new tab, Vue creates a new instance of the `currentTabComponent`.
18+
19+
Recreating dynamic components is normally useful behavior, but in this case, we'd really like those tab component instances to be cached once they're created for the first time. To solve this problem, we can wrap our dynamic component with a `<keep-alive>` element:
20+
21+
```html
22+
<!-- Inactive components will be cached! -->
23+
<keep-alive>
24+
<component v-bind:is="currentTabComponent"></component>
25+
</keep-alive>
26+
```
27+
28+
Check out the result below:
29+
30+
<dynamic-2/>
31+
32+
Now the _Posts_ tab maintains its state (the selected post) even when it's not rendered. See [this example](https://codesandbox.io/s/github/vuejs/vuejs.org/tree/master/src/v2/examples/vue-20-keep-alive-with-dynamic-components) for the complete code.
33+
34+
:::tip Note
35+
Note that `<keep-alive>` requires the components being switched between to all have names, either using the `name` option on a component, or through local/global registration
36+
:::
37+
38+
Check out more details on `<keep-alive>` in the [API reference](TODO:../api/#keep-alive).
39+
40+
## Async Components
41+
42+
In large applications, we may need to divide the app into smaller chunks and only load a component from the server when it's needed. To make that easier, Vue allows you to define your component as a factory function that asynchronously resolves your component definition. Vue will only trigger the factory function when the component needs to be rendered and will cache the result for future re-renders. For example:
43+
44+
```js
45+
Vue.component('async-example', function(resolve, reject) {
46+
setTimeout(function() {
47+
// Pass the component definition to the resolve callback
48+
resolve({
49+
template: '<div>I am async!</div>'
50+
})
51+
}, 1000)
52+
})
53+
```
54+
55+
As you can see, the factory function receives a `resolve` callback, which should be called when you have retrieved your component definition from the server. You can also call `reject(reason)` to indicate the load has failed. The `setTimeout` here is for demonstration; how to retrieve the component is up to you. One recommended approach is to use async components together with [Webpack's code-splitting feature](https://webpack.js.org/guides/code-splitting/):
56+
57+
```js
58+
Vue.component('async-webpack-example', function(resolve) {
59+
// This special require syntax will instruct Webpack to
60+
// automatically split your built code into bundles which
61+
// are loaded over Ajax requests.
62+
require(['./my-async-component'], resolve)
63+
})
64+
```
65+
66+
You can also return a `Promise` in the factory function, so with Webpack 2 and ES2015 syntax you can do:
67+
68+
```js
69+
Vue.component(
70+
'async-webpack-example',
71+
// The `import` function returns a Promise.
72+
() => import('./my-async-component')
73+
)
74+
```
75+
76+
When using [local registration](components-registration.html#Local-Registration), you can also directly provide a function that returns a `Promise`:
77+
78+
```js
79+
new Vue({
80+
// ...
81+
components: {
82+
'my-component': () => import('./my-async-component')
83+
}
84+
})
85+
```
86+
87+
<p class="tip">If you're a <strong>Browserify</strong> user that would like to use async components, its creator has unfortunately [made it clear](https://github.com/substack/node-browserify/issues/58#issuecomment-21978224) that async loading "is not something that Browserify will ever support." Officially, at least. The Browserify community has found [some workarounds](https://github.com/vuejs/vuejs.org/issues/620), which may be helpful for existing and complex applications. For all other scenarios, we recommend using Webpack for built-in, first-class async support.</p>
88+
89+
### Handling Loading State
90+
91+
> New in 2.3.0+
92+
93+
The async component factory can also return an object of the following format:
94+
95+
```js
96+
const AsyncComponent = () => ({
97+
// The component to load (should be a Promise)
98+
component: import('./MyComponent.vue'),
99+
// A component to use while the async component is loading
100+
loading: LoadingComponent,
101+
// A component to use if the load fails
102+
error: ErrorComponent,
103+
// Delay before showing the loading component. Default: 200ms.
104+
delay: 200,
105+
// The error component will be displayed if a timeout is
106+
// provided and exceeded. Default: Infinity.
107+
timeout: 3000
108+
})
109+
```
110+
111+
> Note that you must use [Vue Router](https://github.com/vuejs/vue-router) 2.4.0+ if you wish to use the above syntax for route components.

0 commit comments

Comments
 (0)