是什么导致此 Vue 3 应用程序中的 'Cannot read properties of undefined error'?
What causes the 'Cannot read properties of undefined error' in this Vue 3 application?
我正在开发 Vue 3 应用程序。我有 3 个嵌套组件:一个下拉组件,嵌套在导航组件中,导航组件嵌套在内容组件中。
下拉菜单应按作者过滤 grandparent 组件 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>
在parent组件中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>
在grandparent组件中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')
我的错误在哪里?
在 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/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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>
问题出在您的 Navigation
组件上。您需要在 export default {}
中声明 props 和组件,它们才能工作并传递数据。
添加以下代码:
components: {
UsersDropdown,
},
props: {
usersData: Object,
},
此外,将 usersData
道具映射更新到 Navigation.vue
组件的 HTML 部分中的 UsersDropdown
组件标签。
将现有的替换为:
<UsersDropdown
@getPostsByUser="$emit('getPostsByUser', user)"
:label="'All users'"
:usersData="usersData"
/>
++
在您的“Navigation.vue”组件中,您需要声明一个方法来侦听传入的“用户”数据,然后再次将数据发送到父“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
我正在开发 Vue 3 应用程序。我有 3 个嵌套组件:一个下拉组件,嵌套在导航组件中,导航组件嵌套在内容组件中。
下拉菜单应按作者过滤 grandparent 组件 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>
在parent组件中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>
在grandparent组件中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')
我的错误在哪里?
在 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/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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>
问题出在您的 Navigation
组件上。您需要在 export default {}
中声明 props 和组件,它们才能工作并传递数据。
添加以下代码:
components: {
UsersDropdown,
},
props: {
usersData: Object,
},
此外,将 usersData
道具映射更新到 Navigation.vue
组件的 HTML 部分中的 UsersDropdown
组件标签。
将现有的替换为:
<UsersDropdown
@getPostsByUser="$emit('getPostsByUser', user)"
:label="'All users'"
:usersData="usersData"
/>
++ 在您的“Navigation.vue”组件中,您需要声明一个方法来侦听传入的“用户”数据,然后再次将数据发送到父“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