Deep component
Provide/Inject
Usually, we use prop when we need to transfer data from the parent component to the child component. However, for deeply nested component systems, sometimes deep sub components need part of the contents of the parent component. At this time, it will be very troublesome to still use the prop mechanism. For example, if Grandpa a wants to transfer data to grandson c1, using the prop mechanism, he must define prop from b2, obtain data from a, define prop from c1, and transfer that data from b2.
For the following component hierarchy
Root └─ TodoList ├─ TodoItem └─ TodoListFooter ├─ ClearTodosButton └─ TodoListStatistics
If you want to directly pass the number of item s in todo list to todo list statistics, pass it as: todo list according to the prop mechanism -> todo-list-footer -> todo-list-statistics . Using the Provide/Inject mechanism, we can directly ask todo list to provide dependencies (it doesn't know who depends on the data, but only export), and then todo list statistics injects dependencies (it doesn't know who the dependencies come from, but only knows that they can be imported), and the injected dependencies become the data attributes (injection attributes) of todo list statistics. We modified the example in the previous article
<div id="app"> <todo-list :title="title"> <template #default="{ index: i, item: todo }"> <span>++ {{ i }} ++ </span> <span>{{ todo }}</span> </template> <template #other="{ date: date }"> <hr> <p>{{ date }}</p> </template> </todo-list> </div> <script> const app = Vue.createApp({ data() { return { title: 'Zhang San's off work life' } } }) app.component('todo-list', { props: ['title'], data() { return { items: ['go off work', 'wash hands', 'having dinner', 'take a walk', 'sleep'], date: '2021-11-11' } }, provide: { user: 'Li Si' }, template: ` <h2>{{ title }}</h2> <ul> <li v-for="(item, index) in items"> <slot :item="item" :index="index"></slot> </li> </ul> <slot :date="date" name="other"></slot> <todo-list-footer></todo-list-footer> ` }) app.component('todo-list-footer', { template: ` <todo-list-statistics></todo-list-statistics> ` }) app.component('todo-list-statistics', { inject: ['user'], created() { console.log(`Injected property: ${this.user}`) }, template: `Recorder:{{ user }}` }) app.mount('#app')
In the above code, we let todo list provide a static string data user. If we want the property of the Provider component instance, such as the number of items, the following code cannot achieve the purpose
provide: { user: 'Li Si', itemsCount: this.items.length // Cannot read properties of undefined (reading 'length') },
We must convert provide from a static object to a function that returns the object:
provide() { return { user: 'Li Si', itemsCount: this.items.length } },
The use is similar (as the data attribute of the component)
app.component('todo-list-statistics', { inject: ['user', 'itemsCount'], created() { console.log(`Injected property: ${this.user},${this.itemsCount}`) }, template: `common {{ itemsCount }} Article, recorder:{{ user }}` })
Dynamic and asynchronous components
Use keep alive on dynamic components
In the previous multi tag interface example, we learned about dynamic components, that is, we use is attribute to switch different components:
<component :is="currentTabComponent"></component>
By default, when these tab pages are switched back and forth, they will be rendered repeatedly, which will have a certain impact on performance. Therefore, we may want to maintain the state of these components. Another reason to keep the component state is that by default, we select a post to read on the Posts page, switch to the Archive page halfway, and then switch back to the Posts page, so we can't go back to the post we just read, because Vue creates a new post every time we switch to a new tab currentTabComponent instance. To achieve the above intention, we need a caching mechanism: wrap dynamic components with a < keep alive > element
<!-- Inactive components will be cached!--> <keep-alive> <component :is="currentTabComponent"></component> </keep-alive>
A simple example is given below (you can test whether the input content remains when there is no keep alive and keep alive):
<div id="app"> <button v-for="tab in tabs" :key="tab.name" :class="['tab-button', {active: currentTab === tab.name}]" @click="currentTab = tab.name"> {{ tab.text }} </button> <keep-alive> <component :is="currentTabComponent" class="tab"></component> </keep-alive> </div> <script> const app = Vue.createApp({ data() { return { currentTab: 'Home', tabs: [ {name:'Home', text:'home page'}, {name:'Posts', text:'Posts'}, {name:'Archive', text:'file'} ] } }, computed: { currentTabComponent() { return 'tab-' + this.currentTab.toLowerCase() } } }) app.component('tab-home', { template: `<div class="demo-post">Home page content</div>` }) app.component('tab-posts', { template: `<div class="demo-post">Post<input type="text" /></div>` }) app.component('tab-archive', { template: `<div class="demo-post">((archive)</div>` }) const vm = app.mount('#app') </script>
Asynchronous component
In large applications, we may need to divide the application into smaller code blocks and load a module from the server only when necessary. To achieve this effect, Vue provides the defineAsyncComponent method to implement asynchronous components.
<div id="app"> <async-example></async-example> </div> <script> const { createApp, defineAsyncComponent } = Vue const app = createApp({}) const AsyncComp = defineAsyncComponent(() => new Promise( (resolve, reject) => { setTimeout(() => { let data = { template: '<div>This is asynchronous!</div>' } resolve(data) }, 2000) } )) app.component('async-example', AsyncComp) app.mount('#app') </script>
In the above example, we use delay simulation to load from the server. The parameter of the defineAsyncComponent function is a factory function that returns Promise. After retrieving the component definition from the server (data in the example), the resolve callback of Promise should be called (or reject(reason) can be called in case of failure). We can learn from the debugging plug-in that there is an AsyncComponentWrapper node for asynchronous components. Initially, it is empty. After obtaining data from the server, the internal node (Anonymous Component) Anonymous Component will be generated.
Dynamic import can be realized by using compile build tool and ES2015 + syntax
import { defineAsyncComponent } from 'vue' const AsyncComp = defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) app.component('async-component', AsyncComp)
Locally registered components can also be imported dynamically
import { createApp, defineAsyncComponent } from 'vue' createApp({ // ... components: { AsyncComponent: defineAsyncComponent(() => import('./components/AsyncComponent.vue') ) } })
Template reference
We know that the communication between parent and child components can be realized through component prop and component events (the parent passes data to the child components and passes event callback processing), but sometimes we still want to access the child components directly. Using ref attribute, you can specify the reference ID for sub components or HTML elements (the template with ref attribute will become a referential object, which means that you can directly access the DOM node). In the following example, we use the template reference feature to focus the initial cursor on the second input box component
<div id="app"> <base-input ref="usernameInput"></base-input> <base-input ref="nicknameInput"></base-input> </div> <script> const { createApp } = Vue const app = createApp({}) app.component('base-input', { template: `<input ref="input" />`, methods: { focusInput() { this.$refs.input.focus() } } }) const vm = app.mount('#app') vm.$refs.nicknameInput.focusInput() </script>
vm instance property $refs is an object, which includes all DOM elements and component instances registered with ref attributes. The values of all ref attributes correspond to the keys of the object$ Refs will only take effect after the component rendering is completed, and it essentially accesses the DOM directly, which is contrary to Vue's virtual DOM spirit. Therefore, this mechanism is only a compensation mechanism, and access to $refs in templates or calculation attributes should be avoided (usually used in mounted() hooks, etc.)
Handling boundary conditions
Control update
We know that Vue is a responsive system, and generally it knows when it should be updated. However, in some extreme cases, forced updates may be required (most of the time, forced updates are required because of their own design errors, such as adding data attributes after component creation). In this case, you can use the component instance method $forceUpdate to force the component instance to re render.
In the other case, on the contrary, there may be a component containing a lot of static content. You can add a v-once instruction to the root element to ensure that the value is only evaluated once and cached, which can improve the rendering speed (this boundary processing strategy should be considered only when the content is indeed static and the rendering speed is not ideal).
No matter forced update or single render evaluation, they should be avoided as far as possible. Their existence is only a compensation mechanism.