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

Vuejs 里面在父组件通过$refs.childComp.xxx 改变子组件 state 或者调用方法 很常用么?

  •  
  •   lifesimple · 2022-07-22 13:46:42 +08:00 · 2530 次点击
    这是一个创建于 615 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如 dialog 弹框 之前 react 最常见的写法就是传个 visible prop 来空值显示隐藏,vue 也是这么写,后面看别人的代码里经常就 this.$refs.xxxDialog.visible = true 或者 this.$refs.xxxDialog.open() 直接处理子组件的状态,而不是通过传 prop 的方式,我感觉这样写确实更方便。

    这种处理方式是不是也比较常见?有没有什么弊端这么写的话

    35 条回复    2022-07-23 16:55:50 +08:00
    wednesdayco
        1
    wednesdayco  
       2022-07-22 14:05:00 +08:00
    这么写的话耦合就比较严重
    binhb
        2
    binhb  
       2022-07-22 14:06:53 +08:00
    虽然开了口子, 但是不建议直接操作 dom, 而且这种情况明显不是一定要操作 dom 的
    既然用了 vue, 应该要按着 vue 推荐的方式来写吧
    yaphets666
        3
    yaphets666  
       2022-07-22 14:09:06 +08:00
    为啥要传进 dialog 来控制 dialog 的显隐? 直接在 dialog 的模板上控制不就行了,当然,也可以传进去。ref 只是处理特殊情况的方法。
    lin07hui
        4
    lin07hui  
       2022-07-22 14:17:04 +08:00
    this.$refs.xxxDialog.visible = true 不推荐
    this.$refs.xxxDialog.open() 推荐
    调用组件方法是常见的,如:this.$refs.form.validate()
    最好先看组件文档,如果有方法可调用,一般都会有说明的
    lifesimple
        5
    lifesimple  
    OP
       2022-07-22 14:18:26 +08:00
    @binhb 所以这种操作并不是 vue 的最佳实践吧 但是这种操作组件内部的 state 比 prop 要方便点
    wu67
        6
    wu67  
       2022-07-22 14:33:40 +08:00   ❤️ 1
    调子组件的方法可以. 直接改状态-->打死.
    lifesimple
        7
    lifesimple  
    OP
       2022-07-22 14:38:49 +08:00
    @wu67 就比如页面中打开个弹框 A (没写在当前页面的)要么就是当前页面的 visible state 传入弹框组件中展示;还有就是直接在弹框 A 中写个 visible state 来空值,点击打开的时候通过$refs.dialog.visible 直接改变,或者含蓄点就是在弹框 A 中写个 openDialog() { this.visible = true } 然后$refs.dialog.openDialog() 但实际上没啥区别吧,你一般这种会采用哪种方式写呢
    wu67
        8
    wu67  
       2022-07-22 14:46:57 +08:00
    @lifesimple 你写这一段真的是看了头疼.
    首先弹窗必定与当前页面有关, 你能在当前页面打开与当前页面无关的弹窗(除非全局的错误提示之类的), 那叫 bug. 不然就是全局的状态抽屉之类的...这种自然有其包装层的数据和逻辑控制.

    其次, 打开弹窗, 你可以直接调里面的 openDialog(). 如果不想调, 就绑状态变量 A 到弹窗组件, 然后组件里面 wath 这个 prop A, 合适的时候更改变量, 真正的 dialog 例如 el-dialog 的显示由变量 B 控制, 而不是直接控制弹窗组件里面的变量.
    sjhhjx0122
        9
    sjhhjx0122  
       2022-07-22 14:49:33 +08:00
    既然觉得弹窗通过 true false 奇怪,其实不如封装个服务来调用弹窗,把 show 变量隐藏起来
    cyrbuzz
        10
    cyrbuzz  
       2022-07-22 14:50:37 +08:00
    这种方法我个人感觉是最后山穷水尽的方法,不应该首选。

    非 props 的东西除非明确在文档里注明,否则应该一律视为私有。

    私有内容的改动就不会对外部的调用负责,比如哪一天我去组件里把 visible 的名字改了就改了,不需要让调用者知道,调用者本来也不需要关心。

    Vue3 和 React 也是这个思路,通过`defineExpose`和`useImperativeHandle`只暴露出公开方法。
    Terry05
        11
    Terry05  
       2022-07-22 14:52:03 +08:00
    严重不推荐这么用,通常他们这么弄,基本原因只是不想在界面上去多定义一个 visiable 变量

    有这样使用习惯的,感觉是还停留在 jquery 的使用习惯上,还没切换到数据驱动的模式上来

    通过 ref 去访问组件内部的 method 不到一些特殊需求也是不推荐使用,绝大数情况下通过 props 和 emit 就可以很好的完成功能实现了,就像官方文档里经常提及的,如果这种经典的输入输出不能解决问题,通常情况不是设计出了问题,就是真的需求超级复杂,比如富文本之类

    vue2.x 的 option 定义的 methods 默认都可以直接访问到,到了 vue3.x 的 setup 模式下,只有被 expose 的内容才可以被外部访问,或许就并不能直接调用到所有资源了
    Mark85
        12
    Mark85  
       2022-07-22 14:54:14 +08:00
    弹窗与否本来不就是应该交给外部控制的,并同步显示、隐藏状态的吗?还把 visible 隐藏在组件内让人通过 refs 来改显示隐藏,怎么想的,看到`$refs.dialog.openDialog()`这种代码我就想骂

    ```
    // 弹窗组件
    props: {
    visible: {
    type: Boolean,
    default: false,
    },
    },
    computed: {
    _visible: {
    get() {
    return this.visible
    },
    set(val) {
    this.$emit('update:visible', val)
    },
    },
    },
    // 内部操作_visible
    ```

    外部使用

    ```
    <dialog :visible.sync="xxxVisible" />
    ```
    lifesimple
        13
    lifesimple  
    OP
       2022-07-22 14:56:57 +08:00
    @sjhhjx0122 emm 不是这个觉得奇怪,只是讨论对于一个子组件父子组件通信一般都是 props 方式把值传递给子组件,子组件做了什么操作通过$emit 在父组件中改变 prop 值。如果通过$refs.childComp 的方式,可以把这些 props 值全部设置成子组件的 state 值,操作时候可以调用子组件的一个比如 init() 方法 参数就是原本的 props 值,init(a,b,c,d) { this.a = a;this.b = b...} 把值传过去控制。

    @cyrbuzz 嗯 有道理 谢谢
    wunonglin
        14
    wunonglin  
       2022-07-22 15:02:58 +08:00
    vue 的设计模式决定了只能通过 this.$ref.xxx.open()或者 this.dialog = true 这样打开弹窗。


    @sjhhjx0122 #9

    我曾经在 vue 里实现了一个和 angular dialog 一样的东西,但是有个很严重的问题,无法将值传递给组件还有弹窗的返回值无法回到调用者的页面。


    https://stackblitz.com/edit/vue-ic6mz2?file=src/components/dialog.vue

    这个示例的设计逻辑参考 https://material.angular.io/components/dialog/api#MatDialog
    Moeyua
        15
    Moeyua  
       2022-07-22 15:09:46 +08:00 via iPhone
    Vue 3 子组件需要显式 expose 一些属性后才可以被父组件读取,这种方式应该更合理一些吧。
    sjhhjx0122
        16
    sjhhjx0122  
       2022-07-22 15:12:24 +08:00
    @wunonglin 可以啊,创建一个 promise 就 ok 了,我写过一个文章
    https://juejin.cn/post/7101144285763862565 ,如果喜欢 ng ,用 rxjs 也可以实现
    唯一的缺点是创建的弹窗是重新 new vue 没法使用项目里的上下文依赖
    sjhhjx0122
        17
    sjhhjx0122  
       2022-07-22 15:13:56 +08:00
    @lifesimple 那不如通过 provide ,inject 啊
    ipwx
        18
    ipwx  
       2022-07-22 15:17:23 +08:00
    this.$refs.XXX.open() 我觉得没啥毛病。

    this.$refs.XXX.visible = true 我觉得有大猫饼
    jrtzxh020
        19
    jrtzxh020  
       2022-07-22 15:20:40 +08:00
    当写了无数个弹窗后,发现用 this.$refs.xxxDialog.open() 真香 哈哈
    wunonglin
        20
    wunonglin  
       2022-07-22 15:31:14 +08:00
    @sjhhjx0122 #16

    vue3 还没看,弃坑了。专心 ng 了,我这里以 vue2 为例。


    @sjhhjx0122 #17

    provide ,inject 我记得会有严重问题。如果你是嵌套打开 dialog 的话,依赖注入就不能用了,嵌套的 dialog 不能获取它自己的 ref 。
    lifesimple
        21
    lifesimple  
    OP
       2022-07-22 15:31:35 +08:00
    @jrtzxh020 如果只是打开的话 直接 @click='$refs.xxxDialog.visible = true' 感觉这么直接控制其他组件的 state 写起来确实要比传 prop 顺手点
    sjhhjx0122
        22
    sjhhjx0122  
       2022-07-22 15:41:08 +08:00
    @wunonglin vue2 也可以的,用 Vue.extend 创建就好了,我也是主用 ng ,依赖注入我还没遇到你说的这个问题
    wangtian2020
        23
    wangtian2020  
       2022-07-22 15:56:54 +08:00
    我都是这么写的
    this.$refs['childDialogComponent'].showAddDialog(params)
    调用子组件方法,子组件执行自己的方法修改自身状态
    wunonglin
        24
    wunonglin  
       2022-07-22 16:03:54 +08:00
    @sjhhjx0122 #22

    看到了。简单看下 vue2 的文档,好像可以用 render 创建组件,然后在 context 把 ref 和 data 放进去,有时间我再试试。
    好难受。
    sechi
        25
    sechi  
       2022-07-22 16:47:37 +08:00
    大部分时候都是用 this.$refs.xxxDialog.open(),反正大部分时候都是写 crud 业务,真的没有那么多条条框框,怎么舒服怎么来
    AlphaTr
        26
    AlphaTr  
       2022-07-22 18:03:22 +08:00
    简单一个调用 result = await this.$refs.dialog.open(params) 直接将给 Dialog 传参数并打开、获取弹窗的返回值都做了;比通过给组件传 props ,然后监听组件的事件这样子分布在各处的方式个人感觉更好些
    snarkprayer
        27
    snarkprayer  
       2022-07-22 18:36:50 +08:00
    ref 引用读数据调方法可以,写数据应该禁止
    $refs.ddialog.open()这种的可以加一层封装 点击 trigger slot 显示
    ```
    <Extend-Modal>
    <content-compoennt />
    <button slot="trigger">按钮</button>
    </Extend-Modal>
    ```
    ymcz852
        28
    ymcz852  
       2022-07-22 19:46:57 +08:00 via iPhone
    看那么多人支持 $refs 去调用子组件方法有点惊讶,虽然方便,但个人认为除了用 $refs 去调用第三方组件的方法的场景外,这种方法带来的可维护性肯定不佳的,子组件根本不知道有哪些父组件调用了它的方法,一旦是改动了这些方法就难搞了
    jdi
        29
    jdi  
       2022-07-22 20:46:34 +08:00
    官方是不推荐的,原来开了个口子,现在把口子缩小了点:
    > 使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西
    humbass
        30
    humbass  
       2022-07-22 21:11:02 +08:00   ❤️ 1
    如果是 Alert 、Loading... 这样的框,最好是用 extend 封装一层,然后用函数调用,这样就完全解耦了。

    vue2 的写法 : https://joyran.github.io/yi-blog/blog/alert.html (随便搜的)
    thtznet
        31
    thtznet  
       2022-07-22 21:48:27 +08:00   ❤️ 1
    两种不通的思想,面向对象的写法和数据驱动,Vue 嘛,既然得了 2 者的优势就会有带着 2 者的影子,所以感觉有点不伦不类。
    dengshen
        32
    dengshen  
       2022-07-23 10:07:07 +08:00 via iPhone
    sync 不好用吗?
    xsldebugger0030
        33
    xsldebugger0030  
       2022-07-23 16:13:57 +08:00
    @wunonglin vue 的设计模式决定了你有 n 种方式打开弹窗。不需要调内部方法或修改内部属性。直接把 dialog 的 visible 绑定到 prop 里,由父组件控制就好了。
    wunonglin
        34
    wunonglin  
       2022-07-23 16:17:57 +08:00
    @xsldebugger0030 #33 笑了
    xsldebugger0030
        35
    xsldebugger0030  
       2022-07-23 16:55:50 +08:00
    @wunonglin https://www.v2ex.com/help/assertive
    我们希望能够在 V2EX 建立和倡导一种好好说话的氛围。

    请尽量描述事实,而非观点。
    如果你要反驳什么,请反驳那个主要的要点,而不是一些旁枝末节。
    如果你要说的话是为了伤害别人,那么请不要说。如果你要说的话,你有预感在将来你会想要删掉它,那你最好现在就不要说。
    在一个公共空间的公共讨论中,我们应该关注的,是自己能够在这些讨论中提供什么样的建设性增益,而不是那些纯粹个人的感受。比如当大家在讨论一件你不了解的东西时,你没有必要去回复一条“不明觉厉”。
    回忆一下你看过的电影里的那些正面角色的说话方式——把一件事情好好陈述出来,没有冷笑,没有嘲讽,没有反问,就只是好好说话而已。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1505 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 17:18 · PVG 01:18 · LAX 10:18 · JFK 13:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.