前言

前几天打算使用React开发一个博客系统,由于有许多数据都是要共用的,比如Tags,Categories等,而React相比Vue会组件嵌套的情况会更严重,如果将数据一个个转发势必会造成代码逻辑过于复杂,耦合度过高,所以,我们需要全局状态管理。React有一个好伙伴Redux,Redux是一个用于应用程序状态管理的开源JavaScript库。但是Redux相对复杂,需要编写太多模板代码,而博客系统这种轻应用并没必要使用Redux。这时就需要自制一个简单的Store来管理全局状态。

分析

在正式编写代码之前我们需要先分析一下我们的Store需要什么功能,以及结构。

首先Store需要有State来存储数据,Action来触发全局的事件,以及对State的更改,我们并不需要Reduce,有Reduce可以很好的分辨State的变动,但是我们的应用并没有这么复杂,所以并不需要多一步Reduce来改变State。

由于我使用过Redux和Vuex,通过对比可以发现Vuex的Action调用相对简单灵活,但是并不能很好的应对同步的场景,所以为了后期的开心编码,我们需要结合二者的一些优点。

React在16.x添加了Context,使得我们在组件树中传递数据能够简单的实现,我们只需要将root组件的状态作为全局状态通过Context传递下去即可。

实现

首先,我们需要初始化State和Action,或许你会对下方的this感到疑惑,耐心看完下面的代码就能理解了。

// initialStore.js

export const initialState = {
    count: 1
}

export const initialActions = {
    addCount() {
        this.setState({ ...this.state, count: this.state.count + 1 });
    }
}

然后我们需要导入initialStore并创建Context,同时导出Context,用于子组件

import React from 'react';
import { initialActions, initialState } from './initialStore';

export const StoreContext = React.createContext({
  state: initialState,
  actions: initialActions
});

这时我们就能创建一个Store组件来存储和转发Context

export default class StoreProvider extends React.Component {
  constructor(props) {
    super(props);
    this.state = initialState;
    this.actions = initialActions;
    for (const key in this.actions) {
      this.actions[key] = this.actions[key].bind(this);
    }
  }

  render() {
    return (
      <StoreContext.Provider
        value={{
          state: this.state,
          actions: this.actions
        }}
      >
        {this.props.children}
      </StoreContext.Provider>
    );
  }
}

使用Class组件的原因是为了将该组件的this绑定到Action上,这样Action就能通过this.state和this.setState操作读取State了,你也可以使用Hook来达到相同的目的,只是需要利用useRef来防止函数读取到旧的状态,使用起来比Class复杂,所以这里直接使用Class了。

改进

使用全局状态管理在调试的时候不太容易跟踪状态的变化,所以我们需要在状态变化的时候打印变动信息,让调试更方便。

export default class StoreProvider extends React.Component {
  constructor(props) {
    super(props);
    this.state = initialState;
    if (process.env.NODE_ENV === 'development') {
      this.defSetState = this.setState;
      this.setState = this.debugSetState;
    }
    this.actions = initialActions;
    for (const key in this.actions) {
      this.actions[key] = this.actions[key].bind(this);
    }
  }

  debugSetState(newState) {
    try {
      throw new Error('[!] - 检查到未被移除的Log调用:');
    } catch (e) {
      console.log({
        oldState: this.state,
        newState: newState,
        dispatchAction: e.stack
      });
    }
    if (window.__REACT_DISPATCH_SHOW_TRACE__) {
      console.trace();
    }
    this.defSetState(newState);
  }

  render() {
    return (
      <StoreContext.Provider
        value={{
          state: this.state,
          actions: this.actions
        }}
      >
        {this.props.children}
      </StoreContext.Provider>
    );
  }
}

在setState注入输出日志,我们就能在控制台清晰的看到状态的改变,以及执行栈。

那么,如何使用呢?

使用其实就是从Context提取出State和Action,把它们当成变量和函数执行即可

const store = useContext(StoreContext);
const { count } = store.state;
const { addCount } = store.actions;

//...

<button onClick={() => addCount()}>{count}</button>
说点什么
本博客评论规则(评论规则什么的都是浮云,小声
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...