Skip to content

Commit

Permalink
Merge pull request #16 from filefoxper/3.1.2
Browse files Browse the repository at this point in the history
v3.1.2
  • Loading branch information
filefoxper authored Feb 28, 2021
2 parents 7f26d5b + 3937335 commit e59bc6a
Show file tree
Hide file tree
Showing 134 changed files with 11,457 additions and 2,863 deletions.
14 changes: 12 additions & 2 deletions CHANGE_LOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
## v3.1.1 2020-12-17

[bug] 已修复 reducer.update 方法中当 state 为 undefined 时,
外部 store state 与 agent state 不同步的问题。
* [bug] 已修复 reducer.update 方法中当 state 为 undefined 时,
外部 store state 与 agent state 不同步的问题。

## v3.1.2 2021-02-28

* [bug] 使用常规 debounce 代码修复原`MiddleWares.takeDebounce`因事件堆积导致的不稳定问题。
* [bug] 修复了`globalConfig`在无法获取到`window,global,self`情况下报错的问题。
* [feature] 新增`nextExperience`下一版本体验特性,并在`env`中增加了相应开启配置项。
`MiddleWare`的覆盖优先级将在`nextExperience`环境中变更为:`useMiddleWare` -> `middleWare` -> `createAgentReducer`
* [document] 更改了文档结构以方便分步阅读。
* [document] 新增了英文文档。
* [unit test] 根据文档变更,新增了中英文分离的单元测试案例。
726 changes: 56 additions & 670 deletions README.md

Large diffs are not rendered by default.

132 changes: 132 additions & 0 deletions README_zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
[![npm][npm-image]][npm-url]
[![standard][standard-image]][standard-url]

