20行代码实现React全局状态共享

1,583 阅读4分钟

最近在使用 redux 做项目时,发现其 connect , reducer 等写法十分让人苦恼。给我们带来便利的同时产生了大量的模板代码。虽然一些大佬觉得其设计非常有逻辑且易维护,但其大量的模板代码及设计思路确实会让一些人心烦意乱。我思考了一下,有没有办法可以跳过 dispath 直接修改其值就能更新视图,以及不使用 connect 注入直接 import 导入状态使用。于是我便朝着这个方向去尽可能的实现一个 react 全局共享状态的方法。

首先全局共享即所有组件都可访问的数据,这个并不难。只需定义一个引用类型的数据,可以让其他组件访问到就行。例如:

 const stateModel = {
     count : 0
 }
function App() {
   return (
   <div>
     {stateModel.count}
   </div>
   );
}

接下来就是修改数据更新视图了。要监听到对象的数据变化我首先想到了 Proxy。我们创建一个函数 observe。通过他实现数据的访问控制。

function observe(state){
    return new Proxy(state,{
        set(target,key,value){
            // 组件即将更新
        }
    })
}

const stateModel = observe({
    count : 0
})

接下来就是更新组件了,目前更新 react 组件的方法有 通过父级的传过来的 props (目前大部分 react 状态管理都是这种方式 )、或者通过 context 、react hook 函数、或者是 react 类组件的 component.forceUpdate() 方法。考虑到日前使用 react hook 组件的人占大多数(我本人也习惯使用 hook组件)。我想通过 react hook函数实现状态更新。版本1的 observe函数来了。

function observe(state){
    const [r,reflash] = useState(0);
    return new Proxy(state,{
        set(target,key,value){
            state[key] = value;
            reflash(r + 1);
            return true;
        }
    })
}

带入到 App 组件测试

  const stateModel = observe({
      count : 0
  })
 function App() {
    return (
    <div>
      {stateModel.count}
    </div>
    );
}

当我满怀期待的进行测试时,得到了 react 亲切的问候。

QQ截图20210322202539.png 小丑竟是我自己,我忘记了 hook 函数必须在函数组件中才能被调用。所以我修改了策略。调用 observe时返回一个包含 hook 函数的函数。

function observe(state){
    return ()=>{
         const [r,reflash] = useState(0);
        return new Proxy(state,{
            set(target,key,value){
                state[key] = value;
                reflash(r + 1);
                return true;
            }
        })
    }

}

再在 app 组件中执行。

const stateModel = observe({
  count : 0
})
function App() {
    const state = stateModel()
    return (
    <div>
      {state.count}
      <button onClick={()=>state.count++}>add count</button>
    </div>
    );
}

测试成功 !!!!。目前点击增加 count 没问题。那么是否就真的没问题了呢。看文章接下来的字数就知道没这么简单。我们尝试把 add 按钮单独放入一个组件。

function App() {
    const state = stateModel()
    return (
    <div>
      {state.count}
      <AddBtn/>
    </div>
    );
}
const AddBtn = ()=>{
    const state = stateModel()
    return <button onClick={()=>state.count++}>add count</button>
}

测试你会发现,count 无法正常的增加。发生甚么事了,为什么何在一起可以分开就不行了呢。这是因为你每次调用 stateModel 就会返回不同的 new Proxy,App 组件的 state 和 AddBtn 组件的 state 看视一样实则完全是不同的两变量。AddBtn 中的 state 发生改变无法影响App 组件的 state。那么我们要让他们是同一个人。所以版本3诞生了。

const observe = (state)=>{
    const reflashs;
    const stateProxy = new Proxy(state,{
        set(target,key,value){
            state[key] = value;
            const [r,reflash] = reflash;
            reflash(r + 1);
            return true;
        }
    })
    return ()=>{
        reflashs = useState(0)
        return stateProxy
    }
}

再次测试,发现 count 还是没有增加。再仔细看代码,直接对自己说句我是撒比。对呀之前的new Proxy 返回的不是同一个地址。这次 useState 返回的也不是同一个 hook 函数,这样他也无法更新其他组件。再次修改得到最终版本。

const observe = (state)=>{
    const reflashMap = new Map();
    const stateProxy = new Proxy(state,{
        set(target,key,value){
            state[key] = value;
            for(const [r,reflash] of reflashMap.values()){
                reflash(r+1);
            }
            return true;
        }
    })
    return (key)=>{
        reflashMap.set(key,useState(0))
        return stateProxy
    }
}

function App() {
    const state = stateModel('App')
    return (
    <div>
      {state.count}
      <AddBtn/>
    </div>
    );
}
const AddBtn = ()=>{
    const state = stateModel('AddBtn')
    return <button onClick={()=>state.count++}>add count</button>
}

16行代码实现 observe 函数 react 全局状态共享。其中 reflashMap 用于存储每个组件的 key 及对应的渲染函数,这个 key 可以调用stateModel时传进来,每个组件拥有不同的 key。这篇文章我不仅讲了其实现原理,也讲了我实现的过程,只是希望大家也能多思考每一步代码究竟是怎么来的。 我沿着这个思路,再进行优化及添加了一些功能,开源了一个工具。 地址:https://www.npmjs.com/package/erwin