解决v-html无法理解vue模版的问题-动态获取模版,动态插入app并使用当下app状态数据需求
很多情况下,我们需要使用动态的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是一个非常不错的选择!~