新手入门Pinia状态管理库,看完这篇文章你就懂了!

什么是Pinia?

  • Pinia本质上依然是一个状态管理的库,它允许你跨组件/页面进行状态共享。

Pinia和Vuex的区别?

  • 与Vuex相比,Pinia提供了一个更简单的API,具有更少的规范,提供了Composition-API风格的API
  • 最重要的是,在与TypeScript一起使用时具有可靠的类型推断支持

和Vuex相比,Pinia有什么优势?

  • mutations不再存在,只有state,gettes,actions。
  • 更友好的TypeScript支持。
  • 不再有modules的嵌套结构,每个store都是独立的,互不影响。
  • 没有命名空间模块。
  • 无需动态添加 Store,默认情况下它们都是动态的。
  • 不再需要注入、导入函数、调用函数。
  • 支持插件来扩展自身功能。
  • 支持服务端渲染(SSR)。

上面列出的只是Pinia的一些主要的优点,还有其它的优点,需要大家在使用的时候慢慢去体会,下面一起来看看Pinia是如何使用的。

如何使用Pinia?

安装Pinia
yarn add pinia
或者
npm install pinia
创建Pinia
// src/stores/index.js
import { createPinia } from "pinia"
const pinia = createPinia ()
export default pinia
挂载Pinia
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'

import pinia from './stores'
const app = createApp(App)
app.use(pinia)

app.mount('#app')
定义一个Store
  • 我们需要知道 Store 是使用 defineStore() 定义的。
  • 并且它需要一个唯一名称,作为第一个参数传递。
  • 这个name,也称为id,是必需的,Pinia使用它来将store连接到devtools。
  • 返回的函数统一使用useXXX作为命名方案,这是约定的一种规范。
// src/stores/counter.js
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
    count: 1
  })
})

export default useCounter

image.png

使用Store
  • store在它被使用之前是不会被创建的,我们可以通过use函数来使用store。
// src/views/Home.vue
<script setup>
import useCounter from '@/stores/counter'
const counterStore = useCounter()
</script>

<template>
  <div class="home">
  	<h2>count: {{ counterStore.count }}</h2>
  </div>
</template>
解构Store
注意:
  • 使用传统的方式将属性从store中解构出来,虽然可以获取到值,但是不具有响应式。
// src/views/Home.vue
<script setup>
import useCounter from '@/stores/counter'
const counterStore = useCounter()

// 将counter从store中解构出来,此时我们再去修改count,count的值可以修改,但是页面的值不会发生变化,不具有响应式
const { count } = counterStore
cosnt incrementCount = () => {
  counterStore.count++
}
</script>

<template>
  <div class="home">
  	<!-- count值不会随着counter++响应式更新 -->
  	<h2>count: {{ counterStore.count }}</h2>
  	<button @click="incrementCount">count++</button>
  </div>
</template>

6.gif

  • 为了从store中解构属性的时候保持其响应式,我们可以使用pinia提供的storeToRefs函数。
// src/views/Home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useCounter from '@/stores/counter'
const counterStore = useCounter()

// 使用storeToRefs解构
const { count } = storeToRefs(counterStore)
cosnt incrementCount = () => {
  counterStore.count++
}
</script>

<template>
  <div class="home">
  	<!-- 使用storeToRefs解构 -->
  	<h2>count: {{ count }}</h2>
  	<button @click="incrementCount">count++</button>
  </div>
</template>
  • 或者使用vue提供的toRefs函数。
// src/views/Home.vue
<script setup>
import { toRefs } from 'pinia'
import useCounter from '@/stores/counter'
const counterStore = useCounter()

// 使用toRefs 解构
const { count } = toRefs(counterStore)
cosnt incrementCount = () => {
  counterStore.count++
}
</script>

<template>
  <div class="home">
  	<!-- 使用toRefs 解构 -->
  	<h2>count: {{ count }}</h2>
  	<button @click="incrementCount">count++</button>
  </div>
</template>

8.gif

Pinia中操作state
  • 默认情况下,可以通过store.属性名来修改state。
  • 可以通过调用store上的$reset()方法将state重置到其初始值。
  • 如果想要一次修改多个state,可以调用store上的$patch方法。
// src/views/Home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useUser from '@/stores/user'
const userStore = useUser()

const { name, age, height } = storeToRefs(userStore)

// 修改state,一个个修改
cosnt changeState = () => {
  userStore.name = "jackson"
  userStore.age = 25
  userStore.height = 1.8
}

// 重置state
const resetState = () => {
  userStore.$reset()
}

// 修改state,一次修改多个
const changeMoreState = () => {
  userStore.$patch({
	name: "jackson",
	age: 25,
	height: 1.8
  })
}
</script>

<template>
  <div class="home">
  	<h2>name: {{ name }}</h2>
  	<h2>age: {{ age }}</h2>
  	<h2>height: {{ height }}</h2>
  	<button @click="changeState">修改state</button>
  	<button @click="resetState">重置state</button>
  	<button @click="changeMoreState">修改多个state</button>
  </div>
</template>

// sre/stores/user.js
import { defineStore } from 'pinia'

