当实际值没有改变时,防止计算与其他计算为依赖项重新计算

Posted

技术标签:

【中文标题】当实际值没有改变时,防止计算与其他计算为依赖项重新计算【英文标题】:Prevent computed with other computed as dependency from re-computing when actual value does not change 【发布时间】:2022-01-15 01:34:48 【问题描述】:

我正在尝试在使用多个相互依赖的计算属性方面实现一些非常棘手的事情。

我最初的问题如下: 我们有一个计算值 A 和一个计算值 B。B 具有 A 作为依赖项。现在,A 的依赖发生了变化,但是输出的值还是一样的,所以重新计算了,但是返回的值是一样的。

根据我的调查发现,即使A的输出值没有改变,如果重新计算A,那么B也将被重新计算。我想知道是否有办法实现这种行为

为了给你提供上下文,让我首先给你一个与我的真实用例足够接近的具体例子:

<script setup>
  import  ref, reactive, computed  from 'vue';

  const formState = reactive(
    field1: '',
    field2: 
      field2Child: 'MyChildVal',
    ,
  );

  const mapDependencies = (arrayDependencies) => 
    let dependencies = ;
    for (const  key, value  of arrayDependencies) dependencies[key] = value;
    return dependencies;
  ;

  const resolveFromString = (path, obj, separator = '.') => 
    var properties = Array.isArray(path) ? path : path.split(separator);
    return properties.reduce((prev, curr) => prev && prev[curr], obj);
  ;

  const listDependencies = ['field2.field2Child'];

  // Will re-render every time formState changes
  const dependencies = computed(() =>
    mapDependencies(
      listDependencies.map((key) => (
        key,
        value: resolveFromString(key, Object.assign(, formState)),
      ))
    )
  );
  
  const dependenciesAsString = (dependencies) => 
    return 'The dependencies are' + Object.entries(dependencies).map(([key, value]) => `$key : $value`);
  

  // Expected to re-render only when the output of "dependencies" changes. But on real usage
  const computedBasedOnDependencies = computed(() => dependenciesAsString(Object.assign(, dependencies.value)));

  const ChangeState = () => formState.field1 = new Date().toString(); 
</script>

我创建了这个 stackblitz 的现场复制品:https://stackblitz.com/edit/vue-svtsu2?file=src/App.vue

我期望的行为:如果“dependencies”的输出没有改变,那么“computedBasedOnDependencies”开头的时间戳不应该改变

我得到的行为:即使“依赖项”输出没有改变,但重新计算时 "computedBasedOnDependencies" 也被重新计算

如果您对我无法从 Vue 反应性或任何其他实现此行为的方式中正确理解的内容有任何见解,我欢迎任何帮助。

提前致谢!

【问题讨论】:

【参考方案1】:

即使“依赖项”输出没有改变...

问题是它正在改变 - 即使它返回的对象与以前的内容相同,它仍然是一个新对象

function getObject() 
  return  value: "A" 


const a = getObject()
const b = getObject()

console.log(a === b)

Vue 不进行“深度”比较,因为这会对性能造成重大影响(computed 返回的值可以是任何东西——例如深度嵌套的对象)。所以如果你需要这个,你必须自己实现它大概使用watch而不是computed

但在走这条路之前,你应该确定你所做的不仅仅是premature optimization

【讨论】:

【参考方案2】:

计算属性函数应该是纯函数,即它们不会产生副作用,函数输出仅取决于输入。

对于任何其他逻辑,应使用观察者。观察者就是这种情况,可以通过源函数和/或新旧值的比较来限制:

const computedBasedOnDependencies = ref(null);

watch(
  () => stringifyWithoutTimestamp(dependencies.value)),
  (val, oldVal) => 
    if (val !== oldVal)
      computedBasedOnDependencies.value = stringifyWithTimestamp(dependencies.value);
  ,
   immediate: true 
);

【讨论】:

以上是关于当实际值没有改变时,防止计算与其他计算为依赖项重新计算的主要内容,如果未能解决你的问题,请参考以下文章

useMemo, useCallback, useEffect 三者区别

React使用hooks-useMemo

如果两个依赖路径改变为可观察的,则Knockout在单个动作上计算多次触发

vue-computed计算属性

computed和watch的使用场景

Vue computed和watch的区别