Vue3技术面提升之灵魂拷问(不懂得还是看看吧)

webpack & vite 构建工具对比

1. 构建方式

  • webpack:采用打包的方式,首先将所有的模块和资源静态分析,生成一个或多个包含所有依赖的打包文件。这个过程在开发过程中可能较慢,因为每次代码修改后都需要重新打包。
  • Vite:使用原生的ES模块(ESM),支持热模块替换(HMR),在开发时不进行打包,而是通过服务器直接提供文件,速度更快。只有在生产环境中,Vite才会打包代码(使用rollup进行打包)。因此开发节点优势明显,vite使用的是ES Module,因此在代码中不可使用Commonjs。

2. 启动速度

  • webpack:首次构建较慢,尤其是在项目较大或者依赖较多的时候。增量构建时可能会有所改善,但整体来说启动速度一般。
  • Vite:由于不进行打包(不需要编译,不需要分析模块依赖),启动速度非常快,通常能够在几秒钟内启动开发服务器。(项目越复杂模块越多,vite优势越明显)

3. 热模块替换(HMR)

  • webpack:支持HMR,但在某些情况下(如全局状态变更)可能会导致页面闪烁。(需要把该模块的相关依赖全部编译一遍)
  • Vite:HMR机制非常高效,能够在大部分情况下保持应用状态,更新速度也很快。(改动一个模块仅需要让浏览器重新请求该模块即可)

4. 配置复杂度

  • webpack:配置相对复杂,需要许多插件和loader来处理不同类型的文件。对于新手来说,学习曲线较陡。
  • Vite:配置简单,提高了开发效率,内置了许多常用功能,适合快速开发和原型设计。

5. 插件生态

  • webpack:拥有丰富的插件生态,几乎可以处理任何构建需求,适合大型和复杂项目。
  • Vite:虽然插件生态相对较新,但正在快速发展,支持多种插件来扩展功能。

6. 性能

  • webpack:在生产构建时性能强大,能够进行代码分割、懒加载等优化。
  • Vite:在开发时性能优越,但在某些情况下,生产构建相较于webpack可能稍显不足(不过随着Vite不断更新,这一点在逐渐改善)。

7. 支持的框架 (兼容性)

  • webpack:兼容性好,支持React、Vue、Angular等多种框架。
  • Vite:同样支持现代框架(如Vue 3、React、Svelte等),并提供了以"框架"为中心的优化。

Vue 3 实现了重大的性能提升体现的具体方面

1. 静态提升(Static Tree Hoisting)

html 复制代码
<template>
  <div>
    <h1>Hello, World!</h1> <!-- 静态提升 -->
    <p>{{ message }}</p>
  </div>
</template>

<script>
// `<h1>Hello, World!</h1>` 是一个静态节点,Vue 3 会在编译时识别并提升它,避免在每次渲染时都重新创建
export default {
  data() {
    return {
      message: 'Welcome to Vue 3!'
    };
  }
}
</script>

静态提升是指在编译阶段识别出不变的部分,将其提升为静态节点,从而避免不必要的重渲染。

  • 静态节点:对于那些不会改变的节点,Vue 3 会在编译时将它们提取出来,只在首次渲染时创建,而后续更新时不需要再次处理这些节点。
  • 减少重渲染:通过静态提升,Vue 3 减少了对于静态内容的重复渲染,从而提高了性能,特别是在大型列表和复杂组件中。

2. 预字符串化(Pre-Rendering)

html 复制代码
<template>
  <p>{{ concatenatedMessage }}</p>
</template>

<script setup>
const hello = 'Hello';
const world = 'World';

const concatenatedMessage = `${hello}, ${world}!`; // 预字符串化
</script>

预字符串化是指将模板编译为字符串形式,并在运行时直接使用。

  • 编译优化:在运行时直接使用预编译的字符串,避免了复杂的解析过程。
  • 提高渲染速度:通过减少不必要的编译和解析步骤,提升了渲染速度。

3. 缓存事件处理函数(Cache Event Handler Functions)

html 复制代码
<template>
  <button @click="handleClick">Click me</button>
</template>

<script>
// 在 Vue 3 中,`handleClick` 函数只会创建一次,并被缓存,当组件更新时不会重新创建
export default {
  methods: {
    handleClick() {
      console.log('Button clicked!');
    }
  }
}
</script>

Vue 3 对事件处理函数进行了缓存优化。

  • 函数重用:在组件更新时,重复的事件处理函数不会被重新创建,而是复用先前创建的函数。
  • 提升性能:这样做减少了内存开销和函数创建的次数,提高了性能和效率。

4. 块树(Block Tree)

html 复制代码
<template>
  <div>
    <h1>{{ title }}</h1>
    <p v-for="item in items" :key="item.id">{{ item.text }}</p>
  </div>
</template>

<script>
// `<h1>` 和 `<p>` 组成了不同的块,Vue 3 可以单独处理它们的更新。
// 例如,如果 `title` 修改,只有 `<h1>` 会被重新渲染
export default {
  data() {
    return {
      title: 'Items',
      items: [
        { id: 1, text: 'Item 1' },
        { id: 2, text: 'Item 2' }
      ]
    };
  }
}
</script>

