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

vue3 里面使用 jsx 解构属性,响应式丢失了,怎么处理

  •  1
     
  •   daiwenzh5 · 2022-12-31 21:54:17 +08:00 · 5545 次点击
    这是一个创建于 726 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如:

    const someprops = useAnyHooks();
    // someprops 值
    const getOne = computed(() => anyvalue);
    someprops = {
    	one: getOne.value,
    }
    // 因为存在多个属性,所以直接解构方便一点,但是 one 的响应式好像没了,anyvalue 不会触发更新
    return () => (
    	<component {...someprops} ></component>
    )
    // 这样是正常的
    return () => (
    	<component one={one.value} ></component>
    )
    

    这个属性 someprops 里面 one 属性,丢失了响应式,这个怎么处理优雅一点?

    78 条回复    2023-01-04 09:30:24 +08:00
    shakukansp
        1
    shakukansp  
       2022-12-31 22:10:08 +08:00
    ??你在说什么

    你这是把值赋给了 someprops.one ,又不是把包装对象赋值给 someprops
    lalalaqwer
        2
    lalalaqwer  
       2022-12-31 22:26:30 +08:00
    someprops.one = getOne.value
    suzic
        3
    suzic  
       2022-12-31 22:26:33 +08:00 via Android
    没试过,试试 torefs
    daiwenzh5
        4
    daiwenzh5  
    OP
       2022-12-31 22:32:08 +08:00
    @shakukansp #1 对啊,one 只是 someprops 的一个属性,所以才需要解构啊
    daiwenzh5
        5
    daiwenzh5  
    OP
       2022-12-31 22:33:14 +08:00
    @suzic #3 toRefs 之后所有的属性都是 ref ,解构后就不对了,实际上需要 ref.value 的值
    daiwenzh5
        6
    daiwenzh5  
    OP
       2022-12-31 22:36:47 +08:00
    @lalalaqwer #2
    ```ts
    // 额, 我上面是顺序写反了,只是例举它的值
    export const someprops = {
    one: getOne.value,
    }

    ```
    shakukansp
        7
    shakukansp  
       2022-12-31 23:16:26 +08:00
    @daiwenzh5 我在说你把值给 one ,getOne.value 是值,getOne 是包装对象
    这是 js 基础知识而不是响不响应式
    const a = { c: 2 }
    const b = { a: 1 }
    你现在是 b.a = a.c
    而不是 b.a = a
    引用传递和值传递能分清吗
    shakukansp
        8
    shakukansp  
       2022-12-31 23:22:01 +08:00
    再举一个比较日常的例子
    const a = 1
    const b = { c:2 }
    const fn = (param) => { if (typeof param === 'number') param + 1 else param.c + 1 }

    fn(a)
    fn(b)

    console.log(a)
    console.log(b)
    你能搞清楚最后打印出来的分别是多少,那就能明白你这代码里有什么问题
    suzic
        9
    suzic  
       2023-01-01 10:08:32 +08:00 via Android
    const comp = () => createVnode(xxx)
    setup 里 { comp() }
    大概是这样,突然想起来我之前好像也遇到过这种问题
    johnkiller
        10
    johnkiller  
       2023-01-01 14:02:55 +08:00
    在 render 函数里触发 getter 就行了。
    ```js
    return () => (
    const someprops = {
    one: getOne.value,
    }
    return <component {...someprops} ></component>
    )
    ```
    johnkiller
        11
    johnkiller  
       2023-01-01 14:05:59 +08:00
    再进一步就是封装一个 computed 。

    ```js
    const someprops = computed(() => ({one: xxx.value, two: xxx.value}})
    return () => (
    return <component {...someprops.value} ></component>
    )
    ```

    核心目的是让 value 的 getter 在返回的 render 函数里触发,Vue 才能正常收集依赖。你的写法是在 render 被调用之前就 get 了,当然也就失去了响应式。
    johnkiller
        12
    johnkiller  
       2023-01-01 14:06:55 +08:00
    #10 #11 的括号有些小问题,将就着看吧。
    johnkiller
        13
    johnkiller  
       2023-01-01 14:33:29 +08:00   ❤️ 1
    写了个线上完整的例子,希望能够帮到你:
    https://codesandbox.io/s/vue3-jsx-element-plus-forked-n9lvlk?file=/src/App.vue
    johnkiller
        14
    johnkiller  
       2023-01-01 14:43:13 +08:00
    一楼老兄说明白了 js 值类型,但没明白 vue 的响应式原理。
    daiwenzh5
        15
    daiwenzh5  
    OP
       2023-01-02 00:06:42 +08:00
    @johnkiller #11 非常感谢你的 demo ,看了你的例子,我发现可能是我描述的不够准确,我希望的是在组件中传递一个 value ,而不是一个 ref ,因此在解构后就丢失了响应式,如:
    https://codesandbox.io/s/vue3-jsx-demo-vcjpmo?file=/src/components/Demo1.vue
    正如 demo1 ,所有的行为是符合预期的,但是当 hooks 里面返回的 props 存在多个时,手动设置就太麻烦了,所以我希望的时如 demo2 、demo3 那样,在组件中直接解构 props ,但是在这种情况下,响应式就丢失了,我不知道怎么处理更好一点。

    具体的代码如下:
    ```vue
    <script>
    // demo1
    import { defineComponent, ref, computed } from "vue";

    function useCounter() {
    const number = ref(0);
    const count = computed(() => number.value + 1);
    return {
    // count 是一个 ref
    count,
    number,
    };
    }
    export default defineComponent({
    setup() {
    const { count, number } = useCounter();

    return () => {
    // 传递 count 为 value
    return <Display count={count.value} number={number} />;
    };
    },
    });

    function Display(props) {
    return (
    <div>
    <button onClick={() => props.number.value++}>inc</button>
    <div>{props.count}</div>
    </div>
    );
    }
    </script>

    ```vue
    <script>
    // demo2
    import { defineComponent, ref, computed } from "vue";

    function useCounter() {
    const number = ref(0);
    const count = computed(() => number.value + 1);
    return {
    // count 是一个 ref
    count,
    number,
    };
    }
    export default defineComponent({
    setup() {
    const counter = useCounter();

    return () => {
    // 解构 count 为 ref
    return <Display {...counter} />;
    };
    },
    });

    function Display(props) {
    return (
    <div>
    <button onClick={() => props.number.value++}>inc</button>
    <div>{props.count}</div>
    </div>
    );
    }
    </script>
    ```
    ```vue
    <script>
    // demo3
    import { defineComponent, ref, computed } from "vue";

    function useCounter() {
    const number = ref(0);
    const count = computed(() => number.value + 1);
    return {
    // count 是一个 value
    count: count.value,
    number,
    };
    }
    export default defineComponent({
    setup() {
    const counter = useCounter();

    return () => {
    // 解构 count 是一个 value
    return <Display {...counter} />;
    };
    },
    });

    function Display(props) {
    return (
    <div>
    <button onClick={() => props.number.value++}>inc</button>
    <div>{props.count}</div>
    </div>
    );
    }
    </script>
    daiwenzh5
        16
    daiwenzh5  
    OP
       2023-01-02 00:15:12 +08:00
    @daiwenzh5 #15 感谢每一个愿意给出答复的人,我应该直接上 demo 的,谢罪!
    xiaojie668329
        17
    xiaojie668329  
       2023-01-02 11:46:05 +08:00 via iPhone
    直接上一个 codesandbox 呀。
    johnkiller
        18
    johnkiller  
       2023-01-02 14:16:02 +08:00   ❤️ 3
    daiwenzh5
        19
    daiwenzh5  
    OP
       2023-01-02 15:26:06 +08:00 via Android
    @xiaojie668329 嗯嗯,前几楼上了。
    前端是野路子出生,没习惯这种在线 demo ,上面有个老哥给了我提醒,前端这种确实方便。
    daiwenzh5
        20
    daiwenzh5  
    OP
       2023-01-02 15:33:29 +08:00 via Android
    @johnkiller 看你的例子应该是解决了我的问题的,现在在车上,等下试试。
    vue3 刚开始用,看别人的教程一直用 ref ,说是可以通过编译器提示响应式变量,没怎么用过 reactive 。
    shakukansp
        21
    shakukansp  
       2023-01-02 21:57:05 +08:00
    @johnkiller 主要问题还是引用链断裂而不是没有在 render 里面触发 getter

    原段代码就算把 someprops 改成 reactive ,依然无法通过修改 getOne.value 的值来触发视图更新,就算 getOne.value 里面的值是个对象也一样,具体会表现为修改 getOne.value 中对象的子属性可以触发更新,但是给 getOne.value 赋值一个不同引用的对象就会没法更新视图。

    reactive(someprops)
    someprops.one = getOne.value 和 someprops.one = getOne ,是两码事,后者 vue 会在解包 ref 时额外进行数据代理的链接
    shakukansp
        22
    shakukansp  
       2023-01-02 22:03:55 +08:00
    OP 不用很迷惑

    网上的文章说的 vue3 中失去响应式的陷阱

    无一例外都不是陷阱,是符合 JS 基础特性的预期内行为

    只要你理解好引用传递,面对这个现象就不会感觉奇怪
    johnkiller
        23
    johnkiller  
       2023-01-02 23:02:02 +08:00
    @shakukansp 你所阐述的引用逻辑没问题,但还是只说对了 js 特性,没说 Vue 特性,没解决问题。

    OP 这里提的问题的根本原因还是 Vue 依赖收集。要解决 OP 的问题,就是需要在 render 里触发对代理变量的 getter ,不然你没有任何解法。

    “主要问题还是引用链断裂而不是没有在 render 里面触发 getter”,对于这句话,可以上最简单的例子:
    https://codesandbox.io/s/vue3-jsx-demo-forked-biscpk?file=/src/components/Demo1.vue

    引用链断裂不是导致失去响应式的原因,在以上的例子里,我创建了一个全新的和 count 毫不相干的对象,依然能够保持响应式。

    我给 Vue3 的 @vue/reactivity 模块贡献过 PR ,还是能唠几句的。
    shakukansp
        24
    shakukansp  
       2023-01-02 23:10:11 +08:00
    @johnkiller 你在说啥,大哥,你这是在 return 的函数里面声明的对象,本身这个函数就是对值的引用了,
    你在 return 函数外面声明试试?
    shakukansp
        25
    shakukansp  
       2023-01-02 23:11:15 +08:00
    @johnkiller 你发的这个例子和楼主的原代码根本就是两码事了

    本质就是引用问题
    agileago
        26
    agileago  
       2023-01-02 23:12:44 +08:00 via iPhone
    忘记什么 ref,reactive ,来用 vue3-oop 体验自然的编程
    shakukansp
        27
    shakukansp  
       2023-01-02 23:13:53 +08:00
    你这个代码能生效是因为在函数里面的 count.value 是引用传递,而不是值传递啊

    传 () => string 能和直接传 string 一样吗
    johnkiller
        28
    johnkiller  
       2023-01-02 23:19:16 +08:00
    @shakukansp 请看 OP 的需求,是解决问题。他的源代码在 #15

    我在他的原代码里,创建了一个全新、互不相干的引用对象,不是同样解决了 OP 的问题?
    你不需要纠结引用问题,你说的都对。但这里讨论的是 Vue 特性,以及解决 OP 的问题:如何优雅的解构传值。
    johnkiller
        29
    johnkiller  
       2023-01-02 23:20:17 +08:00
    @daiwenzh5 #27 那你说里面的 count 和 doubleCount 和原先 ref 的还有什么联系?
    johnkiller
        30
    johnkiller  
       2023-01-02 23:24:57 +08:00
    @shakukansp 我用 inc 函数改了原先的 count = ref(0),为什么我新创建的 count: count.value 还能拿到新值,不是一个完全不相干的 “基础值” 吗?

    所以本质还是 render 函数作为原先 count = ref(0) 的依赖,发生变更 => render 重新被执行 => 才拿到的新值 => 返回 vdom => diff => 渲染真实 dom 。
    johnkiller
        31
    johnkiller  
       2023-01-02 23:30:15 +08:00
    @shakukansp #27 强行强调你的“引用”毫无意义,建议多看看 Vue 源码再来。
    shakukansp
        32
    shakukansp  
       2023-01-02 23:32:12 +08:00
    @johnkiller 我的意思是解决这个问题并不需要懂 vue 的响应式原理,丢失响应式就是个引用链断裂的问题

    你把变量声明写在函数里,也是建立了对 js 对象值的引用,这和 vue 并没有什么关系

    要让它按楼主想的一样运行实际操作就是像官方文档一样照本宣科用 reactive 包一下 ref ,这样 vue 自然有内部操作去数据代理

    const a = reacvtive({ c:1 })
    const b = ref(3)
    a.c = b.value
    a.c = b
    console.log(a)得到的结果都是 a 是 { c:3 } 用脚想都知道直接传包装对象 ref 的时候 vue 会有特殊的操作
    不需要懂什么响应式原理,要的就是一句官方文档的说明

    OP 写过 golang 就能对 js 这个基本特性有更深的理解
    shakukansp
        33
    shakukansp  
       2023-01-02 23:33:57 +08:00
    @johnkiller 回你 30 楼 我都说了,你写在函数里面当然没问题,是引用传递,你写函数外面还能这样?你再想想?还要我多看看源码,自己多提升一下 js 基础吧
    shakukansp
        34
    shakukansp  
       2023-01-02 23:39:33 +08:00
    写函数外面然后在函数里面{...props}
    是不是也调用 props 的 getter 了?那为什么写函数外面运行 inc 触发不了视图更新?不就是引用没了吗
    johnkiller
        35
    johnkiller  
       2023-01-02 23:44:20 +08:00
    @shakukansp 把变量声明写在函数里,就是为了正常收集依赖。函数内创建的都是全新的独立的对象,除了 inc 函数其它都是独立的值,没有任何引用。

    “你把变量声明写在函数里,也是建立了对 js 对象值的引用”,暴露了你的水平。
    至于你说 props 的 getter ,我指的是原先 ref 的 getter ,而不是新创建的对象的 getter ,况且它也没有 getter 。

    真回去好好补下基础吧,你的无效阐述解决不了任何问题。
    johnkiller
        36
    johnkiller  
       2023-01-02 23:45:16 +08:00
    @shakukansp 仅存的一点“大概,可能,应该是,绝对是这样”,在你的脑子里支撑着你继续说下去。
    shakukansp
        37
    shakukansp  
       2023-01-02 23:47:26 +08:00
    @johnkiller emmm 只能说得亏是 setup 里面 return 是个函数你才能得瑟,那你倒是讲讲 vue 为什么要你 return 一个函数啊
    shakukansp
        38
    shakukansp  
       2023-01-02 23:53:47 +08:00
    let a = 1
    const fn = () => { console.log(a) }
    fn()
    a = 2
    fn()

    为啥两次打印的结果不一样啊
    楼主你不要听楼上那个 pr 过 vue/reactive 的
    johnkiller
        39
    johnkiller  
       2023-01-02 23:59:09 +08:00
    @shakukansp 因为 setup 用来初始化,创建内部的变量,render 函数产生 vdom 。试想若不返回函数,把 setup 直接当 render 函数,返回 jsx ,vdom 。那每次重新渲染 => 调用 setup => 里面的变量(ref)被重新创建,那就没法保持状态。

    React 它那套 hook 方案就解决了这个问题,所以可以在函数里声明状态,并立即返回 jsx ,而不是函数。

    @shakukansp 至于#38 ,那么把你的 fn 当作 render 函数,怎么让 render 函数在 a 被改变的时候自动再次调用,保证响应式不丢失?是不是又回到了依赖收集问题?怎么收集?还是回到了原点:要在函数内触发 getter 。你别无他法。

    强行解释毫无意义,全靠你那点 JS 第六感撑着。
    johnkiller
        40
    johnkiller  
       2023-01-03 00:04:36 +08:00
    @shakukansp 兄弟,俺睡觉了。也真不用急眼,没说你那套引用逻辑有问题,我你所有的 js 逻辑都没问题,但并无法支撑本楼的主题,并没有说到 Vue 的点子上。
    shakukansp
        41
    shakukansp  
       2023-01-03 00:05:41 +08:00
    @johnkiller 为什么在函数里就能触发 getter 了?本质是什么,我真服了
    johnkiller
        42
    johnkiller  
       2023-01-03 00:12:55 +08:00
    @shakukansp 当 render 函数被调用的时候,才会访问 ref 的.value ,也就是触发 getter ,然后才收集到 render 函数作为依赖啊。要是在 setup 里就触发了 getter ,肯定收集不到 render 函数啊。

    实在不明白加我 tg 咱慢慢聊,跟你在这堆楼真是和大妈吵架一样。

    https://t.me/IIIlIIlllIIIlIIlIIIIIIlIlI
    johnkiller
        43
    johnkiller  
       2023-01-03 00:18:33 +08:00
    @shakukansp 你说传给 watchEffect 的函数为什么必须要马上被调用?为什么不能在变量被修改后再调用?

    为什么用 watch 传两个函数就可以解决?它的第一个函数会被立即调用吗?第二个函数呢?

    你能整的明白吗?
    shakukansp
        44
    shakukansp  
       2023-01-03 00:20:42 +08:00
    @johnkiller 你这又是把 setup 当渲染函数,又把东西放函数里了,要像 react 一样 setup 运行多次才能通过 getter 读取到新值。

    我就问你,假如 setup 返回一个对象而不是渲染函数 那你这 { count: count.value, inc: () => count.value++ } 还能行吗
    shakukansp
        45
    shakukansp  
       2023-01-03 00:25:07 +08:00
    @johnkiller 嘿,回你 43 楼,那你讲讲,const a = ref(1), 为什么 watch 要传 () => a.value 或者 a, 而不能传 a.value?
    johnkiller
        46
    johnkiller  
       2023-01-03 00:25:23 +08:00
    @shakukansp 当然不行,如果行楼主就不会在这里了,你再好好消化一下吧。你除了把楼主的问题再抛一遍,能解决什么问题?无知。
    johnkiller
        47
    johnkiller  
       2023-01-03 00:26:53 +08:00
    @shakukansp #45 ,因为在 watch 调用前 a.value 就访问了 getter ,仅仅传入了一个数字,watch 没法正常收集依赖啊。是不是又回到了这个问题,放到函数内不就可以了?
    shakukansp
        48
    shakukansp  
       2023-01-03 00:29:16 +08:00
    @shakukansp 接 45 楼,这才是为什么 setup 要返回一个函数才能让里面的 count.value 能被触发 getter

    watchEffect 也是传了函数才能收集到依赖
    shakukansp
        49
    shakukansp  
       2023-01-03 00:35:45 +08:00
    @johnkiller 我就是在和楼主讲这个问题,你再看看主楼代码,我回楼主的时候他还没回后面的代码呢。

    仅仅传入一个数字,也是我说的值传递,这也是 vue 为什么要用 ref 包装一下基础类型的原因
    和 setup 要返回一个函数和 options api 里面 data 要返回一个函数是一样的原因

    你只说解决方法而不说下为什么像主楼这么写,改 getOne.value 没法触发视图更新吗
    shakukansp
        50
    shakukansp  
       2023-01-03 00:37:22 +08:00
    我 21 楼也写的很清楚了,什么情况下 vue 会有什么表现我特么都写出来了

    怎么就没想解决楼主问题了,嗯?
    johnkiller
        51
    johnkiller  
       2023-01-03 00:39:59 +08:00
    @shakukansp 最后还是回到了最初 #10 #11 所说的依赖问题,毫无意义的交流。

    vue 内部完全可以通过类似 watchEffect(() => { instance.setup() }) 直接收集到 setup 作为依赖,和你所说的“返回函数”逻辑毫无关联,而是 #42 。

    早点睡吧,减少无意义的倔强。
    我一直同意你的 js 逻辑,但没说清楚 Vue ,你到底急在哪呢?
    johnkiller
        52
    johnkiller  
       2023-01-03 00:41:20 +08:00
    @shakukansp “你只说解决方法而不说下为什么像主楼这么写”

    我都说了多少遍,是让 ref 正常收集依赖。你上下文丢失这么快吗?
    johnkiller
        53
    johnkiller  
       2023-01-03 00:43:00 +08:00
    明天楼主上线估计都 tm 要傻眼了,就跟你在这无意义交流。
    shakukansp
        54
    shakukansp  
       2023-01-03 00:51:19 +08:00
    @johnkiller 当然急啊,怕你误导楼主,为什么主楼这代码会有这种问题我还是得告诉楼主啊,你再看看我 21 楼回了什么

    “原段代码就算把 someprops 改成 reactive ,依然无法通过修改 getOne.value 的值来触发视图更新,就算 getOne.value 里面的值是个对象也一样,具体会表现为修改 getOne.value 中对象的子属性可以触发更新,但是给 getOne.value 赋值一个不同引用的对象就会没法更新视图。”

    此处原代码指主楼代码

    重点在这里 “就算 getOne.value 里面的值是个对象也一样,具体会表现为修改 getOne.value 中对象的子属性可以触发更新,但是给 getOne.value 赋值一个不同引用的对象就会没法更新视图”

    我就说死了,这就是引用链断了,引用问题引用问题引用问题,vue 虽然收集了依赖,但是 someprops 中的 one 已经和 getOne.value 引用的是不同的对象了
    shakukansp
        55
    shakukansp  
       2023-01-03 01:02:26 +08:00
    22 楼结论必没有错

    我也没倔强什么,因为这就是预期内行为,就算不懂 vue 响应式原理也能通过 js 基础特性得出会丢失响应式的结论
    johnkiller
        56
    johnkiller  
       2023-01-03 01:09:18 +08:00 via iPhone
    @shakukansp 你上面这些基础不用一直重复,我家的猫学两天 js 都能明白,我也说了一直同意你的 js 逻辑。只是你的回复并没有说到 vue 点子上,也没有解决任何问题。

    我给出了最简单的答案,以及为什么。
    重复一遍,没有否认你的 js ,不用急,就这么简单。
    shakukansp
        57
    shakukansp  
       2023-01-03 01:18:17 +08:00
    @johnkiller 哦,你 35 楼说我暴露水平什么意思,讲的有问题吗,我看你是后面反应过来才改口

    你家猫学两天能明白你还要这么多楼才能反应过来,讲话恶心人谁不会啊
    shakukansp
        58
    shakukansp  
       2023-01-03 01:20:46 +08:00
    抽象层面,get 就是对一个变量取值的行为,你说况且它也没有 getter ,你确实暴露水平
    johnkiller
        59
    johnkiller  
       2023-01-03 01:52:33 +08:00 via iPhone
    @shakukansp
    第一,35 确实暴露了你的水平,没有问题。
    第二,你我都明白,我们聊天上下文所指的 getter 是指 vue 赋予响应式变量的一个触发回调,普通对象没有被 vue 代理,所以没有。

    只能说拿着放大镜扣几个字眼,是你最后的倔强🤣
    shakukansp
        60
    shakukansp  
       2023-01-03 01:56:55 +08:00
    @johnkiller 呵呵,你的解决方法楼主遇到我说的情况咋办,不是又要迷惑一次

    单就这一点你已经跪了

    我楼里讲的话可没有一点犹豫

    你还要搬出诸如给什么项目提过什么 pr ,属实不行
    shakukansp
        61
    shakukansp  
       2023-01-03 02:00:02 +08:00
    我已经问你 把声明从函数里拿到外面 不能触发怎么说了,还在这嘴硬,你说我 35 楼暴露水平,恰恰是暴露了你的水平

    至于 2 ,我想法又被你代表了,你上下文我懂,但是你没懂我说的 getter 代表什么,这就是暴露水平
    johnkiller
        62
    johnkiller  
       2023-01-03 08:59:25 +08:00 via iPhone
    @shakukansp 我从最初的第一个回复就说了,不能放到函数外面。这就是核心的解决方案,函数内触发才能收集依赖。不管什么手段,触发的是原 ref 的 getter 。不要一直纠结在新创建的原生对象上,它就是没有 getter 。

    楼主说饿了,我建议让他吃饭。
    然后你就跳出来说:“有本事你不让他吃饭试试?”

    我在这里所说的 getter ,它就是 Vue 赋予的监听函数,不用神叨叨把我所说的 getter 赋予这里讨论的其它的意义,然后独自窃喜。非要强行全面扩展来解释,那可以出本书了。

    还是那句话,你只懂 js ,不懂 Vue 。也就看了几篇文章就窃喜的水平。
    johnkiller
        63
    johnkiller  
       2023-01-03 09:06:20 +08:00 via iPhone
    @shakukansp 你的逻辑链条也很容易断裂,上下文频繁丢失,思维也收集不到依赖。
    daolanfler
        64
    daolanfler  
       2023-01-03 09:23:44 +08:00   ❤️ 2
    johonkiller 说的在理,另一位没看明白在说啥。路过
    daiwenzh5
        65
    daiwenzh5  
    OP
       2023-01-03 10:06:37 +08:00
    #28 说的正是我的问题所在,使用 reactive 也确实解决了。我之前的代码也是可以运行的,但是太啰嗦了,reactive 使得响应式不用加 value ,直接解构简洁了不少,看起来舒服多了。
    那个解决问题就好了,能交流是好事,不要吵架🫰🫰🫰
    shakukansp
        66
    shakukansp  
       2023-01-03 10:33:56 +08:00
    不说了,楼主再遇到响应式丢失的问题自然会回来翻我在说什么

    看不懂我在讲什么的自己开个 sandbox 写写我 21 楼的情况吧

    写道函数里面只是治了标,看楼主回复也没懂丢失响应式的本。
    shakukansp
        67
    shakukansp  
       2023-01-03 14:31:48 +08:00
    https://codesandbox.io/s/vue3-jsx-demo-forked-g06sub?file=/src/components/Demo1.vue

    楼主可以先点 2 3 个按钮
    然后点第一个
    继续点 2 3 个按钮
    这是为什么会丢响应式
    官网只告诉你为什么不建议解构 reative 对象,没说为什么
    这是个初学者常犯的错误
    shakukansp
        68
    shakukansp  
       2023-01-03 14:32:27 +08:00
    @shakukansp 只告诉你不建议解构响应式对象,没说为什么
    shakukansp
        69
    shakukansp  
       2023-01-03 14:34:44 +08:00
    同样的使用 toRef 或者 toRefs 等 api 时也要注意这个问题,所以一般传参数的时候我建议用 computed 而不是 toRef 链接一个新的 ref
    AllenCai
        70
    AllenCai  
       2023-01-03 15:05:35 +08:00
    我觉得两个人说的都对,只是各自角度不同。
    还有 demo2 改成 toRefs 会不会就实现 OP 的效果了?只谈实现不谈原理。
    <script>
    import { defineComponent, ref, toRefs, computed, reactive } from "vue";

    function useCounter() {
    const number = ref(0);
    const count = computed(() => number.value + 1);
    return toRefs({
    // count 是一个 ref
    count,
    number,
    });
    }
    export default defineComponent({
    setup() {
    const counter = useCounter();

    return () => {
    // 解构 count 为 ref
    return <Display {...counter} />;
    };
    },
    });

    function Display(props) {
    return (
    <div>
    <button onClick={() => props.number.value++}>inc</button>
    <div>{props.count.value}</div>
    </div>
    );
    }
    </script>
    AllenCai
        71
    AllenCai  
       2023-01-03 15:28:04 +08:00
    我先打脸我自己,其实#70 这样写根本就不用 toRefs
    看了这么久,其实就是 proxy 的问题
    AllenCai
        72
    AllenCai  
       2023-01-03 15:31:23 +08:00
    shakukansp 他说的是解构会导致 proxy 对象被解开,触发不了 proxy 里的 getter ,是原因所在。
    johnkiller 他说的是将之前的 proxy 引用放在一个新作用域里,所以解构后还是在使用之前的 proxy 引用,实现 OP 的效果,是一种解决办法。
    tyx1703
        73
    tyx1703  
       2023-01-03 16:43:39 +08:00
    @shakukansp 确实,reactive 包装之后再解构,传到 Display 组件的值是一个新的 number 了,而不是 `useCounter` 中定义的 number.value
    liaoliaojun
        74
    liaoliaojun  
       2023-01-03 18:33:37 +08:00
    响应式对象不应该通过 props 传参,用 Provide / Inject 吧
    liaoliaojun
        76
    liaoliaojun  
       2023-01-03 18:53:47 +08:00
    @liaoliaojun 应该补充一句:可修改的响应式对象不应该通过 props ,只读的是可以的。
    shakukansp
        77
    shakukansp  
       2023-01-03 21:37:34 +08:00
    @AllenCai 解构赋值并不会丢失响应性

    const a = reactive({ b: { c:1 } })

    const { b } = a

    return { b }

    这个时候不论你改 b.c 还是 a.b.c, 渲染到页面上的值都是会同步变化的

    只有你主动去切断引用链响应性才会丢失

    比如 a. b = { c:2 }
    AllenCai
        78
    AllenCai  
       2023-01-04 09:30:24 +08:00
    @shakukansp 是的,因为 reactive 的 proxy 化时深层的,所以第一层解开会有第二层的 proxy 在,只有当值为基础类型时,解构赋值会把引用断开
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2640 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 15:32 · PVG 23:32 · LAX 07:32 · JFK 10:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.