[npm-image]: https://img.shields.io/npm/v/agent-reducer.svg?style=flat-square
[npm-url]: https://www.npmjs.com/package/agent-reducer
[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square
[standard-url]: http://npm.im/standard

# agent-reducer

想要一个更简单的`reducer`?试试`agent-reducer`

`agent-reducer`能将一个模型 ( class实例或object ) 转换成`reducer` function。这个模型被称为`OriginAgent`,它必须含有一个`state`属性,用来存储需要持续维护的数据,同时它可以包含若干个方法用来处理数据分流。这些方法的返回值被称为`next state`,它将会替换原来的`state`属性值,成为处理后模型的新`state`状态数据。你可以通过使用`MiddleWare`的方式,在`next state`成为新`state`之前,拦截它,并对`next state`进行再加工,丢弃等处理,从而影响最终的新`state`数据。你也可以使用`MiddleWare`做出方法控制效果,比如:debounce ... 等。

## 使用

### 对比经典reducer用法
```typescript
import {OriginAgent} from "agent-reducer";
import {useReducer} from 'react';
import {useAgentReducer} from 'use-agent-reducer';

interface Action {
type?: 'stepUp' | 'stepDown' | 'step' | 'sum',
payload?: number[] | boolean
}

/**
* 经典的reducer写法
* @param state
* @param action
*/
const countReducer = (state: number = 0, action: Action = {}): number => {
switch (action.type) {
case "stepDown":
return state - 1;
case "stepUp":
return state + 1;
case "step":
return state + (action.payload ? 1 : -1);
case "sum":
return state + (Array.isArray(action.payload) ?
action.payload : []).reduce((r, c): number => r + c, 0);
default:
return state;
}
}

/**
* agent-reducer 模型写法与 reducer 经典很接近,
* 但因为使用 class 作为模型,所以在数据分流和处理入参方面更简单
*/
class CountAgent implements OriginAgent<number> {

state = 0;

stepUp = (): number => this.state + 1;

stepDown = (): number => this.state - 1;

step(isUp: boolean):number{
// 内部复用方法'stepUp','stepDown'不能直接产生next state
// 这些方法并不会触发 state 的 dispatch 行为
return isUp ? this.stepUp() : this.stepDown();
}

sum(...counts: number[]): number {
return this.state + counts.reduce((r, c): number => r + c, 0);
};

}

// 经典 reducer
const [ state, dispatch ] = useReducer(countReducer,0);

const handleSum = (...additions:number[]) => {
// 我们只能通过 dispatch 一个 action object 与 reducer 进行沟通
dispatch({type:'sum',payload:additions});
};

// agent-reducer
const { state:agentState, stepUp } = useAgentReducer(CountAgent);

// 通过模型获取的方法可以被直接调用,传参
// 方法中的关键词 this 已被 agent-reducer 绑定在模型上,
// 所以可以通过赋值的方式把该方法赋给任意对象,而不用担心调用时 this 出错的问题
const handleAgentSum = stepUp;
```
`agent-reducer`作为一个独立包不能直接用在类似`react``redux`系统中,我们需要构建系统接驳工具来衔接它。幸运的是我们可以找到[use-agent-reducer](https://www.npmjs.com/package/use-agent-reducer)[use-redux-agent](https://www.npmjs.com/package/use-redux-agent)这些现存的接驳工具分别衔接`react``redux`系统。如果有兴趣你也可以学习如何编写一个衔接器让`agent-reducer`接入更多的系统。

#### 使用MiddleWare:

```typescript
import {MiddleWarePresets,createAgentReducer} from 'agent-reducer';

class CountAgent implements OriginAgent<number> {

state = 0;

stepUp = (): number => this.state + 1;

stepDown = (): number => this.state - 1;

step(isUp: boolean):number{
return isUp ? this.stepUp() : this.stepDown();
}
// 如果希望使用promise resolve的数据作为下一个state,
// 你需要使用 MiddleWare 系统
async sumByRequests(): number {
const counts = await Promise.resolve([1,2,3]);
return counts.reduce((r, c): number => r + c, 0);
};

}

//使用 MiddleWarePresets.takePromiseResolve()
const {agent}=createAgentReducer(CountAgent,MiddleWarePresets.takePromiseResolve());

await agent.sumByRequests();

agent.state; // 6

```
`agent-reducer`提供了一套实用的`MiddleWare`系统,你可以从`MiddleWarePresets``MiddleWares`中挑选合适的`MiddleWare`使用,api:`middleWare``useMiddleWare``createAgentReducer`都为你提供了使用接口。当然如果有特殊需求,你也可以写一个自己的`MiddleWare`

## 连接器
1. [use-agent-reducer](https://www.npmjs.com/package/use-agent-reducer) react hook,用来代替 `react useReducer`.
2. [use-redux-agent](https://www.npmjs.com/package/use-redux-agent) react hook ,用来代替 `react-redux`.

## 文档

如果你对`agent-reducer`感兴趣,想要更深入的了解和使用它,请移步至[document](https://github.com/filefoxper/agent-reducer/blob/master/documents/zh/index.md)
44 changes: 44 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module.exports = (api) => {
return api.env('test') ?{
plugins: [
["@babel/plugin-transform-runtime"],
['@babel/plugin-proposal-export-namespace-from'],
[
'@babel/plugin-proposal-class-properties',
{loose: true},
]
],
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current'
}
}
]
]
}:{
plugins: [
["@babel/plugin-transform-runtime"],
['@babel/plugin-proposal-export-namespace-from'],
[
'@babel/plugin-proposal-class-properties',
{loose: true},
]
],
presets: [
[
'@babel/preset-env',
{
modules: false,
targets: {
"browsers": ["last 2 versions", "ie >=9"]
},
useBuiltIns: "usage",
corejs: {version: 3, proposals: true}
}
]
]
}
}
14 changes: 14 additions & 0 deletions documents/en/api/apply_middle_wares.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# applyMiddleWares(...middleWares)

It is a function for chaining `MiddleWares` together to be one `MiddleWare`.

```typescript
function applyMiddleWares(
...middleWares: (MiddleWare | LifecycleMiddleWare)[]
): MiddleWare | LifecycleMiddleWare
```
* middleWares - `MiddleWares`

You can check the [example](https://github.com/filefoxper/agent-reducer/blob/master/src/libs/middleWarePresets.ts) in our project.

Go back to [API Reference](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/api/index.md)
112 changes: 112 additions & 0 deletions documents/en/api/create_agent_reducer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# createAgentReducer( originAgent, middleWareOrEnv, env )

This function is used for changing an `OriginAgent` model to an `AgentReducer` function, and using MiddleWares on it. If you are not familiar with the concept about `OriginAgent` and `AgentReducer`, you can take a look at the [concept](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/introduction/concept.md).

```typescript
function createAgentReducer<
S,
T extends OriginAgent<S> = OriginAgent<S>
>(
originAgent: T | { new (): T },
middleWareOrEnv?: (MiddleWare & { lifecycle?: boolean }) | Env,
env?: Env
): AgentReducer<S, Action, T>
```

* originAgent - the model class or object.
* middleWareOrEnv - it is an optional param, if you want to use `MiddleWare`, it can be a `MiddleWare`, if you want to set running env without `MiddleWare`, it can be an env config.
* env - if you want to set both `MiddleWare` and running env, you can set an env config here.

Be careful, `LifecycleMiddleWare` can not work with this api directly, so, if you want to use `LifecycleMiddleWares.takeLatest`, you'd better set it with api [useMiddleWare](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/api/use_middle_ware.md) or [middleWare](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/api/middle_ware.md).

You can find how to config an env object [here](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/about_env.md) and what is `MiddleWare` [here](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/about_middle_ware.md). If you want to know how to make `AgentReducer` working with another reducer tool, please check it [here](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/with_other_reducer_tools.md).

You can check example [here](https://github.com/filefoxper/agent-reducer/blob/master/test/en/api/createAgentReducer.spec.ts).

examples about how to use `createAgentReducer`:
```typescript
import {
createAgentReducer,
middleWare,
MiddleWares,
OriginAgent
} from "agent-reducer";

describe('how to use createAgentReducer',()=>{

/**
* this is a simple count model (OriginAgent)
*/
class CountAgent implements OriginAgent<number> {
// it persist a number type state
state = 0;

constructor(state?:number) {
this.state=state||0;
}

// you can write a arrow function method
// the method returns a changed state
increase = (): number => this.state + 1;

// you can write a normal method too
decrease(): number {
return this.state - 1;
}

walk(increment: boolean): number {
return increment ? this.increase() : this.decrease();
}

// the method params is instead of 'action' in reducer, this is freedom in 'agent-reducer'
sum(...counts: number[]): number {
return this.state + counts.reduce((r, c): number => r + c, 0);
};

async sumRemoteValue(remoteValue:number){
return this.sum(remoteValue);
}

}

it('a basic usage',()=>{
// createAgentReducer has a simple reducer processor inside.
const {agent}=createAgentReducer(CountAgent);
agent.increase();
expect(agent.state).toBe(1);
});

it('input params into model by use model construct',()=>{
// createAgentReducer has a simple reducer processor inside.
// we can pass a model instance in, if we need some params for model.
const {agent}=createAgentReducer(new CountAgent(1));
agent.increase();
expect(agent.state).toBe(2);
});

it('use MiddleWare directly on createAgentReducer',async ()=>{
// createAgentReducer has a simple reducer processor inside.
const {agent}=createAgentReducer(CountAgent,MiddleWares.takePromiseResolve());
await agent.sumRemoteValue(2);
expect(agent.state).toBe(2);
});

it('use env directly on createAgentReducer',async ()=>{
// createAgentReducer has a simple reducer processor inside.
const {agent}=createAgentReducer(CountAgent,{expired:true});
await agent.increase();
expect(agent.state).toBe(0);
});

it('use both MiddleWare and env directly on createAgentReducer',async ()=>{
// createAgentReducer has a simple reducer processor inside.
const {agent}=createAgentReducer(CountAgent,MiddleWares.takePromiseResolve(),{strict:false});
await agent.sumRemoteValue(2);
expect(agent.state).toBe(2);
});

});
```
All what a `AgentReducer` function can provide have described clear in section [with other reducer tools](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/guides/with_other_reducer_tools.md), please check it in that section.

Go back to [API Reference](https://github.com/filefoxper/agent-reducer/blob/master/documents/en/api/index.md)
Loading

0 comments on commit e59bc6a

Please sign in to comment.