为什么用这个,首先想要手写仓库,那么你使用的数据就必须是响应式数据,不然你改变了,页面却没有改。
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>
     {{ userInfo.name }}
   </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

思路

  1. 全局访问:在根组件中通过 provide 提供数据,在任意子组件中通过 inject 即可获取到相应的数据。
  2. 全局更新:在根组件中提供的数据通过 ref、reactive 修饰后返回的 Proxy 对象为响应式变量,当其产生变化后,组件会被重新渲染。
  3. 可控修改:在根组件中通过 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>{{ user.name }}</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>{{ user.name }}</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>{{ user.name }}</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>