前言
在学习Redux之前,我希望做出一个精简版的redux。这能帮助我们更好地理解redux。
为什么我们需要redux
Redux 的创造者 Dan Abramov 说过:“只有遇到react解决不了的问题,你才需要redux”。
因此,redux不是必须,我们需要知道,什么时候我们才需要redux,像一些业务逻辑简单的应用,状态不会多到难以管理,那我们就不需要redux,相反,这种情况下引入redux会使你的应用变的难看,难以阅读。
redux的作用就在于它能集中的管理状态,让我们更能以数据驱动的方式思考应用构建,因此,很多时候,redux还是很有必要的。
redux有几个重要的概念,Store,Action,Reducer,这里不在赘述,我们马上就会讲到它,接下来我们试着实现一个基于typescript精简版的redux。
实现一个精简版的redux
在实现之前,我们需要简单的说明一下redux的三个核心。
Store
store能集中的管理状态,它好似一个状态的数据库,但是每个状态都是只读的,如果你需要修改它,你必须创建一个描述,让Reducer来修改它
Action
action就是一个描述,它默认带有两个字段,type和payload,type是必须的,payload却不是必须的,type描述需要怎样修改,payload翻译过来是有效载荷,在程序设计中,可以把payload理解为有效数据。
Reducer
reducer是可以用来改变state的一个对象,它接收一个state和一个action作为参数,action描述怎样去更新state,redux并不赞成直接修改state,所以reducer会作为一个pure function 存在。
Start
在开始之前,我们确定我们的这个demo是实现一个简单的计数器。
首先我们必须明确state是什么类型。
创建一个state来对它进行约束,明确的指出state带有一个count的属性
interface AppState{
count:number;
}
接下来我们创建一个用于描述的Action,
拥有一个type属性用于描述更新类型,
payload作为可选属性,类型可以是任意类型
interface Action{
type:string,
payload?:any
}
创建完了Action和state,接下来我们可以试着写一个reducer
interface Reducer<T>{
(state:T,action:Action):T
}
创建一个具体的reducer
let reducer:Reducer = (state:AppState,action:Action) => {
switch(action.type){
case "INCREMENT":
return Object.assign({},state,{count:state.count + 1} as AppState);
case "DECREMENT"
return Object.assign({},state,{count:state.count - 1} as AppState);
default:
return state;
}
}
reducer内部用了一个switch语句,用于进行多态分发,值得注意的是,reducer是一个pure function,它不试图改变state的值,而是用Object.assign进行合并返回一个新的对象,这符合函数式编程,这样做的一个大好处是我们可以很简单的实现撤销/恢复,不会改变状态的值,因此可以避免很多莫名其妙的错误。
store
store的创建,让我们可以保存自己的状态,对状态进行集中的管理,下面就来写一个具体的store
class Store<T>{
private state:T;
constructor(private reducer:Reducer, state:T){
this.state = state;
}
dispatch(action:Action){
this.state = this.reducer(this.state,action);
}
getState():T{
return this.state;
}
}
到现在为止,我们已经创建出了一个精简版的redux,我们进行尝试
//创建store
let store:Store<AppState> = new Store(reducer,{count:0} as AppState);
store.dispatch({type:"INCREMENT"} as Action);
console.log(store.getState()); //1
我们可以发现已经成功了。但是我们发现这并不能为我们带来许多的便利,很大的一个原因是我们整个demo的目的是要做出一个计时器,整个应用也只有一个状态需要管理,如果这是一个真实的例子,那么我们这样无疑是小题大做。
另一方面,我们希望在dispatch进行分发过后马上通知自己,而不用再去打印getState的值。我们可以添加一些监听者。
没有多麻烦,我们只需要修改一下store即可,在此之前,我们需要一个listener
它形如
它只是一个普通的无参函数,没有什么可讨论的
interface Listener{
():void;
}
现在再来修改store
class Store<T>{
private state:T;
//存储listener的容器
private listeners:Array<Listener> = [];
constructor(private reducer:Reducer, state:T){
this.state = state;
}
dispatch(action:Action){
this.state = this.reducer(this.state,action);
//分发过后遍历整个监听器容器并执行
this.listeners.forEach(l => l());
}
getState():T{
return this.state;
}
//订阅
subscribe(listener:Listener){
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
}
}
}
现在再试试看?
//创建store
let store:Store<AppState> = new Store(reducer,{count:0} as AppState);
let unsubscribe = store.subscribe(() => {
console.log(store.getState());
});
store.dispatch({type:"INCREMENT"} as Action); //1
store.dispatch({type:"INCREMENT"} as Action); //2