什么原因导致此 Vue 3 应用程序中出现“无法读取未定义错误的属性”?
我正在开发一个 Vue 3 应用程序。我有 3 个嵌套组件:一个下拉组件,嵌套在导航组件中,导航组件嵌套在内容组件中。
下拉菜单应按作者过滤祖父组件
Main.vue
中的帖子。
我尝试向上发出
getPostsByUser(user)
方法,
一次一个组件
。
在孙组件
UsersDropdown.vue
中:
<template>
<div class="dropdown">
<button type="button" class="btn btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{{ label }}
</button>
<ul v-if="usersData?.length" class="dropdown-menu">
<li v-for="user in usersData" :key="user.userId">
<a class="dropdown-item" @click="handleClick(user)">{{ user.first_name }} {{ user.last_name }}</a>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'UsersDropdown',
props: {
label: String,
usersData: Object,
user: Object
},
methods: {
handleClick(user) {
this.$emit('getPostsByUser', user.userId)
}
}
}
</script>
在父组件
Navigation.vue
中:
<template>
<div class="navigation">
<UsersDropdown
@getPostsByUser="$emit('getPostsByUser', user)"
:label='"All users"'
:usersData='users'
/>
</div>
</template>
<script>
import UsersDropdown from './UsersDropdown'
export default {
props: {
usersData: Object,
},
components: {
UsersDropdown,
},
emits: ['getPostsByUser'],
data() {
return {
users: [],
gateways: []
}
},
async mounted() {
// Users
await this.axios.get(`${this.$apiBaseUrl}/users`).then((response) => {
if (response.data.code == 200) {
this.users = response.data.data;
}
}).catch((errors) => {
console.log(errors);
});
}
}
</script>
在祖父组件
Main.vue
中:
<template>
<div class="main">
<Navigation
@getPostsByUser='getPostsByUser(user)'
:label='"All users"'
:usersData='users' />
</div>
</template>
<script>
import Navigation from './Ui/Navigation'
export default {
name: 'Main',
components: {
Navigation
},
props: {
title: String,
tagline: String,
},
data() {
return {
userId: '',
posts: [],
// more code
}
},
methods: {
getPostsByUser(user) {
// get user id
this.userId = user.userId;
},
}
}
</script>
问题
由于我无法理解的原因,Chrome 控制台抛出了错误:
Cannot read properties of undefined (reading 'userId')
我的错误在哪里?
问题出在您的
Navigation
组件上。您需要在
export default {
中声明 props 和组件,以使它们工作并传递数据。
将以下代码添加到其中:
components: {
UsersDropdown,
},
props: {
usersData: Object,
},
此外,将您的
usersData
prop 映射更新为
Navigation.vue
组件 HTML 部分中的
UsersDropdown
组件标签。
将现有内容替换为:
<UsersDropdown
@getPostsByUser="$emit('getPostsByUser', user)"
:label="'All users'"
:usersData="usersData"
/>
++ 在您的 `Navigation.vue` 组件中,您需要声明一个方法来监听传入的 `user` 数据,然后将数据再次发送到父 `Main.vue` 组件。您尝试直接执行此操作,因此出现未定义问题。
更新您的 `UsersDropdown` 组件标签,如下所示:
<UsersDropdown
@getPostsByUser="getPostsByUser"
:label="'All users'"
:usersData="usersData"
/>
同时将以下方法添加到
Navigation.vue
组件的
export default {
部分。
methods: {
getPostsByUser(user) {
// get user id
console.log(user, "js");
this.$emit("getPostsByUser", user);
},
},
最后,仅使用相应的函数名称更新您的
@getPostsByUser
事件监听器。不需要传递参数。
在
Main.vue
组件的 HTML
Navigation
标记中,将
@getPostsByUser="getPostsByUser(user)"
替换为
@getPostsByUser="getPostsByUser"
。
为了更清楚地理解,请尝试此沙盒: https://codesandbox.io/s/vue-3-event-handling-nested-comps-s626kp?file=/src/App.vue
在
UsersDropdown.vue
中,您正在传递 userId,而在
Main.vue
中,您正在等待整个用户对象,请尝试使用
this.userId = user;
const app = Vue.createApp({
el: "#demo",
props: {
title: String,
tagline: String,
},
data() {
return {
userId: '',
posts: [],
// more code
}
},
methods: {
getPostsByUser(user) {
this.userId = user;
},
}
})
app.component("Navigation", {
template: `
<div class="navigation">
<users-dropdown
@getpostsbyuser="getPostsByUser"
:label='"All users"'
:users-data='users'
></users-dropdown>
</div>
`,
props: ['usersData'],
data() {
return {
users: [],
gateways: []
}
},
async mounted() {
// Users
/*await this.axios.get(`${this.$apiBaseUrl}/users`).then((response) => {
if (response.data.code == 200) {
this.users = response.data.data;
}
}).catch((errors) => {
console.log(errors);
});*/
this.users = [{userId: 1, first_name: "aaa", last_name: "bbb"}, {userId: 2, first_name: "ccc", last_name: "ddd"}, {userId: 3, first_name: "eee", last_name: "fff"}]
},
methods: {
getPostsByUser(user) {
this.$emit('getpostsbyuser', user);
},
}
})
app.component("UsersDropdown", {
template: `
<div class="dropdown">
<button type="button" class="btn btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
{{ label }}
</button>
<ul v-if="usersData?.length" class="dropdown-menu">
<li v-for="user in usersData" :key="user.userId">
<a class="dropdown-item" @click="handleClick(user)">
{{ user.first_name }} {{ user.last_name }}
</a>
</li>
</ul>
</div>
`,
props: {
label: String,
usersData: Array,
},
methods: {
handleClick(user) {
this.$emit('getpostsbyuser', user.userId)
}
}
})
app.mount('#demo')
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="main">
<navigation @getpostsbyuser='getPostsByUser' :label='"All users"'></navigation>
<p>user id: {{ userId }}</p>
</div>
</div>