网站搜索

javascript - vue.js 无法在计算属性方法中使用 data 属性


我正在尝试在计算方法中使用我的数据的一个属性,如下所示:

data() {
    return {
      ToDoItems: [
        { id: uniqueId("todo-"), label: "Learn Vue", done: false },
        {
          id: uniqueId("todo-"),
          label: "Create a Vue project with the CLI",
          done: true,
        },
        { id: uniqueId("todo-"), label: "Have fun", done: true },
        { id: uniqueId("todo-"), label: "Create a to-do list", done: false },
      ],
    };
  },
computed: {
listSummary() {
  const numberFinishedItems = this.ToDoItems.filter((item) => item.done)
    .length;
  return `${numberFinishedItems} out of ${this.ToDoItems.length} items completed`;
},
},

但是 IDE(Visual Studio Code)和编译器抛出一个错误:

属性 'ToDoItems' 在类型 'ComponentPublicInstance<{}, {}, {}, {}, {}, EmitsOptions, {}, {}, false, ComponentOptionsBase<{}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, EmitsOptions, string, {}>>' 上不存在。

我正在按照 Mozilla 的 vue.js 教程(https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties#adding_a_summary_counter)学习,但使用的是 v3。

有什么改变导致这不再可能或方式不同了吗?

提前感谢

完整代码:

<template>
  <div id="app">
    <h1>To-Do List</h1>
    <to-do-form @todo-added="addToDo"></to-do-form>
    <h2 id="list-summary">{{ listSummary }}</h2>
    <ul aria-labelledby="list-summary" class="stack-large">
      <li v-for="item in ToDoItems" :key="item.id">
        <to-do-item :label="item.label" :done="true" :id="item.id"></to-do-item>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import uniqueId from "lodash.uniqueid";
import { defineComponent } from "vue";
import ToDoItem from "./components/ToDoItem.vue";
import ToDoForm from "./components/ToDoForm.vue";

export default defineComponent({
  name: "App",
  components: {
    ToDoItem,
    ToDoForm,
  },
  data() {
    return {
      ToDoItems: [
        { id: uniqueId("todo-"), label: "Learn Vue", done: false },
        {
          id: uniqueId("todo-"),
          label: "Create a Vue project with the CLI",
          done: true,
        },
        { id: uniqueId("todo-"), label: "Have fun", done: true },
        { id: uniqueId("todo-"), label: "Create a to-do list", done: false },
      ],
    };
  },
  methods: {
    addToDo(toDoLabel: string) {
      this.ToDoItems.push({
        id: uniqueId("todo-"),
        label: toDoLabel,
        done: false,
      });
    },
  },
  computed: {
    listSummary() {
      const numberFinishedItems = this.ToDoItems.filter((item) => item.done)
        .length;
      return `${numberFinishedItems} out of ${this.ToDoItems.length} items completed`;
    },
  },
});
</script>

<style>
/* Global styles */
.btn {
  padding: 0.8rem 1rem 0.7rem;
  border: 0.2rem solid #4d4d4d;
  cursor: pointer;
  text-transform: capitalize;
}
.btn__danger {
  color: #fff;
  background-color: #ca3c3c;
  border-color: #bd2130;
}
.btn__filter {
  border-color: lightgrey;
}
.btn__danger:focus {
  outline-color: #c82333;
}
.btn__primary {
  color: #fff;
  background-color: #000;
}
.btn-group {
  display: flex;
  justify-content: space-between;
}
.btn-group > * {
  flex: 1 1 auto;
}
.btn-group > * + * {
  margin-left: 0.8rem;
}
.label-wrapper {
  margin: 0;
  flex: 0 0 100%;
  text-align: center;
}
[class*="__lg"] {
  display: inline-block;
  width: 100%;
  font-size: 1.9rem;
}
[class*="__lg"]:not(:last-child) {
  margin-bottom: 1rem;
}
@media screen and (min-width: 620px) {
  [class*="__lg"] {
    font-size: 2.4rem;
  }
}
.visually-hidden {
  position: absolute;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px 1px 1px 1px);
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: rect(1px, 1px, 1px, 1px);
  white-space: nowrap;
}
[class*="stack"] > * {
  margin-top: 0;
  margin-bottom: 0;
}
.stack-small > * + * {
  margin-top: 1.25rem;
}
.stack-large > * + * {
  margin-top: 2.5rem;
}
@media screen and (min-width: 550px) {
  .stack-small > * + * {
    margin-top: 1.4rem;
  }
  .stack-large > * + * {
    margin-top: 2.8rem;
  }
}
/* End global styles */
#app {
  background: #fff;
  margin: 2rem 0 4rem 0;
  padding: 1rem;
  padding-top: 0;
  position: relative;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);
}
@media screen and (min-width: 550px) {
  #app {
    padding: 4rem;
  }
}
#app > * {
  max-width: 50rem;
  margin-left: auto;
  margin-right: auto;
}
#app > form {
  max-width: 100%;
}
#app h1 {
  display: block;
  min-width: 100%;
  width: 100%;
  text-align: center;
  margin: 0;
  margin-bottom: 1rem;
}
</style>

