Vue3:组合式API、Vue3.3新特性、Pinia

1、Vue3

优势

 

Vue3组合式API vs Vue2选项式API

使用create-vue创建项目

        create-vue是Vue官方新的脚手架工具,底层切换到了vite,为开发提供快速响应


<!-- 加上setup允许在script中直接编写组合式API -->
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import TheWelcome from './components/TheWelcome.vue'
</script>

2、组合式API

setup选项

写法:

特点:

1、执行时机:在beforeCreate之前

2、setup函数中,获取不到this(this是undefined)

3、数据和函数需要在setup最后return中写,才能在模板中应用

语法糖

reactive函数

使用:先导包,调方法把对象转为响应式并赋值给state

ref函数

既支持简单类型又支持对象类型的数据,语法与reactive类似(声明数据统一使用ref,统一编码规范

本质:在原有传入数据的基础上,外层包了一层对象,包成复杂类型。底层包成复杂类型之后,再借助reactive实现的响应式。

注意点:

1、脚本中访问数据,需通过 .value 访问

2、在template中,不需要加 .value(帮我们扒了一层)

computed计算属性

        注意:计算属性中不应该有“副作用”(eg:异步请求/修改dom);计算属性应该是只读的,特殊情况可以配置 get、set

watch

// 监视单个数据的变化
watch(ref对象,(newValue,oldValue) => { ... })

// 监视多个数据的变化
watch([ref对象1,ref对象2],(newValue,oldValue) => { ... })

侦听多个数据:

immediate

一进页面立即执行

deep

默认watch进行的是 浅层监视

eg:

        const ref1=ref(简单类型) 可以直接监视

        const ref2=ref(复杂类型) 监视不到复杂类型内部数据的变化

生命周期函数

父子通信
父传子

        注意:由于写了setup,所以无法直接配置props选项;因此,这里需要借助于“编译器宏”defineProps函数接收子组件传递的数据。

        props传递的数据,模板中可以直接使用,脚本中props.属性名访问

子传父

模板引用和defineExpose宏函数
模板引用

        通过ref标识获取真实的dom对象或者组件实例对象

模板引用时机:组件挂载完毕

通过ref对象.value即可访问到绑定的元素(必须渲染完成后,才能拿到)

defineExpose宏函数

显式暴露组件内部的属性和方法

provide和inject实现跨层传递数据

作用和场景:顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信。

修改数据:谁的数据,由谁维护。(使用跨层级函数)

3、Vue3.3新特性

defineOptions

defineModel快速实现双向绑定

配置defineModel:

4、Pinia

        pinia是Vue的最新状态管理工具,是Vuex的替代品

添加pinia到vue项目:

基本语法

Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的名字:

import { defineStore } from 'pinia'

//  `defineStore()` 的返回值的命名是自由的
// 但最好含有 store 的名字,且以 `use` 开头,以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {
  // 其他配置...
})
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const name = ref('Eduardo')
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, name, doubleCount, increment }
})

action异步写法:与组件中获取异步数据的写法完全一致。

storeToRefs方法

请注意,store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value。就像 setup 中的 props 一样,我们不能对它进行解构

使用前需要从pinia中导入,可以解构且不会使数据丢失响应式。

<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 都是响应式引用
// 下面的代码同样会提取那些来自插件的属性的响应式引用
// 但是会跳过所有的 action 或者非响应式(非 ref 或者 非 reactive)的属性
const { name, doubleCount } = storeToRefs(store)
// 名为 increment 的 action 可以被解构
const { increment } = store
</script>

为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:

<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 都是响应式引用
// 下面的代码同样会提取那些来自插件的属性的响应式引用
// 但是会跳过所有的 action 或者非响应式(非 ref 或者 非 reactive)的属性
const { name, doubleCount } = storeToRefs(store)
// 名为 increment 的 action 可以被解构
const { increment } = store
</script>
数据持久化

官网:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/

1、安装依赖

npm i pinia-plugin-persistedstate

2、将插件添加到 pinia 实例中

import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

用法:

在声明store时,将新persist选项设置为 true

