一个叫木头,一个叫马尾

【译】24行代码实现一个Redux

90% convention, 10% library (90%是约定,10%是代码)。

Redux 是当前最重要的 JavaScript 库之一。受到诸如 Flux 和 Elm 等前辈们的启发,Redux 由3个简单组件组成(state,actions,reducer),它的架构具有良好的扩展性。redux 也使得 JavaScript 函数式编程更加流行。

如果你是初次接触 Redux,请先移步到官方文档: Three Principles

Redux 更多地是一种约定

考虑一下这个使用 Redux 架构的计数程序。如果你心急,可以先查看该程序的 github仓库

Redux计数程序
Redux计数程序

State 存在于单个树结构中

程序的状态看起来像下面这样:

const initialState = { count0 };

Actions 用来声明状态的变化

按照 Redux 的惯例,我们并不直接修改状态

// 在Redux程序中,不要这么做
state.count = 1;

取而代之的,我们创建一些代表这些改变的 actions,供程序后序使用。

const actions = {
  increment: { type'INCREMENT' },
  decrement: { type'DECREMENT' }
};

Reducer 用来对 actions 做出反应

Redux 架构的最后一块是一个reducer,它是一个纯函数,它会基于之前的 state 和当前的 action 返回一个新的 state

const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

并未用到 Redux

不知你注意到没,我们到现在为止并没有用到 Redux 库。我们刚才只是创建了一些对象和一个函数。这就是我上面说的『90%是约定』,Redux 90% 的部分并不需要 Redux库!

让我们自己实现一个Redux库

要让这个架构能用起来,我们必须把它放在一个 store 中。我们只需实现一个函数 —— createStore

像这样:

import { createStore } from 'redux'

const store = createStore(countReducer);

store.subscribe(() => {
  console.log(store.getState());
});

store.dispatch(actions.increment);
// logs { count: 1 }

store.dispatch(actions.increment);
// logs { count: 2 }

store.dispatch(actions.decrement);
// logs { count: 1 }

下面是初始的代码段。我们需要一组监听器(listeners)以及reducer所提供的初始状态。

const createStore = (yourReducer) => {
    let listeners = [];
    let currentState = yourReducer(undefined, {});
}

每当有人订阅我们的store,这些订阅者就会被添加进 listeners 数组。这一步很重要,因为每当有人派发 actionlisteners 中的订阅者需要一一被通知到。

通过传入 undefined 和 空对象 来调用 yourReducer,我们可以方便地得到上面定义的 initialState。如此,在我们执行 store.getState() 时,它会返回一个合适的值。下面将会讲到 getState 方法。

store.getState()

该方法返回的是 store 中最新的 state。每次用户点击按钮时,我们需要根据 state 来更新UI。

const createStore = (yourReducer) => {
    let listeners = [];
    let currentState = yourReducer(undefined, {});
    
    return {
        getState() => currentState
    };
}

store.dispath(action)

这是一个以 action 作为参数的方法。它会把 actioncurrentState 传给 yourReducer ,以获取一个新的 state。然后 dispatch 会通知到所有的 store订阅者

const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState() => currentState,
    dispatch(action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    }
  };
};

store.subscribe(listener)

该方法用于,当 store 接收 action 后,你需要获得通知。获取通知后,最好使用 store.getState()`` 来拿到最新的state`,然后再更新UI。

const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState() => currentState,
    dispatch(action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe(newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l !== newListener);
      };

      return unsubscribe;
    }
  };
};

subscribe 返回了一个叫作 unsubscribe 的函数。当你不再关心 store 的变化时,可以调用它。

用起来

把它们和按钮绑定。最后的代码像下面这样。

// simplified createStore function
// 简化版的 createStore 函数
const createStore = (yourReducer) => {
  let listeners = [];
  let currentState = yourReducer(undefined, {});

  return {
    getState: () => currentState,
    dispatch: (action) => {
      currentState = yourReducer(currentState, action);

      listeners.forEach((listener) => {
        listener();
      });
    },
    subscribe: (newListener) => {
      listeners.push(newListener);

      const unsubscribe = () => {
        listeners = listeners.filter((l) => l !== newListener);
      };

      return unsubscribe;
    }
  };
};

// Redux architecture pieces
// Redux架构的各个组件
const initialState = { count: 0 };

const actions = {
  increment: { type'INCREMENT' },
  decrement: { type'DECREMENT' }
};

const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case actions.increment.type:
      return {
        count: state.count + 1
      };

    case actions.decrement.type:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
};

const store = createStore(countReducer);

// DOM elements
// DOM 元素
const incrementButton = document.querySelector('.increment');
const decrementButton = document.querySelector('.decrement');

// Wire click events to actions
// 让点击事件触发actions
incrementButton.addEventListener('click'() => {
  store.dispatch(actions.increment);
});

decrementButton.addEventListener('click'() => {
  store.dispatch(actions.decrement);
});

// Initialize UI display
// 初始化 UI展示
const counterDisplay = document.querySelector('h1');
counterDisplay.innerHTML = parseInt(initialState.count);

// Update UI when an action fires
// action触发后,更新UI
store.subscribe(() => {
  const state = store.getState();

  counterDisplay.innerHTML = parseInt(state.count);
});

再发一下我们最终的UI:

Redux计数程序
Redux计数程序

如果你对图片中的 HTML/CSS 也有兴趣,可以查看github仓库

谢谢阅读。


原文地址: How to Implement Redux in 24 Lines of JavaScript