const useUser = defineStore("user", {
  state: () => ({
	name: "james",
	age: 18,
	height: 1.7
  })
})

export default useUser

12.gif

Pinia中的getters
定义和访问getter
  • getters相当于store的计算属性。
  • getters中可以接收一个state作为参数的函数。
// sre/stores/counter.js
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
    count: 1,
    firstName: "Jackson",
    lastName: "vision",
  }),
  getters: {
	doubleCount: (state) => state.count * 2,
	fullName: (state) => `${state.firstName} ${state.lastName}`,
  }
})

export default useCounter
// src/views/Home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useCounter from '@/stores/counter'
const counterStore = useCounter()

</script>

<template>
  <div class="home">
  	<h2>doubleCount: {{ counterStore.doubleCount }}</h2>
  	<h2>fullName: {{ counterStore.fullName}}</h2>
  </div>
</template>
  • 在一个getters方法中也可以调用其它的getters方法。下图中,我们定义了一个 doubleCountAddOne 的getter方法,在该方法中,我们通过this去调用了另一个getter方法 doubleCount,这里的this指向的是store实例。
// src/views/home.vue
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
    count: 1
  }),
  getters: {
	doubleCount: (state) => state.count * 2,
	doubleCountAddOne() {
	  return this.doubleCount + 1
	}	
  }
})

export default useCounter
  • getters也支持返回一个函数,这样就可以接收参数。

什么是Pinia?

  • Pinia本质上依然是一个状态管理的库,它允许你跨组件/页面进行状态共享。

Pinia和Vuex的区别?

  • 与Vuex相比,Pinia提供了一个更简单的API,具有更少的规范,提供了Composition-API风格的API
  • 最重要的是,在与TypeScript一起使用时具有可靠的类型推断支持

和Vuex相比,Pinia有什么优势?

  • mutations不再存在,只有state,gettes,actions。
  • 更友好的TypeScript支持。
  • 不再有modules的嵌套结构,每个store都是独立的,互不影响。
  • 没有命名空间模块。
  • 无需动态添加 Store,默认情况下它们都是动态的。
  • 不再需要注入、导入函数、调用函数。
  • 支持插件来扩展自身功能。
  • 支持服务端渲染(SSR)。

上面列出的只是Pinia的一些主要的优点,还有其它的优点,需要大家在使用的时候慢慢去体会,下面一起来看看Pinia是如何使用的。

如何使用Pinia?

安装Pinia
yarn add pinia
或者
npm install pinia
创建Pinia
// src/stores/index.js
import { createPinia } from "pinia"
const pinia = createPinia ()
export default pinia
挂载Pinia
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'

import pinia from './stores'
const app = createApp(App)
app.use(pinia)

app.mount('#app')
定义一个Store
  • 我们需要知道 Store 是使用 defineStore() 定义的。
  • 并且它需要一个唯一名称,作为第一个参数传递。
  • 这个name,也称为id,是必需的,Pinia使用它来将store连接到devtools。
  • 返回的函数统一使用useXXX作为命名方案,这是约定的一种规范。
// src/stores/counter.js
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
    count: 1
  })
})

export default useCounter

image.png

使用Store
  • store在它被使用之前是不会被创建的,我们可以通过use函数来使用store。
// src/views/Home.vue
<script setup>
import useCounter from '@/stores/counter'
const counterStore = useCounter()
</script>

<template>
  <div class="home">
  	<h2>count: {{ counterStore.count }}</h2>
  </div>
</template>
解构Store
注意:
  • 使用传统的方式将属性从store中解构出来,虽然可以获取到值,但是不具有响应式。
// src/views/Home.vue
<script setup>
import useCounter from '@/stores/counter'
const counterStore = useCounter()

// 将counter从store中解构出来,此时我们再去修改count,count的值可以修改,但是页面的值不会发生变化,不具有响应式
const { count } = counterStore
cosnt incrementCount = () => {
  counterStore.count++
}
</script>

<template>
  <div class="home">
  	<!-- count值不会随着counter++响应式更新 -->
  	<h2>count: {{ counterStore.count }}</h2>
  	<button @click="incrementCount">count++</button>
  </div>
</template>

6.gif

  • 为了从store中解构属性的时候保持其响应式,我们可以使用pinia提供的storeToRefs函数。
// src/views/Home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useCounter from '@/stores/counter'
const counterStore = useCounter()

// 使用storeToRefs解构
const { count } = storeToRefs(counterStore)
cosnt incrementCount = () => {
  counterStore.count++
}
</script>

<template>
  <div class="home">
  	<!-- 使用storeToRefs解构 -->
  	<h2>count: {{ count }}</h2>
  	<button @click="incrementCount">count++</button>
  </div>
</template>
  • 或者使用vue提供的toRefs函数。
// src/views/Home.vue
<script setup>
import { toRefs } from 'pinia'
import useCounter from '@/stores/counter'
const counterStore = useCounter()

// 使用toRefs 解构
const { count } = toRefs(counterStore)
cosnt incrementCount = () => {
  counterStore.count++
}
</script>

