Skip to content

AI 辅助声明

本文的所有示例由 Deepseek 完成.

有些组件的渲染消耗不少性能, 这其中包括规划中的直接渲染 DirectDraw Surface (.dds) 格式图像的组件.

Vue.js 提供了异步组件完成这一点, 异步组件只会在需要时才渲染, 然后将结果缓存以供之后的重渲染. 例:

JavaScript
Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 在此处定义组件
    resolve({
      template: `
        <div>
          此处内容异步加载
        </div>
      `
    });
  }, 1000);
});

作为工厂函数, 其将收到 resolve 回调, 这一回调函数只会在从服务器得到组件定义时被调用. 此外亦可调用 reject (reason) 表示加载失败.

工厂函数

定义: 工厂函数

返回非单一固定值, 每次调用都返回新实例的函数为工厂函数.

在 Vue.js 中主要用于确保每个组件实例拥有独立的状态副本.

以下是 Vue 2 中选项式 API 的例子:

js
// 非工厂函数: 所有组件实例共享同一 data 对象
Vue.component("my-component", {
    data: {
        count: 0,
        user: {
            name: "Alice",
        },
    },
});

// 工厂函数: 每个组件实例各自拥有独立对象
Vue.component("my-component", {
    data() {
        return {
            count: 0,
            user: {
                name: "Bell",
            },
        };
    },
});

为何使用工厂函数?

  • 组件可能被多次实例化;
  • 不使用工厂函数, 所有实例将持有同一 data 对象. (你知道吗, Rust 的所有权系统正为此而诞生.)
  • 单个实例修改数据将同时影响所有其他实例.

下面是其他示例:

js
// Vue 3 组合式 API
// 组合式函数(Composables)本身就是工厂函数
import { ref, reactive } from 'vue'

function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const double = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }
  
  return { count, double, increment }
}

// 每个组件调用时获得独立状态
export default {
  setup() {
    const counter1 = useCounter(10)  // 独立的 ref
    const counter2 = useCounter(20)  // 独立的 ref
    return { counter1, counter2 }
  }
}
js
// provide/inject 中的工厂函数
// 父组件
export default {
  provide() {
    return {
      // 使用工厂函数提供动态值
      theme: computed(() => this.theme),
      // 每次注入都获得新实例
      createLogger: () => new Logger(this.userId)
    }
  }
}

// 子组件
export default {
  inject: ['createLogger'],
  created() {
    // 每个组件获得独立的 logger 实例
    this.logger = this.createLogger()
  }
}
js
// Vue Router 的路由组件传参
const router = createRouter({
  routes: [
    {
      path: '/user/:id',
      component: UserView,
      props: route => ({  // 工厂函数,每次路由变化重新计算
        userId: route.params.id,
        timestamp: Date.now()
      })
    }
  ]
})

核心理念

场景非工厂函数工厂函数
组件复用所有实例共享状态实例隔离, 互不干扰
服务端渲染请求间互相污染每个请求状态独立
测试测试用例互相影响每个测试创建新实例
组合式函数无法独立使用随时调用, 状态独立

注意

  1. Vue 2 根实例 data 允许为对象, 这是唯一例外;
  2. 需要访问 this 时使用普通函数而非箭头函数;
  3. 性能问题: 工厂函数返回的对象不应包含重复创建的大对象.