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.count
state.count
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 库。我们刚才只是创建了一些对象和一个函数。这就是我上面说的『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仓库。
谢谢阅读。