为什么用这个,首先想要手写仓库,那么你使用的数据就必须是响应式数据,不然你改变了,页面却没有改。
而 Vue.observable 和 reactive 都是把数据变成响应式的方法
# 手写一个状态管理
# vue2 的做法是
| import Vue from 'vue' | |
| export const store=Vue.observable({ | |
| userInfo:null, | |
| }) | |
| // 定义 mutations, 修改属性 | |
| export const mutation={ | |
| setUserInfo(user){ | |
| store.userInfo=user | |
|     } | |
| } | |
| //actions 处理异步 | |
| export const actions={ | |
| async login(loginInfo){ | |
|         // 登录处理... | |
|     } | |
| } | 
但是这样做有缺陷,如果外部直接改变 store 对象里面的数据而不通过 mutations 的方法改变,那写这个 mutations 又有什么意义呢
所以我们得思考怎么把返回的 store 对象是个只读属性不让外部进行修改,要修改值只能通过 mutations 里面的方法进行修改;
于是想到了 es6 的 proxy 代理对象来进行操作,返回的是一个代理对象,mutations 修改的是这个 js 内部的对象,而不是直接修改 store 对象
修改后代码如下
| import Vue from 'vue' | |
| // 初始化数据只能内部修改,外部只能通过 mutations 中的方法才能访问 | |
| let _state = Vue.observable({ | |
| userInfo: { | |
| name:{ | |
| name1: '12323' | |
| }, | |
| }, | |
| testVal: {a: 1, b: 2} | |
| }); | |
| // 使用 proxy 代理对象更好的监听数据变化 | |
| export const state = readonlyPoxy(_state); | |
| // 在 mutations 设置值的时候设置的是 | |
| export const mutations = { | |
| setUserInfo(user) { | |
| _state.userInfo = user; | |
| }, | |
| setTest(val){ | |
| _state.testVal= val; | |
|     } | |
| } | |
| // 创建只读的代理对象 | |
| function readonlyPoxy(needProxyObj) { | |
| return new Proxy(needProxyObj, { | |
| set(target, propertyKey, value) { | |
| console.error(`${propertyKey} is readonly `) | |
| return true | |
| }, | |
| get(target, propertyKey) { | |
| if(target[propertyKey] instanceof Object){ | |
| return readonlyPoxy(target[propertyKey]) | |
|             } | |
| return Reflect.get(target, propertyKey); | |
|         } | |
| }) | |
| } | 
以上做法就相当于 vue3 中的 readonly 方法一样外部无法更改该值
# vue3 中的做法
| import {reactive, readonly} from "vue"; | |
| import {getAbout} from "../api/about/index"; | |
| // 数据初始化 | |
| const state = reactive({ | |
| isLoading: false, | |
| userInfo: "" | |
| }) | |
| //readonly 保证该数据导出是只读数据 | |
| export const aboutInfo = readonly(state); | |
| // 做一些异步处理 | |
| export async function getAboutInfo() { | |
|     //... | |
| } | 
# 在组件中直接导出使用就行
| <template> | |
| <div> | |
| <!--swig0--> | |
| </div> | |
| </template> | |
| <script> | |
| import { state, mutations } from '../store' | |
| export default { | |
| computed: { | |
| userInfo() { | |
| return state.userInfo | |
|       } | |
| }, | |
| created() { | |
| mutations.setUserInfo({ | |
| name: '子君' | |
| }) | |
|    } | |
| } | |
| </script> | 
# Vue3 如何在组件中定义单独的 store
首先,定义页面组件 store,同时引入全局 store(如果有需要用到全局 store 的话)到 modules:
| // store.jsimport global from '@/store' | |
| import { createStore } from "vuex" | |
| const store = createStore({ | |
| state: { | |
| userInfo: null | |
| }, | |
| mutations: { | |
| }, | |
| actions: {}, | |
| modules: { | |
| global | |
|     } | |
| }) | |
| export default store | 
第一种:在页面组件中引入 store 并注册:
| // page.vue | |
| <script> | |
| import { getCurrentInstance } from 'vue'; | |
| import store from './store'; | |
| const app = getCurrentInstance() | |
| app.appContext.app.use(store, 'my_child_store') | |
| </script> | 
注意:在 use 注册新的 store 时必须传入第二个参数 key 来确保注册的 store 的唯一性。
最后,在页面组件中使用单独定义的 store:
| // page_child.vue | |
| <script> | |
| import { useStore } from "vuex"; | |
| const store = useStore('my_child_store') | |
| </script> | 
第二种:直接引入,通过 provide/inject 传递给子组件
| <script> | |
| import { provide} from 'vue'; | |
| import store from './store'; | |
| provide('my_child_store', store) | |
| </script> | 
在子组件中使用 store
| <script> | |
| import { inject } from "vue"; | |
| const store = inject('my_child_store') | |
| </script> | 
# VUE3 全局 provide/inject 方式 store
思路
- 全局访问:在根组件中通过 provide 提供数据,在任意子组件中通过 inject 即可获取到相应的数据。
- 全局更新:在根组件中提供的数据通过 ref、reactive 修饰后返回的 Proxy 对象为响应式变量,当其产生变化后,组件会被重新渲染。
- 可控修改:在根组件中通过 provide 提供的数据利用 readonly 修饰,禁止外部直接修改,通过根组件将修改方法提供出来供调用。
# 创建全局状态
我们单独规划一个目录用于创建全局数据,比如我放在 src/context 路径下。
其中用户全局数据放在 src/context/user.js,本文主要是提供方案,没有写过多的复杂逻辑。
| import { reactive, provide, readonly, inject } from 'vue' | |
| // 用户全局变量命名 | |
| const UserSymbol = 'USER' | |
| // 用户全局变量(建立为响应式变量) | |
| const user = reactive({ name: '333', authKeys: [] }) | |
| // 用户变量修改方法 | |
| const login = ( loginInfo ) => { user.name = loginInfo } | |
| // 用户全局数据提供方法 | |
| export const userProvide = () => { | |
|   // 提供一个只读的变量和修改方法 | |
| provide( UserSymbol, {user: readonly(user), login} ) | |
| } | |
| // 用户全局数据注入方法 | |
| export const userInject = () => { | |
| return inject(UserSymbol) | |
| } | 
在 src/context/index.js 对用户全局数据统一管理,因为我们未来还会有其他类型或者模块的数据。
| // 导入用户模块全局数据 | |
| import { userProvide, userInject } from './data/user' | |
| // 导出相关的注入方法 | |
| export { userInject } | |
| // 导出相关的提供方法 | |
| export const contextProvider = () => { | |
| userProvide() | |
| } | 
# 根组件提供数据
App.vue 作为根组件,依照概念我们在根组件中提供的数据,整个应用的其他组件都是子组件,自然可以自由访问数据。
| // 导入全局上下文方法 | |
| import { contextProvider } from './context' | |
| import HelloWorld from './components/HelloWorld.vue' | |
| export default { | |
| name: 'App', | |
| setup() { | |
|     // 根组件注册全局上下文 | |
| contextProvider() | |
| }, | |
| components: { | |
| HelloWorld | |
|   } | |
| } | 
# 子组件读取数据
HelloWorld 子组件中尝试读取数据。
| <template> | |
|   <div class="hello"> | |
| <h1>以下为子组件</h1> | |
| <h1></h1> | |
|   </div> | |
| </template> | |
| <script> | |
| import { userInject } from '/src/context' | |
| export default { | |
| name: 'HelloWorld', | |
|   setup() { | |
|     const { user } = userInject() | |
|     return { user } | |
| } | |
| } | |
| </script> | 
结果可以成功读取到 user.name 为 333,因此整个流程是畅通的,我们成功完成了【特性 1 全局访问】。
# 可控修改
我们在子组件中导出了 user,上文我们说过为了确保可控的修改,导出的数据时只读的,我们编写一个方法尝试修改下。
| <template> | |
|   <div class="hello"> | |
| <h1>以下为子组件</h1> | |
| <h1></h1> | |
| <el-button type="primary" @click="setName()">我来修改</el-button> | |
|   </div> | |
| </template> | 
编写一个方法 setName () 如下:
| methods: | |
|   { | |
| setName() { | |
|         //user 是只读的,理论上我们无法修改 | |
| this.user.name = Math.ceil(Math.random()*1000) + '' | |
|     } | |
|   } | 
尝试修改,确实无法修改。
# 修改与更新
回顾我们在定义全局数据时,不仅导出了数据,同时暴露一个修改方法,这个方法就是用于修改数据的。
因此我们修改子组件 HelloWorld.vue 如下:
| <template> | |
| <div class="hello"> | |
| <h1>以下为子组件</h1> | |
| <h1><!--swig3--></h1> | |
| <el-button type="primary" @click="setName()">我来修改</el-button> | |
| </div> | |
| </template> | |
| <script> | |
| import { userInject } from '/src/context' | |
| export default { | |
| name: 'HelloWorld', | |
| setup() { | |
|     // 注入修改方法 | |
| const { user, login } = userInject() | |
| return { user, login } | |
| }, | |
|   methods: | |
|   { | |
| setName() { | |
|       // 调用提供的修改方法 | |
| this.login(Math.ceil(Math.random()*1000) + '') | |
|     } | |
|   } | |
| } | |
| </script> | 
