【问题标题】:svelte: how can I declare two cyclically reactive variables?svelte:如何声明两个循环反应变量?
【发布时间】:2023-02-22 06:17:11
【问题描述】:

我有两个变量 ab 加起来为 100。如何设置反应式声明,以便当 a 更改 b 更改为 100 - a 时,反之亦然?当我尝试类似的东西时

let total = 100;
$: a = total - b;
$: b = total - a;

我收到“检测到循环依赖性”错误。有什么办法可以做到这一点?

【问题讨论】:

    标签: javascript svelte svelte-3 sveltekit


    【解决方案1】:

    问题来自这样一个事实,即 Svelte 希望按照它们相互依赖的顺序对反应块进行排序:它希望首先计算那些被依赖但没有依赖关系的块,以避免不必要的计算......或陷入困境环形。

    此外,Svelte 将反应式表达式的依赖性视为出现在其中的任何反应式变量。

    因此,错误的实际来源是变量 ab 出现在两个反应表达式中。

    解决方案是从反应式表达式中删除不需要的变量,即分配给的变量。这可以通过将赋值移动到反应块之外的函数来完成。不幸的是,它更冗长......

    <script>
        let total = 100;
        let a = 0
        let b = 0
        
        const setA = () => {
            // the assignment to a (or b in the other function) are still
            // reactive, of course, but Svelte won't propagate changes to
            // variable that are assigned their current value, so this
            // will break the loop
            a = total - b
        }
        
        const setB = () => {
            b = total - a
        }
        
        $: setA(total - b);
        $: setB(total - a);
    </script>
    
    <pre>
        a: {a}
        b: {b}
    </pre>
    
    <label>
        a <input type="number" bind:value={a} />
    </label>
    
    <label>
        b <input type="number" bind:value={b} />
    </label>
    

    编辑

    正如 @avf 在 cmets 中所说,上面的代码具有误导性,并且对于代码的读者来说不是那么明显。我当时是这样写的,因为我专注于演示 Svelte 反应性的原理。

    在现实世界中,我宁愿推荐以下两种形式中的任何一种。

    只要有可能,都倾向于显而易见:

    // this function is just normal
    const setA = (value) => {
      a = value
    }
    
    // this reactive expression makes it obvious and straightforward that 
    // its dependencies are total and b, and only those
    $: setA(total - b)
    

    当显而易见性不是一个选项时(例如,因为函数的内容比单纯的赋值有更多的副作用),我会依赖以下对 Svelte 来说非常惯用的形式。不幸的是,对于通常不熟悉 Svelte 的人来说,它也很晦涩……但我想这只是属于你必须学习才能真正精通该框架的那些东西。

    const recomputeEverythingOrWhatever = () => {
      ...
    }
    
    // in Svelte lingo, this is broadly understood as "whenever a, or b, or
    // total changes, then recompute everything (or whatever)"
    $: a, b, total, recomputeEverythingOrWhatever()
    

    这种语法在 IMO 中很好,因为它通常为 Svelte 开发人员所熟悉和很好理解。

    但是要小心不要在重新计算函数中重新实现依赖循环,例如通过将ab 更改为彼此的函数。这次代码可以正常编译,因为编译器不会面临必须对相互依赖的反应块进行排序的不可能完成的任务,并且在运行时 Svelte 甚至会打破无限更新循环。但是这样的代码的效果仍然很难理解和预测。

    【讨论】:

    • 我不知道,这感觉有点像滥用反应系统。我花了一段时间才明白这是如何工作的,因为 setAsetB 不接受任何参数,但是当你在反应式 $ 块中调用它们时,你正在提供参数。你把它们放在那里是因为你想让反应块触发,但实际上反应块内的方程是任意的。即使您将第一个 $: 更改为:$: setA(total + b);,这仍然可以正常工作。或者任何同时包含 total 和 b 的表达式。基本上,您使用 setAsetB 作为副作用。
    【解决方案2】:

    那行不通; Svelte 不允许这样,这个循环也无法解决。如果您希望变量可编辑,则不能将其声明为反应式 ($: x = ...)。您可以使用反应性语句/块反应性地设置常规变量,也可以改用事件(请参阅其他答案)。

    下面解释一下为什么这也无法从逻辑上解决。

    将其视为具有两个未知数的两个方程式,您会得到这种无用的简化:

    a = total - b
    b = total - a
    b = total - (total - b)
    b = total - total + b
    b = b
    

    您必须至少修复其中一个值,否则所有可能的值在这里都有效。


    您还可以先将方程归一化:

    a = total - b => a + b = total => a + b = total
    b = total - a => b + a = total => a + b = total
    

    如您所见,它们是相同的,所以您实际上有两个未知数和只有一个方程,所以这是未指定的。

    (请注意,即使这会产生有效的解决方案,Svelte 也无法为您求解线性方程组。)

    【讨论】:

      【解决方案3】:

      除非您需要从程序更改中更新值,否则我会完全放弃反应式语句并改为使用事件:当用户调整一个变量时,更新另一个。

      <script>
        const total = 100;
        let a = total;
        let b = 0;
      </script>
      
      a: {a} <br>
      b: {b}
      
      <label>
        a <input type="number" bind:value={a}
                 on:input={() => b = total - a} />
      </label>
      
      <label>
        b <input type="number" bind:value={b}
                 on:input={() => a = total - b} />
      </label>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-10-15
        • 2011-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-07
        • 2012-02-02
        相关资源
        最近更新 更多