<template>
  <div class="home">
  	<!-- 使用toRefs 解构 -->
  	<h2>count: {{ count }}</h2>
  	<button @click="incrementCount">count++</button>
  </div>
</template>

8.gif

Pinia中操作state
  • 默认情况下,可以通过store.属性名来修改state。
  • 可以通过调用store上的$reset()方法将state重置到其初始值。
  • 如果想要一次修改多个state,可以调用store上的$patch方法。
// src/views/Home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useUser from '@/stores/user'
const userStore = useUser()

const { name, age, height } = storeToRefs(userStore)

// 修改state,一个个修改
cosnt changeState = () => {
  userStore.name = "jackson"
  userStore.age = 25
  userStore.height = 1.8
}

// 重置state
const resetState = () => {
  userStore.$reset()
}

// 修改state,一次修改多个
const changeMoreState = () => {
  userStore.$patch({
	name: "jackson",
	age: 25,
	height: 1.8
  })
}
</script>

<template>
  <div class="home">
  	<h2>name: {{ name }}</h2>
  	<h2>age: {{ age }}</h2>
  	<h2>height: {{ height }}</h2>
  	<button @click="changeState">修改state</button>
  	<button @click="resetState">重置state</button>
  	<button @click="changeMoreState">修改多个state</button>
  </div>
</template>

// sre/stores/user.js
import { defineStore } from 'pinia'

const useUser = defineStore("user", {
  state: () => ({
	name: "james",
	age: 18,
	height: 1.7
  })
})

export default useUser

12.gif

Pinia中的getters
定义和访问getter
  • getters相当于store的计算属性。
  • getters中可以接收一个state作为参数的函数。
// sre/stores/counter.js
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
    count: 1,
    firstName: "Jackson",
    lastName: "vision",
  }),
  getters: {
	doubleCount: (state) => state.count * 2,
	fullName: (state) => `${state.firstName} ${state.lastName}`,
  }
})

export default useCounter
// src/views/Home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useCounter from '@/stores/counter'
const counterStore = useCounter()

</script>

<template>
  <div class="home">
  	<h2>doubleCount: {{ counterStore.doubleCount }}</h2>
  	<h2>fullName: {{ counterStore.fullName}}</h2>
  </div>
</template>
  • 在一个getters方法中也可以调用其它的getters方法。下图中,我们定义了一个 doubleCountAddOne 的getter方法,在该方法中,我们通过this去调用了另一个getter方法 doubleCount,这里的this指向的是store实例。
// src/views/home.vue
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
    count: 1
  }),
  getters: {
	doubleCount: (state) => state.count * 2,
	doubleCountAddOne() {
	  return this.doubleCount + 1
	}	
  }
})

export default useCounter
  • getters也支持返回一个函数,这样就可以接收参数。
// src/stores/user.js
import { defineStore } from 'pinia'

const useUser = defineStore("user", {
  state: () => ({
	users: [
	  { id: 1, name: "james", age: 38 },
	  { id: 2, name: "jackson", age: 25 },
	  { id: 3, name: "kurry", age: 34 },
    ]
  }),
  getters: {
	getUserById: (state) => {
	  return userId => {
	    return state.users.find(item => item.id === userId)
      }
	}
  }
})

export default useUser
// src/views/Home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useUser from '@/stores/user'
const userStore = useUser() 
</script>

<template>
  <div class="home">
    <h2>查找userId等于1的用户:{{ userStore.getUsersById(1) }}</h2>
  </div>
</template>
  • getters也可以访问其它store中的getters。
// sre/stores/counter.js
import { defineStore } from 'pinia'
import useUser from './user'

const useCounter = defineStore("counter", {
  state: () => ({
	count: 1
  }),
  getters: {
	showMessage: (state) => {
      const userStore = useUser()
      return `name: ${ userStore.name } - count: ${ state.count }`
    }
  }
})

export default useCounter
// sre/stores/user.js
import { defineStore } from 'pinia'
const useUser = defineStore("user", {
  state: () => ({
  	name: "james",
  })
})

export default useUser
// src/views/Home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useCounter from '@/stores/counter'
const counterStore = useCounter() 
</script>

<template>
  <div class="home">
    <h2>showMessage:{{ counterStore.showMessage }}</h2>
  </div>
</template>
Pinia中的actions
  • actions相当于组件中的methods。
  • 和getters一样,在action中可以通过this访问整个store实例的所有操作。
  • 并且actions中是支持异步操作的,我们可以在actions中编写异步函数,在函数中使用await。
// src/stores/counter.js
import { defineStore } from 'pinia'

const useCounter = defineStore("counter", {
  state: () => ({
	count: 1
  }),
  actions: {
  	increment() {
      this.count++
    }
  }
})

export default useCounter
// src/views/home.vue
<script setup>
import { storeToRefs } from 'pinia'
import useCounter from '@/stores/counter'
const counterStore = useCounter()

const changeCount = () => {
  counterStore.increment()
}
</script>

<template>
  <div class="home">
  	<h2>count: {{ counterStore.count }}</h2>
  	<button @click="changeCount">修改count</button>
  </div>
</template>
总结

总的来说,Pinia的语法更加的简洁,极易上手,学起来吧!