1. What is Redux
A Predictable State Container for JS Apps
Redux
In the official introduction, it is described that it is an ongoing 可预测
library . It guarantees that programs behave consistently and are easy to test.状态管理
JS
Why we need Redux
- The need for global state management
- Sharing of data between different components
- Ensure that the program behavior is consistent and easy to test.
Three principles
-
single source of truth
- The entire application's
state
is stored in oneobject tree
, and thisobject tree
one exists in only onestore
.
- The entire application's
-
State
is read-only- The only way to
state
change is to triggeraction
, whichaction
is an ordinary object that describes an event that has occurred.
- The only way to
-
Use pure functions to perform modifications
- In order to describe
action
how to changestate tree
, you need to writereducers
.
- In order to describe
Second, the basic use of Redux
Here we briefly introduce Redux
the basic usage, you can refer to redux.js.org .
Since it Redux
is not only supported React
, or even has React
nothing to do with it, the above tutorial only explains the use of the JS
following Redux
, and we will combine React
it for demonstration.
2.1 Install dependencies
Under our project, newly install Redux
related dependencies.
npm install redux
2.2 Build the store
store
Build according to the official documentation . (Note: The official recommends that we use it, but it is still adopted here ).@reduxjs/toolkit
redux
import { AnyAction, createStore } from 'redux';
function counterReducer(state = 0, action: AnyAction) {
switch (action.type) {
case 'incremented':
return state + 1;
case 'decremented':
return state - 1;
default:
return state;
}
}
const store = createStore(counterReducer);
export default store;
2.3 Components used
Only class components are used for discussion here.
import React, { Component } from 'react';
import store from '../store';
class ReduxExample extends Component {
subscribe: any = null;
componentDidMount() {
this.subscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
this.subscribe();
}
incremented = () => {
store.dispatch({ type: 'incremented' });
}
decremented = () => {
store.dispatch({ type: 'decremented' });
}
render() {
return (
<div>
count: {store.getState()}
<div>
<button onClick={this.incremented}>+</button>
<button onClick={this.decremented}>-</button>
</div>
</div>
);
}
}
export default ReduxExample;
3. Workflow of Redux
我们在了解Redux
的工作流程的时候,我们先明白几个前置概念。
概念
State
State (also called the state tree) is a broad term, but in the Redux API it usually refers to the single state value that is managed by the store and returned by
getState()
. It represents the entire state of a Redux application, which is often a deeply nested object.
状态在Redux
中通常被Store
所管理,并通过getState
暴露得到。实际上,我们在页面/全局管理中,我们所使用的就是变量/状态。
Action
An action is a plain object that represents an intention to change the state. Actions are the only way to get data into the store. Any data, whether from UI events, network callbacks, or other sources such as WebSockets needs to eventually be dispatched as actions.
Action
主要提供修改store
中的state
的能力。Action
必须有对应的type
。
Reducer
A reducer (also called a reducing function) is a function that accepts an accumulation and a value and returns a new accumulation. They are used to reduce a collection of values down to a single value.
reducer
是一个函数,它接受一个初始值,并返回一个新的值。实质上是对State
的修改。
Dispatching Function
A dispatching function (or simply dispatch function) is a function that accepts an action or an async action; it then may or may not dispatch one or more actions to the store.
dispatch
可以接收一个action
, 告诉store
要进行具体的action
,之后store
会通知reduces
进行更新,并把更新值返回给store
。
Action Creator
An action creator is, quite simply, a function that creates an action.
这里很直白,是一个用于创建action
的方法、
流程
这里的store
你可以理解为存储中心,变量的最新状态都可以在这里去到。
Component
通过store
暴露的getState
接口获取最新的state
值。
Component
可以通过dispatch
,并传入对应的action
,通知store
需要做更新。
store
接收通知之后,让reducers
进行操作,并返回最新的值给store
。
Component
通过store
暴露的getState
接口获取最新的state
值。
四、实现简易的Redux
下面,我们就实现一个简易的redux
,目前仅支持同步的版本,后续会更新迭代。
确定返回值
我们使用redux
一般进行的是如下的操作,有getState
,dispatch
,subscribe
等操作。
const store = createStore(xxxReducer);
const state = store.getState();
store.dispatch({ type: 'xxxx' });
store.subscribe(() => {}) ;
于是,我们的redux
函数可以返回为
function createStore(reduceer) {
return {
getState,
dispatch,
subscribe
};
}
我们编写对应的类型。
export interface Dispatch<A extends AnyAction> {
(params: A): void;
}
export interface Action<T = any> {
type: T
}
export interface AnyAction<T = any> extends Action<T> {
[extraProps: string]: any
}
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S
export interface Store<T extends any, A extends AnyAction> {
getState: () => T | undefined;
subscribe: (fn: () => void) => () => void;
dispatch: Dispatch<A>;
}
详细功能实现
上方,我们讲到了我们需要去返回getState
,dispatch
,subscribe
等函数。
我们先看整体,在来拆解,整体代码结构如下图所示。
import { Reducer, Store, Dispatch, Action, AnyAction } from './typings';
export function createStore<S, A extends AnyAction>(reducer: Reducer<S, A>): Store<S, A> {
let currentState: S | undefined;
let currentListeners: Array<() => void> = [];
const getState = () => {};
const subscribe: Store<S, A>['subscribe'] = (fn) => {
}
const dispatch: Dispatch<A> = (action) => {
}
return {
getState,
subscribe,
dispatch,
}
}
上图中,我们用来currentState
来进行状态保存,同时使用currentListeners
存放订阅函数。
getState--获取最新状态值
这里实现比较简单,直接把currentState
返回即可。
const getState = () => currentState;
dispatch--通知修改
这里的我们的dispatch
会接收对应的action
,之后产生最新状态值的工作会交给reducer
。
const dispatch: Dispatch<A> = (action) => {
currentState = reducer(currentState, action);
currentListeners.forEach(listener => listener());
}
subscribe--订阅修改的回调
这里需要注意以下几点
- 这里订阅的回调是在
state
发生修改后返回的。
- 需要返回一个解绑回调的函数。
- 可以存在多个回调。
const subscribe: Store<S, A>['subscribe'] = (fn) => {
currentListeners.push(fn);
return () => {
const index = currentListeners.findIndex(fn);
currentListeners.splice(index, 1);
}
}
整体实现
代码
src\lib\redux-nut\index.ts
import { Reducer, Store, Dispatch, Action, AnyAction } from './typings';
export function createStore<S, A extends AnyAction>(reducer: Reducer<S, A>): Store<S, A> {
let currentState: S | undefined;
let currentListeners: Array<() => void> = [];
const getState = () => currentState;
const subscribe: Store<S, A>['subscribe'] = (fn) => {
currentListeners.push(fn);
return () => {
const index = currentListeners.findIndex(fn);
currentListeners.splice(index, 1);
}
}
const dispatch: Dispatch<A> = (action) => {
currentState = reducer(currentState, action);
currentListeners.forEach(listener => listener());
}
dispatch({ type: 'SystemInit' } as A);
return {
getState,
subscribe,
dispatch,
}
}
src\lib\redux-nut\typings.ts
export interface Dispatch<A extends AnyAction> {
(params: A): void;
}
export interface Action<T = any> {
type: T
}
export interface AnyAction<T = any> extends Action<T> {
[extraProps: string]: any
}
export type Reducer<S = any, A extends Action = AnyAction> = (
state: S | undefined,
action: A
) => S
export interface Store<T extends any, A extends AnyAction> {
getState: () => T | undefined;
subscribe: (fn: () => void) => () => void;
dispatch: Dispatch<A>;
}
src\store\index.ts
import { Action } from './../lib/redux-nut/typings';
import { createStore } from '../lib/redux-nut';
function counterReducer(state = 0, action: Action<'incremented' | 'decremented'>) {
switch (action.type) {
case 'incremented':
return state + 1;
case 'decremented':
return state - 1;
default:
return state;
}
}
const store = createStore(counterReducer);
export default store;
src/pages/ReduxExample.tsx
import React, { Component } from 'react';
import store from '../store';
class ReduxExample extends Component {
subscribe: any = null;
componentDidMount() {
this.subscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
this.subscribe();
}
incremented = () => {
store.dispatch({ type: 'incremented' });
}
decremented = () => {
store.dispatch({ type: 'decremented' });
}
render() {
return (
<div>
count: {store.getState()}
<div>
<button onClick={this.incremented}>+</button>
<button onClick={this.decremented}>-</button>
</div>
</div>
);
}
}
export default ReduxExample;
效果
总结
这里我们简单的了解了redux
的大概原理,也实现了一个最简单的redux
。当然有很多其他的知识和概念没有提交,如中间件
等,实际上redux
还有很多知识点在里面,这些内容会在后续进行更新。