vue.js - 如何在 vue js web 组件中正确使用插槽并应用样式

标签 vue.js web-component vue-cli-3

我遇到了一个问题,即 webcomponent 中的插槽实现未按预期运行。我对 Web Components、Custom Elements 和 Slots 的理解是,在 slot 中呈现的元素应该从文档而不是 Shadow DOM 继承它们的样式,但是 slot 中的元素实际上是被添加到 Shadow DOM 中的,因此忽略了全局样式。我创建了以下示例来说明我遇到的问题。

共享用户界面

这是一个使用 cli ( --target wc --name shared-ui ./src/components/*.vue ) 编译为 web 组件的 Vue 应用程序

折叠组件.vue

<template>
    <div :class="[$style.collapsableComponent]">
        <div :class="[$style.collapsableHeader]" @click="onHeaderClick" :title="title">
            <span>{{ title }}</span> 
        </div>
        <div :class="[$style.collapsableBody]" v-if="expanded">
            <slot name="body-content"></slot>
        </div>
    </div>
</template>

<script lang="ts">
    import { Vue, Component, Prop } from 'vue-property-decorator'

    @Component({})
    export default class CollapsableComponent extends Vue {
        @Prop({ default: "" })
        title!: string;

        @Prop({default: false})
        startExpanded!: boolean;

        private expanded: boolean = false;

        constructor() {
            super();
            this.expanded = this.startExpanded;
        }

        get isVisible(): boolean {
            return this.expanded;
        }

        onHeaderClick(): void {
            this.toggle();
        }

        public toggle(expand?: boolean): void {
            if(expand === undefined) {
                this.expanded = !this.expanded;
            }
            else {
                this.expanded = expand;
            }
            this.$emit(this.expanded? 'expand' : 'collapse');
        }

        public expand() {
            this.expanded = true;

        }

        public collapse() {
            this.expanded = false;
        }
    }
</script>

<style module>
    :host {
        display: block;
    }

    .collapsableComponent {
        background-color: white;
    }

    .collapsableHeader {
        border: 1px solid grey;
        background: grey;
        height: 35px;
        color: black;
        border-radius: 15px 15px 0 0;
        text-align: left;
        font-weight: bold;
        line-height: 35px;
        font-size: 0.9rem;
        padding-left: 1em;
    }

    .collapsableBody {
        border: 1px solid black;
        border-top: 0;
        border-radius: 0 0 10px 10px;
        padding: 1em;
    }
</style>

共享用户界面消费者

这是一个使用标准脚本包含文件导入 shared-ui web 组件的 vue 应用程序。

应用程序

<template>
  <div id="app">
    <shared-ui title="Test">
      <span class="testClass" slot="body-content">
        Here is some text
      </span>
    </shared-ui>
  </div>
</template>

<script lang="ts">
import 'vue'
import { Component, Vue } from 'vue-property-decorator';

@Component({ })
export default class App extends Vue {

}
</script>

<style lang="scss">
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

.testClass{
  color: red;
}
</style>

主文件

import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;

new Vue({
  render: h => h(App),
}).$mount('#app');


在这个例子中,我希望容器内的内容有红色文本,但是因为 Vue 将元素克隆到 Shadow DOM 中,.testClass 样式被忽略,文本以黑色填充呈现。

如何将 .testClass 应用于我的 Web 组件内部的元素?

最佳答案

好的,所以我设法找到了一种解决方法,它使用 native 插槽并在 DOM 中的正确位置正确呈现子组件。

在安装的事件中,下一个勾号用一个新的插槽替换你的插槽容器的 innerHtml。您可以幻想并为命名插槽等进行一些很酷的替换,但这应该足以说明解决方法。

共享用户界面

这是一个使用 cli ( --target wc --name shared-ui ./src/components/*.vue ) 编译为 web 组件的 Vue 应用程序

折叠组件.vue

<template>
    <div :class="[$style.collapsableComponent]">
        <div :class="[$style.collapsableHeader]" @click="onHeaderClick" :title="title">
            <span>{{ title }}</span> 
        </div>
        <div ref="slotContainer" :class="[$style.collapsableBody]" v-if="expanded">
            <slot></slot>
        </div>
    </div>
</template>

<script lang="ts">
    import { Vue, Component, Prop } from 'vue-property-decorator'

    @Component({})
    export default class CollapsableComponent extends Vue {
        @Prop({ default: "" })
        title!: string;

        @Prop({default: false})
        startExpanded!: boolean;

        private expanded: boolean = false;

        constructor() {
            super();
            this.expanded = this.startExpanded;
        }

        get isVisible(): boolean {
            return this.expanded;
        }

        onHeaderClick(): void {
            this.toggle();
        }
        //This is where the magic is wired up
        mounted(): void {
            this.$nextTick().then(this.fixSlot.bind(this));
        }
        // This is where the magic happens
        fixSlot(): void {
            // remove all the innerHTML that vue has place where the slot should be
            this.$refs.slotContainer.innerHTML = '';
            // replace it with a new slot, if you are using named slot you can just add attributes to the slot
            this.$refs.slotContainer.append(document.createElement('slot'));
        }

        public toggle(expand?: boolean): void {
            if(expand === undefined) {
                this.expanded = !this.expanded;
            }
            else {
                this.expanded = expand;
            }
            this.$emit(this.expanded? 'expand' : 'collapse');
        }

        public expand() {
            this.expanded = true;

        }

        public collapse() {
            this.expanded = false;
        }
    }
</script>

<style module>
    :host {
        display: block;
    }

    .collapsableComponent {
        background-color: white;
    }

    .collapsableHeader {
        border: 1px solid grey;
        background: grey;
        height: 35px;
        color: black;
        border-radius: 15px 15px 0 0;
        text-align: left;
        font-weight: bold;
        line-height: 35px;
        font-size: 0.9rem;
        padding-left: 1em;
    }

    .collapsableBody {
        border: 1px solid black;
        border-top: 0;
        border-radius: 0 0 10px 10px;
        padding: 1em;
    }
</style>

共享用户界面消费者

这是一个使用标准脚本包含文件导入 shared-ui web 组件的 vue 应用程序。

应用程序

<template>
  <div id="app">
    <shared-ui title="Test">
      <span class="testClass" slot="body-content">
        Here is some text
      </span>
    </shared-ui>
  </div>
</template>

<script lang="ts">
import 'vue'
import { Component, Vue } from 'vue-property-decorator';

@Component({ })
export default class App extends Vue {

}
</script>

<style lang="scss">
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

.testClass{
  color: red;
}
</style>

主文件

import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;

new Vue({
  render: h => h(App),
}).$mount('#app');

关于vue.js - 如何在 vue js web 组件中正确使用插槽并应用样式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55169976/

相关文章:

javascript - 避免直接 prop 突变会增加变量的重新声明

javascript - Vue - 在方法中过滤数组时传递过滤条件

javascript - 向下传递超过两个组件时数据会丢失

vue.js - Vue Cli 3 生成的项目设置 HTML 标题

webpack - Vue-Cli - 保留类名 Webpack UglifyJS

javascript - 如何使用 vue-cli-3 设置从我的 webpack 构建中排除一个目录

php - 如何动态地将 Id 添加到 laravel/VueJs SPA 上使用的导航栏

asp.net-core - AllowAnyOrigin Cors 不工作 Axios Vuejs

javascript - 为什么无法读取属性 'classList'?

jquery - 使用 JQuery 访问 Polymer 创建的 DOM