使用 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

从我的代码中可以看出,整个列表都放在&lt;b-list-group&gt; 中,它根据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 属性(truefalse)? @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 &gt; 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条件渲染及列表渲染

Vue条件渲染及列表渲染

Vue中使用v-for渲染数据为何要添加key属性?(原理及作用)

Vue.js - 如何按特定属性对数组中的对象进行排序并使用“v-for”进行渲染

如何在 Vue.js 插槽中使用条件渲染?

vue基础知识简述(v-if&v-for&v-show&v-model)