Vue 子组件双向绑定父组件数据对象内部多个属性,应该用什么方式实现? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ignor
V2EX    Vue.js

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

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

    假设父组件数据对象 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
    umaker
        31
    umaker  
       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     Solana     3490 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 04:40 PVG 12:40 LAX 21:40 JFK 00:40
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86