目录
1. Redux 是什么?
2. Redux 有什么用?
3. Redux 什么时候该用?
4. Redux 准则?
4.1. 状态管理
4.2. 不可变性
5. Redux 术语?
5.1. Actions
5.2. Action Creators
5.3. Reducers
5.4. Store
5.5. Dispatch
5.6. Selectors
6. Redux 数据流向?
7. Redux 工具包?
8. Redux 源码分析(上)
8.1. 总体目录结构
8.2. index.ts(入口)
8.3. 辅助——isPlainObject.ts
8.4. 辅助——warning.ts
8.5. 辅助——kindOf.ts
8.6. 内核——最基本的一个redux用例
8.7. 内核——createStore.ts
8.7.1. redux 的重要防御技能
8.7.2. redux 的 Observable 技能
8.7.3. 详解
8.8. 内核——bindActionCreators.ts
8.9. 内核——actionTypes.ts
8.10. 内核——combineReducers.ts
8.10.1. combineReducers 的重要防御技能
8.10.2. 详解
9. Redux 如何处理异步
9.1. reducer 中不能直接写异步逻辑
9.2. 有哪些异步处理中间件
9.3. redux-thunk
9.4. redux-promise
9.5. redux-promise-middleware
9.6. 总结一下
10. Redux 源码分析(下)
10.1. 内核——compose.ts
10.2. 内核——applyMiddleware.ts
11. Redux Toolkit
11.1. 是什么?
11.2. 包含什么?
11.3. 一个例子
1. Redux 是什么?
Redux 是JavaScript 应用的状态管理容器,提供集中式、可预测的状态管理。
Redux is a predictable state container for JavaScript apps. https://redux.js.org/introduction/getting-started
Redux is a pattern and library for managing and updating application state, using events called "actions". It serves as a centralized store for state that needs to be used across your entire application, with rules ensuring that the state can only be updated in a predictable fashion.
2. Redux 有什么用?
Redux helps you manage "global" state - state that is needed across many parts of your application.
The patterns and tools provided by Redux make it easier to understand when, where, why, and how the state in your application is being updated, and how your application logic will behave when those changes occur.
As the requirements for JavaScript single-page applications have become increasingly complicated, our code must manage more state than ever before. This state can include server responses and cached data, as well as locally created data that has not yet been persisted to the server. UI state is also increasing in complexity, as we need to manage active routes, selected tabs, spinners, pagination controls, and so on.
Managing this ever-changing state is hard. If a model can update another model, then a view can update a model, which updates another model, and this, in turn, might cause another view to update. At some point, you no longer understand what happens in your app as you have lost control over the when, why, and how of its state. When a system is opaque and non-deterministic, it's hard to reproduce bugs or add new features.
As if this weren't bad enough, consider the new requirements becoming common in front-end product development. As developers, we are expected to handle optimistic updates, server-side rendering, fetching data before performing route transitions, and so on. We find ourselves trying to manage a complexity that we have never had to deal with before, and we inevitably ask the question: is it time to give up? The answer is no.
This complexity is difficult to handle as we're mixing two concepts that are very hard for the human mind to reason about: mutation and asynchronicity. I call them Mentos and Coke. Both can be great in separation, but together they create a mess. Libraries like React attempt to solve this problem in the view layer by removing both asynchrony and direct DOM manipulation. However, managing the state of your data is left up to you. This is where Redux enters.
Following in the steps of Flux, CQRS, and Event Sourcing, Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen. These restrictions are reflected in the three principles of Redux.
3. Redux 什么时候该用?
Redux helps you deal with shared state management, but like any tool, it has tradeoffs. There are more concepts to learn, and more code to write. It also adds some indirection to your code, and asks you to follow certain restrictions. It's a trade-off between short term and long term productivity.
Redux is more useful when:
- You have large amounts of application state that are needed in many places in the app
- The app state is updated frequently over time
- The logic to update that state may be complex
- The app has a medium or large-sized codebase, and might be worked on by many people
Not all apps need Redux. Take some time to think about the kind of app you're building, and decide what tools would be best to help solve the problems you're working on.
个人体会,当你的应用:
- 状态很多、很复杂
- 一个状态可能要在多个地方使用
- 多个地方可能会更新同一个状态
- 多个地方的状态展示要能够及时得到同步
- 状态变动之间存在联动关系(比如:如果要改A,那么B、C、D也得跟着改;如果改B,那么C、D、E要跟着改)
- 存在“切面”需求
- 系统有类似“记录操作历史”的需求,系统中可能有 N 个功能点都需要提供操作日志记录功能,想在统一的地方进行处理,而不是将日志记录功能分散到 N 个功能点上。
- 比如只有在做 xx、yy、zz 等操作的时候,才需要做 kk,希望能在统一的位置上处理,而不是将逻辑分散到 xx、yy、zz 等操作处。
使用 Redux 是个好的选择
但是需要注意,如果用了 Redux,需要把应用的所有状态都存进去么?
NO. Global state that is needed across the app should go in the Redux store. State that's only needed in one place should be kept in component state.
In a React Redux app, your global state should go in the Redux store, and your local state should stay in React components.
If you're not sure where to put something, here are some common rules of thumb for determining what kind of data should be put into Redux:
- Do other parts of the application care about this data?
- Do you need to be able to create further derived data based on this original data?
- Is the same data being used to drive multiple components?
- Is there value to you in being able to restore this state to a given point in time (ie, time travel debugging)?
- Do you want to cache the data (ie, use what's in state if it's already there instead of re-requesting it)?
- Do you want to keep this data consistent while hot-reloading UI components (which may lose their internal state when swapped)?
4. Redux 准则?
4.1. 状态管理
Let's start by looking at a small React counter component. It tracks a number in component state, and increments the number when a button is clicked:
代码语言:javascript复制function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter(prevCounter => prevCounter 1)
}
// View: the UI definition
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
It is a self-contained app with the following parts:
- The state, the source of truth that drives our app;
- The view, a declarative description of the UI based on the current state
- The actions, the events that occur in the app based on user input, and trigger updates in the state
This is a small example of "one-way data flow":
- State describes the condition of the app at a specific point in time
- The UI is rendered based on that state
- When something happens (such as a user clicking a button), the state is updated based on what occurred
- The UI re-renders based on the new state
However, the simplicity can break down when we have multiple components that need to share and use the same state, especially if those components are located in different parts of the application. Sometimes this can be solved by "lifting state up" to parent components, but that doesn't always help.
One way to solve this is to extract the shared state from the components, and put it into a centralized location outside the component tree. With this, our component tree becomes a big "view", and any component can access the state or trigger actions, no matter where they are in the tree!
By defining and separating the concepts involved in state management and enforcing rules that maintain independence between views and states, we give our code more structure and maintainability.
This is the basic idea behind Redux: a single centralized place to contain the global state in your application, and specific patterns to follow when updating that state to make the code predictable.
4.2. 不可变性
"Mutable" means "changeable".If something is "immutable", it can never be changed.
JavaScript objects and arrays are all mutable by default. If I create an object, I can change the contents of its fields. If I create an array, I can change the contents as well:
代码语言:javascript复制const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3
const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'
This is called mutating the object or array. It's the same object or array reference in memory, but now the contents inside the object have changed.
In order to update values immutably, your code must make copies of existing objects/arrays, and then modify the copies.
We can do this by hand using JavaScript's array / object spread operators, as well as array methods that return new copies of the array instead of mutating the original array:
代码语言:javascript复制const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3
},
b: 2
}
const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42
}
}
const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')
// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
Redux expects that all state updates are done immutably.
5. Redux 术语?
5.1. Actions
An action is a plain JavaScript object that has a type field. You can think of an action as an event that describes something that happened in the application.
The type field should be a string that gives this action a descriptive name, like "todos/todoAdded". We usually write that type string like "domain/eventName", where the first part is the feature or category that this action belongs to, and the second part is the specific thing that happened.
An action object can have other fields with additional information about what happened. By convention, we put that information in a field called payload.
A typical action object might look like this:
代码语言:javascript复制const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
5.2. Action Creators
An action creator is a function that creates and returns an action object. We typically use these so we don't have to write the action object by hand every time:
代码语言:javascript复制const addTodo = text => {
return {
type: 'todos/todoAdded',
payload: text
}
}
5.3. Reducers
A reducer is a function that receives the current state and an action object, decides how to update the state if necessary, and returns the new state: (state, action) => newState.
Reducers must always follow some specific rules:
- They should only calculate the new state value based on the state and action arguments
- They are not allowed to modify the existing state. Instead, they must make immutable updates, by copying the existing state and making changes to the copied values.
- They must not do any asynchronous logic, calculate random values, or cause other "side effects"
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/increment') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value 1
}
}
// otherwise return the existing state unchanged
return state
}
5.4. Store
The current Redux application state lives in an object called the store.
The store is created by passing in a reducer, and has a method called getState that returns the current state value:
代码语言:javascript复制import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
5.5. Dispatch
The Redux store has a method called dispatch. The only way to update the state is to call store.dispatch() and pass in an action object. The store will run its reducer function and save the new state value inside, and we can call getState() to retrieve the updated value:
代码语言:javascript复制store.dispatch({ type: 'counter/increment' })
console.log(store.getState())
// {value: 1}
We typically call action creators to dispatch the right action:
代码语言:javascript复制const increment = () => {
return {
type: 'counter/increment'
}
}
store.dispatch(increment())
console.log(store.getState())
// {value: 2}
5.6. Selectors
Selectors are functions that know how to extract specific pieces of information from a store state value. As an application grows bigger, this can help avoid repeating logic as different parts of the app need to read the same data:
代码语言:javascript复制const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
6. Redux 数据流向?
Earlier, we talked about "one-way data flow", which describes this sequence of steps to update the app:
- State describes the condition of the app at a specific point in time
- The UI is rendered based on that state
- When something happens (such as a user clicking a button), the state is updated based on what occurred
- The UI re-renders based on the new state
For Redux specifically, we can break these steps into more detail:
- Initial setup:
- A Redux store is created using a root reducer function
- The store calls the root reducer once, and saves the return value as its initial state
- When the UI is first rendered, UI components access the current state of the Redux store, and use that data to decide what to render. They also subscribe to any future store updates so they can know if the state has changed.
- Updates:
- Something happens in the app, such as a user clicking a button
- The app code dispatches an action to the Redux store, like dispatch({type: 'counter/increment'})
- The store runs the reducer function again with the previous state and the current action, and saves the return value as the new state
- The store notifies all parts of the UI that are subscribed that the store has been updated
- Each UI component that needs data from the store checks to see if the parts of the state they need have changed.
- Each component that sees its data has changed forces a re-render with the new data, so it can update what's shown on the screen
Here's what that data flow looks like visually:
7. Redux 工具包?
- React-Redux:Redux can integrate with any UI framework, and is most frequently used with React. React-Redux is our official package that lets your React components interact with a Redux store by reading pieces of state and dispatching actions to update the store.
- Redux Toolkit:Redux Toolkit is our recommended approach for writing Redux logic. It contains packages and functions that we think are essential for building a Redux app. Redux Toolkit builds in our suggested best practices, simplifies most Redux tasks, prevents common mistakes, and makes it easier to write Redux applications.
- Redux DevTools Extension:The Redux DevTools Extension shows a history of the changes to the state in your Redux store over time. This allows you to debug your applications effectively, including using powerful techniques like "time-travel debugging".
8. Redux 源码分析(上)
本文分析的源码是
目前Redux的最新发布版本
4.0.4
8.1. 总体目录结构
8.2. 入口——index.ts
8.3. 辅助——isPlainObject.ts
8.4. 辅助——warning.ts
8.5. 辅助——kindOf.ts
8.6. 内核——最基本的一个redux用例
代码语言:javascript复制import { createStore, combineReducers, bindActionCreators } from 'redux';
/**
* Initial State & Reducer & Selector & Action Creators
*/
// Initial State
const counterInitialState = {
value: 0,
};
// Reducer
function counterReducer(state = counterInitialState, action: any) {
const { type, payload } = action;
switch (type) {
case 'counter/INC': {
return {
...state,
value: state.value payload,
};
}
case 'counter/DEC': {
return {
...state,
value: state.value - payload,
};
}
default:
return state;
}
}
// Selector
export const selectCount = (state: any) => state.count.value;
// Action Creators
function inc() {
return {
type: 'count/INC',
payload: 1,
};
}
function dec() {
return {
type: 'count/DEC',
payload: 1,
};
}
/**
* Another one
* Initial State & Reducer & Selector & Action Creators
*/
// Initial State
const timeInitialState = {
value: 0,
};
// Reducer
function timeReducer(state = timeInitialState, action: any) {
const { type, payload } = action;
switch (type) {
case 'time/UPDATE': {
return {
...state,
value: payload,
};
}
default:
return state;
}
}
export const selectTime = (state: any) => state.time.value;
function updateTime() {
return {
type: 'time/UPDATE',
payload: new Date().toLocaleTimeString(),
};
}
// combine reducer
const rootReducer = combineReducers({
count: counterReducer,
time: timeReducer,
});
// Create a store
const store = createStore(rootReducer);
// subscribe store change event
store.subscribe(() => {
const state = store.getState();
const count = selectCount(state);
const time = selectTime(state);
console.log(`count: ${count}, time: ${time}.`);
});
// bind Dispatch & Action Creator
const dispatchCounterInc = bindActionCreators(inc, store.dispatch);
const dispatchCounterDec = bindActionCreators(dec, store.dispatch);
const dispatchTimeUpdate = bindActionCreators(updateTime, store.dispatch);
// Dispatch actions
(async () => {
// 可以这样:
dispatchCounterInc();dispatchTimeUpdate();await delay(1000);
dispatchCounterInc();dispatchTimeUpdate();await delay(1000);
dispatchCounterDec();dispatchTimeUpdate();await delay(1000);
// 也可以这样:
store.dispatch(inc());store.dispatch(updateTime());await delay(1000);
store.dispatch(inc());store.dispatch(updateTime());await delay(1000);
store.dispatch(dec());store.dispatch(updateTime());await delay(1000);
})();
// some utils for test purpose
function delay(count: number) {
return new Promise((resolve) => setTimeout(resolve, count));
}
8.7. 内核——createStore.ts
8.7.1. redux 的重要防御技能
createStore.ts 中有一些逻辑,是为了能在不恰当使用 redux 时给出异常提示。
- 在 reducer 中调用 store.getState()
- 在 reducer 中调用 store.subscribe 订阅或取消订阅事件
- 在 reducer 中调用 store.dispatch 发送 Action
在 Redux 的设计中,reducer 应该是个纯函数、能重入、不应该有副作用。所以 redux 会当你在 reducer 中调用 getState、dispatch、subscribe、unsubscribe 时给出异常提示。
上述这些功能是 isDispatching变量负责实现
createStore.ts 中还有有一些逻辑,是为了即使是不恰当地使用 redux,也能规避掉错误。
- 通知订阅者的过程中发生了其他的订阅和退订
上述功能是 ensureCanMutateNextListeners 负责实现
8.7.2. redux 的 Observable 技能
redux 的 store 也实现了 tc39/proposal-observable 接口,方便与 Rxjs 等库对接。
8.7.3. 详解
8.8. 内核——bindActionCreators.ts
用于把 store.dispatch 和 action creator 整合起来,方便使用。
8.9. 内核——actionTypes.ts
8.10. 内核——combineReducers.ts
当业务应用变得复杂,我们就需要对 reducer 函数进行拆分,拆分后的每一块独立负责管理 state 的一部分。combineReducers() 的作用就是把由多个不同 reducer 函数作为 value 的 object 合并成为一个总的 root reducer 函数。然后可以对这个 root reducer 调用 createStore()。合并后的 root reducer 可以调用各个子 reducer,并把他们的结果合并成一个 state 对象。
8.10.1. combineReducers 的重要防御技能
- combineReducers 传入的对象没有包含 reducer...
- state 中的数据,不是一个 PlainObject
- state 中的字段,比reducer能处理的字段少...
上述功能由
getUnexpectedStateShapeWarningMessage 负责实现
- reducer 在处理不属于自己的 Action 时,未返回当前 state
上述功能由
assertReducerShape
负责实现
8.10.2. 详解
9. Redux 如何处理异步
9.1. 异步逻辑应该写在哪?
异步逻辑不能直接写在 reducer 中
异步逻辑应放置在
Redux中间件中处理
!! Middleware !!
Redux中间件实质是
store.dispatch函数的增强器
它们拦截特定的Action
并在其中把带有副作用的工作完成
(例如:异步处理、日志记录...)
9.2. 有哪些异步处理中间件
redux-thunk:不到10行代码,精简到炸! redux-promise:基于Promise的异步处理; redux-promise-middleware:还是Promise; redux-saga:最优雅!最复杂!
9.3. redux-thunk
redux-thunk 中间件扩展了 redux 的 dispatch 功能,它允许你 dispatch 一个函数(即:thunk),异步逻辑就放在这个函数中处理;
源码:
示例:注意现在 dispatch 的是一个函数
代码语言:javascript复制import { createStore, combineReducers, bindActionCreators, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
/**
* Initial State & Reducer & Selector & Action Creators
*/
// Initial State
const counterInitialState = {
value: 0,
};
// Reducer
function counterReducer(state = counterInitialState, action: any) {
const { type, payload } = action;
switch (type) {
case 'count/INC': {
return {
...state,
value: state.value payload,
};
}
default:
return state;
}
}
// Selector
export const selectCount = (state: any) => state.count.value;
// Action Creators
function inc() {
return {
type: 'count/INC',
payload: 1,
};
}
function asyncInc(){
return (dispatch: any, getState: any) => {
setTimeout(() => {
dispatch(inc());
}, 5 * 1000);
};
}
// combine reducer
const rootReducer = combineReducers({
count: counterReducer,
});
// apply Middleware
const enhancer = applyMiddleware(thunkMiddleware);
// Create a store
const store = createStore(rootReducer, enhancer);
store[Symbol.observable]().subscribe({
next(state: any) {
const count = selectCount(state);
console.log(count);
},
});
// Dispatch actions
(async () => {
store.dispatch(asyncInc());
})();
9.4. redux-promise
源码:
9.5. redux-promise-middleware
源码:
示例:
代码语言:javascript复制import { createStore, combineReducers, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
/**
* Initial State & Reducer & Selector & Action Creators
*/
// Initial State
const counterInitialState = {
value: 0,
loading: false,
};
// Reducer
function counterReducer(state = counterInitialState, action: any) {
const { type, payload } = action;
switch (type) {
case 'count/INC_PENDING': {
return {
...state,
loading: true
};
}
case 'count/INC_FULFILLED': {
return {
...state,
value: state.value payload,
loading: false
};
}
default:
return state;
}
}
// Action Creators
function asyncInc() {
return {
type: 'count/INC',
payload: new Promise((resolve) => setTimeout(()=>{
resolve(10)
}, 5000)),
};
}
// combine reducer
const rootReducer = combineReducers({
count: counterReducer,
});
// apply Middleware
const enhancer = applyMiddleware(promiseMiddleware);
// Create a store
const store = createStore(rootReducer, enhancer);
store[Symbol.observable]().subscribe({
next(state: any) {
console.log(state.count);
},
});
// Dispatch actions
(async () => {
store.dispatch(asyncInc());
})();
9.6. 总结一下
redux:
- redux 自身只支持 dispatch 普通的 action
store.dispatch({type: "INC", payload:....});
redux-thunk:
- redux-thunk 允许 dispatch 一个 thunk 函数;
store.dispatch((dispatch, getState) => { ... })
redux-promise:
- redux-promise 允许 dispatch 一个 payload 为 Promise 的 action;
store.dispatch({type:"INC", payload: Promise});
redux-promise-middleware:
- redux-promise-middleware 同样允许 dispatch 一个 payload 为 Promise 的 action,但是它在 action 的处理上更加细致
store.dispatch({type:"INC", payload: Promise});
redux-saga:
- redux-saga 是功能强大、复杂的异步中间件:
- redux-saga 没有变动 action 的本来含义,仍然是一个普通的 JS 对象;
- 有强大的异步流程控制功能(例:takeEvery、takeLatest);
- 异步业务逻辑被集中在 Saga 中管理;
- 没有修改 action 原本的含义;
- 结合 Generator、Promise 特性,用同步的方式书写异步代码;
store.dispatch({type:"INC", payload:...});
10. Redux 源码分析(下)
10.1. 内核——compose.ts
compose 是函数式编程中的组合,compose 将 chain 中的所有匿名函数,[f1, f2, ... , fx, ..., fn],组装成一个新的函数,即新的 dispatch,当新 dispatch 执行时,[f1, f2, ... , fx, ..., fn],从右到左依次执行( 所以顺序很重要)。
以当 compose 执行完后,我们得到的 dispatch 是这样的,假设 n = 3。
代码语言:javascript复制dispatch = f1(f2(f3(store.dispatch))))
10.2. 内核——applyMiddleware.ts
applyMiddleware 是 Redux 官方附带的一个 Store 增强器。
回顾一下 createStore 中的 enhancer 增强技能
使用示例
applyMiddleware 机制的核心在于组合 compose,将不同的 middlewares 一层一层包裹到原生的 dispatch 之上,而为了方便进行 compose,需对 middleware 的设计采用柯里化 curry 的方式,达到动态产生 next 方法以及保持 store 的一致性。
middleware 中 next 与 dispatch 间的关系图:
11. Redux Toolkit
11.1. 是什么?
Redux Toolkit 是官方提供的为简化 Redux 开发而退出的工具箱。
Redux Toolkit was originally created to help address three common concerns about Redux: "Configuring a Redux store is too complicated" "I have to add a lot of packages to get Redux to do anything useful" "Redux requires too much boilerplate code" https://redux.js.org/redux-toolkit/overview
11.2. 包含什么?
Redux Toolkit includes:
- A configureStore() function with simplified configuration options. It can automatically combine your slice reducers, adds whatever Redux middleware you supply, includes redux-thunk by default, and enables use of the Redux DevTools Extension.
- A createReducer() utility that lets you supply a lookup table of action types to case reducer functions, rather than writing switch statements. In addition, it automatically uses the immer library to let you write simpler immutable updates with normal mutative code, like state.todos[3].completed = true.
- A createAction() utility that returns an action creator function for the given action type string. The function itself has toString() defined, so that it can be used in place of the type constant.
- A createSlice() function that accepts a set of reducer functions, a slice name, and an initial state value, and automatically generates a slice reducer with corresponding action creators and action types.
- The createSelector utility from the Reselect library, re-exported for ease of use.
11.3. 一个例子
代码语言:javascript复制import { createSlice, configureStore } from '@reduxjs/toolkit';
const delay = (count: number) => new Promise((resolve) => setTimeout(resolve, count));
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
INC: (state, action) => {
state.value = action.payload;
},
DEC: (state, action) => {
state.value -= action.payload;
},
},
});
const { INC, DEC } = counterSlice.actions;
const timeSlice = createSlice({
name: 'time',
initialState: {
value: 0,
},
reducers: {
UPDATE: (state, action) => {
state.value = action.payload;
},
},
});
const { UPDATE } = timeSlice.actions;
const store = configureStore({
reducer: {
counter: counterSlice.reducer,
time: timeSlice.reducer,
},
});
store.subscribe(() => {
const state = store.getState();
console.log(state);
});
// Dispatch actions
(async () => {
store.dispatch(INC(1));
store.dispatch(UPDATE(new Date().toLocaleTimeString()));
await delay(1000);
store.dispatch(INC(1));
store.dispatch(UPDATE(new Date().toLocaleTimeString()));
await delay(1000);
store.dispatch(DEC(1));
store.dispatch(UPDATE(new Date().toLocaleTimeString()));
await delay(1000);
})();
参考:
《深入React技术栈》 https://github.com/reduxjs/redux https://github.com/reduxjs/redux-thunk https://github.com/evgenyrodionov/redux-logger