V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Aruforce
V2EX  ›  程序员

问个 Vue 的问题,父组件通过 props 传值给子组件,在父组件内更改数据 子组件的 class 属性没有变更,详情见代码

  •  
  •   Aruforce · 2021-02-02 14:09:06 +08:00 · 2343 次点击
    这是一个创建于 1388 天前的主题,其中的信息可能已经有所发展或是发生改变。

    父组件代码

    <template>
      <div class="page_content_container">
        <div class="recommand_communities_container">
          <community_card v-for="(community,index) in communities" v-bind:key="index" v-bind:community="community" v-on:onclick="onChosen" />
        </div>
      </div>
    </template>
    <script>
    import community_card from '../community_card.vue';
    export default {
      components: { community_card},
      name: "page_content",
      data: function () {
        return {
          communities:[
    		{
    			id:'1'
    			name:'1-C',
    			intro:'1-C Test',
    			avatar:'/1-C-avatar.jpg'
    			selected:true
    		},
    		{
    			id:'2'
    			name:'2-C',
    			intro:'2-C Test',
    			avatar:'/2-C-avatar.jpg'
    			selected:false
    		},
    		{
    			id:'3'
    			name:'3-C',
    			intro:'3-C Test',
    			avatar:'/3-C-avatar.jpg'
    			selected:false
    		},
    	  ]
        }
      },
      methods: {
        onChosen:function(communityId){
          for (let index = 0; index < this.communities.length; index++) {
            const element = this.communities[index];
            if(element.id == communityId ){
               element.selected = true;
    		   // element.name = 'DoTest'; 有这一行 子组件的 class 会变更 ,没有则不会变更
            }else{
              element.selected = false
            }
          }
        }
      },
    };
    </script>
    <style scoped>
    .page_content_container {
      height: 800px;
      width: 100%;
      display:flex;
    }
    .recommand_communities_container {
      height: 800px;
      width: 240px;
      margin: 0;
      padding: 0;
      display: inline-block;
    }
    .recommand_community_post_container {
      border-radius: 15px;
      height: 100%;
      width: 100%;
      margin: 0;
      padding: 0;
      border-left:1px solid rgba(0, 0, 0, 0.22);
      display: inline;
    }
    </style>
    
    

    子组件

    <template>
      <div class="community_card_container" v-bind:class="{'selected':community.selected}" v-on:click="onclick">
        <div class="community_card_avatar_container">
          <img :src="community.avatar" :title="community.name" />
        </div>
        <div class="community_card_base_info_container">
          <div>{{ community.name }}</div>
          <div>{{ community.intro }}</div>
        </div>
      </div>
    </template>
    <script>
    export default {
      name: "community_card",
      data: function () {
        return {
    	
        }
      },
      methods: {
        onclick: function () {
          this.$emit('onclick',this.community.id)
        },
      },
      props:['community']
    };
    </script>
    <style scoped>
    .community_card_container {
      height: 76px;
      width: 236px;
      border: lightslategray 2px solid;
      display: flex;
      border-radius: 10px;
    }
    .selected{
      background-color:lightskyblue ;
    }
    .community_card_container .community_card_avatar_container {
      height: 76px;
      width: 76px;
      display: inline;
      text-align: center;
    }
    
    .community_card_container .community_card_base_info_container {
      width: 136px;
      border-left:rgb(177, 171, 204) 2px solid;
    }
    .community_card_container .community_card_avatar_container img {
      width: auto;
      height: auto;
      max-width: 100%;
      max-height: 100%;
    }
    .community_card_container .community_card_base_info_container div {
      height: 38px;
      width: 136px;
      line-height: 38px;
      text-align:center;
      overflow:hidden;
    }
    </style>
    

    请教原因和怎么解决

    第 1 条附言  ·  2021-02-02 14:43:56 +08:00
    写死的数据无法复现,Deprecated
    第 2 条附言  ·  2021-02-02 17:19:09 +08:00

    问题解决了(我也不知道对不对算不算解决,只是样式跟着变了)

    使用axios 在变更数据后 使用this.$forceupdate() 重新渲染视图

    还存在的问题

    子组件更新了selected的数据 父组件实际上并没有更新 而name 这些是同步更新的

    可以猜测到问题和留下的疑问

    1. 可以确定的是 selected的 property 的setter 没被代理
    2. 父组件传入子组件props的数据 到底是一个 指针 还是 类似于 Go的 value copy?从name 的行为 表现来看 是个 指针 但是 seleced 表现来看 又像是个 copy。。。我倾向是个指针 如果是copy的话 那么多的数据副本 数据同步就太麻烦了 。。只是selected 这个行为 搞的我也不太肯定了
    28 条回复    2021-02-03 06:40:55 +08:00
    bootvue
        1
    bootvue  
       2021-02-02 14:27:59 +08:00
    props:['community'] 这种写法 子组件 仅仅会把 props 数据 copy 一份为本地变量使用

    如果希望观察到 props 的变化 子组件应该用 computed 官方文档说的很清楚

    https://cn.vuejs.org/v2/guide/components-props.html#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
    shintendo
        2
    shintendo  
       2021-02-02 14:30:13 +08:00
    一个建议:提问时为了方便他人阅读,尽可能先整理代码,至少不要有缺少逗号这种问题,最重要的是删除所有与问题无关的干扰代码,当你最后得到一个能复现问题的最短代码时,大概率你自己就发现问题的所在了。
    ZZ222ZZ
        3
    ZZ222ZZ  
       2021-02-02 14:32:40 +08:00
    好像这个需要用到深度监听,如果只是 community 属性的值发生变化,props 是不会更新的
    Lemeng
        4
    Lemeng  
       2021-02-02 14:33:53 +08:00
    代码有点多,把不要的去掉,尽量精简,一眼就知道问题所在
    chenluo0429
        5
    chenluo0429  
       2021-02-02 14:39:45 +08:00
    凭感觉口胡一下。
    子组件响应式的监听只到 community 这一层,对于其属性的变更不会响应。解决方案是将 community 的属性分别在 props 里面传给子组件,或者子组件添加若干 computed 计算属性来监听具体属性的变化。
    jadeborner
        6
    jadeborner  
       2021-02-02 14:46:13 +08:00
    用 this.$set
    hengshenyu
        7
    hengshenyu  
       2021-02-02 14:49:18 +08:00
    我本地运行了下,没有问题。className 可以响应变化。建议看看缓存?或者 vue 版本升级?
    hengshenyu
        8
    hengshenyu  
       2021-02-02 14:49:35 +08:00
    就是少了些逗号
    ragnaroks
        9
    ragnaroks  
       2021-02-02 14:50:39 +08:00
    复制你的代码,使用 vue-cli 运行,没有任何问题,点击后就切换为蓝色底色了
    wunonglin
        10
    wunonglin  
       2021-02-02 14:53:11 +08:00
    同意#5 的说话。

    其次我是建议你更改子组件的状态的话就用一个 bind: selected 即可,不要包装在对象里面,不然检测不到对象变化,除非你重新赋值 community 。

    可以试一下 watch,watch 有一个 deep 选项,或者试一下 computed 。
    Aruforce
        11
    Aruforce  
    OP
       2021-02-02 14:54:06 +08:00
    @bootvue 不是,’拷贝‘这句事实错误,
    @ZZ222ZZ @chenluo0429 @jadeborner @bootvue 如当前代码这种写死数据的情况,是能够根据 selected 的变更 class value 的
    @shintendo @Lemeng 抱歉。。。
    Aruforce
        12
    Aruforce  
    OP
       2021-02-02 14:55:49 +08:00
    @ragnaroks 是的 但是 我的数据是从后台通过 axios 抓取的数据 这种情况下 不行 颜色无法改变
    wunonglin
        13
    wunonglin  
       2021-02-02 14:57:48 +08:00
    父组件改成
    ```
    <community_card
    v-for="(community,index) in communities"
    :selected="community.selected"
    :avatar="community.avatar"
    :name="community.name"
    :intro="community.intro"
    />
    ```

    子组件改成
    ```
    export default {
    name: "community_card",
    props:['selected', 'avatar', 'name', 'intro']
    };
    ```


    这样试试?
    JXS
        14
    JXS  
       2021-02-02 15:01:55 +08:00
    data 接收 prop,然后监听一下 prop,在监听里面去把数值改变一下看可以不
    Aruforce
        15
    Aruforce  
    OP
       2021-02-02 15:08:01 +08:00
    @wunonglin 并不行
    hengshenyu
        16
    hengshenyu  
       2021-02-02 15:13:43 +08:00
    @Aruforce 因为你是从 axios 获取的数据,所以我猜测你可能没有在 data 对它进行声明。Vue 实例生成时也就没有进行代理。
    Vue 需要使用的数据都需要预先声明,否则就需要使用 this.$set(),手动处理。
    Curtion
        17
    Curtion  
       2021-02-02 15:19:54 +08:00
    既然写死数据那就说明不是组件传值问题引起的,你应该从其他地方入手。 我说一个可能的原因:你的 communities 变量在实际业务上可能是异步获取的,并且其中并没有 selected 属性? 考虑使用 Vue.set
    Aruforce
        18
    Aruforce  
    OP
       2021-02-02 15:22:42 +08:00
    @hengshenyu 应该不是吧。。子组件的数据是我从父组件传递进去的。。所有的属性都有值。。peopertySetter 应该被代理了才对了
    ugu
        19
    ugu  
       2021-02-02 15:23:07 +08:00
    通过索引直接修改数据,vue 无法监听到,建议使用 splice 或者 set
    Aruforce
        20
    Aruforce  
    OP
       2021-02-02 15:30:02 +08:00
    @Curtion 在 axios complete 的返回里面 element.selected = true 做了这个操作了
    jrtzxh020
        21
    jrtzxh020  
       2021-02-02 15:33:23 +08:00
    不考虑性能的话。最简单的方法新建一个变量 a,将 communities 深拷贝赋值给 a,遍历 a 修改 selected 后。最后重新赋值 this.communities = a
    wunonglin
        22
    wunonglin  
       2021-02-02 15:34:53 +08:00 via iPhone
    @Aruforce 把 key 改成 name 试试,是不是 index 没变化
    jrtzxh020
        23
    jrtzxh020  
       2021-02-02 15:37:56 +08:00
    @jrtzxh020 或者在 onChosen 方法最后调用 this.communities = JSON.parse(JSON.stringify(this.communities)) 哈哈 不是很建议
    yor1g
        24
    yor1g  
       2021-02-02 16:22:00 +08:00
    更新 element 方法不对 子组件检查不到变更
    https://cn.vuejs.org/v2/api/#Vue-set
    Doracis
        25
    Doracis  
       2021-02-02 16:29:10 +08:00
    不看代码先凭经验说,加深度监听 deep immediate,要么父组件 this.$set
    RoshanWu
        26
    RoshanWu  
       2021-02-02 20:39:19 +08:00
    用个定时器模拟了下 LZ 说的「从后台通过 axios 抓取的数据 」,也没看出啥异常
    ```
    setTimeout(() => {
    this.communities[0].selected = false
    }, 3000);
    ```
    rodrick
        27
    rodrick  
       2021-02-02 22:21:57 +08:00
    总得来说 community.id 如果要传回去的话就最好 copy 一下再传,毕竟我总觉得把 prop 去$set 响应式有点怪,专一原则上来说 prop 就作为一个父子传参读取就好了
    zqx
        28
    zqx  
       2021-02-03 06:40:55 +08:00 via Android
    总的来说就是不要推测 vue 组件什么时候会重新渲染
    forceupdate 写多了会让代码不好维护,导致下一个程序员不容易从代码中追踪数据变化
    你明知道 vue 组件不会响应的数据变化(几种常见情况,官网有实例),就用 watch 手动监听它(开启 deep 选项)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2806 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 00:31 · PVG 08:31 · LAX 16:31 · JFK 19:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.