无法在使用TypeScript的Vue中使用Mixins

26

我的文件夹结构像这样

--Page    
   -group.vue    
--Services
  -groupMixin.ts

group.vue的脚本

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

     import { GroupMixin } from '../../services/groupMixin';
     @Component
     export default class Group extends Mixins(GroupMixin) {
        created () {
          console.log(this.test)
        }
      }
</script>

groupMixin.ts 的代码

import { Vue } from 'vue-property-decorator'
//creating mixins.
export class GroupMixin extends Vue {
  test: string = 'sss'
}

我在这里面遇到了两个问题。

首先,要导入一个ts文件我使用了../../,有没有办法使用./或@/。不使用lang="ts",我可以像这样导入js文件@/services/...

第二个问题是,无法访问我在groupmixin.ts中声明的变量 test


你正在使用 vue-cli 吗? - Marlon Barcarol
是的,我正在使用它。 - Sam
你忘记在groupMixin.ts中添加@Component了。 - Matthias Sommer
9个回答

22

今天我花了很多时间试图弄清楚在TypeScript项目中如何使用Vue mixins。显然,所有教程中通常使用的混合方法在TypeScript中都不起作用。组件无法访问在其mixins中定义的属性,因为Vue框架的mixin代码似乎不兼容TypeScript。

最终,我找到了一种在TypeScript中让mixins工作的方法,实际上运行得非常好。我的项目中有多层混合继承,一个mixin扩展另一个mixin,所有东西都按照我预期的方式工作。秘密是我必须安装这个第三方软件包来修复TypeScript中的mixins:

https://www.npmjs.com/package/vue-typed-mixins

请注意以下事项(但都不是什么大问题):

  1. 对于我而言,只有在.ts文件中定义mixins才能使此插件正常工作,而不是在.vue文件中定义。对我来说这不是问题,因为我的mixins只包含代码,没有html或css(我甚至想不到可能有这种情况)。

  2. 当您在组件上包含mixin时,请确保按照软件包网站(上面的url)中的示例进行操作。如果您只是安装软件包,而不重构代码以遵循网站上的示例,则无法正常工作。

这是一个简单的示例:

// /src/mixins/MyMixin.ts

import Vue from "vue";

export default Vue.extend({
    data: function () {
        return {
            mixinMessage: "this came from MyMixin!"
        };
    },
    created: function () {
        console.log("MyMixin.created()");
    },
    mounted: function () {
        console.log("MyMixin.mounted()");
    },
    methods: {
        mixinOutput: function (text:string) {
            console.log("mixin says: " + text);
        }
    }
});

接下来被使用的是:

// /src/components/MyComponent.vue

<template>
    <div>
        whatever
    </div>
</template>

<style>
    /* whatever */
</style>

<script lang="ts">
    import mixins from "vue-typed-mixins";
    import MyMixin from "../mixins/MyMixin";

    export default mixins(MyMixin).extend({
        created: function () {
            console.log("MyComponent.created()");
        },
        mounted: function () {
            console.log("MyComponent.mounted()");

            this.mixinOutput("hello from MyComponent");
            this.mixinOutput(this.mixinMessage);
        }
    });
</script>

我没有使用这个解决方案,但是你在(1)中建议使用.ts而不是.vue文件对我来说解决了问题。无需插件。使用.Vue的一个用例是,例如,如果您有一些依赖于特定CSS的代码,则需要同时从同一源混合css和与其关联的代码...一个.vue文件。 - philw
这个解决方案使得包含多个mixin变得非常容易 export default mixins(mixin1, mixin2, mixin3).extend({}) - RichC

11
请尝试按照以下步骤使您的 mixin 生效:

group.vue


<script lang="ts">
 import Vue from 'vue';
 // mixins only exist in `vue-class-component` and Component is a default export.
 import Component, { mixins } from 'vue-class-component';

 import { GroupMixin } from '../Services/groupMixin';

 @Component
 export default class Group extends mixins(GroupMixin) {
    created () {
      console.log(this.test)
    }
  }
</script>

groupMixin.ts

->

groupMixin.ts

import { Vue } from 'vue'

export class GroupMixin extends Vue {
  test: string = 'sss'
}

我在使用 import Vue from 'vue'; 导入Vue的原因是,一些IDE会在从 vue-class-component 中导入Vue函数,如 $emit 时进行高亮显示。

