https://github.com/filefoxper/agent-reducer
This is a model container for javascript or typescript app
https://github.com/filefoxper/agent-reducer
agent class class-reducer javascript middleware model model-management model-sharing reducer state state-management typescript
Last synced: about 1 month ago
JSON representation
This is a model container for javascript or typescript app
- Host: GitHub
- URL: https://github.com/filefoxper/agent-reducer
- Owner: filefoxper
- License: mit
- Created: 2020-06-11T04:16:03.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2022-12-07T03:12:27.000Z (almost 3 years ago)
- Last Synced: 2024-08-13T06:03:07.279Z (about 1 year ago)
- Topics: agent, class, class-reducer, javascript, middleware, model, model-management, model-sharing, reducer, state, state-management, typescript
- Language: TypeScript
- Homepage:
- Size: 702 KB
- Stars: 6
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGE_LOG.md
- License: LICENSE.md
Awesome Lists containing this project
README
[![npm][npm-image]][npm-url]
[![NPM downloads][npm-downloads-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
[npm-downloads-image]: https://img.shields.io/npm/dm/agent-reducer.svg?style=flat-square# agent-reducer
`agent-reducer` is a model container for Javascript apps.
It helps you write applications with a micro `mvvm` pattern and provides a great developer experience, you can see details [here](https://filefoxper.github.io/agent-reducer/#/).
## Other language
[中文](https://github.com/filefoxper/agent-reducer/blob/master/README_zh.md)
## Basic usage
Let's have some examples to learn how to use it.
The example below is a counter, we can increase or decrease the state.
```typescript
import {
effect,
Flows,
create,
act,
strict,
flow,
Model
} from "agent-reducer";describe("basic", () => {
// a class model template for managing a state
class Counter implements Model {
// state of this model
state: number = 0;// a method for generating a next state
increase() {
// keyword `this` represents model instance, like: new Counter()
return this.state + 1;
}decrease() {
const nextState = this.state - 1;
if (nextState < 0) {
// use another method for help
return this.reset();
}
return nextState;
}reset() {
return 0;
}
}test("call method from agent can change state", () => {
// 'agent' is an avatar object from model class,
// call method from 'agent' can lead a state change
const { agent, connect, disconnect } = create(Counter);
connect();
// 'increase' method is from 'agent',
// and returns a new state for model.
agent.increase();
// model state is changed to 1
// We call these state change methods 'action methods'.
expect(agent.state).toBe(1);
disconnect();
});test("only the method get from agent object directly, can change state", () => {
const actionTypes: string[] = [];
const { agent, connect, disconnect } = create(Counter);
connect(({ type }) => {
// record action type, when state is changed
actionTypes.push(type);
});
// 'decrease' method is from 'agent',
// and returns a new state for model.
agent.decrease();
// model state is changed to 0
expect(agent.state).toBe(0);
// the 'reset' method called in 'decrease' method,
// it is not from 'agent',
// so, it can not lead a state change itself,
// and it is not an action method in this case.
expect(actionTypes).toEqual(["decrease"]);
disconnect();
});
});
```The operation is simple:
1. create `agent` object
2. connect
3. call method from `agent` object
4. the method called yet can use what it `returns` to change model state (this step is automatic)
5. disconnectIt works like a redux reducer, that is why it names `agent-reducer`.
Let's see a more complex example, and we will use it to manage a filterable list actions.
```typescript
import {
effect,
Flows,
create,
act,
strict,
flow,
Model
} from "agent-reducer";describe("use flow", () => {
type State = {
sourceList: string[];
viewList: string[];
keyword: string;
};const remoteSourceList = ["1", "2", "3", "4", "5"];
class List implements Model {
state: State = {
sourceList: [],
viewList: [],
keyword: "",
};// for changing sourceList,
// which will be used for filtering viewList
private changeSourceList(sourceList: string[]): State {
return { ...this.state, sourceList};
}// for changing viewList
private changeViewList(viewList: string[]): State {
return { ...this.state, viewList };
}// for changing keyword,
// which will be used for filtering viewList
changeKeyword(keyword: string): State {
return { ...this.state, keyword };
}// fetch remote sourceList
// `flow` decorator can make a flow method,
// in flow method, keyword `this` is an agent object,
// so, you can call action method to change state.
@flow()
async fetchSourceList() {
// fetch remote sourceList
const sourceList = await Promise.resolve(remoteSourceList);
// keyword `this` represents an agent object in flow method,
// `changeSourceList` is from this agent object,
// and it is marked as an action method,
// so, it can change state.
this.changeSourceList(sourceList);
}// effect of action methods: changeSourceList, changeKeyword for filtering viewList.
// `effect` decorator makes an effect method,
// the effect method can be used for listening the state change from action methods.
// effect method is a special flow method, it can not be called manually.
// We can add a flow mode by using `flow` decorator with effect,
// now, we have told the effect method works in a debounce mode with 100 ms
@flow(Flows.debounce(100))
@effect(() => [
// listen to action method `changeSourceList`
List.prototype.changeSourceList,
// listen to action method `changeKeyword`
List.prototype.changeKeyword,
])
private effectForFilterViewList() {
const { sourceList, keyword } = this.state;
// filter out the viewList
const viewList = sourceList.filter((content) =>
content.includes(keyword.trim())
);
// use action method `changeViewList` to change state
this.changeViewList(viewList);
}
}test("flow method is used for composing action methods together to resolve more complex works", async () => {
const { agent, connect, disconnect } = create(List);
connect();
// use flow to fetch remote sourceList
await agent.fetchSourceList();
expect(agent.state.sourceList).toEqual(remoteSourceList);
disconnect();
});test('effect method can listen to the state change of action methods',async ()=>{
const { agent, connect, disconnect } = create(List);
connect();
// use flow to fetch remote sourceList
await agent.fetchSourceList();
// change sourceList, the effect method `effectForFilterViewList` will start after 100 ms
expect(agent.state.sourceList).toEqual(remoteSourceList);
// change keyword,
// the effect method `effectForFilterViewList` will cancel itself,
// then start after 100 ms
agent.changeKeyword('1');
await new Promise((r)=>setTimeout(r,110));
// effect `effectForFilterViewList` filter out the viewList
expect(agent.state.sourceList).toEqual(remoteSourceList);
expect(agent.state.viewList).toEqual(['1']);
disconnect();
})
});
```The example above uses decorators like `@flow` and `@effect` to make a list manage model, which can fetch list from remote service and filter by keywords.
## Share state change synchronously
`agent-reducer` stores state, caches, listeners in the model instance, so you can share state change synchronously between two or more different agent objects from the same model instance.
```typescript
import {
create,
Action,
Model
} from 'agent-reducer';describe('update by observing another agent',()=>{
// this is a counter model,
// we can increase or decrease its state
class Counter implements Model {state = 0; // initial state
// consider what the method returns as a next state for model
stepUp = (): number => this.state + 1;stepDown = (): number => this.state - 1;
step(isUp: boolean):number{
return isUp ? this.stepUp() : this.stepDown();
}}
const counter = new Counter();
test('an agent can share state change with another one, if they share a same model instance',()=>{
// we create two listeners `dispatch1` and `dispatch2` for different agent reducer function
const dispatch1 = jest.fn().mockImplementation((action:Action)=>{
// the agent action contains a `state` property,
// this state is what the model state should be now.
expect(action.state).toBe(1);
});
const dispatch2 = jest.fn().mockImplementation((action:Action)=>{
expect(action.state).toBe(1);
});
// use create api,
// you can create an `Agent` object from its `Model`
const reducer1 = create(counter);
const reducer2 = create(counter);
// before call the methods,
// you need to connect it first,
// you can add a listener to listen the agent action,
// by using connect function
reducer1.connect(dispatch1);
reducer2.connect(dispatch2);
// calling result which is returned by method `stepUp` will be next state.
// then reducer1.agent will notify state change to reducer2.agent.
reducer1.agent.stepUp();expect(dispatch1).toBeCalled(); // dispatch1 work
expect(dispatch2).toBeCalled(); // dispatch2 work
expect(counter.state).toBe(1);
});});
```
This example may not easy for understanding, but consider if we use this feature in a view library like React, we can update state synchronously between different components without `props` or `context`, and these components will rerender synchronously. You can use it easily with its React connnector [use-agent-reducer](https://www.npmjs.com/package/use-agent-reducer).
## Connector
* [use-agent-reducer](https://www.npmjs.com/package/use-agent-reducer)
## Document
If you want to learn more, you can go into our [document](https://filefoxper.github.io/agent-reducer/#/) for more details.