90% convention, 10% library (90%是约定,10%是代码)。
Redux 是当前最重要的 JavaScript 库之一。受到诸如 Flux 和 Elm 等前辈们的启发,Redux 由3个简单组件组成(state,actions,reducer),它的架构具有良好的扩展性。redux 也使得 JavaScript 函数式编程更加流行。
如果你是初次接触 Redux,请先移步到官方文档: Three Principles。
考虑一下这个使用 Redux 架构的计数程序。如果你心急,可以先查看该程序的 github仓库。

程序的状态看起来像下面这样:
const initialState = { count: 0 };
按照 Redux 的惯例,我们并不直接修改状态。
// 在Redux程序中,不要这么做
state.count = 1;
取而代之的,我们创建一些代表这些改变的 actions,供程序后序使用。
const actions = {
increment: { type: 'INCREMENT' },
decrement: { type: 'DECREMENT' }
};
Redux 架构的最后一块是一个reducer,它是一个纯函数,它会基于之前的 state 和当前的 action 返回一个新的 state。
state.countstate.countconst 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 库。我们刚才只是创建了一些对象和一个函数。这就是我上面说的『90%是约定』,Redux 90% 的部分并不需要 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 数组。这一步很重要,因为每当有人派发 action,listeners 中的订阅者需要一一被通知到。
通过传入 undefined 和 空对象 来调用 yourReducer,我们可以方便地得到上面定义的 initialState。如此,在我们执行 store.getState() 时,它会返回一个合适的值。下面将会讲到 getState 方法。
该方法返回的是 store 中最新的 state。每次用户点击按钮时,我们需要根据 state 来更新UI。
const createStore = (yourReducer) => {
let listeners = [];
let currentState = yourReducer(undefined, {});
return {
getState: () => currentState
};
}
这是一个以 action 作为参数的方法。它会把 action 和 currentState 传给 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 接收 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:

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