Vue 3:计算属性不跟踪其在组合 API 中的依赖关系

Posted

技术标签:

【中文标题】Vue 3:计算属性不跟踪其在组合 API 中的依赖关系【英文标题】:Vue 3: computed property doesn't track its dependency in composition API 【发布时间】:2021-01-13 23:15:22 【问题描述】:

考虑这个说明性示例:

const App = 
 setup() 
  const name = Vue.ref("");
  let firstTime = true;
  const message = Vue.computed(() => 
    if (firstTime) 
      firstTime = false;
      return "Welcome stranger";
    
    return `Hello $name.value`;
  );
  
  return 
    name,
    message
  
 
;

Vue.createApp(App).mount("#root");
<script src="https://unpkg.com/vue@next"></script>
<div id="root">
  name: <input v-model="name"/> <br/>
  message:  message 
</div>

如您所见,message 存储了一个计算 值,该值应该跟踪对name 的更新,但事实并非如此。 为什么会这样以及如何解决?

【问题讨论】:

【参考方案1】:

在这个特殊的例子中,当初始渲染时预期不同的结果时,我们可以使用watch 而不是computed,因为watch 是惰性的并且不会在初始渲染时执行(仅当它的依赖项:name更改),而这正是这里所需要的。

const App = 
  setup() 
    const name = Vue.ref("");

    Vue.watch(name, () => state.message = `Hello $name.value`);

    const state = 
      name,
      message: "Welcome stranger"
    ;
    return state;
  
;

Vue.createApp(App).mount("#root");
<script src="https://unpkg.com/vue@next"></script>
<div id="root">
  name: <input v-model="name" /> <br/> message:  message 
</div>

【讨论】:

【参考方案2】:

这是因为如果你这样写,Vue 无法发现你的计算属性message 和引用name 之间的依赖关系。问题在于firstTime 变量。

发生的事情是 Vue 在 运行时(而不是编译时)通过 运行 计算属性并 观察 什么反应来发现依赖关系在此过程中访问引用:

您的计算属性需要有机会至少运行一次。 Vue 通过在注册后立即运行来确保它。 在执行计算属性期间,需要访问响应式引用。这对您来说是错误的,因为您的计算属性第一次运行时不会访问name.value。而且因为在第一次运行期间根本没有访问任何响应式引用,所以您的计算属性将永远不会被再次触发。

如果您第一次不访问 name.value 也可以,但是您需要访问其他一些反应性的东西,并且会在 firstTime 变为 false 时发生变化:

const App = 
 setup() 
  const name = Vue.ref("");
  const firstTime = Vue.ref(true);

  Vue.watch(()=> name.value, ()=>firstTime.value=false;);

  const message = Vue.computed(() => 
   
    if (firstTime.value) 
      return "Welcome stranger";
    
    return `Hello $name.value`;
  );
  
  return 
    name,
    message
  
 
;

Vue.createApp(App).mount("#root");
<script src="https://unpkg.com/vue@next"></script>
<div id="root">
  name: <input v-model="name"/> <br/>
  message:  message 
</div>

【讨论】:

很好的解释,但在这里添加一个观察者有点过头了(至少你应该在 firstTime.value = false 之后取消观察它,因为它不再需要了)。 @marzelin 对,这实际上只是为了说明机制。话虽如此,如果您观看了某些内容,但在第一次突变后该内容从未改变,那么您是否停止观看实际上并没有什么不同。 是的,但在这里你看到的是name,它会随着用户输入而改变,而不仅仅是一次。 @marzelin 啊,我明白了。以为手表是第一次。在这种情况下你是对的【参考方案3】:

name.value分配给计算属性开头的变量,然后在末尾返回它

const App = 
 setup() 
  const name = Vue.ref("");
  let firstTime =true
  const message = Vue.computed(() => 
  let _name=name.value
    if (firstTime) 
      firstTime= false;
      return "Welcome stranger";
    
    return `Hello $_name`;
  );
  
  return 
    name,
    message
  
 
;

Vue.createApp(App).mount("#root");
<script src="https://unpkg.com/vue@next"></script>
<div id="root">
  name: <input v-model="name" /> <br/> message:  message  <br/> name:name
</div>

【讨论】:

【参考方案4】:

Computed 应始终使用您要计算的不可变响应式 ref 对象。

因此,如果您在开始时声明正在使用的反应性对象,它将起作用。

const App = 
 setup() 
  const name = Vue.ref("");
  let firstTime = true;
  const message = Vue.computed(() => 
    name.value;
    if (firstTime) 
      firstTime = false;
      return "Welcome stranger";
    
    return `Hello $name.value`;
  );
  
  return 
    name,
    message
  
 
;

Vue.createApp(App).mount("#root");
<script src="https://unpkg.com/vue@next"></script>
<div id="root">
  name: <input v-model="name"/> <br/>
  message:  message 
</div>

【讨论】:

以上是关于Vue 3:计算属性不跟踪其在组合 API 中的依赖关系的主要内容,如果未能解决你的问题,请参考以下文章

Vue3 组合式 API 的基础 —— setup

Vue 3 组合 API,如何在 setup() 函数中获取上下文父属性?

Vue3.x computed函数----计算属性

多个组件中的 Vue 3 组合 API 重用

Vue 3 中的 this.$forceUpdate 等效项 - 组合 API?

从 Vue 3 组合 API 中的函数内部返回 Apollo useQuery 结果