使用 Vue 在 v-for 中进行条件渲染
Posted
技术标签:
【中文标题】使用 Vue 在 v-for 中进行条件渲染【英文标题】:Conditional rendering inside v-for with Vue 【发布时间】:2021-11-13 22:51:51 【问题描述】:问题
我正在使用 Vue 构建一个项目,其中有一个项目列表,每个项目都有一个打开和删除按钮。现在我需要使用单独的组件为每个项目添加标签。我想为每个列表项单独切换此组件的呈现,以创建一个包含可折叠项的列表。
我的第一次尝试看起来像这样:
<b-list-group v-if="!isLoading">
<b-list-group-item v-for="project in projects" :key="project.path">
project.name
<b-btn variant="danger" size="sm" class="list-button" @click="deleteProject(project.path)">Löschen</b-btn>
<b-btn variant="secondary" size="sm" class="list-button" @click="openProject(project.path)">Öffnen</b-btn>
<b-btn variant="secondary" size="sm" class="list-button" @click="project.showTags = !project.showTags">Tags</b-btn>
<search-tags v-if="project.showTags" style="margin-top: 1em;"></search-tags>
</b-list-group-item>
如果我控制台记录我的this.projects
,我可以看到每个项目的值都正确更改。尽管如此,即使project.showTags == true
,v-if 也不会呈现。
然后我读到,你不应该在 v-for
中使用 v-if
,所以我尝试了 Bootstrap-Vue collapse 组件:
<b-list-group v-if="!isLoading">
<b-list-group-item v-for="project in projects" :key="project.path">
project.name
<b-btn variant="danger" size="sm" class="list-button" @click="deleteProject(project.path)">Löschen</b-btn>
<b-btn variant="secondary" size="sm" class="list-button" @click="openProject(project.path)">Öffnen</b-btn>
<b-btn variant="secondary" size="sm" class="list-button" @click="project.showTags = !project.showTags">Tags</b-btn>
<b-collapse v-model="project.showTags">
<search-tags style="margin-top: 1em;"></search-tags>
</b-collapse>
</b-list-group-item>
但这也不起作用。
我认为这不会那么困难,我在这里遗漏了一些东西。任何帮助表示赞赏。
编辑 1
从我的代码中可以看出,整个列表都放在<b-list-group>
中,它根据isLoading
有条件地呈现。如果我通过翻转isLoading
强制重新加载,然后翻转project.showTags
,然后将isLoading
翻转回来,它可以工作。但我觉得这个解决方案超级阴暗和hacky。
工作但丑陋的代码:
<b-list-group-item v-for="project in projects" :key="project.path">
project.name
<b-btn variant="danger" size="sm" class="list-button" @click="deleteProject(project.path)">Löschen</b-btn>
<b-btn variant="secondary" size="sm" class="list-button" @click="openProject(project.path)">Öffnen</b-btn>
<b-btn variant="secondary" size="sm" class="list-button" @click="test(project)">Tags</b-btn>
<search-tags v-show="project.showTags" style="margin-top: 1em;"></search-tags>
</b-list-group-item>
</b-list-group>
与:
test(project)
this.isLoading = !this.isLoading;
project.showTags = !project.showTags;
console.log(this.projects);
this.isLoading = !this.isLoading;
,
编辑 2
我的数据首先是这样定义的:
data()
return
projects: [],
tags: [],
newProject: false,
isLoading: true,
isError: false,
form:
name: '',
,
;
,
然后在created()
我打电话给getAllProjects()
从后端获取我的项目:
created()
this.getAllProjects();
,
我的项目没有 showTags
属性,所以我在获取后直接分配它:
async getAllProjects()
try
this.isLoading = true;
const res = await fetch(store.state.urlEndpoints.getProjects,
...AuthPost(), // request config object
);
this.projects = JSON.parse(await res.text());
this.projects.forEach((project) => project.showTags = false; );
console.log(this.projects);
this.isLoading = false;
catch (err)
this.isError = true;
this.isLoading = false;
,
我在每次单击“标签”按钮时 console.log this.projects
,如上面 test()
函数中所示。值会按照我的意图翻转。
【问题讨论】:
projects
是如何定义的?当您为其赋值时,每个项目 (project
) 是否具有 showTags
属性(true
或 false
)?
@Phil 是的,没错。 projects
是一个对象数组,每个project
都有一个属性showTags
初始化为false
并被@click
翻转。 projects
数组的行为与我想要的完全一样,v-for 循环内的元素只是没有响应更改。如您所见,整个内容都包裹在b-list-group
中,它也是有条件地呈现的。如果我通过两次翻转isLoading
来强制重新呈现列表,则一切正常。但我觉得这是一个超级靠不住的解决方案。
这里没问题~codesandbox.io/s/mystifying-swanson-63kop?file=/src/components/…
我的第一个想法是反应不够深:参考这个链接:vuejs.org/v2/guide/reactivity.html#For-Objects
您的数据是如何定义的?你能用v-if="projects.length > 0"
做吗?
【参考方案1】:
@Rouben 您需要对您想要设置的this.projects
进行排序,就像现在在您的created
中一样,您假设您将从getAllProjects
获得固定的项目数组。
尝试做这样的事情:
async getAllProjects()
try
this.isLoading = true;
const res = await fetch(store.state.urlEndpoints.getProjects,
...AuthPost(), // request config object
);
return await res.json().map(project =>
project.showTags = false;
return project;
);
您也可以在@click 中尝试这个:
<b-list-group-item v-for="(project, index) in projects" :key="project.path">
project.name
<b-btn variant="danger" size="sm" class="list-button" @click="deleteProject(project.path)">Löschen</b-btn>
<b-btn variant="secondary" size="sm" class="list-button" @click="openProject(project.path)">Öffnen</b-btn>
<b-btn variant="secondary" size="sm" class="list-button" @click="flipShowTags(index, project)">Tags</b-btn>
<search-tags v-show="project.showTags" style="margin-top: 1em;"></search-tags>
</b-list-group-item>
methods:
flipShowTags(index, obj)
this.$set(this.projects, index, ...obj, showTags: !obj.showTags)
如果是因为它没有反应性
【讨论】:
谢谢!created()
是一个愚蠢的错误,感谢您指出这一点!虽然它并没有改变行为,但this.$set
解决了我的问题。
@Rouben 你使用created()
的方式没有错【参考方案2】:
问题就在这里……
this.projects = JSON.parse(await res.text()); this.projects.forEach((project) => project.showTags = false; );
像这样添加 showTags
属性不是响应式的,这就是为什么 Vue 不会像您期望的那样响应更改。
见Change Detection Caveats
Vue 无法检测属性添加或删除。由于 Vue 在实例初始化期间执行 getter/setter 转换过程,因此数据对象中必须存在一个属性,以便 Vue 对其进行转换并使其具有响应性。
当您将值分配到您的projects
数据属性时,您需要具有所有反应属性。例如
export default
data: () => (
projects: [],
isLoading: true,
// etc
),
methods:
async loadAllProjects ()
try
this.isLoading = true;
const res = await fetch(store.state.urlEndpoints.getProjects,
...AuthPost(), // request config object
);
if (!res.ok)
throw new Error(`$res.status: $await res.text()`)
// note that the value assigned to `this.projects` now has the
// `showTags` property at the time of assignment
this.projects = (await res.json()).map(project => (
...project,
showTags: false
))
catch (err)
this.isError = true;
this.isLoading = false
,
created ()
this.loadAllProjects()
【讨论】:
以上是关于使用 Vue 在 v-for 中进行条件渲染的主要内容,如果未能解决你的问题,请参考以下文章
Vue中使用v-for渲染数据为何要添加key属性?(原理及作用)