如果您没有使用 vue-cli ,则需要设置 webpack的解析别名以及在tsconfig.json文件中进行配置,并可能需要使用tsconfig-paths


1
是 Vue 的 vue-class-component 对吧?不是 vue-class-decorator。 - Sam
是的,你说得对,我混淆了 'vue-property-decorator'。已更新。谢谢。 - Marlon Barcarol
已更新以匹配您提供的文件夹结构。 - Marlon Barcarol
是的,文件夹结构都没问题,但一旦我构建它,this.test 就显示为未定义。 - Sam
为什么在.vue文件中有一个默认的Vue导入,而在mixin中有一个非默认的Vue导入 - 为什么组件中需要导入Vue? - Tobias Feil

10

根据@Joe Irby的回答,我刚发现它可以在没有vue-typed-mixins的情况下工作。

由于你的mixin扩展了Vue,所以你可以通过扩展你的mixin创建你的组件:

// MyMixin.ts

import Vue from "vue";

export default Vue.extend({
    data: function () {
        return {
            message: "Message from MyMixin!"
        };
    }
});


// MyComponent.vue

<template>
    ...
</template>

<script lang="ts">
    import MyMixin from "@/mixins/MyMixin";

    export default MyMixin.extend({
        mounted: function () {
            console.log(this.message);
        }
    });
</script>

2
如果我需要使用多个不同的mixin怎么办? - Alexander Kim
2
你可以尝试类似这样的写法:export default MyMixin1.extend(MyMixin2.extend({...}))。是的,这看起来很丑陋,但这种解决方案更像是一种 hack 而不是可行的模式... - Finrod
优秀的解决方案。 - Charlie

8
截止至今天,在使用Typescript/Vue时有两种使用mixin的方式:
  1. 如果你的mixin只包含变量:
// mixins/title.ts
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class titleMixin extends Vue {
  public test: string = 'Hello, hello, hello'
}

// Page.vue
import { Component, Vue, Mixins } from 'vue-property-decorator'
import titleMixin from '@/mixins/title'

export default class Page extends Mixins(titleMixin) {
  private mounted(): void {
    console.log(this.test) // would print: Hello, hello, hello
  }
}

如果你正在使用生命周期钩子:
// mixins/title.ts
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class titleMixin extends Vue {
  private mounted(): void {
    console.log('somevalue')
  }
}

// Page.vue
import { Component, Vue } from 'vue-property-decorator'
import titleMixin from '@/mixins/title'

@Component({
  mixins: [titleMixin]
})
export default class Page extends Vue {} // this will print console.log

这是我的做法。你可以看看“vue-class-component”包:https://github.com/vuejs/vue-class-component/blob/master/test/test.ts#L389


7

mixins.ts

import { Vue, Component } from "vue-property-decorator";

@Component
export default class Mixin extends Vue {
  public perfectScrollbarSetting: object = {
    maxScrollbarLength: 750
  };
  public widthClient: number = 0;
  public heightClient: number = 0;
}

文件 About.vue

<template>
</template>
<script lang="ts">
import { Vue, Component, Mixins } from "vue-property-decorator";
import { generalInfo } from "../../store/modules/general";
import Mixin from "../../mixin/mixins";
@Component({
  mixins: [Mixin]
})
export default class About extends Mixins(Mixin) {
  mounted() {
    console.log(this.widthClient) //it's work
  }

}
</script>

1
你只需要使用 mixins: [Mixin] 或者 extends Mixins(Mixin) - Matthias Sommer
你绝对不需要为每个文件声明mixin。通过声明mixin的模块,可以使用全局方式。 - cuongdevjs

5
如果你不使用vue-class-component(目前我没有使用它,因为它与setup/ composition api 不兼容),你可以将defineComponent用作mixin,在Vue 3中使用typescript。
mixin的示例:

yor_mixin.ts

import {defineComponent} from "vue"

interface MixinState{
    lastScrollPosition: number;
}

export default defineComponent({
    data(): MixinState{
        return {
            lastScrollPosition: 0,
        }
    },
    methods:{
        scrollDisable(){
            document.body.style.overflow = "hidden";
            this.lastScrollPosition = window.pageYOffset;
        },
        scrollEnable(){
            document.body.style.overflow = "auto";
            window.scrollTo(0, this.lastScrollPosition)
        }
    }
})

