0. 什么是Redux?
一套状态管理工具。
Redux is a predictable state container for JavaScript apps.
Redux是JavaScript应用程序的可预测状态容器。
1. 你不一定需要Redux
阮一峰老师在他的Redux系列教程里面对于你是否需要Redux有过非常精辟的论述。
如果你不知道是否需要 Redux,那就是不需要它。
只有遇到 React 实在解决不了的问题,你才需要 Redux 。
这个是阮一峰老师的系列教程,一共有两篇,感兴趣的小伙伴可以去看一下,讲的非常好,不过是基于ReactJs讲的,我们这篇文章会基于React+Typescript来讲解他的用法。
2. Store, Action, Reducer
2.1 Store
就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
每一个Component都将自己的状态数据存放到Store中,当Store中的状态发生改变,会下发给每一个Connect到Store的Component,调用Component的render方法。
按照Redux的官方文档,Store中的State是只读的,如果要修改State的状态,只能通过Reducer生成新的State实例。
2.2 Action
每一个Component只能通过发送Action到Store来修改Store中的State。Action是一个数据结构,可以参考这个规范来定义Action的结构。
对于Action的定义,一般会按照如下方式定义:
export interface Action {
// Action的Type一般是一个字符串常量,Reducer会使用这个Type来修改状态
type: string;
// Payload的类型与Component的State的类型对应
payload: number;
}
- Action Creator 一般会创建一个纯函数来创建一个View相对应的Action。
export function onLike(count: number): LikeAction {
return {
type: FETCH_LIKE_COUNT,
payload: count
};
}
- 发送Action
一般会通过调用
store.dispatch(action)
,将action发送给Store,store将action分发给对应的reducer来处理。不过在本篇的Demo中,我们使用了typescript推荐的connect方式,所以dispatch
的方式略有不同,后续会通过Demo详细介绍。
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) => ({
onLike: (count: number) => dispatch(onLike(count))
});
export const LikeWrapper = connect(mapStateToProps, mapDispatchToProps)(Like);
2.3 Reducer
在上一节中,提到了Reducer,这一节来详细介绍Reducer。
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
Reducer会接受两个参数,一个是
state
,另一个是action
。下面的例子展示了如何编写一个Reducer
函数。
export function likeReducer(state = initState, action: any): LikeState {
let result = initState;
// 前面提到,Reducer会根据action.type返回各种新的state。
// 按照Redux的官方文档,state是只读的,不能修改,只能生成新的state。
switch (action.type) {
case FETCH_LIKE_COUNT:
result = {
...state,
count: action.payload
};
break;
default:
result = state;
break;
}
return result;
}
3. Redux工作流程
下面这个图展示了从Component生成一个Action开始,如何更新State,之后如何根据new-state更新Component的UI。
sequenceDiagram
Component->>Component: create action1
Component->>Store: dispatch(action1)
Store->>Reducers: send action1 to <br/> specific reducer
Reducers->>Reducers: Generate new State <br/> by action1.type
Reducers->>Store: update(new-state)
Store->>Component: new-state
Component->>Component: merge props with new-state, call render()
4. 异步
根据上图,其实有一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。
为了解决异步的问题,Redux引入了中间件的概念。
4.1 中间件
其实各种平台都有中间件的概念,核心思想就是把所有要做的事情编成队列,一个一个执行。这里也是一样的,其实就是把Action分解成不同的阶段,放到队里额里面,然后由第三方的中间件,进行dispatch。
在阮一峰老师的教程里面,介绍了两种第三方中间件,一个是thunk一个是promise,个人感觉promise比较好用,而且网络少大部分教程都是介绍thunk中间件,很少有介绍promise中间件的,所以,我们这篇文档就基于promise中间件来实现我们的demo。
下一篇文章我们将介绍如何使用thunk中间件进行异步操作。
5. 实现
本篇文章的示例代码repo:gitlab.com/yafeya/reac…
下面我们分步骤讲解一下。
5.1 安装
npm i redux react-redux redux-promise-middleware
5.2 为每一个component定义State
, Action
, Reducer
这个demo中,对于每一个component,我都新建了一个叫做[Component]Redux.ts
的文件,存放这个component
所有的actions
,state
,以及reducer
。
其实,同步或者异步的差别主要在于ActionCreator
。
Action Creator | |
---|---|
同步 | 直接返回Action |
Promise Middleware | 直接返回Action,Action的Payload类型必须是Promise< RealType >。 |
Thunk Middleware | 返回ThunkAction,ThunkAction泛型,有四个泛型参数, 分别为: 1. 最后Dispatch的Action的Promise类型 2. 最后Dispatch的Action的Payload类型 3. Action Creator接收的参数类型 4. 最后Dispatch的Action类型。 我们将在下一篇文档中详细描述。 |
-
Like Component(同步)
// LikeRedux.ts export interface LikeState { count: number; } export const FETCH_LIKE_COUNT = 'FETCH_LIKE_COUNT'; export interface LikeAction { type: string; payload: number; } export function onLike(count: number): LikeAction { return { type: FETCH_LIKE_COUNT, payload: count }; } const initState: LikeState = { count: 0 }; export function likeReducer(state = initState, action: any): LikeState { let result = initState; switch (action.type) { case FETCH_LIKE_COUNT: result = { ...state, count: action.payload }; break; default: result = state; break; } return result; }
-
Clock Component(Promise Middleware)
Promise Middleware 有一个特别之处,在于在
ActionCreator
中你需要创建Action.Type
, 然后Promise Middleware会为你创建3个additional action,然后这3个Action由promise middlewaredispatch给reducer。作为开发者只需要在reducer中处理相应的action,创建不同的state即可。Additional Action类型分别为:
- *_PENDING // 表示正在获取state
- *_FULFILLED // 表示已经获取到state
- *_REJECTED // 表示获取state失败
export interface ClockState { time: Date; isFetching: boolean; succeed: boolean; } export const FETCH_TIME = 'FETCH_TIME'; export const FETCH_TIME_PENDING = 'FETCH_TIME_PENDING'; export const FETCH_TIME_FULFILLED = 'FETCH_TIME_FULFILLED'; export const FETCH_TIME_REJECTED = 'FETCH_TIME_REJECTED'; export interface FetchTimeAction { type: string; payload: Promise<Date>; } // Mock Api function getTimeAsync(): Promise<Date> { return new Promise<Date>((resolve, reject) => { resolve(new Date()); }); } export function fetchTime(): FetchTimeAction { return { type: FETCH_TIME, payload: getTimeAsync() }; } const initState: ClockState = { time: new Date(), isFetching: false, succeed: true }; export function clockReducer(state = initState, action: any): ClockState { let result = initState; switch (action.type) { case FETCH_TIME_PENDING: result = { ...state, isFetching: true }; break; case FETCH_TIME_FULFILLED: result = { ...state, isFetching: false, succeed: true, time: action.payload }; break; case FETCH_TIME_REJECTED: result = { ...state, isFetching: false, succeed: false }; break; default: result = state; break; } return result; }
5.3 在Compnent中Connect
Redux引入了一个语法糖为connect
方法,下面我们对它最有用的前两个参数和返回值进行说明。
// Clock.tsx
interface ClockProps {
time: Date;
fetchTime(): void;
}
export const Clock: React.FC<ClockProps> = (props) => {
const clockRef = useRef<HTMLParagraphElement>(null);
useEffect(() => {
setInterval(() => props.fetchTime(), 1000);
}, [clockRef]);
return <p ref={clockRef}>The current time is {props.time.toLocaleTimeString()}</p>;
};
const mapStateToProps = (state: State) => {
const { time } = state.clock;
return { time };
}
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) => ({
fetchTime: () => dispatch(fetchTime())
});
export const ClockWrapper = connect(mapStateToProps, mapDispatchToProps)(Clock);
- mapStateToProps
将reducer返回的new-state merge到component的
props
中,component
如果需要展示数据,都是用props
里面的数据进行render。 - mapDispatchToProps
将
dispatch
方法merge到props的方法中,之前我们提到过,redux的思想就是,整个redux就是一个状态机,状态的改变只能通过dispatch(action)
,而发起这个动作的只能是UX,也就是component
。所以Component
需要调用props
中merge的方法dispatch action
。 - 返回值
返回一个新的
Component
, 提供给App.tsx
引用。// Asp.tsx export class App extends Component { render() { return ( <div className="App"> <header className="App-header"> <Image src={logo} alt="logo"></Image> <Paragraph part1="Edit" code="src/App.tsx" part2="and save to reload."></Paragraph> <HyperLink href="https://reactjs.org" title="Learn React"></HyperLink> <LikeWrapper></LikeWrapper> // ClockWrapper 就是我们上面的返回值 <ClockWrapper></ClockWrapper> </header> </div> ); } }
5.4 Index.tsx
中启动Redux
const appReducer = combineReducers({
clock: clockReducer,
like: likeReducer
});
const middleware = [promise];
const store = createStore(appReducer, applyMiddleware(...middleware));
ReactDOM.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
document.getElementById('root')
);
- 添加middleware
const middleware = [promise];
- Combine Reducer
const appReducer = combineReducers({ clock: clockReducer, like: likeReducer });
- Create Store
const store = createStore(appReducer, applyMiddleware(...middleware));
- 添加Provider
<Provider store={store}> <React.StrictMode> <App /> </React.StrictMode> </Provider>
5.5 在Component
中使用state
的数据
我们肯定希望在自己的Component
使用相对应的Reducer
创建的state
。举例来说,上面例子中,我们希望在ClockComponent
中使用ClockReducer
创建的state
。
之前在Clock Component
我们也看到了,Clock
中使用了state.clock
访问他自己的state
,这是因为,我们在combineReducers
时,将clockReducer
生成的state
放到了clock
下面,将likeReducer
生成的state
放到了like
下面。
因此,在component
中使用时,可以通过state.clock
访问clockReducer
生成的state
,通过state.like
访问likeReducer
生成的state
。
6 什么时候使用Redux
讲了这么半天,估计大家应该也看明白了,既然Redux
增加了项目的复杂度,为什么还要使用Redux
呢?因为,普通的React
应用程序中,很难在Component
之间share
状态,Redux
提供给我们一个方法,可以在各个Component
之间share
状态,并且,可以进行状态的修改。
希望这篇帖子对大家有帮助,下一篇文章,我们将给大家share
如何使用thunk
中间件。