Vue 将任何标签中的 "open" 属性替换为值 "open"

Vue replaces "open" attribute to value "open" in any tag

我在 laravel blade 模板中使用 vue.js (v2.6.12) 组件。 对于该项目,我还使用了 MathML,其中我需要使用 <mfenced> 标记的 open 属性来设置一些自定义值。下面是用 mathml 表达数学的例子。

<math xmlns="http://www.w3.org/1998/Math/MathML">
    <mi>f</mi>
    <mfenced close="]" open="[">
        <mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
    </mfenced>
</math>

但是一旦页面呈现,open 属性就会转换成这个 open="open"。我 100% 确定没有加载其他库或脚本来像这样更新,只是普通的 vue。这实际上打破了数学表达式。所以它看起来像这样:

<math xmlns="http://www.w3.org/1998/Math/MathML">
    <mi>f</mi>
    <mfenced close="]" open="open">
        <mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
    </mfenced>
</math>

后来我意识到,不仅在数学表达式中,任何标签,无论是 <div open="anything">...</div><span open="anything">...</span><custom-element open="something">...</custom-element> 具有 open 属性,其行为都是相同的。即使我使用 v-pre 属性将其从 vue js 模板编译器中排除。

这并没有发生,因为我禁用了 vue 应用程序初始化。


这里的问题是:

  1. 为什么 vue 会这样改变 open 属性?
  2. 我怎样才能停止这种行为,在 vue 应用程序区域内的整个页面或至少在我选择的地方(类似于使用 v-pre),是否有 ary 配置或任何其他方式?

为什么

在 HTML 规范中有一些属性称为 boolean attributes。规范规定了此类属性的值:

If the attribute is present, its value must either be the empty string or a value that is an ASCII case-insensitive match for the attribute's canonical name, with no leading or trailing whitespace.

The values "true" and "false" are not allowed on boolean attributes. To represent a false value, the attribute has to be omitted altogether.

open 是布尔属性之一 - 它是为 <details> element

定义的

Vue 2 的问题在于,它将大部分 boolean attributes 视为全局 - 没有考虑放置它的元素。结果是 open 属性总是以值“open”呈现,或者如果该值是假的(当 v-binding 时)则被删除。这在 Vue 3 中已修复,如第二个示例所示...

如何

使用 v-pre 是可行的方法,但不幸的是你有一个错误。
参见this issue. The bug was already fixed with this commit(2020 年 9 月 21 日)但尚未发布...

  1. 示例 - “使用 v-pre”应该在 Vue 版本> 2.6.12 中工作

    const vm = new Vue({
      el: '#app',
      data() {
        return {
          message: 'Hi!',
          html: `<div open="[" close="]">Hi from html</div>`
        }
      },
    })
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
    <div id="app">
      <div open="[" close="]">{{ message }}</div>
      <div v-html="html"></div>
      <div v-pre>
        <p open="[" close="]">With v-pre</p>
      </div>
    </div>
    

  2. 示例 - 它适用于 Vue 3 - open 仅当放置在 <details> 上时才被视为布尔属性

    const app = Vue.createApp({
      data() {
        return {
          message: 'This works in Vue 3!',
        }
      },
    })
    
    app.mount('#app')
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.11/vue.global.js" integrity="sha512-1gHWIGJfX0pBsPJHfyoAV4NiZ0wjjE1regXVSwglTejjna0/x/XG8tg+i3ZAsDtuci24LLxW8azhp1+VYE5daw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    
    <div id="app">
      <div open="[" close="]">{{ message }}</div>
      <details open="[">
        <summary>Details</summary>
        open attribute on details element is treated as boolean (renders empty value)
      </details>
    </div>
    

一种解决方法是创建一个 directive(名为 "attr")来设置属性:

Vue.directive('attr', (el, binding) => el.setAttribute(binding.arg, binding.value || ''))

然后像 v-bind 一样在您的模板中使用它,但使用 v-attr:

<mfenced v-attr:open="'['">

Vue.directive('attr', (el, binding) => el.setAttribute(binding.arg, binding.value || ''))

new Vue({ el: '#app' })
<script src="https://unpkg.com/vue@2.6.12"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script>

<div id="app">
  <math xmlns="http://www.w3.org/1998/Math/MathML">
    <mi>f</mi>
    <mfenced close="]" v-attr:open="'['">
      <mrow><mi>a</mi><mo>,</mo><mi>b</mi></mrow>
    </mfenced>
  </math>
</div>

我找到了解决此问题的简单方法。

为什么要破解?

因为正如@Michal 所指出的那样,它最终将在即将发布的版本中得到修复,所以现在只需快速而肮脏的 hack 就足够了。

我所做的是将数学内容放在 content 中,并将其添加到 data 属性中,并在 vue 完成其不良工作后替换原始内容(抱歉,这里只使用 blade 语法,但它会有意义).我把它放在两个地方只是为了 SEO 目的。

我需要显示数学表达式的模板。

...
<div class="proxy-content" data-proxy-content="{{ $article->content }}">
    {!! $article->content !!}
</div>
...

我将它与 jQuery 一起使用,但您可以轻松地用 vue.js' $el 代替。这就是它在我的 app.js 文件中的样子。

...
const app = new Vue({
    el: '#app',

    methods: {

        proxyContent() {
            // Set Proxy Content.
            jQuery('.proxy-content').each((i, el) => {
                const $el = jQuery(el);
                $el.html( jQuery('<textarea />').html( $el.data('proxy-content')).text() );
            });
        }
        
        loadMathJax() {
            // Load & Initialize MathJax Library.
            const script = document.createElement("script");
            script.type = "text/javascript";
            script.src  = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js";
            document.getElementsByTagName("head")[0].appendChild(script);
        }

    }

    mounted(){
        // Enable proxy content after mount, so we are sure no more rendering issue for templates.
        this.proxyContent();

        // Load MathJax library with a little delay to make sure everything is ready before loading the library.
        setTimeout(() => this.loadMathJax(), 10);
    }
});
...

有人可能会争辩说,我混淆了 vue 应用程序范围之外的东西。对我来说这不是问题,因为整个页面都在使用 vue.js 并且即使有另一个范围正在使用 mathml 单个东西也不会造成任何伤害(尽管它取决于实际实施)。

在那种情况下,如果您想很好地确定范围,只需使用 vue 的 $el