和组件


<script lang="ts">
import {computed, defineComponent} from 'vue';
import {useStore, MUTATIONS} from "@/store";
import scrollDisableMixin from "@/mixins/scrollDisable";

export default defineComponent({
  mixins: [scrollDisableMixin],

  setup(){
    const store = useStore();
    return {
      expanded: computed(() => store.state.menu.expanded),
      toggle: () => store.commit(MUTATIONS.TOGGLE_MENU_MAIN),
    }
  },

  watch: {
    expanded: function(){
      this.expanded ? this.scrollDisable() :this.scrollEnable();
    }
  }

})

1
这是处理vue3和选项API时最透明的解决方案。缺点是mixin不再知道组件的_this_,因此可能需要进行一些重构。 - Onno van der Zee
@OnnovanderZee mixin 如何知道组件的 this?mixin 不知道它将在哪个组件中使用,因此它只知道自己的 this。也许我不知道某些东西(我从 Vue 3 开始,之前我专注于 React)。 - Robert
Mixins被合并到组件中,就像Vue执行Object.assign(component, mixin1, mixin2)一样。因此,在合并后,组件中声明的所有内容都会暴露给mixin,反之亦然。这包括已知的名称冲突风险。 - Onno van der Zee
好的。所以你可以在mixin和组件中声明相同的变量。在默认合并策略中,组件中的变量会覆盖mixin中的变量。但是你可以编写自己的合并策略,然后用mixin中的选项覆盖组件中的选项。文档。我知道这是一个解决办法,但当你在mixin中声明所有变量时,它更加清晰。最后文档说我们应该使用hooks而不是mixins来使用composition api。 - Robert

0

我不认为这还是一个“mixin”,但它起作用了。

SortHelperMixin.ts

import Vue from "vue";
export default Vue.extend({
  methods: {
    sortDate(a: string, b: string): number {
       ...
    },
  }
})

MyComponent.vue

import SortHelperMixin from '@/mixins/SortHelperMixin'
export default Vue.extend({
  name: "MyComponent",
  data() {
    return {
      sortHelperMixin: new SortHelperMixin(),
    };
  },
})

使用

this.sortHelperMixin.sortDate(a, b)

您的回答可以通过提供更多的支持性信息来改善。请[编辑]以添加更多细节,例如引用或文档,以便他人确认您的回答是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Compositr

0
这篇答案是给那些愿意使用vue-class-component(装饰器)的人...你需要做的就是从'vue-class-component'导入Options,然后在那里添加你的mixin。
步骤:
1- 创建你的mixin:在这个例子中,我正在创建一个mixin来格式化时间(HH:MM:ss)。
//Mixin
 export default {
   methods: {
     formatTime(date: string) {
       return new Date(date)
         .toTimeString()
         .replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1");
     },
   },
 };

2- 通过在选项装饰器中添加它来在组件中使用mixin。

   //Home.vue

    import { Options, Vue } from "vue-class-component";

    import formatTimeMixin from '../mixin/formatTime';

    <template>
       <h1> Time left: {{formatTime(timeLeft)}} </h1>
    </template>

    @Options({
      mixins: [formatTimeMixin],
    })
    export default class Home extends Vue {
       timeLeft = 10000;
    }

就是这样,希望能对某些人有所帮助!


我可能错了,但目前来看,vue-class-component中还没有可用的@Options语法。我认为它仍处于测试阶段。 - OzzyTheGiant

0

关于混入的缺点,为什么不尝试将混入重构为Vue3的setup: https://codesandbox.io/s/delicate-sea-0xmim?file=/src/components/HelloWorld.vue:2262-2495 Comp.vue

export default {
  name: "HelloWorld",
  setup() {
    const { test } = useGroup();
    console.log(test);
    return Object.assign({}, useGroup());
  },
  mounted() {
    this.compRef = this;
  },
  props: {
    msg: String,
  },
};

UseGroup.js

import { ref, nextTick, onMounted } from "vue";

export default () => {
  const compRef = ref(null);
  const test = "Hello World!";

  onMounted(() => {
    nextTick(() => {
      if (compRef.value) {
        const vm = compRef.value;
        console.log(vm.$el, vm.msg);
      }
    });
  });
  return {
    test,
    compRef
  };
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接