很多情況下,我們需要使用動態的html作為某個dom元素的inner html,如果這個內容是標准的html的話,則v-html能夠圓滿滿足需求,而如果內容包含了vue組件,則使用v-html就不能達到你希望的目標了。
我研究有兩種方案來解決,一種原生使用v-if提供的compile和mount特性;第二種類則使用render函數帶來的特殊能力實現這一點。
其中render函數的方案有一個現成的vue組件作為參考. v-runtime-template.
該組件產生的背景:
在項目開發中需要從server上接收template string.比如,即允許用戶自己通過拖拽來設計接口風格的程序中,通常被保存為vue template code,而該code會使用由前端工程師開發好的預定義組件。這些組件將通過api調用請求數據並且填充頁面的一個部分。
正如上面所說的,v-html很大程度上能夠部分滿足需求:
<template> <div> <div v-html="template"></div> </div> </template> <script> export default { data: () => ({ template: ` <h2>Howdy Yo!</h2> <a href="croco-fantasy">Go to the croco-fantasy</a> ` }), }; </script>
上面的template字符串可以從server ajax call中獲得。問題是,如果該template string中包含了vue template code,則無能為力!
export default { data: () => ({ template: ` <app-title>Howdy Yo!</app-title> <vue-router to="/croco-fantasy">Go to the croco-fantasy</vue-router> ` }), };
v-html並不能理解vue-router這個tag,因此將會丟棄很多重要功能。
這時作者就需要開發一個能夠解決該類問題的"proxy"組件
v-runtime-template的實現原理是: 自動獲得v-runtime-template的父組件的context並且使得vue編譯並且attach.
render(h) { if (this.template) { const parent = this.parent || this.$parent const { $data: parentData = {}, $props: parentProps = {}, $options: parentOptions = {} } = parent; const { components: parentComponents = {}, computed: parentComputed = {}, methods: parentMethods = {} } = parentOptions; const { $data = {}, $props = {}, $options: { methods = {}, computed = {}, components = {} } = {} } = this; const passthrough = { $data: {}, $props: {}, $options: {}, components: {}, computed: {}, methods: {} }; //build new objects by removing keys if already exists (e.g. created by mixins) Object.keys(parentData).forEach(e => { if (typeof $data[e] === "undefined") passthrough.$data[e] = parentData[e]; }); Object.keys(parentProps).forEach(e => { if (typeof $props[e] === "undefined") passthrough.$props[e] = parentProps[e]; }); Object.keys(parentMethods).forEach(e => { if (typeof methods[e] === "undefined") passthrough.methods[e] = parentMethods[e]; }); Object.keys(parentComputed).forEach(e => { if (typeof computed[e] === "undefined") passthrough.computed[e] = parentComputed[e]; }); Object.keys(parentComponents).forEach(e => { if (typeof components[e] === "undefined") passthrough.components[e] = parentComponents[e]; }); const methodKeys = Object.keys(passthrough.methods || {}); const dataKeys = Object.keys(passthrough.$data || {}); const propKeys = Object.keys(passthrough.$props || {}); const templatePropKeys = Object.keys(this.templateProps); const allKeys = dataKeys.concat(propKeys).concat(methodKeys).concat(templatePropKeys); const methodsFromProps = buildFromProps(parent, methodKeys); const finalProps = merge([ passthrough.$data, passthrough.$props, methodsFromProps, this.templateProps ]); const provide = this.$parent._provided; const dynamic = { template: this.template || "<div></div>", props: allKeys, computed: passthrough.computed, components: passthrough.components, provide: provide }; return h(dynamic, { props: finalProps }); } }
以上代碼就是v-runtime-template的核心render函數.
<template> <div> <v-runtime-template :template="template"/> </div> </template> <script> import VRuntimeTemplate from "v-runtime-template"; import AppTitle from "./AppTitle"; export default { data: () => ({ template: ` <app-title>Howdy Yo!</app-title> <vue-router to="/croco-fantasy">Go to the croco-fantasy</vue-router> ` }), components: { AppTitle } }; </script>
上面就是使用v-runtime-template核心用法代碼。有一點需要額外指出的是,它還可以訪問父組件的scope,意味着任何通過data,props,computed或者methods能夠訪問的都能用.這樣你就可以擁有dynaimc templates的能力:可被父組件訪問的活躍reactive數據.
比如:
export default { data: () => ({ animal: "Crocodile", template: ` <app-title>Howdy {{animal}}!</app-title> <button @click="goToCrocoland">Go to crocoland</button> ` }), methods: { goToCrocoland() { // ... } }
如果你需要動態地應用從server端返回的template,並能插入vue app中獲得對應的scope context訪問能力,那么v-runtime-template是一個非常不錯的選擇!~