块树是指在虚拟 DOM 中引入的一种新数据结构,用于优化渲染。

  • 组件分块:将组件分为多个块,每个块独立处理自己的更新,提升了更新时的粒度。
  • 控制渲染:通过高效地管理不同块之间的逻辑关系,Vue 3 可以更精准地控制哪些部分需要重新渲染,从而提高整体性能。

5. 补丁标记(Patch Flags)

html 复制代码
<template>
  <div>
    <h1>{{ title }}</h1>
    <button @click="changeTitle">Change Title</button>
  </div>
</template>

<script>
// 当 `title` 改变时,补丁标记可以标识出这是一处"文本更新",而不是解析整个组件,这样可以更高效地进行 DOM 更新
export default {
  data() {
    return {
      title: 'Initial Title'
    };
  },
  methods: {
    changeTitle() {
      this.title = 'Updated Title';
    }
  }
}
</script>

补丁标记是一种机制,用于标识虚拟 DOM 更新的类型。

  • 优化更新过程:通过使用补丁标记,Vue 3 可以在 diff 过程中快速识别哪些节点发生了变化,并以最小的成本进行更新。
  • 细粒度更新:补丁标记的引入使得 Vue 3 的更新变得更加细粒度,这样在页面更新时可以避免不必要的完整渲染,进一步提升性能。

为何vue3去掉了构造函数

1. 简化 API 设计

在 Vue 2 中,开发者需要通过 new Vue() 构造函数来创建应用实例,这种方式虽然直观,但在大型项目中可能会导致代码冗余和复杂性增加。Vue 3 引入了 createApp 函数,使得创建应用实例更加简洁和直观

js 复制代码
// Vue 2 即是一个应用又是一个特殊的vue组件 概念模糊
const app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue 2!'
  }
});

// Vue 3 把组件实例和应用分开 提供的方式的针对整个应用 而不是一个特殊的组件
import { createApp } from 'vue';

const app = createApp({
  data() {
    return {
      message: 'Hello Vue 3!'
    };
  }
});

app.mount('#app');

2. 提高灵活性

Vue 2 的构造函数方式限制了应用的扩展性。例如,全局配置(如 Vue.useVue.mixin 等)会影响到所有实例,这在某些场景下可能会导致冲突或不可预期的行为。

Vue 3 通过 createApp 创建的应用实例是独立的,每个实例可以有自己的配置,避免了全局污染:

js 复制代码
const app1 = createApp({});
app1.use(plugin1);

const app2 = createApp({});
app2.use(plugin2);

3. Tree Shaking

vue2构造函数集成了太多功能,不利于Tree Shaking,vue3把这些功能使用普通函数导出充分使用Tree Shaking 减少打包体积。

小结

Vue 3 去掉构造函数,改用 createApp 函数,主要是为了:

  • 简化 API 设计,使代码更简洁;
  • 提高应用的灵活性和隔离性;
  • 更好地支持 TypeScript;
  • 符合现代 JavaScript 生态;
  • 优化 Tree Shaking,减小打包体积;
  • 提供更清晰的代码结构。

vue3数据响应式的理解