答案#1

Boussadjra Brahim 的回答是“还可以”,但实际上并没有解决问题。user16362509 是正确的,但并没有回答问题。你需要做的是,是的,为你的数据属性和计算属性的返回类型添加注解,这样 TS 才能明白发生了什么。不仅如此,如果你为所有类型注解,还能提供更严格的类型检查。我不认为在正确使用 Vue3 的组合式 API 时会出现这个问题,但正如你所看到的,在选项式 API 中确实存在这个问题。试试这个:(或者考虑使用组合式 API)。奇怪的是,当你为计算方法的返回类型添加注解时,它可以理解上下文,这只是 Vue 的一些怪癖,但最好是明确类型,这是良好惯例。

<script lang="ts">
import uniqueId from "lodash.uniqueid";
import { defineComponent } from "vue";
import ToDoItem from "./components/ToDoItem.vue";
import ToDoForm from "./components/ToDoForm.vue";

export default defineComponent({
  name: "App",
  components: {
    ToDoItem,
    ToDoForm,
  },
  data(): { ToDoItems: Array<{id: *string*, label: string, done: boolean}> } {
    return {
      ToDoItems: [
        { id: uniqueId("todo-"), label: "Learn Vue", done: false },
        {
          id: uniqueId("todo-"),
          label: "Create a Vue project with the CLI",
          done: true,
        },
        { id: uniqueId("todo-"), label: "Have fun", done: true },
        { id: uniqueId("todo-"), label: "Create a to-do list", done: false },
      ],
    };
  },
  methods: {
    addToDo(toDoLabel: string): void {
      this.ToDoItems.push({
        id: uniqueId("todo-"),
        label: toDoLabel,
        done: false,
      });
    },
  },
  computed: {
    listSummary(): string {
      const numberFinishedItems = this.ToDoItems.filter((item) => item.done)
        .length;
      return `${numberFinishedItems} out of ${this.ToDoItems.length} items completed`;
    },
  },
});
</script>

答案#2

你已经在使用 Vue 3。为什么不使用带有 script setup 的组合式 API 来获得更好的 TypeScript 支持呢?

现场演示

<template>
  <div id="app">
    <h1>To-Do List</h1>
    <form @submit.prevent="addToDo">
      <input type="text" ref="label" />
      <button type="submit">Add</button>
    </form>
    <h2 id="list-summary">{{ listSummary }}</h2>
    <ul aria-labelledby="list-summary" class="stack-large">
      <li v-for="item in ToDoItems" :key="item.id">
        <input type="checkbox" v-model="item.done" />
        {{ item.id }} {{ item.label }}
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue';
interface ToDoItem {
  id: string;
  label: string;
  done: boolean;
}
const label = ref(null)
const ToDoItems = ref<ToDoItem[]>([
  { id: 1, label: 'Learn Vue', done: false },
  { id: 2, label: 'Create a Vue project with the CLI', done: true },
  { id: 3, label: 'Have fun', done: true },
  { id: 4, label: 'Create a to-do list', done: false },
]);
const addToDo = () => {
  ToDoItems.value.push({
    id: ToDoItems.value.length + 1,
    label: label.value.value,
    done: false,
  });
  label.value.value = '';
};
const listSummary = computed(() => {
  return `${ToDoItems.value.filter((item) => item.done).length} out of ${ToDoItems.value.length} items completed`;
});
</script>