// 组合式
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useStore = defineStore(
  'main',
  () => {
    const someState = ref('hello pinia')
    return { someState }
  },
  {
    persist: true,
  },
)
<think>我们使用组合式API风格来创建Pinia store。在Pinia中,我们可以使用`defineStore`函数,第二个参数可以是一个函数(组合式风格),类似于Vue的`setup()`函数。 在组合式风格的store中: - `ref()` 就是 state 属性 - `computed()` 就是 getters - `function()` 就是 actions 下面是一个使用组合式API风格创建Pinia store的示例: ### 创建组合式API风格的store (userStore) ```typescript // src/stores/user.ts import { defineStore } from &#39;pinia&#39; import { ref, computed, reactive } from &#39;vue&#39; import type { Ref } from &#39;vue&#39; // 定义用户类型 export interface User { id: number name: string email: string role: &#39;admin&#39; | &#39;user&#39; | &#39;guest&#39; } // 使用组合式API风格 export const useUserStore = defineStore(&#39;user&#39;, () => { // state (使用ref或reactive) const currentUser: Ref<User | null> = ref(null) const loading = ref(false) const error: Ref<string | null> = ref(null) // getters (使用computed) const isAdmin = computed(() => { return currentUser.value?.role === &#39;admin&#39; }) const fullUserInfo = computed(() => { return currentUser.value ? `${currentUser.value.name} (${currentUser.value.email})` : &#39;未登录&#39; }) // actions (普通函数) async function login(email: string, password: string) { loading.value = true error.value = null try { // 模拟API调用 const response = await new Promise<{ user: User }>((resolve) => { setTimeout(() => { resolve({ user: { id: 1, name: &#39;张三&#39;, email: email, role: email === &#39;admin@example.com&#39; ? &#39;admin&#39; : &#39;user&#39; } }) }, 1000) }) currentUser.value = response.user } catch (err) { error.value = (err as Error).message || &#39;登录失败&#39; } finally { loading.value = false } } function logout() { currentUser.value = null } function updateUser(partialUser: Partial<User>) { if (currentUser.value) { currentUser.value = { ...currentUser.value, ...partialUser } } } // 必须返回所有需要暴露的状态和函数 return { currentUser, loading, error, isAdmin, fullUserInfo, login, logout, updateUser } }) ``` ### 在组件中使用组合式风格的store 在组件中使用时,和选项式store的用法完全一致,因为Pinia在底层做了统一处理。 ```vue <!-- src/components/UserProfile.vue --> <script setup lang="ts"> import { useUserStore } from &#39;@/stores/user&#39; import { storeToRefs } from &#39;pinia&#39; const userStore = useUserStore() // 使用storeToRefs来解构保持响应性 const { currentUser, isAdmin, fullUserInfo, loading } = storeToRefs(userStore) const handleLogin = () => { userStore.login(&#39;user@example.com&#39;, &#39;password123&#39;) } </script> <template> <!-- 模板代码与之前相同 --> </template> ``` ### 组合式API风格的优势 1. **更灵活**:可以像编写Vue组件的`setup()`一样组织代码 2. **更好的类型推断**:TypeScript类型推断更自然 3. **可组合性**:可以在store内部使用其他组合函数 4. **更细粒度的响应式控制**:可以使用`ref`、`reactive`等响应式API ### 注意事项 1. **必须返回所有需要暴露的状态和方法**,否则在组件中无法访问 2.组合式函数中,**不能使用`this`访问state**,而是直接使用变量 3. 推荐使用`storeToRefs`在组件中解构store,以保持响应性 4. 对于复杂的store,可以将部分逻辑抽取到独立的组合函数中 ### 组合式风格与选项式风格的选择 - **组合式风格**:适合复杂逻辑、需要组合多个功能、或者需要重用部分逻辑的场景 - **选项式风格**:适合简单store、或者习惯于Vuex风格开发的场景 Pinia 同时支持两种风格,开发者可以根据项目需求和个人偏好选择[^1]。 ### 相关问题 1.组合式Pinia store中如何处理异步操作和错误处理? 2. 如何在不同组件之间共享Pinia store的状态? 3.组合式Pinia store中如何实现模块化? 4. 组合式Pinia store与Vue组合函数有什么区别和联系? 5. 如何在组合式Pinia store中使用其他store?</think># Vue3 + Pinia + TypeScript 组合式API风格Store示例 在Vue3中,Pinia提供了两种创建store的方式:选项式API组合式API组合式API风格更接近Vue3的`setup()`语法,提供了更好的类型推断和代码组织能力。 ## 组合式API风格Store示例 ### 用户Store (组合式API风格) ```typescript // src/stores/user.ts import { defineStore } from &#39;pinia&#39; import { ref, computed, reactive } from &#39;vue&#39; import type { User } from &#39;@/types&#39; export const useUserStore = defineStore(&#39;user&#39;, () => { // 状态定义 (使用ref或reactive) const currentUser = ref<User | null>(null) const loading = ref(false) const error = ref<string | null>(null) // 计算属性 (getters) const isAdmin = computed(() => { return currentUser.value?.role === &#39;admin&#39; }) const fullUserInfo = computed(() => { return currentUser.value ? `${currentUser.value.name} (${currentUser.value.email})` : &#39;未登录&#39; }) // 异步操作 (actions) async function login(email: string, password: string) { loading.value = true error.value = null try { // 模拟API调用 const response = await mockApiCall(email, password) currentUser.value = response.user } catch (err) { error.value = (err as Error).message || &#39;登录失败&#39; } finally { loading.value = false } } function logout() { currentUser.value = null } function updateUser(partialUser: Partial<User>) { if (currentUser.value) { currentUser.value = { ...currentUser.value, ...partialUser } } } // 模拟API调用 function mockApiCall(email: string, password: string): Promise<{ user: User }> { return new Promise((resolve, reject) => { setTimeout(() => { if (email.includes(&#39;@&#39;)) { resolve({ user: { id: Math.floor(Math.random() * 1000), name: &#39;张三&#39;, email, role: email.includes(&#39;admin&#39;) ? &#39;admin&#39; : &#39;user&#39; } }) } else { reject(new Error(&#39;无效的邮箱地址&#39;)) } }, 1000) }) } // 暴露状态和方法 return { currentUser, loading, error, isAdmin, fullUserInfo, login, logout, updateUser } }) ``` ### 类型定义 (types.ts) ```typescript // src/types/index.ts export interface User { id: number name: string email: string role: &#39;admin&#39; | &#39;user&#39; | &#39;guest&#39; } export interface Product { id: number name: string price: number description: string } ``` ### 在组件中使用组合式Store ```vue <!-- src/components/UserProfileComposition.vue --> <script setup lang="ts"> import { useUserStore } from &#39;@/stores/user&#39; import { storeToRefs } from &#39;pinia&#39; import { ref } from &#39;vue&#39; const userStore = useUserStore() const { currentUser, isAdmin, loading } = storeToRefs(userStore) // 表单状态 const email = ref(&#39;user@example.com&#39;) const password = ref(&#39;password123&#39;) // 登录函数 const handleLogin = () => { userStore.login(email.value, password.value) } </script> <template> <div class="user-profile"> <h2>组合式API风格示例</h2> <div v-if="loading">登录中...</div> <template v-else> <div v-if="currentUser"> <h3>{{ currentUser.name }}</h3> <p>邮箱: {{ currentUser.email }}</p> <p>角色: {{ currentUser.role }} <span v-if="isAdmin">(管理员)</span></p> <div class="form-group"> <label>更新用户名:</label> <input type="text" :value="currentUser.name" @input="userStore.updateUser({ name: ($event.target as HTMLInputElement).value })" /> </div> <button @click="userStore.logout()">退出登录</button> </div> <div v-else class="login-form"> <div class="form-group"> <label>邮箱:</label> <input type="email" v-model="email" /> </div> <div class="form-group"> <label>密码:</label> <input type="password" v-model="password" /> </div> <button @click="handleLogin">登录</button> </div> </template> </div> </template> <style scoped> .user-profile { padding: 20px; border: 1px solid #42b983; border-radius: 8px; max-width: 400px; margin: 20px auto; } .form-group { margin: 10px 0; } label { display: block; margin-bottom: 5px; font-weight: bold; } input { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; } button { margin-top: 15px; padding: 8px 16px; background-color: #42b983; color: white; border: none; border-radius: 4px; cursor: pointer; transition: background-color 0.3s; } button:hover { background-color: #3aa876; } </style> ``` ## 组合式API风格的优势 1. **更好的类型推断**:组合式API天然支持TypeScript,提供更精确的类型推断 2. **代码组织更灵活**:可以像编写Vue组件一样组织store代码 3. **更好的可组合性**:可以轻松提取和重用逻辑片段 4. **更细粒度的响应式控制**:直接使用`ref`、`computed`等响应式API 5. **更简洁的代码**:减少了`this`的使用,代码更简洁 ## 组合式API最佳实践 1. **使用`storeToRefs`**:在组件中解构store时保持响应性 ```typescript const { currentUser, loading } = storeToRefs(userStore) ``` 2. **封装复杂逻辑**:将复杂逻辑提取为独立函数 ```typescript // 在store内部 function fetchUserData() { // 复杂数据获取逻辑 } ``` 3. **使用reactive处理对象**:当状态是对象时,使用`reactive`代替多个`ref` ```typescript const userProfile = reactive({ name: &#39;&#39;, email: &#39;&#39;, preferences: {} }) ``` 4. **组合多个store**:在需要时组合多个store的功能 ```typescript const userStore = useUserStore() const cartStore = useCartStore() const combinedData = computed(() => ({ user: userStore.currentUser, cartItems: cartStore.items })) ``` ## 组合式API vs 选项式API | 特性 | 组合式API | 选项式API | |------|-----------|-----------| | 类型支持 | ⭐⭐⭐⭐ | ⭐⭐⭐ | | 代码组织 | ⭐⭐⭐⭐ | ⭐⭐⭐ | | 学习曲线 | ⭐⭐⭐ | ⭐⭐⭐⭐ | | 逻辑复用 | ⭐⭐⭐⭐ | ⭐⭐ | | 响应式控制 | ⭐⭐⭐⭐ | ⭐⭐⭐ | | 与Vue3一致性 | ⭐⭐⭐⭐ | ⭐⭐ | 组合式API风格是Pinia推荐的现代方式,特别适合Vue3项目和TypeScript用户[^1]。 ## 相关问题 1. 如何在组合式Pinia store中处理跨store的依赖关系? 2. 组合式API风格与Vue的`setup()`函数有何异同? 3. 在大型项目中如何组织多个组合式Pinia store? 4. 如何为组合式Pinia store编写单元测试? 5. 组合式Pinia store在服务端渲染(SSR)中的最佳实践是什么?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值