vue不再不在使用 Object.defineProperty 方式定义完成数据响应式,而是使用Proxy, 除了Proxy本身效率比Object.defineProperty高,由于不用遍历所有属性直接得到一个Proxy,所以vue3中对数据的访问是动态的,访问某个属性时候再动态的获取和设置,极大提升了组件初始阶段的效率问题。同时,Proxy可以监控到成员的新增和删除,并且均可以出发视图重新渲染 这些在vue2`中直接是难以做到的。

1. Proxy 和 Reflect

Vue 3 使用 Proxy 来拦截对象的操作(如读取、写入、删除等),并通过 Reflect 来执行这些操作。Proxy 可以监听对象的所有属性变化,而无需像 Vue 2 那样通过 Object.defineProperty 逐个定义属性的 gettersetter

js 复制代码
const target = { count: 0 };
const handler = {
  get(target, key, receiver) {
    console.log(`读取属性: ${key}`);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log(`设置属性: ${key} = ${value}`);
    return Reflect.set(target, key, value, receiver);
  }
};
const proxy = new Proxy(target, handler);

proxy.count; // 输出: 读取属性: count
proxy.count = 1; // 输出: 设置属性: count = 1

2. Reactive 和 Ref

Vue 3 提供了 reactiveref 两个函数来创建响应式数据。

  • reactive:用于将对象转换为响应式对象。
  • ref:用于将基本类型数据(如 numberstring 等)转换为响应式对象,通过 .value 访问和修改值。
js 复制代码
import { reactive, ref } from 'vue';

// 响应式对象
const state = reactive({ count: 0 });
state.count++; // 自动触发更新

// 响应式基本类型
const count = ref(0);
count.value++; // 自动触发更新

3. 依赖收集与触发更新

Vue 3 的响应式系统通过依赖收集和触发更新来实现数据的动态响应。

  • 依赖收集:当组件渲染时,Vue 会追踪哪些响应式数据被使用,并建立依赖关系。
  • 触发更新:当响应式数据发生变化时,Vue 会通知所有依赖该数据的组件进行重新渲染。
js 复制代码
import { reactive, watchEffect } from 'vue';

const state = reactive({ count: 0 });

// 依赖收集
watchEffect(() => {
  console.log(`count 的值是: ${state.count}`);
});

// 触发更新
state.count++; // 输出: count 的值是: 1

4. Computed 和 Watch

Vue 3 提供了 computedwatch 来处理复杂的响应式逻辑。

  • computed:用于创建基于响应式数据的计算属性。
  • watch:用于监听响应式数据的变化并执行回调函数。
js 复制代码
import { reactive, computed, watch } from 'vue';

const state = reactive({ count: 0 });

// 计算属性
const doubleCount = computed(() => state.count * 2);

// 监听器
watch(() => state.count, (newValue, oldValue) => {
  console.log(`count 从 ${oldValue} 变为 ${newValue}`);
});

state.count++; // 输出: count 从 0 变为 1

5. 响应式系统的优势

  • 性能更好Proxy 可以监听整个对象,无需像 Object.defineProperty 那样逐个定义属性。
  • 支持更多操作Proxy 可以拦截更多操作,如 deletePropertyhas 等。
  • 更灵活reactiveref 提供了更灵活的方式来创建响应式数据。

vue3中v-model的变化

1. 更灵活的自定义组件支持

在Vue 3中,你可以通过指定不同的属性名和事件来改变v-model的行为。

父组件:

html 复制代码
<template>
  <CustomInput v-model="message" />
</template>

<script>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';

export default {
  components: { CustomInput },
  setup() {
    const message = ref('Hello Vue 3');
    return { message };
  }
};
</script>

子组件 (CustomInput.vue):

html 复制代码
<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>

<script>
export default {
  props: ['modelValue'],
  emits: ['update:modelValue']
};
</script>

2. 多值绑定

Vue 3允许在一个组件上使用多个v-model绑定。

父组件:

html 复制代码
<template>
  <UserForm v-model:name="name" v-model:age="age" />
</template>

<script>
import { ref } from 'vue';
import UserForm from './UserForm.vue';

export default {
  components: { UserForm },
  setup() {
    const name = ref('John');
    const age = ref(30);
    return { name, age };
  }
};
</script>

子组件 (UserForm.vue):

html 复制代码
<template>
  <div>
    <input :value="name" @input="$emit('update:name', $event.target.value)" placeholder="Name" />
    <input :value="age" @input="$emit('update:age', $event.target.value)" placeholder="Age" />
  </div>
</template>

<script>
export default {
  props: ['name', 'age'],
  emits: ['update:name', 'update:age']
};
</script>

3. 性能优化

虽然性能优化是内部实现的变化,但你可以在实际项目中感受到更流畅的数据更新。这里不再提供具体代码示例,因为这是框架层面的改进。

4. 移除.sync修饰符

在Vue 3中,.sync修饰符已经被移除。你可以使用命名的v-model来替代它。

旧方式(Vue 2):

html 复制代码
<ChildComponent :count.sync="parentCount" />

新方式(Vue 3):

html 复制代码
<ChildComponent v-model:count="parentCount" />

5. 自定义v-model指令

虽然官方文档中并没有提到通过Vue.directive来定义自己的v-model指令,但在Vue 3中,你可以通过组件选项来定制v-model的行为。

子组件 (CustomCheckbox.vue):

html 复制代码
<template>
  <label>
    <input type="checkbox" :checked="modelValue" @change="$emit('update:modelValue', $event.target.checked)" />
    {{ label }}
  </label>
</template>

<script>
export default {
  props: {
    modelValue: Boolean,
    label: String
  },
  emits: ['update:modelValue']
};
</script>

父组件:

html 复制代码
<template>
  <CustomCheckbox v-model="isChecked" label="Check me!" />
</template>

<script>
import { ref } from 'vue';
import CustomCheckbox from './CustomCheckbox.vue';

export default {
  components: { CustomCheckbox },
  setup() {
    const isChecked = ref(false);
    return { isChecked };
  }
};
</script>

清醒认识vue3异步组件用法

在 Vue 3 中,可以使用 defineAsyncComponent 来定义异步组件。它接受一个返回 Promise 的工厂函数,通常用于动态导入组件

1. 基本用法

在 Vue 3 中,可以使用 defineAsyncComponent 来定义异步组件。它接受一个返回 Promise 的工厂函数,通常用于动态导入组件。

js 复制代码
import { defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
);

export default {
  components: {
    AsyncComponent
  }
};

2. 加载状态和错误处理

defineAsyncComponent 支持配置加载状态和错误处理,通过 loadingComponenterrorComponent 参数来实现。

js 复制代码
import { defineAsyncComponent } from 'vue';

const AsyncComponent = defineAsyncComponent({
  loader: () => import('./components/MyComponent.vue'),
  loadingComponent: LoadingSpinner, // 加载时显示的组件
  errorComponent: ErrorComponent,   // 加载失败时显示的组件
  delay: 200,                       // 延迟显示加载组件的时间(毫秒)
  timeout: 3000                     // 超时时间(毫秒)
});

export default {
  components: {
    AsyncComponent
  }
};

3. 结合 Suspense 使用

Vue 3 引入了 Suspense 组件,可以更好地处理异步组件的加载状态。Suspense 提供了两个插槽:#default 用于渲染异步组件,#fallback 用于显示加载状态。

html 复制代码
<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

<script>
import { defineAsyncComponent } from 'vue';
import LoadingSpinner from './components/LoadingSpinner.vue';

const AsyncComponent = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
);

export default {
  components: {
    AsyncComponent,
    LoadingSpinner
  }
};
</script>

4. 动态导入

异步组件通常与动态导入(import())结合使用,以实现按需加载。这种方式可以减小初始包体积,提升应用性能。

js 复制代码
const AsyncComponent = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
);

5. 结合路由使用

在 Vue Router 中,异步组件常用于实现路由懒加载,从而优化应用的加载速度。

js 复制代码
import { defineAsyncComponent } from 'vue';

const routes = [
  {
    path: '/about',
    component: defineAsyncComponent(() =>
      import('./views/AboutView.vue')
    )
  }
];

6. 高级用法:自定义加载逻辑

如果需要更复杂的加载逻辑,可以在 defineAsyncComponent 的工厂函数中实现。

js 复制代码
const AsyncComponent = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(import('./components/MyComponent.vue'));
    }, 1000); // 模拟延迟加载
  });
});

7. 与 Vue 2 的对比

在 Vue 2 中,异步组件通常通过 Vue.componentcomponent 选项来定义,语法较为繁琐:

js 复制代码
// Vue 2
Vue.component('async-component', () => import('./components/MyComponent.vue'));

8. 封装加载异步组件的工具类

js 复制代码
// utils/loadAsync.js
import { defineAsyncComponent } from 'vue';

/**
 * 动态加载异步组件或页面
 * @param {string} path - 组件或页面的路径
 * @param {Object} options - 配置项
 * @param {Component} options.loadingComponent - 加载时显示的组件
 * @param {Component} options.errorComponent - 加载失败时显示的组件
 * @param {number} options.delay - 延迟显示加载组件的时间(毫秒)
 * @param {number} options.timeout - 超时时间(毫秒)
 * @returns {Promise<Component>} - 异步组件或页面
 */
export function loadAsync(path, options = {}) {
  return defineAsyncComponent({
    loader: () => import(/* @vite-ignore */ `@/${path}`),
    loadingComponent: options.loadingComponent || null,
    errorComponent: options.errorComponent || null,
    delay: options.delay || 200,
    timeout: options.timeout || 3000
  });
}

Vue 3 的 defineAsyncComponent 提供了更简洁、更灵活的 API,同时支持加载状态和错误处理,提升了开发体验。如果需要更灵活地处理加载状态,可以结合 Suspense 使用。

composition api 相比 options api有什么优势

  • 更好的逻辑复用和代码组织
  • 更好的类型推导(TS)没有this指向导致的奇怪问题产生 更符合函数式编程规范

vue3 setup作用和原理 以及 scrip setup做了啥事

作用

  1. 组织逻辑setup 函数是 Vue 3 中用于替代 Vue 2 的 datamethodscomputed 等选项的核心函数,它将组件的逻辑集中在一个地方。
  2. 响应式数据 :在 setup 函数中,可以使用 refreactive 等响应式 API 来创建和管理响应式数据。
  3. 生命周期钩子 :可以通过 onMountedonUpdated 等钩子函数来监听组件的生命周期。
  4. 暴露数据和方法setup 函数返回的对象中的属性和方法可以直接在模板中使用。

原理

  1. 执行时机setup 函数在组件实例创建之前执行,因此无法访问 this,因为此时组件实例还未创建。
  2. 响应式系统setup 函数内部使用 Vue 3 的响应式 API(如 refreactive)来创建响应式数据,这些 API 会追踪数据的变化并自动更新视图。
  3. 生命周期钩子setup 函数中可以使用生命周期钩子函数(如 onMountedonUpdated),这些钩子函数会在相应的生命周期阶段被调用。
  4. 返回值setup 函数返回一个对象,该对象中的属性和方法会被暴露给模板和组件的其他部分使用。

<script setup> 的作用

<script setup> 是 Vue 3 提供的一种语法糖,用于简化 setup 函数的使用。它允许开发者直接在 <script> 标签中编写 setup 函数的逻辑,而无需显式定义 setup 函数。

做了什么

  1. 自动暴露变量和方法 :在 <script setup> 中定义的变量和函数会自动暴露给模板,无需通过 return 显式返回。
  2. 简化代码:减少了模板和逻辑之间的代码量,使组件更加简洁。
  3. 支持顶层 await :在 <script setup> 中可以直接使用 await,方便处理异步逻辑。
  4. 更好的 TypeScript 支持<script setup> 对 TypeScript 的支持更加友好,类型推断更加准确。

setup<script setup> 的区别

特性 setup 函数 <script setup>
代码量 需要显式定义 setup 函数并返回对象 无需显式定义 setup 函数,代码更简洁
暴露变量和方法 需要手动 return 自动暴露
顶层 await 不支持 支持
TypeScript 支持 需要额外配置 更友好,类型推断更准确
js 复制代码
export default {
    setup(props,context){
      // ...
    }
}
html 复制代码
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const count = ref(0);

function increment() {
  count.value++;
}
</script>

介绍 pinia 以及其持久化最佳实践

Pinia 是 Vue 3 的轻量级状态管理库,它提供了两种风格的使用方式:Options API 风格组合式 API 风格

sh 复制代码
# 安装
pnpm add pinia

pinia持久化

1. 安装 Pinia 和持久化插件

sh 复制代码
# 安装
pnpm add pinia pinia-plugin-persistedstate

2. 配置 Pinia 和持久化插件

js 复制代码
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
// 是一个官方推荐的插件
pinia.use(piniaPluginPersistedstate);

export default pinia;

3. 定义 Store

  • 3.1 state:定义状态

  • 3.2 getters:定义计算属性

  • 3.3 actions:定义同步和异步方法

  • 3.4 persist:配置持久化

js 复制代码
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  // 状态(State)
  state: () => ({
    userInfo: null, // 用户信息
    token: '', // 用户 token
    isLoading: false, // 加载状态
  }),

  // 计算属性(Getters)
  getters: {
    isLoggedIn: (state) => !!state.token, // 是否已登录
    userName: (state) => state.userInfo?.name || 'Guest', // 用户名
  },

  // 方法(Actions)
  actions: {
    // 同步方法:设置用户信息
    setUserInfo(userInfo) {
      this.userInfo = userInfo;
    },

    // 同步方法:设置 token
    setToken(token) {
      this.token = token;
    },

    // 异步方法:模拟登录
    async login(credentials) {
      this.isLoading = true;
      try {
        // 模拟 API 请求
        const response = await fetch('/api/login', {
          method: 'POST',
          body: JSON.stringify(credentials),
        });
        const data = await response.json();

        // 更新状态
        this.setUserInfo(data.userInfo);
        this.setToken(data.token);
      } catch (error) {
        console.error('Login failed:', error);
      } finally {
        this.isLoading = false;
      }
    },

    // 异步方法:模拟退出登录
    async logout() {
      this.isLoading = true;
      try {
        // 模拟 API 请求
        await fetch('/api/logout', { method: 'POST' });

        // 清空状态
        this.setUserInfo(null);
        this.setToken('');
      } catch (error) {
        console.error('Logout failed:', error);
      } finally {
        this.isLoading = false;
      }
    },
  },

  // 持久化配置
  persist: {
    enabled: true,
    strategies: [
      {
        key: 'user-store', // 存储的键名
        storage: localStorage, // 存储方式
        paths: ['token', 'userInfo'], // 只持久化 token 和 userInfo
      },
    ],
  },
});

4. 在组件中使用 Store

  • 4.1 使用 stategetters

  • 4.2 调用 actions 方法

  • 4.3 使用辅助函数(如 mapStatemapGettersmapActions

html 复制代码
<template>
  <div>
    <p v-if="isLoading">Loading...</p>
    <div v-else>
      <p>User Name: {{ userName }}</p>
      <p>Logged In: {{ isLoggedIn ? 'Yes' : 'No' }}</p>
      <button @click="handleLogin">Login</button>
      <button @click="handleLogout">Logout</button>
    </div>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'pinia';
import { useUserStore } from './stores/user';

export default {
  computed: {
    // 使用 mapState 辅助函数映射 state
    ...mapState(useUserStore, ['userInfo', 'token', 'isLoading']),
    // 使用 mapGetters 辅助函数映射 getters
    ...mapGetters(useUserStore, ['isLoggedIn', 'userName']),
  },
  methods: {
    // 使用 mapActions 辅助函数映射 actions
    ...mapActions(useUserStore, ['login', 'logout']),

    // 处理登录
    handleLogin() {
      this.login({ username: 'admin', password: '123456' });
    },

    // 处理退出登录
    handleLogout() {
      this.logout();
    },
  },
};
</script>

基于 组合式 API 风格 的 Pinia 使用示例

1、2步骤和 options api 一致

3. 定义 Store

  • 3.1 state:定义状态

  • 3.2 getters:定义计算属性

  • 3.3 actions:定义同步和异步方法

  • 3.4 persist:配置持久化

js 复制代码
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useUserStore = defineStore('user', () => {
  // 状态(State)
  const userInfo = ref({ name: 'Guest', age: 18 });
  const token = ref('');
  const isLoading = ref(false);

  // 计算属性(Getters)
  const isLoggedIn = computed(() => !!token.value);
  const userName = computed(() => userInfo.value.name || 'Guest');

  // 方法(Actions)
  function setUserInfo(info) {
    userInfo.value = info;
  }

  function setToken(newToken) {
    token.value = newToken;
  }

  async function login(credentials) {
    isLoading.value = true;
    try {
      // 模拟 API 请求
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
      });
      const data = await response.json();

      // 更新状态
      setUserInfo(data.userInfo);
      setToken(data.token);
    } catch (error) {
      console.error('Login failed:', error);
    } finally {
      isLoading.value = false;
    }
  }

  async function logout() {
    isLoading.value = true;
    try {
      // 模拟 API 请求
      await fetch('/api/logout', { method: 'POST' });

      // 清空状态
      setUserInfo({ name: 'Guest', age: 18 });
      setToken('');
    } catch (error) {
      console.error('Logout failed:', error);
    } finally {
      isLoading.value = false;
    }
  }

  return {
    userInfo,
    token,
    isLoading,
    isLoggedIn,
    userName,
    setUserInfo,
    setToken,
    login,
    logout,
  };
}, {
  // 持久化配置
  persist: {
    enabled: true, // 启用持久化
    strategies: [
      {
        key: 'user-store', // 存储的键名
        storage: localStorage, // 存储方式 默认为 localStorage
        paths: ['token', 'userInfo'], // 存储字段 key
      },
    ],
  },
});

4. 在组件中使用 Store

  • 4.1 使用 storeToRefs 解构 stategetters

  • 4.2 调用 actions 方法

  • 4.3 使用 $patch 批量更新状态

html 复制代码
<template>
  <div>
    <p v-if="isLoading">Loading...</p>
    <div v-else>
      <p>User Name: {{ userName }}</p>
      <p>Logged In: {{ isLoggedIn ? 'Yes' : 'No' }}</p>
      <button @click="handleLogin">Login</button>
      <button @click="handleLogout">Logout</button>
      <button @click="updateUserInfo">Update User Info</button>
    </div>
  </div>
</template>

<script setup>
import { useUserStore } from './stores/user';
import { storeToRefs } from 'pinia';

const userStore = useUserStore();
// 使用 storeToRefs 解构 state 和 getters
const { userInfo, token, isLoading, isLoggedIn, userName } = storeToRefs(userStore);
// 直接解构 actions
const { login, logout } = userStore;

// 处理登录
function handleLogin() {
  login({ username: 'admin', password: '123456' });
}

// 处理退出登录
function handleLogout() {
  logout();
}

// 使用 $patch 批量更新状态
function updateUserInfo() {
  userStore.$patch({
    userInfo: { name: 'Admin', age: 30 },
    token: 'new-token',
  });
}
</script>

pinia其他存储方案

手动实现持久化

  1. 保存状态到本地存储 :在 Store 的 actions 或生命周期钩子中,将状态保存到 localStoragesessionStorage
  2. 从本地存储恢复状态 :在 Store 初始化时,从 localStoragesessionStorage 中读取并恢复状态。
js 复制代码
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: JSON.parse(localStorage.getItem('count')) || 0,
  }),
  actions: {
    increment() {
      this.count++;
      this.saveState();
    },
    decrement() {
      this.count--;
      this.saveState();
    },
    saveState() {
      localStorage.setItem('count', JSON.stringify(this.count));
    },
  },
});

使用插件 pinia-plugin-persist

pinia-plugin-persist 是另一个流行的持久化插件,功能与 pinia-plugin-persistedstate 类似。

安装

sh 复制代码
npm install pinia-plugin-persist

配置

在创建 Pinia 实例时,使用该插件。

js 复制代码
import { createPinia } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';

const pinia = createPinia();
pinia.use(piniaPluginPersist);

export default pinia;

在 Store 中使用

在定义 Store 时,添加 persist 配置。

js 复制代码
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
  },
  persist: {
    enabled: true, // 启用持久化
    strategies: [
      {
        key: 'counter', // 存储的键名
        storage: localStorage, // 存储方式,默认为 localStorage
      },
    ],
  },
});

4. 持久化方案对比

方案 优点 缺点
手动实现持久化 无需额外依赖,完全可控 代码量较多,容易出错
pinia-plugin-persistedstate 官方推荐,配置简单,功能强大 需要额外安装插件
pinia-plugin-persist 功能丰富,支持多种存储方式 社区维护,可能不如官方插件稳定

ref与reactive的区别源码级别比较

refreactive 是 Vue 3 中用于创建响应式数据的两个核心 API。

ref 的源码实现

  • 核心逻辑

ref 用于将一个基本类型或对象转换为响应式数据。它的核心逻辑是:

  • 如果传入的值是对象,则调用 reactive 将其转换为响应式对象。

  • 如果传入的值是基本类型,则将其包装为一个包含 value 属性的对象。

  • 源码片段

js 复制代码
function ref(value) {
  return createRef(value);
}

function createRef(rawValue, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}

class RefImpl<T> {
  private _value: T;
  private _rawValue: T;

  public readonly __v_isRef = true;

  constructor(value: T, public readonly _shallow: boolean) {
    this._rawValue = _shallow ? value : toRaw(value);
    this._value = _shallow ? value : toReactive(value);
  }

  get value() {
    track(this, TrackOpTypes.GET, 'value');
    return this._value;
  }

  set value(newVal) {
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal;
      this._value = this._shallow ? newVal : toReactive(newVal);
      trigger(this, TriggerOpTypes.SET, 'value', newVal);
    }
  }
} 

关键点

  • ref 通过 RefImpl 类实现,内部维护了一个 _value 属性。
  • 访问 value 属性时,会触发依赖收集(track)。
  • 修改 value 属性时,会触发更新(trigger)。
  • 如果传入的值是对象,ref 会调用 toReactive,实际上是调用 reactive

reactive 的源码实现

  • 核心逻辑

reactive 用于将一个对象转换为响应式对象。它的核心逻辑是:

  • 使用 Proxy 代理对象,拦截对对象的访问和修改操作。

  • 通过 tracktrigger 实现依赖收集和更新触发。

  • 源码片段

js 复制代码
function reactive(target) {
  if (target && (target as any).__v_isReadonly) {
    return target;
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers
  );
}

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  if (!isObject(target)) {
    return target;
  }
  if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
    return target;
  }
  const proxy = new Proxy(target, baseHandlers);
  return proxy;
}

关键点

  • reactive 使用 Proxy 代理对象,拦截对象的 getsetdeleteProperty 等操作。
  • get 操作中,调用 track 进行依赖收集。
  • set 操作中,调用 trigger 触发更新。
  • reactive 只能用于对象,不能用于基本类型。

refreactive 的区别

3.1 数据类型

  • ref:可以用于基本类型(如 numberstring)和对象。
  • reactive:只能用于对象。

3.2 访问方式

  • ref:需要通过 .value 访问或修改值。
  • reactive:直接访问或修改对象的属性。

3.3 实现方式

  • ref:通过 RefImpl 类实现,内部维护一个 _value 属性。
  • reactive:通过 Proxy 实现,直接代理对象。

3.4 性能

  • ref:对于基本类型,性能更高,因为不需要 Proxy 代理。
  • reactive:对于对象,性能与 ref 类似,但更直观。

3.5 使用场景

  • ref:适合用于基本类型或需要明确 .value 访问的场景。
  • reactive:适合用于对象,尤其是嵌套对象。

源码级别比较总结

特性 ref reactive
数据类型 基本类型和对象 对象
访问方式 通过 .value 访问 直接访问属性
实现方式 通过 RefImpl 类实现 通过 Proxy 实现
性能 对于基本类型性能更高 对于对象性能与 ref 类似
使用场景 基本类型或需要 .value 访问的场景 对象,尤其是嵌套对象

keep-alive 最佳实践

  • <keep-alive> 是 Vue 3 中用于缓存组件实例的内置组件,可以提升性能。
  • 适用于动态组件切换、路由切换等场景。
  • 通过 includeexcludemax 属性可以精确控制缓存行为。
  • 结合 activateddeactivated 生命周期钩子,可以更好地管理组件的状态

缓存动态组件

html 复制代码
<template>
  <keep-alive>
    <component :is="currentComponent" />
  </keep-alive>
  <button @click="toggleComponent">Toggle Component</button>
</template>

<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

const currentComponent = ref('ComponentA');

function toggleComponent() {
  currentComponent.value = currentComponent.value === 'ComponentA' ? 'ComponentB' : 'ComponentA';
}
</script>

缓存路由组件

html 复制代码
<template>
  <keep-alive>
    <router-view />
  </keep-alive>
</template>

注意事项

includeexclude 属性依赖于路由组件的 name 属性,因此需要确保路由组件设置了 name。 对于动态路由(如 /user/:id),默认情况下,不同参数的路由会共享同一个组件实例。如果需要为每个参数单独缓存,可以使用 key 属性。

html 复制代码
<template>
  <keep-alive>
    <router-view :key="$route.fullPath" />
  </keep-alive>
</template>

在嵌套路由中,<keep-alive> 只会缓存当前层级的组件。如果需要缓存嵌套的子组件,可以在子路由的 <router-view> 外层也包裹 <keep-alive>

缓存过多路由组件会导致内存占用过高,应根据实际需求合理使用 <keep-alive>。可以通过 max 属性限制缓存数量

当路由组件被缓存或激活时,会触发 activateddeactivated 生命周期钩子。可以在这些钩子中执行特定逻辑,如数据加载或清理

  • 结合 meta 字段控制缓存
html 复制代码
<template>
  <router-view v-slot="{ Component }">
    <keep-alive>
      <component :is="Component" v-if="$route.meta.keepAlive" />
    </keep-alive>
    <component :is="Component" v-if="!$route.meta.keepAlive" />
  </router-view>
</template>
  • 处理滚动位置

在缓存路由组件时,可能需要保留滚动位置。可以通过 scrollBehavior 配置实现。

js 复制代码
const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition; // 恢复滚动位置
    } else {
      return { top: 0 }; // 滚动到顶部
    }
  },
});

vuex以及最佳实践以及其持久化方案

Vuex 是 Vue.js 的官方状态管理库,用于管理应用中的共享状态。它通过集中式存储管理应用的所有组件的状态,并以可预测的方式更新状态。

Vuex Store 定义 (vue2版本)

js 复制代码
import Vue from 'vue';
import Vuex from 'vuex';
// 持久化插件
import createPersistedState from 'vuex-persistedstate';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0,
    user: null,
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    setUser(state, user) {
      state.user = user;
    },
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    },
  },
  plugins: [createPersistedState()],
});

export default store;

组件中使用

html 复制代码
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>User: {{ user ? user.name : 'Guest' }}</p>
    <button @click="increment">Increment</button>
    <button @click="incrementAsync">Increment Async</button>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState(['count', 'user']),
  },
  methods: {
    ...mapActions(['incrementAsync']),
    increment() {
      this.$store.commit('increment');
    },
  },
};
</script>

Vue 3 组合式 API 结合 Vuex 的最佳实践以及持久化方案

安装 Vuex

sh 复制代码
pnpm add vuex@next

创建 Vuex Store

js 复制代码
import { createStore } from 'vuex';

const store = createStore({
  state: {
    count: 0,
    user: null,
  },
  mutations: {
    // 同步方法
    increment(state) {
      state.count++;
    },
    setUser(state, user) {
      state.user = user;
    },
  },
  actions: {
    // 异步方法
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    },
  },
  getters: {
    doubleCount: (state) => state.count * 2,
  },
});

export default store;

在 Vue 应用中使用 Vuex

js 复制代码
// main.js / main.ts
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';

createApp(App).use(store).mount('#app');

组合式 API 结合 Vuex

使用 useStore 访问 Store

在组合式 API 中,可以使用 useStore 函数访问 Vuex Store。

html 复制代码
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
    <button @click="incrementAsync">Increment Async</button>
  </div>
</template>

<script>
import { computed } from 'vue';
import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();

    const count = computed(() => store.state.count);
    const doubleCount = computed(() => store.getters.doubleCount);

    function increment() {
      store.commit('increment');
    }

    function incrementAsync() {
      store.dispatch('incrementAsync');
    }

    return {
      count,
      doubleCount,
      increment,
      incrementAsync,
    };
  },
};
</script

使用 mapStatemapGettersmapActions 辅助函数

在组合式 API 中,可以使用 mapStatemapGettersmapActions 辅助函数简化代码。

html 复制代码
<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double Count: {{ doubleCount }}</p>
    <button @click="increment">Increment</button>
    <button @click="incrementAsync">Increment Async</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState(['count']),
    ...mapGetters(['doubleCount']),
  },
  methods: {
    ...mapActions(['incrementAsync']),
    increment() {
      this.$store.commit('increment');
    },
  },
};
</script>

最佳实践

模块化 将 store 分割成多个模块,便于管理和维护。

js 复制代码
const userModule = {
  state: () => ({ user: null }),
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
  },
};

const store = createStore({
  modules: {
    user: userModule,
  },
});

使用 namespaced 模块 为模块启用命名空间,避免命名冲突。

js 复制代码
const userModule = {
  namespaced: true,
  state: () => ({ user: null }),
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
  },
};

避免直接修改 state 始终通过 mutations 修改 state,确保状态变更可追踪。

使用 actions 处理异步操作 将异步逻辑放在 actions 中,保持 mutations 的同步性。

使用 vuex-persistedstate 插件 vuex-persistedstate 是一个流行的 Vuex 持久化插件,可以自动将 state 保存到 localStoragesessionStorage

sh 复制代码
# 安装
pnpm add vuex-persistedstate

配置

js 复制代码
import createPersistedState from 'vuex-persistedstate';

const store = createStore({
  state: { ... },
  mutations: { ... },
  actions: { ... },
  plugins: [createPersistedState()],
});

自定义配置

js 复制代码
createPersistedState({
  key: 'my-app-state', // 存储的键名
  storage: window.sessionStorage, // 存储方式
  paths: ['user', 'cart'], // 只持久化指定模块
});

使用 vuex-persist 插件

vuex-persist 是另一个 Vuex 持久化插件,支持更多存储方式(如 localForage)。

sh 复制代码
# 安装
npm install vuex-persist

配置

js 复制代码
import VuexPersistence from 'vuex-persist';

const vuexLocal = new VuexPersistence({
  storage: window.localStorage,
});

const store = createStore({
  state: { ... },
  mutations: { ... },
  actions: { ... },
  plugins: [vuexLocal.plugin],
});

vuex 小结

  • 组合式 API 提供了更灵活的方式来组织组件逻辑,结合 Vuex 可以更好地管理应用状态。
  • 最佳实践 :模块化、使用辅助函数、避免直接修改 state、处理异步操作。
  • 持久化方案 :使用 vuex-persistedstatevuex-persist 插件,确保状态在页面刷新后能够正确恢复。
相关推荐
ss273几秒前
被催更了,2025元旦源码继续免费送
java·vue.js·spring boot·后端·微信小程序·开源
@ 前端小白10 分钟前
封装倒计时自定义react hook
前端·javascript·react.js
咔咔库奇13 分钟前
【react】Redux的设计思想与工作原理
前端·react.js·前端框架
码农君莫笑16 分钟前
在 Blazor 和 ASP.NET Core 中使用依赖注入和Scoped 服务实现数据共享方法详解
前端·后端·c#·.netcore·visual studio
Catherinemin42 分钟前
HTML5新特性|05 CSS3边框&CSS3背景
前端·css3
JINGWHALE142 分钟前
设计模式 结构型 装饰器模式(Decorator Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·装饰器模式
李是啥也不会44 分钟前
番外篇-CSS3新增特性
前端·css·css3
CodeClimb1 小时前
【华为OD-E卷 - 最优资源分配 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
16年上任的CTO1 小时前
一文大白话讲清楚CSS性能优化
前端·javascript·css·性能优化·css性能优化
jjw_zyfx1 小时前
vue3 css实现文字输出带光标显示,文字输出完毕,光标消失的效果
前端·javascript·css