grace-redux v1.0.7
grace-redux
- 优雅的redux
- type 常量命名空间
- action 自生成
- dispatch 条理化
- 简化 和 规范reducer、 saga 代码
温馨提示
- 安装grace-redux 之后不需要再安装 redux 和 redux-saga 这两个库。
- grace-redux 库 导出了 redux-saga 的 API ,可以直接使用。使用方式参考redux-saga 文档
- 推荐使用ts以获得更好的代码提示体验
安装
npm i grace-redux
API
GraceRedux
基类,需要子类继承 GraceRedux
并实现它的属性和方法。
代码示例
testRedux.ts
import { GraceRedux } from 'grace-redux';
// 继承 GraceRedux 类
class TestRedux extends GraceRedux <{}, {}> {
namespace = ''; // 命名空间分割符, 默认值:类名 + 8位[0-9,a-z]组成的随机字符串
constructor() {
super();
this.init(); // 初始化子类,必须在 constructor 里面执行,该方法继承父类,封装了一系列初始化动作
}
reducerTypes = {}; // 定义reducer type 常量
sagaTypes = {}; // 定义saga type 常量
quickReducer = {}; // 定义 reducer
takeEverySagas() {}; // 返回 takeEvery 监听事件
takeLatestSagas() {}; // 返回 takeLatest 监听事件
takeLeadingSagas() {}; // 返回 takeLeading 监听事件
throttleSagas() {}; // 返回 throttle 监听事件
debounceSagas() {}; // 返回 debounce 监听事件
*takeCustomSagas() {} // 返回 take 监听事件
}
const testRedux = new TestRedux(); // 在当前文件创建出实例对象,可以使得引用改文件时的对象实例名统一
export { testRedux };
GraceRedux类内置的方法
init
子类数据初始化。
必须在构造函数中调用: this.init();
代码示例
testRedux.ts
import { GraceRedux } from 'grace-redux';
// 继承 GraceRedux 类
class TestRedux extends GraceRedux <{}, {}> {
constructor() {
super();
this.init(); // 初始化子类,必须在 constructor 里面执行,该方法继承父类,封装了一系列初始化动作
}
}
const testRedux = new TestRedux(); // 在当前文件创建出实例对象,可以使得引用改文件时的对象实例名统一
export { testRedux };
常量命名空间
namespace
: 命名空间,默认值:类名 + 8位0-9,a-z组成的随机字符串(建议自己设置成文件目录地址)
reducerTypes
如果子类有创建state, 则需要实现该属性,自动生成对应的action type。调用 this.createReducerType
返回reducerTypes值。
createReducerType
子类快捷创建 Reducer Action 函数。 this.createReducerType({name: '', key1: '', key2: '', ...})
说明:
- 实参值有用的只有key,value介设置为""即可。
- 将参数key值经过处理之后生成新的值({同key: 新value})并赋值给
reducerTypes
。 - 自动生成 作用于 Reducer 的 ActionCreator 函数:
this.reducerAction.name('张三')
调用代码示例
testRedux.ts
import { GraceRedux } from 'grace-redux';
interface ReducerStates {
name: string;
age: number;
info: {addr: string};
bikes: number[];
}
// 继承 GraceRedux 类
class TestRedux extends GraceRedux <ReducerStates, {}> {
constructor() {
super();
this.init(); // 初始化子类,必须在 constructor 里面执行,该方法继承父类,封装了一系列初始化动作
}
reducerTypes = this.createReducerType({name:'', age: '', info: '', bikes: ''});
}
const testRedux = new TestRedux(); // 在当前文件创建出实例对象,可以使得引用改文件时的对象实例名统一
export { testRedux };
quickReducer
创建 reucer state
- key值与reducerTypes的key值对应
- value 值则是state的初始值 示例:
quickReducer = {
name: '',
info: { addr: '' },
};
上面的代码与下面代码作用相同
function name(state = '', action) {
switch (action.type) {
case '命名空间/name':
return action.payload;
default:
return state;
}
}
function info(state = { addr: '' }, action) {
switch (action.type) {
case '命名空间/info':
return {...state, ...action.payload};
default:
return state;
}
}
将产生的state属性 如下
{
name: '',
info: {
addr: ''
}
}
完整调用示例
testRedux.ts
import { GraceRedux } from 'grace-redux';
interface ReducerStates {
name: string;
age: number;
info: {addr: string};
bikes: number[];
}
// 继承 GraceRedux 类
class TestRedux extends GraceRedux <ReducerStates, {}> {
constructor() {
super();
this.init(); // 初始化子类,必须在 constructor 里面执行,该方法继承父类,封装了一系列初始化动作
}
reducerTypes = this.createReducerType({name: '', age: '', info: '', bikes: ''});
quickReducer = {
name: '',
age: 0,
info: { addr: '' },
bikes: [],
};
}
const testRedux = new TestRedux(); // 在当前文件创建出实例对象,可以使得引用改文件时的对象实例名统一
export { testRedux };
rootStroe.ts
import { startRedux } from 'grace-redux';
import { testRedux } from './testRedux';
const store = startRedux({ testRedux: testRedux.reducer }, [], true); // 启动 redux
将创建完整的state结构:store.getState()
{
testRedux: {
name: '',
info: {
addr: ''
}
}
}
reducerAction
reducerAction
方法,在使用this.createReducerType
时自动创建。
该方法实际上就是一个 reducer action creator
使用示例 this.reducerAction.name('张三')
。
其中的 name
属性名与在调用this.createReducerType时的对象key值一致。
该方法返回值:
{
type: '命名空间/name',
payload: '张三'
}
代码使用示例 testRedux.reducerAction.name('张三')
rootStroe.ts
import { startRedux } from 'grace-redux';
import { testRedux } from './testRedux';
const store = startRedux({ testRedux: testRedux.reducer }, [ ...testRedux.saga, ], true);
store.dispatch(testRedux.reducerAction.name('张三'));
export default store;
结果:state.testRedux.name = '张三'
。
除通过实例化对象调用。还可以在子类中调用。
例如:在saga函数中yield put(this.reducerAction.name(action.payload));
sagaTypes
如果子类有注册saga函数,则需要实现该属性。生成 action type 使用,用来与saga函数绑定。 调用 this.createSagaType
返回sagaTypes值。
createSagaType
使用方式与 createReducerType
相同,不同的是内部对数组的处理方式不同。自动生成 作用于 saga 的 ActionCreator 函数
代码示例
sagaTypes = this.createSagaType({fetchName: '', key1: '', key2: '', ...});
takeEverySagas
该方法是对 takeEvery 方法的封装。
子类需要注册的 takeEvery saga 函数 需要在这里定义
使用this.sagaTypes.fetchName
作为函数名,函数将自动与对应的 action type 绑定。
testRedux.ts
interface SagaTypes {
fetchName: string;
}
class TestRedux extends GraceRedux<{}, SagaTypes> {
...
sagaTypes = this.createSagaType({ fetchName: '', });
takeEverySagas = {
[this.sagaTypes.fetchName]: function* (action) {
const payload = action.payload;
},
...
};
}
rootStroe.ts
import { startRedux } from 'grace-redux';
import { testRedux } from './testRedux';
const store = startRedux({ testRedux: testRedux.reducer }, [ ...testRedux.saga, ], true);
store.dispatch(testRedux.sagaAction.fetchName('李四'));
export default store;
testRedux.sagaAction.fetchName('李四')
实际上返回一个action
{
type: '命名空间/fetchName',
payload: '李四'
}
触发 takerEverySaga 的 this.sagaTypes.fetchName
事件。
takeLatestSagas
该属性是对 takeLatest 方法的封装,使用方式与 takeEverySagas
属性相同
takeLeadingSagas
该属性是对 takeLeading 方法的封装,使用方式与 takeEverySagas
属性相同
throttleSagas
该属性是对 throttle 方法的封装, 函数的注册方式如下
throttleSagas = {
[this.sagaTypes.fetchBikes]: {ms: 10, gen: function* (action) {
yield put(this.reducerAction.bikes(action.payload));
}},
};
与takeEverySagas
不同的地方在于属性值是一个对象,该对象有两个属性 ms 和 gen
ms: 时间
gen: Generator 函数
触发方式与 takeEverySagas
属性相同
debounceSagas
该属性是对 debounce 方法的封装,使用方式与 throttleSagas
属性相同
*takeCustomSagas
自定义监听事件。redux-saga 帮我们实现了 五种监听事件的方式 takeEvery、takeLatest、takeLeading、throttle、debounce。如果业务上不满足,可以自定义自己的take事件类型。
如果子类有需要自定义的take事件,需要通过 在*takeCustomSagas
中注册才会被监听。
示例代码 utils.ts
import { ReduxSagaEffects } from 'grace-redux';
const { fork, take } = ReduxSagaEffects;
// 自定义take事件
export function takeEveryOne(patternOrChannel, saga, ...args) {
return fork(function* () {
while (true) {
const action = yield take(patternOrChannel);
yield fork(saga, ...args.concat(action));
}
});
}
testRedux.ts
import { GraceRedux } from 'grace-redux';
import { takeEveryOne } from './utils';
class TestRedux extends GraceRedux <{}, {fetchName: string}> {
sagaTypes = this.createSagaType({fetchName: ''});
*takeCustomSagas() {
yield takeEveryOne(this.sagaTypes.fetchName, function* (action) {
yield console.log('自定义take', action);
});
}
}
工具 API
startRedux
快捷启动redux的方法。返回启动后的store.
startRedux(reducers, sagas, dev)
参数
reducers
: object 类型,注册GraceRedux 子类的reducersagas
: 数组,注册GraceRedux 子类的sagadev
: boolean 类型, 是否为开发模式,true 为开启 chrome 的插件 React Developer Tools
返回值
store
代码示例
rootStroe.ts
import { startRedux } from 'grace-redux';
import { testRedux } from './testRedux';
const store = startRedux({ testRedux: testRedux.reducer }, [ ...testRedux.saga, ], true); // 启动 redux
quickState
快捷获取redux 的 state 的属性值
quickState(store, args, defaultValue)
参数
store
: redux启动之后返回的store对象args
: Array 'a', 'b', 'c' | string 'a.b.c'defaultValue
: 取不到值的默认值
返回值
state.a.b.c 的值 或者 defaultValue
代码示例
const cState = quickState(store, 'a.b.c', []);
ReduxSagaEffects
redux-saga/effects 的 API 集合 文档地址:redux-saga/effects
代码使用示例
import { ReduxSagaEffects } from 'grace-redux';
const { put, fork, take, select } = ReduxSagaEffects;
与下面的使用方式等价
import { put, fork, take, select } from 'redux-saga/effects';
ReduxSaga
redux-saga 的 API 集合 文档地址:redux-saga
代码使用示例
import { ReduxSaga } from 'grace-redux';
const createSagaMiddleware = ReduxSaga;
与下面的使用方式等价
import createSagaMiddleware from 'redux-saga';
各API使用方式完整的示例代码
utils.ts
import { ReduxSagaEffects } from 'grace-redux';
const { fork, take } = ReduxSagaEffects;
// 自定义take事件
export function takeEveryOne(patternOrChannel, saga, ...args) {
return fork(function* () {
while (true) {
const action = yield take(patternOrChannel);
yield fork(saga, ...args.concat(action));
}
});
}
demoRedux.ts
import { GraceRedux, ReduxSagaEffects } from 'grace-redux';
import { takeEveryOne } from './utils';
const { put, fork, take, select } = ReduxSagaEffects;
interface ReducerState {
name: string;
age: number;
info: { addr: string; }
bikes: number[]
}
interface SagaTypes {
fetchName: string;
fetchAge: string;
fetchInfo: string;
fetchBikes: string;
fetchState: string;
}
class DemoRedux extends GraceRedux<ReducerState, SagaTypes> {
namespace = 'src/routes/demo';
constructor() {
super();
this.init();
}
reducerTypes = this.createReducerType({name: '', age: '', info: '', bikes: ''});
sagaTypes = this.createSagaType({fetchName: '', fetchAge: '', fetchInfo: '', fetchBikes: '', fetchState: ''});
quickReducer = {
name: '',
age: 0,
info: { addr: '' },
bikes: [],
};
takeEverySagas(){
const that = this;
return {
[this.sagaTypes.fetchName]: function* (action) {
yield put(that.reducerAction.name(action.payload));
},
};
}
takeLatestSagas(){
const that = this;
return {
[this.sagaTypes.fetchAge]: function* (action) {
yield put(that.reducerAction.age(action.payload));
},
};
}
takeLeadingSagas(){
const that = this;
return {
[this.sagaTypes.fetchInfo]: function* (action) {
yield put(that.reducerAction.info(action.payload));
},
};
}
throttleSagas(){
const that = this;
return {
[this.sagaTypes.fetchBikes]: {ms: 10, gen: function* (action) {
yield put(that.reducerAction.bikes(action.payload));
}},
};
}
debounceSagas() {
return {
[this.sagaTypes.fetchState]: {ms: 10, gen: function* () {
const state = yield select();
console.log(state.demoRedux);
}},
};
}
*takeCustomSagas() {
yield takeEveryOne(this.sagaTypes.fetchName, function* (action) {
yield console.log('自定义take', action);
});
}
}
function takeEveryOne(patternOrChannel, saga, ...args) {
return fork(function* () {
while (true) {
const action = yield take(patternOrChannel);
yield fork(saga, ...args.concat(action));
}
});
}
const demoRedux = new DemoRedux();
export { demoRedux };
rootStroe.ts
import { startRedux } from 'grace-redux';
import { demoRedux } from './demoRedux';
const store = startRedux({ demoRedux: demoRedux.reducer }, [ ...demoRedux.saga, ], true);
store.dispatch(demoRedux.reducerAction.name('张三'));
store.dispatch(demoRedux.reducerAction.age(20));
store.dispatch(demoRedux.reducerAction.info({addr: '广东'}));
store.dispatch(demoRedux.reducerAction.bikes([1, 2]));
store.dispatch(demoRedux.sagaAction.fetchAge(30));
store.dispatch(demoRedux.sagaAction.fetchName('李四'));
store.dispatch(demoRedux.sagaAction.fetchInfo({addr: '北京'}));
store.dispatch(demoRedux.sagaAction.fetchBikes([1, 2, 3]));
store.dispatch(demoRedux.sagaAction.fetchState());
export default store;