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

Vue 3: computed property doesn't track its dependency in composition API

考虑这个说明性的例子:

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 的更新,但事实并非如此。
为什么会这样,如何解决?

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>

在计算 属性 的开头将 name.value 分配给一个变量,然后在末尾 return 它

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>

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

发生的事情是 Vue 在 运行 时间 (而不是编译时间)通过 运行ning 计算的 属性 和 观察 在此过程中 访问了 哪些反应性引用:

  • 您的计算 属性 需要至少 运行 一次 的机会。 Vue 通过在注册时立即 运行ning 来确保它。
  • 在您的计算 属性 执行期间,需要 访问 响应式引用。这对你来说是坏的,因为 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>

在这个特定的例子中,当初始渲染预期不同的结果时,我们可以使用 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>