V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ignor
V2EX  ›  Vue.js

Vue 子组件双向绑定父组件数据对象内部多个属性,应该用什么方式实现?

  •  
  •   ignor · 2022-10-29 11:09:10 +08:00 · 2741 次点击
    这是一个创建于 593 天前的主题,其中的信息可能已经有所发展或是发生改变。

    假设父组件数据对象 parentData 的结构为:

    {
      a: xxx,
      b: yyy,
    (后续会添加其他属性)
    }
    

    现在需要写一个可以编辑 parentData 的 component ,能像这样使用:

    <EditorComponent v-model:parent-data="parentData"/>
    

    在 EditorComponent 里,有两个 input 可以修改 a 、b 这两个属性。由于子组件不能直接修改父组件的数据,emit update 又只能对 parentData 做整块的赋值,所以目前的思路如下

    先在 EditorComponent 内部复制一份 dataCopy:

    data: {
      dataCopy = null
    },
    watch: {
      parentData: {
        deep: true,
        handler: function (val) {
          this.dataCopy = val
        }
      }
    }
    

    然后有两种处理方式:

    1.
    两个 input 绑定 dataCopy 的数据:

    <input v-model="dataCopy.a"/>
    <input v-model="dataCopy.b"/>
    

    这样的话,如果要更新 parentData ,得在 dataCopy 上加一个 watcher ,然后

    this.$emit('update:parentData', val)
    

    但这似乎是不可取的,会造成来回无限更新

    2.
    把 input 的 v-model 拆开,加两个方法 updateA 、updateB:

    <input :value="dataCopy.a" @input="updateA"/>
    <input :value="dataCopy.b" @input="updateB"/>
    

    然后在每个 update 里把 event 的值先赋给 dataCopy ,再

    this.$emit('update:parentData', this.dataCopy)
    

    这样属性一多就很麻烦很臃肿了

    所以实现这样一个 EditorComponent 的规范思路应该是怎样的?(只看了基础教学就上手做了,不知道是否漏掉了什么重要概念……)

    第 1 条附言  ·  2022-10-29 21:21:48 +08:00
    结帖了,方案 1 的做法其实可行,是因为在 watcher 里加了永远赋新值的语句,导致的死循环。结论就是,这种地方写赋值语句要像递归调用那样注意一下死循环的可能
    37 条回复    2022-10-30 09:37:15 +08:00
    renmu
        1
    renmu  
       2022-10-29 11:17:39 +08:00 via Android
    邪道:直接传 object ,然后子组件直接修改 object ,副作用也会使父组件的 object 变化。
    如果你觉得过于邪道,还能子组件直接 watch object ,触发 emit ,这样就是 emit 了。

    再正常一点就在子组件 deepcopy 一个 object ,然后再 watch
    cydysm
        2
    cydysm  
       2022-10-29 11:57:54 +08:00 via iPhone
    对于你这个情况 其实你第二种可以使用 computed 了
    localValue:{get( return moduleValue),set(){emit()}}
    手机写的 看个大概吧
    cydysm
        3
    cydysm  
       2022-10-29 11:58:55 +08:00 via iPhone
    #2 纠正下
    localValue:{get(){return moduleValue}),set(){emit()}}
    ChefIsAwesome
        4
    ChefIsAwesome  
       2022-10-29 12:02:25 +08:00
    直接传子组件,让子组件改。
    什么事件走来走去,或者是子组件复制一份数据,都瞎搞。罗里吧嗦,组件多了还严重影响性能。你琢磨琢磨如果这个子组件跟父组件写一个文件里,是不是就是直接改这个 data 。如果搞个什么 vuex ,是不是也就是直接改。
    Carseason
        5
    Carseason  
       2022-10-29 12:55:47 +08:00
    provide/inject ?
    zqx
        6
    zqx  
       2022-10-29 13:06:10 +08:00 via Android
    只要不会复用,我永远不会新写一个组件。只要影响我晚下班的技术都不会用✔
    ignor
        7
    ignor  
    OP
       2022-10-29 13:07:39 +08:00 via Android
    @renmu copy object 再 watch ,第 1 点里提到了,copy object 会随原 object 更新,而 copy object 每次更新又会让原 object 更新,就死循环了
    ignor
        8
    ignor  
    OP
       2022-10-29 13:10:01 +08:00 via Android
    @cydysm computed 的确可以避免 1 里面的死循环,但这个 set 似乎不能 deep watch ,没办法感知 a 和 b 的 input 变化吧
    ignor
        9
    ignor  
    OP
       2022-10-29 13:12:27 +08:00 via Android
    @ChefIsAwesome 传子组件是什么操作能细说一下吗?查了下没找到相关文档
    ignor
        10
    ignor  
    OP
       2022-10-29 13:14:00 +08:00 via Android
    @Carseason 这好像是类似于全局变量的东西了吧…个人感觉应该慎用?
    leadfast
        11
    leadfast  
       2022-10-29 13:14:56 +08:00
    是这个意思么?

    ```
    <script setup lang="ts">
    const props = defineProps({ parentData: { type: Object } });
    const emit = defineEmits(['update:parentData']);
    const myData = reactive({ ...props.parentData });
    const updateFun = () => emit('update:parentData', myData);
    </script>

    <template>
    <div>
    <div><input type="text" v-model="myData.title" /></div>
    <div><input type="text" v-model="myData.content" /></div>
    <div><button @click="updateFun">update</button></div>
    </div>
    </template>
    ```
    ignor
        12
    ignor  
    OP
       2022-10-29 13:16:27 +08:00 via Android
    @zqx 哈哈,业余项目,解决问题手段固然很多,但就是想抱着学习的态度看看有没有好的实践方法

    照理来说这也不是什么很复杂的需求,应该有比较标准的做法才对吧…
    ignor
        13
    ignor  
    OP
       2022-10-29 13:23:30 +08:00 via Android
    @leadfast 这个是手动更新了吧,我这边是想让 input 更新后自动把 parentData 更新
    leadfast
        14
    leadfast  
       2022-10-29 13:25:48 +08:00
    @ignor #13 把事件放在 input 的 change 上?
    leadfast
        15
    leadfast  
       2022-10-29 13:37:14 +08:00
    watch(myData, () => updateFun());
    ignor
        16
    ignor  
    OP
       2022-10-29 13:37:16 +08:00 via Android
    @leadfast 这里有个问题,就是不知道 change 调用 update 的时候,是在该 input 的 v-model 更新之前还是更新之后?如果是之前就赋不了新值了
    ignor
        17
    ignor  
    OP
       2022-10-29 13:38:42 +08:00 via Android
    @leadfast watch 就是第一点提到的,会死循环
    leadfast
        18
    leadfast  
       2022-10-29 13:47:23 +08:00
    没有死循环

    ```
    <script setup lang="ts">
    import EditorComponent from './components/EditorComponent.vue';
    const parentData = reactive({
    title: 'Hello World',
    content: 'This is a test',
    });

    const parentUpdate = (data: any) => {
    console.log("mydata", JSON.stringify(data));
    console.log("parentData-old", JSON.stringify(parentData));
    Object.assign(parentData, data);
    console.log("parentData-new", JSON.stringify(parentData));
    };
    </script>

    <template>
    <EditorComponent v-model:parent-data="parentData" @update:parent-data="parentUpdate" />
    </template>
    ```
    Manweill
        19
    Manweill  
       2022-10-29 14:00:08 +08:00
    ref 一把梭,属性传递我是怕了
    Manweill
        20
    Manweill  
       2022-10-29 14:00:45 +08:00
    vue 这个不伦不类的东西,react 刚转来写 vue 各种不习惯
    cuicuiv5
        21
    cuicuiv5  
       2022-10-29 14:06:54 +08:00
    用 vuex
    ignor
        22
    ignor  
    OP
       2022-10-29 14:30:09 +08:00 via Android
    @leadfast 你这里 myData=props.parent ,似乎不会对 parent 的变化做出响应,所以没有死循环,得用两个 watcher 才能实现双向的响应吧
    ignor
        23
    ignor  
    OP
       2022-10-29 14:33:12 +08:00 via Android
    @Manweill 第一次尝试响应式框架,为了好上手选了 Vue ,所以这是……踩了坑了? react 是怎么规避这种问题的呢?
    ignor
        24
    ignor  
    OP
       2022-10-29 14:35:50 +08:00 via Android
    @cuicuiv5 这是不是有些重了…
    ignor
        25
    ignor  
    OP
       2022-10-29 14:40:27 +08:00 via Android
    @leadfast 补充一下,是对父组件里 parentData 这个对象的改变没有响应
    ignor
        26
    ignor  
    OP
       2022-10-29 14:58:23 +08:00 via Android
    @ignor #22 刚才又试了下,原来是因为在 watcher 里对新值做了改动才导致死循环……没事了
    Manweill
        27
    Manweill  
       2022-10-29 15:15:01 +08:00
    @ignor react,,,没有这问题,react 基本是单向数据流模式
    Garwih
        28
    Garwih  
       2022-10-29 15:15:04 +08:00
    正确的方法是用 computed ,get 的时候 return parentData ,set 的时候 this.$emit('update:parentData', value)。
    并不需要 watch 。
    dog82
        29
    dog82  
       2022-10-29 15:56:31 +08:00
    我建议 vuex
    ShayneWang
        30
    ShayneWang  
       2022-10-29 16:01:16 +08:00
    chenzhq2
        31
    chenzhq2  
       2022-10-29 16:30:08 +08:00   ❤️ 1
    1. 使用 slot 实现
    2. 使用一个状态管理工具

    为了做到不直接修改对象类型的 prop ,搞一堆 emit 和 watch ,我觉得挺闹心的。
    vanillacloud
        32
    vanillacloud  
       2022-10-29 20:57:32 +08:00 via iPhone
    是否会觉得你的 model 在设计上就跟 Vue 的理念有所不同,导致做起来觉得各种麻烦?

    我觉得思考一下「 Vue 建议怎么做这样的事」比较好。就像刚才你问「 react 怎么处理这类问题」,别人会说「 react 一般单向,没这个问题」。

    有没有可能 Vue 其实也是这个答案?

    我不是 expert ,但一般做的时候都是 #28 @Garwih 那样的处理方式。这似乎也是 Vue document 的方法。有什么情况是不能这样解决的吗?

    当你觉得你的情况很复杂的时候,是时候回头想一想 data model / structure 的设计。
    ignor
        33
    ignor  
    OP
       2022-10-29 21:14:40 +08:00 via Android
    @vanillacloud #28 的问题我在#8 已经提到了,主楼的问题在#26 也解决了,主楼里方案 1 的做法其实可行,是我在 watcher 里加了赋值导致的死循环
    kevin1
        34
    kevin1  
       2022-10-29 21:57:35 +08:00
    子组件维护自己的 input ,change 事件触发的时候拿到 value ,$emit 通知父组件(传递要修改的 key 和 value )直接修改 parentData 对应的 key ?
    ignor
        35
    ignor  
    OP
       2022-10-29 23:11:12 +08:00 via Android
    @kevin1 我帖子里没提到,a b 这些属性内部的结构也是不确定的,例如 a 可能是由数组构成的一串 input ,你的这个办法似乎需要针对不同的结构写不同的 emit
    cjd6568358
        36
    cjd6568358  
       2022-10-30 01:34:34 +08:00
    v-bind.sync?
    encro
        37
    encro  
       2022-10-30 09:37:15 +08:00
    找到现实中一个类似的功能,然后看它怎么实现的,研究 2-3 个后,思考下他们的适用场景,选择合适的。

    这个有非常多类似案例,比如嵌套 select ,modal, richtext editor 。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1106 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 22:53 · PVG 06:53 · LAX 15:53 · JFK 18:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.