Родитель
fef0e18680
Коммит
31363af6f7
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
dist
|
||||
lib
|
||||
etc
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"esbenp.prettier-vscode",
|
||||
"coenraads.bracket-pair-colorizer",
|
||||
"kisstkondoros.vscode-codemetrics",
|
||||
"orta.vscode-jest",
|
||||
"wayou.vscode-todo-highlight",
|
||||
"eg2.tslint",
|
||||
"rbbit.typescript-hero"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
|
@ -1,6 +1,19 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"middlewares",
|
||||
"reset"
|
||||
]
|
||||
}
|
||||
"cSpell.words": ["middlewares", "reset"],
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.svn": true,
|
||||
"**/.hg": true,
|
||||
"**/CVS": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/node_modules": true,
|
||||
"**/.gitignore": true,
|
||||
"**/.npmignore": true,
|
||||
"**/package-lock.json": true,
|
||||
"**/lib": true,
|
||||
"**/etc": true,
|
||||
"**/dist": true
|
||||
},
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
|
|
103
README.md
103
README.md
|
@ -1,4 +1,3 @@
|
|||
|
||||
<div>
|
||||
<img src="docs/redux-dynamic-modules.png" alt="logo" width="100">
|
||||
</img>
|
||||
|
@ -7,102 +6,111 @@
|
|||
|
||||
[![Pipelines](https://dev.azure.com/redux-dynamic-modules/redux-dynamic-modules/_apis/build/status/Microsoft.redux-dynamic-modules?branchName=master)](https://dev.azure.com/redux-dynamic-modules/redux-dynamic-modules/redux-dynamic-modules%20Team/_build?definitionId=1&_a=summary) [![npm (scoped)](https://img.shields.io/npm/v/redux-dynamic-modules.svg)](https://npmjs.org/package/redux-dynamic-modules) ![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)
|
||||
|
||||
|
||||
## What is it?
|
||||
**redux-dynamic-modules** is a library that aims to make Redux Reducers and middleware easy to modular-ize and add/remove dynamically.
|
||||
|
||||
**redux-dynamic-modules** is a library that aims to make Redux Reducers and middleware easy to modular-ize and add/remove dynamically.
|
||||
|
||||
## Motivation
|
||||
|
||||
In large Javascript applications, it is often desired to perform some kind of code-splitting, so that the initial script size is small. However, in Redux, you are required to define your reducers and middleware up-front; there is no good way to dynamically add/remove these constructs at runtime.
|
||||
|
||||
**redux-dynamic-modules** is designed to make dynamically loading these constructs easier. You can define a **module**, which contains reducers and middleware, and add it to the Redux store at runtime. We also provide a React component `DynamicModuleLoader`, which can load/unload modules on mount/unmount.
|
||||
|
||||
Modules provide the following benefits:
|
||||
* Modules can be easily re-used across the application, or between multiple similar applications.
|
||||
* Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
|
||||
* Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
|
||||
|
||||
- Modules can be easily re-used across the application, or between multiple similar applications.
|
||||
- Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
|
||||
- Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
|
||||
|
||||
## Features
|
||||
* Group together reducers, middleware, and state into a single, re-usable module.
|
||||
* Add and remove modules from a Redux store at any time.
|
||||
* Use the included `<DynamicModuleLoader />` component to automatically add a module when a component is rendered
|
||||
* Extensions provide integration with popular libraries, including `redux-saga` and `redux-thunk`
|
||||
|
||||
- Group together reducers, middleware, and state into a single, re-usable module.
|
||||
- Add and remove modules from a Redux store at any time.
|
||||
- Use the included `<DynamicModuleLoader />` component to automatically add a module when a component is rendered
|
||||
- Extensions provide integration with popular libraries, including `redux-saga` and `redux-thunk`
|
||||
|
||||
## Example Scenarios
|
||||
* You don't want to load the code for all your reducers up front. Define a module for some reducers and use `DynamicModuleLoader` and a library like [react-loadable](https://github.com/jamiebuilds/react-loadable) to download and add your module at runtime.
|
||||
* You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
|
||||
* You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
|
||||
|
||||
|
||||
- You don't want to load the code for all your reducers up front. Define a module for some reducers and use `DynamicModuleLoader` and a library like [react-loadable](https://github.com/jamiebuilds/react-loadable) to download and add your module at runtime.
|
||||
- You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
|
||||
- You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
|
||||
|
||||
## Install
|
||||
Run
|
||||
|
||||
Run
|
||||
|
||||
```
|
||||
npm install redux-dynamic-modules
|
||||
```
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
```
|
||||
yarn add redux-dynamic-modules
|
||||
```
|
||||
|
||||
## Usage
|
||||
* Create a module with the following format
|
||||
|
||||
- Create a module with the following format
|
||||
|
||||
```typescript
|
||||
export function getUsersModule(): IModule<IUserState> {
|
||||
return {
|
||||
id: "users",
|
||||
reducerMap: {
|
||||
users: usersReducer
|
||||
},
|
||||
// Actions to fire when this module is added/removed
|
||||
// initialActions: [],
|
||||
// finalActions: []
|
||||
}
|
||||
return {
|
||||
id: "users",
|
||||
reducerMap: {
|
||||
users: usersReducer,
|
||||
},
|
||||
// Actions to fire when this module is added/removed
|
||||
// initialActions: [],
|
||||
// finalActions: []
|
||||
};
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* Create a `ModuleStore`
|
||||
- Create a `ModuleStore`
|
||||
|
||||
```typescript
|
||||
import {createStore, IModuleStore} from "redux-dynamic-modules";
|
||||
import {getUsersModule} from "./usersModule";
|
||||
import { createStore, IModuleStore } from "redux-dynamic-modules";
|
||||
import { getUsersModule } from "./usersModule";
|
||||
|
||||
const store: IModuleStore<IState> = createStore(
|
||||
/* initial state */
|
||||
{},
|
||||
|
||||
/** enhancers **/
|
||||
[],
|
||||
/* initial state */
|
||||
{},
|
||||
|
||||
/* extensions to include */
|
||||
[],
|
||||
/** enhancers **/
|
||||
[],
|
||||
|
||||
getUsersModule(),
|
||||
/* ...any additional modules */
|
||||
/* extensions to include */
|
||||
[],
|
||||
|
||||
getUsersModule()
|
||||
/* ...any additional modules */
|
||||
);
|
||||
```
|
||||
|
||||
* Use like a standard Redux store
|
||||
* Use the `DynamicModuleLoader` React component to add/remove modules when components mount/unmount
|
||||
- Use like a standard Redux store
|
||||
- Use the `DynamicModuleLoader` React component to add/remove modules when components mount/unmount
|
||||
|
||||
```jsx
|
||||
<DynamicModuleLoader modules={[getHelloWorldModule()]}>
|
||||
<div>Hello World!!</div>
|
||||
<div>Hello World!!</div>
|
||||
</DynamicModuleLoader>
|
||||
```
|
||||
```
|
||||
|
||||
## Extensions
|
||||
* redux-saga - [redux-dynamic-modules-saga](https://www.npmjs.com/package/redux-dynamic-modules-saga)
|
||||
* redux-thunk - [redux-dynamic-modules-thunk](https://www.npmjs.com/package/redux-dynamic-modules-thunk)
|
||||
|
||||
- redux-saga - [redux-dynamic-modules-saga](https://www.npmjs.com/package/redux-dynamic-modules-saga)
|
||||
- redux-thunk - [redux-dynamic-modules-thunk](https://www.npmjs.com/package/redux-dynamic-modules-thunk)
|
||||
|
||||
## Examples
|
||||
|
||||
See the [Widgets](https://github.com/Microsoft/redux-dynamic-modules/tree/master/packages/widgets-example) for a quick of example of the library in practice.
|
||||
|
||||
Step by step walthrough of start consuming ```redux-dynamic-modules``` in the widget app. [Youtube](https://www.youtube.com/watch?v=SktRbSZ-4Tk)
|
||||
Step by step walthrough of start consuming `redux-dynamic-modules` in the widget app. [Youtube](https://www.youtube.com/watch?v=SktRbSZ-4Tk)
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
|
@ -113,4 +121,3 @@ provided by the bot. You will only need to do this once across all repos using o
|
|||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
|
||||
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
vmImage: "Ubuntu 16.04"
|
||||
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "8.x"
|
||||
displayName: "Install Node.js"
|
||||
|
||||
- script: |
|
||||
npm install
|
||||
npm run bootstrap
|
||||
npm run test
|
||||
displayName: 'npm install and build'
|
||||
- script: |
|
||||
npm install
|
||||
npm run bootstrap
|
||||
npm run test
|
||||
displayName: "npm install and build"
|
||||
|
|
|
@ -1,58 +1,62 @@
|
|||
# Install
|
||||
Run
|
||||
|
||||
Run
|
||||
|
||||
```
|
||||
npm install redux-dynamic-modules
|
||||
```
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
```
|
||||
yarn add redux-dynamic-modules
|
||||
```
|
||||
|
||||
# Usage
|
||||
* Create a module with the following format
|
||||
|
||||
- Create a module with the following format
|
||||
|
||||
```typescript
|
||||
export function getUsersModule(): IModule<IUserState> {
|
||||
return {
|
||||
id: "users",
|
||||
reducerMap: {
|
||||
users: usersReducer
|
||||
},
|
||||
// Actions to fire when this module is added/removed
|
||||
// initialActions: [],
|
||||
// finalActions: []
|
||||
}
|
||||
return {
|
||||
id: "users",
|
||||
reducerMap: {
|
||||
users: usersReducer,
|
||||
},
|
||||
// Actions to fire when this module is added/removed
|
||||
// initialActions: [],
|
||||
// finalActions: []
|
||||
};
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* Create a `ModuleStore`
|
||||
- Create a `ModuleStore`
|
||||
|
||||
```typescript
|
||||
import {createStore, IModuleStore} from "redux-dynamic-modules";
|
||||
import {getUsersModule} from "./usersModule";
|
||||
import { createStore, IModuleStore } from "redux-dynamic-modules";
|
||||
import { getUsersModule } from "./usersModule";
|
||||
|
||||
const store: IModuleStore<IState> = createStore(
|
||||
/* initial state */
|
||||
{},
|
||||
/* initial state */
|
||||
{},
|
||||
|
||||
/** enhancers **/
|
||||
[],
|
||||
/** enhancers **/
|
||||
[],
|
||||
|
||||
/* Extensions to load */
|
||||
[],
|
||||
/* Extensions to load */
|
||||
|
||||
getUsersModule(),
|
||||
/* ...any additional modules */
|
||||
[],
|
||||
|
||||
getUsersModule()
|
||||
/* ...any additional modules */
|
||||
);
|
||||
```
|
||||
|
||||
* Use like a standard Redux store
|
||||
* Use the `DynamicModuleLoader` React component to add/remove modules when components mount/unmount
|
||||
- Use like a standard Redux store
|
||||
- Use the `DynamicModuleLoader` React component to add/remove modules when components mount/unmount
|
||||
|
||||
```jsx
|
||||
<DynamicModuleLoader modules={modules}>
|
||||
<div>Hello World!!</div>
|
||||
<div>Hello World!!</div>
|
||||
</DynamicModuleLoader>
|
||||
```
|
||||
```
|
||||
|
|
|
@ -1,36 +1,41 @@
|
|||
|
||||
[![Pipelines](https://dev.azure.com/redux-dynamic-modules/redux-dynamic-modules/_apis/build/status/Microsoft.redux-dynamic-modules?branchName=master)](https://dev.azure.com/redux-dynamic-modules/redux-dynamic-modules/redux-dynamic-modules%20Team/_build?definitionId=1&_a=summary) ![npm (scoped)](https://img.shields.io/npm/v/redux-dynamic-modules.svg) ![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)
|
||||
|
||||
## What is it?
|
||||
**redux-dynamic-modules** is a library that aims to make Redux Reducers and middleware easy to modular-ize and add/remove dynamically.
|
||||
|
||||
**redux-dynamic-modules** is a library that aims to make Redux Reducers and middleware easy to modular-ize and add/remove dynamically.
|
||||
|
||||
## Motivation
|
||||
|
||||
In large Javascript applications, it is often desired to perform some kind of code-splitting, so that the initial script size is small. However, in Redux, you are required to define the your reducers and middleware up-front; there is no good way to dynamically add/remove these constructs at runtime.
|
||||
|
||||
**redux-dynamic-modules** is designed to make dynamically loading these constructs easier. You can define a **module**, which contains reducers and middleware, and add it to the Redux store at runtime. We also provide a React component `DynamicModuleLoader`, which can load/unload modules on mount/unmount.
|
||||
|
||||
Modules provide the following benefits:
|
||||
* Modules can be easily re-used across the application, or between multiple similar applications.
|
||||
* Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
|
||||
* Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
|
||||
|
||||
- Modules can be easily re-used across the application, or between multiple similar applications.
|
||||
- Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
|
||||
- Modules can be added/removed from the store dynamically, ex. when a component mounts or when a user performs an action
|
||||
|
||||
## Features
|
||||
* Group together reducers, middleware, and state into a single, re-usable module.
|
||||
* Add and remove modules from a Redux store at any time.
|
||||
* Use the included `<DynamicModuleLoader />` component to automatically add a module when a component is rendered
|
||||
* Extensions provide integration with popular libraries, including `redux-saga` and `redux-thunk`
|
||||
|
||||
- Group together reducers, middleware, and state into a single, re-usable module.
|
||||
- Add and remove modules from a Redux store at any time.
|
||||
- Use the included `<DynamicModuleLoader />` component to automatically add a module when a component is rendered
|
||||
- Extensions provide integration with popular libraries, including `redux-saga` and `redux-thunk`
|
||||
|
||||
## Example Scenarios
|
||||
* You don't want to load the code for all your reducers up front. Define a module for some reducers and use `DynamicModuleLoader` and a library like [react-loadable](https://github.com/jamiebuilds/react-loadable) to download and add your module at runtime.
|
||||
* You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
|
||||
* You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
|
||||
|
||||
- You don't want to load the code for all your reducers up front. Define a module for some reducers and use `DynamicModuleLoader` and a library like [react-loadable](https://github.com/jamiebuilds/react-loadable) to download and add your module at runtime.
|
||||
- You have some common reducers/middleware that need to be re-used in different areas of your application. Define a module and easily include it in those areas.
|
||||
- You have a mono-repo that contains multiple applications which share similar state. Create a package containing some modules and re-use them across your applications
|
||||
|
||||
## Examples
|
||||
|
||||
See the [Widgets](https://github.com/Microsoft/redux-dynamic-modules/tree/master/packages/widgets-example) for a quick of example of the library in practice.
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||
the rights to use your contribution. For details, visit https://cla.microsoft.com.
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<!-- _coverpage.md -->
|
||||
|
||||
![logo](./redux-dynamic-modules.png)
|
||||
|
||||
# redux-dynamic-modules
|
||||
|
||||
> A modular approach to Redux
|
||||
|
||||
[GitHub](https://github.com/Microsoft/redux-dynamic-modules/)
|
||||
[Get Started](#what-is-it)
|
||||
<!-- _coverpage.md -->
|
||||
|
||||
![logo](./redux-dynamic-modules.png)
|
||||
|
||||
# redux-dynamic-modules
|
||||
|
||||
> A modular approach to Redux
|
||||
|
||||
[GitHub](https://github.com/Microsoft/redux-dynamic-modules/)
|
||||
[Get Started](#what-is-it)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
* Getting Started
|
||||
* [Quickstart](GettingStarted.md)
|
||||
- Getting Started
|
||||
|
||||
* Guide
|
||||
* [Modules](reference/Modules.md)
|
||||
* [Module Store](reference/ModuleStore.md)
|
||||
* [Dynamic Module Loader](reference/DynamicModuleLoader.md)
|
||||
* [Usage with Redux Saga](reference/ReduxSaga.md)
|
||||
* [Usage with Redux Thunk](reference/ReduxThunk.md)
|
||||
* [Module Reference Counting](reference/ModuleCounting.md)
|
||||
* [Extensions](reference/Extensions.md)
|
||||
* [Lifecycle Actions](reference/LifecycleActions.md)
|
||||
- [Quickstart](GettingStarted.md)
|
||||
|
||||
- Guide
|
||||
- [Modules](reference/Modules.md)
|
||||
- [Module Store](reference/ModuleStore.md)
|
||||
- [Dynamic Module Loader](reference/DynamicModuleLoader.md)
|
||||
- [Usage with Redux Saga](reference/ReduxSaga.md)
|
||||
- [Usage with Redux Thunk](reference/ReduxThunk.md)
|
||||
- [Module Reference Counting](reference/ModuleCounting.md)
|
||||
- [Extensions](reference/Extensions.md)
|
||||
- [Lifecycle Actions](reference/LifecycleActions.md)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Dynamic Module Loader
|
||||
|
||||
`redux-dynamic-modules` provides a `DynamicModuleLoader` React component, which allows you to add/remove modules when a component is rendered.
|
||||
|
||||
## Example {docsify-ignore}
|
||||
|
||||
```typescript
|
||||
import { DynamicModuleLoader } from "redux-dynamic-modules";
|
||||
import { getNewUserDialogModule } from "./newUserDialogModule";
|
||||
|
@ -10,10 +12,11 @@ export class NewUserDialog extends React.Component {
|
|||
public render() {
|
||||
return (
|
||||
<DynamicModuleLoader modules={[getNewUserDialogModule()]}>
|
||||
<ConnectedNewUserDialogContent/>
|
||||
<ConnectedNewUserDialogContent />
|
||||
</DynamicModuleLoader>
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
When `<NewUserDialog>` is rendered, the `newUserDialog` module will be added to the store. When it is unmounted, the module will be removed from the store and the state will be cleaned up.
|
||||
|
||||
When `<NewUserDialog>` is rendered, the `newUserDialog` module will be added to the store. When it is unmounted, the module will be removed from the store and the state will be cleaned up.
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
# Extensions
|
||||
`redux-dynamic-modules` supports the concept of extensions, which allow for custom behavior when adding/removing modules.
|
||||
|
||||
`redux-dynamic-modules` supports the concept of extensions, which allow for custom behavior when adding/removing modules.
|
||||
The `redux-dynamic-modules-saga` and `redux-dynamic-modules-thunk` packages are built as extensions.
|
||||
|
||||
## Building a custom extension
|
||||
|
||||
To build a custom extension, create an object that implements the following interface
|
||||
|
||||
```typescript
|
||||
|
@ -21,20 +23,24 @@ export interface IExtension {
|
|||
```
|
||||
|
||||
## Adding extensions to the store
|
||||
|
||||
To add an extension to the `ModuleStore`, pass it as the second argument to `createStore`
|
||||
|
||||
```typescript
|
||||
const store: IModuleStore<IState> = createStore({}, [], [getMyExtension()])
|
||||
const store: IModuleStore<IState> = createStore({}, [], [getMyExtension()]);
|
||||
```
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
//LoggingExtension.ts
|
||||
|
||||
export function getLoggingExtension(): IExtension {
|
||||
return {
|
||||
onModuleAdded: (module: IModule<any>) => console.log(`Module ${module.id} added`),
|
||||
onModuleRemoved: (module: IModule<any>) => console.log(`Module ${module.id} removed`),
|
||||
}
|
||||
onModuleAdded: (module: IModule<any>) =>
|
||||
console.log(`Module ${module.id} added`),
|
||||
onModuleRemoved: (module: IModule<any>) =>
|
||||
console.log(`Module ${module.id} removed`),
|
||||
};
|
||||
}
|
||||
```
|
||||
```
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Lifecycle Actions
|
||||
|
||||
In addition to the `initialActions` and `finalActions` properties specified on the module, the `ModuleStore` will also dispatch lifecycle actions that indicate a module has been added or removed.
|
||||
|
||||
When a module is added, the `"@@Internal/ModuleManager/ModuleAdded"` action is fired, where the payload is the module id. Similarly, when a module is removed, the `"@@Internal/ModuleManager/ModuleRemoved"` action is fired.
|
||||
When a module is added, the `"@@Internal/ModuleManager/ModuleAdded"` action is fired, where the payload is the module id. Similarly, when a module is removed, the `"@@Internal/ModuleManager/ModuleRemoved"` action is fired.
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
# Module Reference Counting
|
||||
|
||||
**redux-dynamic-modules** will automatically peform "reference counting" of your modules and their contents.
|
||||
|
||||
## For Modules
|
||||
|
||||
The library will reference count entire modules. This means:
|
||||
* If you add a module, adding it again is a NO-OP
|
||||
* If you add a module twice, it must be removed twice.
|
||||
|
||||
- If you add a module, adding it again is a NO-OP
|
||||
- If you add a module twice, it must be removed twice.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
|
@ -46,8 +49,10 @@ render() {
|
|||
```
|
||||
|
||||
## For Module Contents
|
||||
|
||||
The library will also reference count the contents of your modules, including reducers and middleware. This means:
|
||||
* If two different modules add the same middleware, only one copy of the middleware is added. Only until **both** modules are removed will the middleware be removed
|
||||
|
||||
- If two different modules add the same middleware, only one copy of the middleware is added. Only until **both** modules are removed will the middleware be removed
|
||||
|
||||
```jsx
|
||||
export function createModuleA() {
|
||||
|
@ -72,8 +77,8 @@ removeB(); // LoggingMiddleware is removed
|
|||
|
||||
```
|
||||
|
||||
* If two different modules add a reducer with the same key, only the first added reducer will be used.
|
||||
For this reason, **it is recommended to keep state keys unique where possible.**
|
||||
- If two different modules add a reducer with the same key, only the first added reducer will be used.
|
||||
For this reason, **it is recommended to keep state keys unique where possible.**
|
||||
|
||||
```jsx
|
||||
export function createModuleA() {
|
||||
|
|
|
@ -1,28 +1,30 @@
|
|||
# Module Store
|
||||
|
||||
The **Module Store** is a Redux store with the added capability of managing **Redux Modules**. It adds the following additional functions to the basic Redux store.
|
||||
|
||||
* `addModule(module: IModule<any>)`: Add a single module to the store. The return result is a function that can be used to remove the added module
|
||||
* `addModules(modules: IModule<any>[])`: Same as `addModule`, but for multiple modules. The return function will remove all the added modules.
|
||||
* `dispose()`: Remove all of the modules added to the store and dispose of the object
|
||||
- `addModule(module: IModule<any>)`: Add a single module to the store. The return result is a function that can be used to remove the added module
|
||||
- `addModules(modules: IModule<any>[])`: Same as `addModule`, but for multiple modules. The return function will remove all the added modules.
|
||||
- `dispose()`: Remove all of the modules added to the store and dispose of the object
|
||||
|
||||
To create a `ModuleStore`, use the `createStore` function from our package
|
||||
|
||||
## Example {docsify-ignore}
|
||||
|
||||
```typescript
|
||||
import { createStore, IModuleStore } from "redux-dynamic-modules";
|
||||
import {getUsersModule} from "./usersModule";
|
||||
import { getUsersModule } from "./usersModule";
|
||||
|
||||
const store: IModuleStore<IState> = createStore(
|
||||
/* initial state */
|
||||
{},
|
||||
/* initial state */
|
||||
{},
|
||||
|
||||
/** enhancers **/
|
||||
[],
|
||||
/** enhancers **/
|
||||
[],
|
||||
|
||||
/* Extensions to load */
|
||||
[],
|
||||
/* Extensions to load */
|
||||
[],
|
||||
|
||||
getUsersModule(),
|
||||
/* ...any additional modules */
|
||||
getUsersModule()
|
||||
/* ...any additional modules */
|
||||
);
|
||||
```
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
# Module
|
||||
|
||||
A 'module' consists of the following properties
|
||||
* `id`: A unique ID to identify this module
|
||||
* `reducerMap`: The reducers that this module will register
|
||||
* `initialActions`: Any optional actions to fire when this module is added
|
||||
* `finalActions`: Any optional actions to fire when this module is removed.
|
||||
|
||||
- `id`: A unique ID to identify this module
|
||||
- `reducerMap`: The reducers that this module will register
|
||||
- `initialActions`: Any optional actions to fire when this module is added
|
||||
- `finalActions`: Any optional actions to fire when this module is removed.
|
||||
|
||||
An example module definition looks like the following:
|
||||
|
||||
```typescript
|
||||
export function getUsersModule(): IModule<IUserState> {
|
||||
return {
|
||||
id: "todo-module",
|
||||
reducerMap: {
|
||||
todoState: todoReducer
|
||||
},
|
||||
initialActions: [TodoActions.initializeTodos()],
|
||||
finalActions: [TodoActions.disposeTodos()]
|
||||
}
|
||||
return {
|
||||
id: "todo-module",
|
||||
reducerMap: {
|
||||
todoState: todoReducer,
|
||||
},
|
||||
initialActions: [TodoActions.initializeTodos()],
|
||||
finalActions: [TodoActions.disposeTodos()],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, the key `todoState` will be added to the Redux store along with the `todoReducer`. Note that you can use `combineReducers` here to allow for more nested state.
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
## Usage with redux-observable
|
||||
|
||||
You can use `redux-dynamic-modules` alongside `redux-observable` so that you can add/remove Epics along with your modules.
|
||||
|
||||
To use
|
||||
* `npm install redux-dynamic-modules-observable`
|
||||
* Add the observable extension to the `createStore` call
|
||||
|
||||
- `npm install redux-dynamic-modules-observable`
|
||||
- Add the observable extension to the `createStore` call
|
||||
|
||||
```typescript
|
||||
import { createStore, IModuleStore } from "redux-dynamic-modules";
|
||||
|
@ -11,27 +13,28 @@ import { getObservableExtension } from "redux-dynamic-modules-observable";
|
|||
import { getUsersModule } from "./usersModule";
|
||||
|
||||
const store: IModuleStore<IState> = createStore(
|
||||
/* initial state */
|
||||
{},
|
||||
/* initial state */
|
||||
{},
|
||||
|
||||
/** enhancers **/
|
||||
[],
|
||||
/** enhancers **/
|
||||
[],
|
||||
|
||||
/* Extensions to load */
|
||||
[getObservableExtension()],
|
||||
/* Extensions to load */
|
||||
[getObservableExtension()],
|
||||
|
||||
getUsersModule(),
|
||||
/* ...any additional modules */
|
||||
getUsersModule()
|
||||
/* ...any additional modules */
|
||||
);
|
||||
```
|
||||
|
||||
* Add the `epics` property to your modules, and specify a list of observables to run
|
||||
- Add the `epics` property to your modules, and specify a list of observables to run
|
||||
|
||||
```typescript
|
||||
return {
|
||||
id: "users-module",
|
||||
reducerMap: {
|
||||
users: usersReducer
|
||||
users: usersReducer,
|
||||
},
|
||||
epics: [usersEpic]
|
||||
epics: [usersEpic],
|
||||
};
|
||||
```
|
||||
```
|
||||
|
|
|
@ -1,42 +1,43 @@
|
|||
## Usage with redux-saga
|
||||
|
||||
You can use `redux-dynamic-modules` alongside `redux-saga` so that you can add/remove sagas along with your modules.
|
||||
|
||||
To use
|
||||
* `npm install redux-dynamic-modules-saga`
|
||||
* Add the saga extension to the `createStore` call
|
||||
|
||||
|
||||
- `npm install redux-dynamic-modules-saga`
|
||||
- Add the saga extension to the `createStore` call
|
||||
|
||||
```typescript
|
||||
import { createStore, IModuleStore } from "redux-dynamic-modules";
|
||||
import { getSagaExtension } from "redux-dynamic-modules-saga";
|
||||
import { getUsersModule } from "./usersModule";
|
||||
|
||||
const store: IModuleStore<IState> = createStore(
|
||||
/* initial state */
|
||||
{},
|
||||
/* initial state */
|
||||
{},
|
||||
|
||||
/** enhancers **/
|
||||
[],
|
||||
/** enhancers **/
|
||||
[],
|
||||
|
||||
/* Extensions to load */
|
||||
[getSagaExtension({} /* saga context */)],
|
||||
/* Extensions to load */
|
||||
[getSagaExtension({} /* saga context */)],
|
||||
|
||||
getUsersModule(),
|
||||
/* ...any additional modules */
|
||||
getUsersModule()
|
||||
/* ...any additional modules */
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
* Add the `sagas` property to your modules, and specify a list of sagas to run
|
||||
|
||||
- Add the `sagas` property to your modules, and specify a list of sagas to run
|
||||
|
||||
```typescript
|
||||
return {
|
||||
id: "users-module",
|
||||
reducerMap: {
|
||||
users: usersReducer
|
||||
users: usersReducer,
|
||||
},
|
||||
sagas: [
|
||||
usersSaga,
|
||||
{ saga: usersSagaWithArguments, argument: {a: "argument"}}
|
||||
]
|
||||
{ saga: usersSagaWithArguments, argument: { a: "argument" } },
|
||||
],
|
||||
};
|
||||
```
|
||||
```
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
## Usage with redux-thunk
|
||||
|
||||
You can use `redux-dynamic-modules` alongside `redux-thunk`.
|
||||
|
||||
To use
|
||||
* `npm install redux-dynamic-modules-thunk`
|
||||
* Add the thunk extension to the `createStore` call
|
||||
|
||||
- `npm install redux-dynamic-modules-thunk`
|
||||
- Add the thunk extension to the `createStore` call
|
||||
|
||||
```typescript
|
||||
import { createStore, IModuleStore } from "redux-dynamic-modules";
|
||||
|
@ -11,18 +13,18 @@ import { getThunkExtension } from "redux-dynamic-modules-thunk";
|
|||
import { getUsersModule } from "./usersModule";
|
||||
|
||||
const store: IModuleStore<IState> = createStore(
|
||||
/* initial state */
|
||||
{},
|
||||
/* initial state */
|
||||
{},
|
||||
|
||||
/** enhancers **/
|
||||
[],
|
||||
/** enhancers **/
|
||||
[],
|
||||
|
||||
/* Extensions to load */
|
||||
[getThunkExtension()],
|
||||
/* Extensions to load */
|
||||
[getThunkExtension()],
|
||||
|
||||
getUsersModule(),
|
||||
/* ...any additional modules */
|
||||
getUsersModule()
|
||||
/* ...any additional modules */
|
||||
);
|
||||
```
|
||||
|
||||
Now, you will be able to use thunks as well
|
||||
Now, you will be able to use thunks as well
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.cover-main img {max-width: 100px;}
|
||||
.cover-main img {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.app-name-link img {max-width: 100px;}
|
||||
.app-name-link img {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
{
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "3.0.3"
|
||||
"packages": ["packages/*"],
|
||||
"version": "3.0.3"
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
24
package.json
24
package.json
|
@ -1,12 +1,14 @@
|
|||
{
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"build:prod": "lerna run build:prod",
|
||||
"test": "npm run build:prod && lerna run test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lerna": "^3.4.0"
|
||||
}
|
||||
}
|
||||
"name": "root",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"build:prod": "lerna run build:prod",
|
||||
"test": "npm run build:prod && lerna run test",
|
||||
"prettier": "prettier \"**/*.{ts,tsx,json,js,scss,yaml,yml,css,md}\" --write --config prettier.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lerna": "^3.4.0",
|
||||
"prettier": "1.15.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
#This is still a WIP. Contributions are welcome
|
||||
#This is still a WIP. Contributions are welcome
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -43,35 +43,39 @@ export function getEpicManager(): IEpicManager {
|
|||
});
|
||||
|
||||
runningEpics = runningEpics.filter(e => {
|
||||
!!epicNameMap[e.name] && epicRefCounter.getCount(e.name) !== 0
|
||||
!!epicNameMap[e.name] && epicRefCounter.getCount(e.name) !== 0;
|
||||
});
|
||||
},
|
||||
dispose: () => {
|
||||
runningEpics = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createRootEpic(runningEpics: Epic[]): Epic {
|
||||
const merger = (...args) => merge(
|
||||
runningEpics.map(epic => {
|
||||
//@ts-ignore
|
||||
const output$ = epic(...args);
|
||||
if (!output$) {
|
||||
throw new TypeError(`combineEpics: one of the provided Epics "${epic.name || '<anonymous>'}" does not return a stream. Double check you\'re not missing a return statement!`);
|
||||
}
|
||||
return output$;
|
||||
})
|
||||
);
|
||||
const merger = (...args) =>
|
||||
merge(
|
||||
runningEpics.map(epic => {
|
||||
//@ts-ignore
|
||||
const output$ = epic(...args);
|
||||
if (!output$) {
|
||||
throw new TypeError(
|
||||
`combineEpics: one of the provided Epics "${epic.name ||
|
||||
"<anonymous>"}" does not return a stream. Double check you\'re not missing a return statement!`
|
||||
);
|
||||
}
|
||||
return output$;
|
||||
})
|
||||
);
|
||||
|
||||
// Technically the `name` property on Function's are supposed to be read-only.
|
||||
// While some JS runtimes allow it anyway (so this is useful in debugging)
|
||||
// some actually throw an exception when you attempt to do so.
|
||||
try {
|
||||
Object.defineProperty(merger, 'name', {
|
||||
value: '____MODULES_ROOT_EPIC',
|
||||
Object.defineProperty(merger, "name", {
|
||||
value: "____MODULES_ROOT_EPIC",
|
||||
});
|
||||
} catch (e) { }
|
||||
} catch (e) {}
|
||||
|
||||
return merger;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,6 @@ export function getObservableExtension(): IExtension {
|
|||
if (module.epics) {
|
||||
epicManager.remove(module.epics);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015",
|
||||
"es2016"
|
||||
],
|
||||
"lib": ["dom", "es2015", "es2016"],
|
||||
"declaration": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitAny": false,
|
||||
|
@ -14,6 +10,8 @@
|
|||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts", "../widgets-example/src/widgets/hacker-news/index.js", "../widgets-example/src/widgets/hacker-news/component/index.jsx"
|
||||
"src/**/*.ts",
|
||||
"../widgets-example/src/widgets/hacker-news/index.js",
|
||||
"../widgets-example/src/widgets/hacker-news/component/index.jsx"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
{
|
||||
|
||||
}
|
||||
{}
|
||||
|
|
|
@ -1,39 +1,43 @@
|
|||
let webpack = require("webpack");
|
||||
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
let BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
|
||||
.BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
let mode_env = argv.mode || 'development';
|
||||
let mode_env = argv.mode || "development";
|
||||
|
||||
return {
|
||||
mode: mode_env,
|
||||
devtool: 'source-map',
|
||||
devtool: "source-map",
|
||||
entry: {
|
||||
main: './lib/index'
|
||||
main: "./lib/index",
|
||||
},
|
||||
|
||||
output: {
|
||||
library: "redux-dynamic-modules-observable",
|
||||
libraryTarget: "umd",
|
||||
filename: mode_env === "production" ? "redux-dynamic-modules-observable.min.js" : "redux-dynamic-modules-observable.js",
|
||||
path: __dirname + "/dist/"
|
||||
filename:
|
||||
mode_env === "production"
|
||||
? "redux-dynamic-modules-observable.min.js"
|
||||
: "redux-dynamic-modules-observable.js",
|
||||
path: __dirname + "/dist/",
|
||||
},
|
||||
|
||||
externals: {
|
||||
"react": "react",
|
||||
"redux": "redux",
|
||||
react: "react",
|
||||
redux: "redux",
|
||||
"react-redux": "react-redux",
|
||||
"redux-saga": "redux-saga",
|
||||
"redux-dynamic-modules": "redux-dynamic-modules",
|
||||
"rxjs": "rxjs",
|
||||
rxjs: "rxjs",
|
||||
"rxjs/operators": "rxjs/operators",
|
||||
"rxjs/observable": "rxjs/observable",
|
||||
},
|
||||
plugins: [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
analyzerMode: "static",
|
||||
reportFilename: `react-redux-module.stats.html`,
|
||||
openAnalyzer: false
|
||||
})
|
||||
]
|
||||
openAnalyzer: false,
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,47 +1,50 @@
|
|||
## Install
|
||||
Run
|
||||
|
||||
Run
|
||||
|
||||
```
|
||||
npm install redux-dynamic-modules-saga
|
||||
```
|
||||
|
||||
or
|
||||
or
|
||||
|
||||
```
|
||||
yarn add redux-dynamic-modules-saga
|
||||
```
|
||||
|
||||
## Usage
|
||||
* Create a module with the following format
|
||||
|
||||
- Create a module with the following format
|
||||
|
||||
```typescript
|
||||
export function getUsersModule(): ISagaModule<IUserState> {
|
||||
return {
|
||||
id: "users",
|
||||
reducerMap: {
|
||||
users: usersReducer
|
||||
},
|
||||
sagas: [userSagas]
|
||||
// Actions to fire when this module is added/removed
|
||||
// initialActions: [],
|
||||
// finalActions: [],
|
||||
}
|
||||
return {
|
||||
id: "users",
|
||||
reducerMap: {
|
||||
users: usersReducer,
|
||||
},
|
||||
sagas: [userSagas],
|
||||
// Actions to fire when this module is added/removed
|
||||
// initialActions: [],
|
||||
// finalActions: [],
|
||||
};
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
* Create a `ModuleStore`
|
||||
- Create a `ModuleStore`
|
||||
|
||||
```typescript
|
||||
import {configureStore, IModuleStore} from "redux-dynamic-modules";
|
||||
import {getUsersModule} from "./usersModule";
|
||||
import { configureStore, IModuleStore } from "redux-dynamic-modules";
|
||||
import { getUsersModule } from "./usersModule";
|
||||
|
||||
const store: IModuleStore<IState> = configureStore(
|
||||
/* initial state */
|
||||
{},
|
||||
/* initial state */
|
||||
{},
|
||||
|
||||
/* extensions to include */
|
||||
[getSagaExtension(/* saga context object */)],
|
||||
/* extensions to include */
|
||||
[getSagaExtension(/* saga context object */)],
|
||||
|
||||
getUsersModule(),
|
||||
/* ...any additional modules */
|
||||
getUsersModule()
|
||||
/* ...any additional modules */
|
||||
);
|
||||
```
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -4,7 +4,9 @@ export interface ISagaWithArguments<T> {
|
|||
saga: (argument?: T) => Iterator<any>;
|
||||
argument?: T;
|
||||
}
|
||||
export type ISagaRegistration<T> = (() => Iterator<any>) | ISagaWithArguments<T>;
|
||||
export type ISagaRegistration<T> =
|
||||
| (() => Iterator<any>)
|
||||
| ISagaWithArguments<T>;
|
||||
|
||||
export interface ISagaModule<T> extends IModule<T> {
|
||||
sagas?: ISagaRegistration<any>[];
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { ISagaRegistration, ISagaWithArguments } from "./Contracts";
|
||||
export function sagaEquals(a: ISagaRegistration<any>, b: ISagaRegistration<any>): boolean {
|
||||
export function sagaEquals(
|
||||
a: ISagaRegistration<any>,
|
||||
b: ISagaRegistration<any>
|
||||
): boolean {
|
||||
if (typeof a === "function" && typeof b === "function") {
|
||||
return a === b;
|
||||
}
|
||||
|
@ -12,7 +15,6 @@ export function sagaEquals(a: ISagaRegistration<any>, b: ISagaRegistration<any>)
|
|||
const sagaA = a as () => Iterator<any>;
|
||||
const sagaB = b as ISagaWithArguments<any>;
|
||||
return sagaA === sagaB.saga && !sagaB.argument;
|
||||
|
||||
} else if (typeof b === "function") {
|
||||
const sagaA = a as ISagaWithArguments<any>;
|
||||
const sagaB = b as () => Iterator<any>;
|
||||
|
@ -23,8 +25,8 @@ export function sagaEquals(a: ISagaRegistration<any>, b: ISagaRegistration<any>)
|
|||
const sagaA = a as ISagaWithArguments<any>;
|
||||
const sagaB = b as ISagaWithArguments<any>;
|
||||
|
||||
return sagaA.saga === sagaB.saga && (
|
||||
sagaA.argument === sagaB.argument // TODO: This needs to be a deep equals
|
||||
return (
|
||||
sagaA.saga === sagaB.saga && sagaA.argument === sagaB.argument // TODO: This needs to be a deep equals
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import { default as createSagaMiddleware, SagaMiddleware } from "redux-saga";
|
||||
import { IExtension, IItemManager, getRefCountedManager, IModuleManager } from "redux-dynamic-modules";
|
||||
import {
|
||||
IExtension,
|
||||
IItemManager,
|
||||
getRefCountedManager,
|
||||
IModuleManager,
|
||||
} from "redux-dynamic-modules";
|
||||
import { ISagaRegistration, ISagaModule } from "./Contracts";
|
||||
import { getSagaManager } from "./SagaManager";
|
||||
import { sagaEquals } from "./SagaComparer";
|
||||
|
@ -8,7 +13,10 @@ import { sagaEquals } from "./SagaComparer";
|
|||
* Get an extension that integrates saga with the store
|
||||
* @param sagaContext The context to provide to the saga
|
||||
*/
|
||||
export function getSagaExtension<C>(sagaContext?: C, onError?: (error: Error) => void): IExtension {
|
||||
export function getSagaExtension<C>(
|
||||
sagaContext?: C,
|
||||
onError?: (error: Error) => void
|
||||
): IExtension {
|
||||
let sagaMonitor = undefined;
|
||||
|
||||
//@ts-ignore
|
||||
|
@ -17,15 +25,15 @@ export function getSagaExtension<C>(sagaContext?: C, onError?: (error: Error) =>
|
|||
}
|
||||
|
||||
// Setup the saga middleware
|
||||
let sagaMiddleware: SagaMiddleware<C> = createSagaMiddleware<any>(
|
||||
{
|
||||
context: sagaContext,
|
||||
sagaMonitor,
|
||||
onError
|
||||
}
|
||||
);
|
||||
let sagaMiddleware: SagaMiddleware<C> = createSagaMiddleware<any>({
|
||||
context: sagaContext,
|
||||
sagaMonitor,
|
||||
onError,
|
||||
});
|
||||
|
||||
let _sagaManager: IItemManager<ISagaRegistration<any>> = getRefCountedManager(getSagaManager(sagaMiddleware), sagaEquals);
|
||||
let _sagaManager: IItemManager<
|
||||
ISagaRegistration<any>
|
||||
> = getRefCountedManager(getSagaManager(sagaMiddleware), sagaEquals);
|
||||
|
||||
return {
|
||||
middleware: [sagaMiddleware],
|
||||
|
@ -50,6 +58,6 @@ export function getSagaExtension<C>(sagaContext?: C, onError?: (error: Error) =>
|
|||
|
||||
dispose: () => {
|
||||
_sagaManager.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { ISagaRegistration, ISagaWithArguments } from "./Contracts";
|
||||
import { SagaMiddleware, Task } from "redux-saga";
|
||||
import { sagaEquals as sagaEquals } from "./SagaComparer";
|
||||
import { sagaEquals } from "./SagaComparer";
|
||||
import { IItemManager, getMap } from "redux-dynamic-modules";
|
||||
|
||||
/**
|
||||
* Creates saga items which can be used to start and stop sagas dynamically
|
||||
*/
|
||||
export function getSagaManager(sagaMiddleware: SagaMiddleware<any>): IItemManager<ISagaRegistration<any>> {
|
||||
export function getSagaManager(
|
||||
sagaMiddleware: SagaMiddleware<any>
|
||||
): IItemManager<ISagaRegistration<any>> {
|
||||
const tasks = getMap<ISagaRegistration<any>, Task>(sagaEquals);
|
||||
|
||||
return {
|
||||
|
@ -35,11 +37,14 @@ export function getSagaManager(sagaMiddleware: SagaMiddleware<any>): IItemManage
|
|||
dispose: () => {
|
||||
// Cancel everything
|
||||
tasks.keys.forEach(k => tasks.get(k).cancel());
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function runSaga(sagaMiddleware: SagaMiddleware<any>, sagaRegistration: ISagaRegistration<any>): Task {
|
||||
function runSaga(
|
||||
sagaMiddleware: SagaMiddleware<any>,
|
||||
sagaRegistration: ISagaRegistration<any>
|
||||
): Task {
|
||||
if (typeof sagaRegistration === "function") {
|
||||
const saga = sagaRegistration as () => Iterator<any>;
|
||||
return sagaMiddleware.run(saga);
|
||||
|
@ -48,4 +53,3 @@ function runSaga(sagaMiddleware: SagaMiddleware<any>, sagaRegistration: ISagaReg
|
|||
const argument = (sagaRegistration as ISagaWithArguments<any>).argument;
|
||||
return sagaMiddleware.run(saga, argument);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,43 +1,41 @@
|
|||
import { sagaEquals } from "../SagaComparer";
|
||||
|
||||
it("Saga comparer tests", () => {
|
||||
|
||||
expect(sagaEquals(null, null)).toBe(true);
|
||||
expect(sagaEquals(func1 as any, func1 as any)).toBe(true);
|
||||
expect(sagaEquals(saga1, saga1)).toBe(true);
|
||||
expect(sagaEquals(
|
||||
{
|
||||
saga: sagaWithArgs,
|
||||
argument: "abc"
|
||||
},
|
||||
{
|
||||
saga: sagaWithArgs,
|
||||
argument: "abc"
|
||||
})).toBe(true);
|
||||
|
||||
expect(
|
||||
sagaEquals(
|
||||
{
|
||||
saga: sagaWithArgs,
|
||||
argument: "abc",
|
||||
},
|
||||
{
|
||||
saga: sagaWithArgs,
|
||||
argument: "abc",
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
|
||||
expect(sagaEquals(undefined, null)).toBe(false);
|
||||
expect(sagaEquals(null, func1 as any)).toBe(false);
|
||||
expect(sagaEquals(func1 as any, saga1)).toBe(false);
|
||||
expect(sagaEquals(
|
||||
{
|
||||
saga: sagaWithArgs,
|
||||
argument: "abc"
|
||||
},
|
||||
{
|
||||
saga: sagaWithArgs,
|
||||
argument: "pqr"
|
||||
})).toBe(false);
|
||||
expect(
|
||||
sagaEquals(
|
||||
{
|
||||
saga: sagaWithArgs,
|
||||
argument: "abc",
|
||||
},
|
||||
{
|
||||
saga: sagaWithArgs,
|
||||
argument: "pqr",
|
||||
}
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
function func1() {
|
||||
function func1() {}
|
||||
|
||||
}
|
||||
function* saga1() {}
|
||||
|
||||
function* saga1() {
|
||||
|
||||
}
|
||||
|
||||
function* sagaWithArgs(name: string) {
|
||||
|
||||
}
|
||||
function* sagaWithArgs(name: string) {}
|
||||
|
|
|
@ -16,12 +16,11 @@ describe("Saga extension tests", () => {
|
|||
function getTestModule(): ISagaModule<{ a: string }> {
|
||||
return {
|
||||
id: "test-module",
|
||||
sagas: [testSaga]
|
||||
}
|
||||
sagas: [testSaga],
|
||||
};
|
||||
}
|
||||
|
||||
let called = false;
|
||||
function* testSaga(): SagaIterator {
|
||||
called = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { getSagaManager } from '../SagaManager';
|
||||
import { getSagaManager } from "../SagaManager";
|
||||
function getSagaMiddleware(callback) {
|
||||
return {
|
||||
run: () => {
|
||||
return {
|
||||
cancel: callback
|
||||
}
|
||||
}
|
||||
cancel: callback,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,4 @@ it("saga manager tests", () => {
|
|||
expect(index).toBe(1);
|
||||
});
|
||||
|
||||
function* saga1() {
|
||||
|
||||
}
|
||||
function* saga1() {}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
export * from "./Contracts";
|
||||
export * from "./SagaExtension";
|
||||
export * from "./SagaExtension";
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015",
|
||||
"es2016"
|
||||
],
|
||||
"lib": ["dom", "es2015", "es2016"],
|
||||
"declaration": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitAny": false,
|
||||
|
@ -13,10 +9,6 @@
|
|||
"jsx": "react",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/__tests__/**/**.ts"
|
||||
]
|
||||
}
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["src/__tests__/**/**.ts"]
|
||||
}
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
{
|
||||
|
||||
}
|
||||
{}
|
||||
|
|
|
@ -1,36 +1,40 @@
|
|||
let webpack = require("webpack");
|
||||
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
let BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
|
||||
.BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
let mode_env = argv.mode || 'development';
|
||||
let mode_env = argv.mode || "development";
|
||||
|
||||
return {
|
||||
mode: mode_env,
|
||||
devtool: 'source-map',
|
||||
devtool: "source-map",
|
||||
entry: {
|
||||
main: './lib/index'
|
||||
main: "./lib/index",
|
||||
},
|
||||
|
||||
output: {
|
||||
library: "redux-dynamic-modules-saga",
|
||||
libraryTarget: "umd",
|
||||
filename: mode_env === "production" ? "redux-dynamic-modules-saga.min.js" : "redux-dynamic-modules-saga.js",
|
||||
path: __dirname + "/dist/"
|
||||
filename:
|
||||
mode_env === "production"
|
||||
? "redux-dynamic-modules-saga.min.js"
|
||||
: "redux-dynamic-modules-saga.js",
|
||||
path: __dirname + "/dist/",
|
||||
},
|
||||
|
||||
externals: {
|
||||
"react": "react",
|
||||
"redux": "redux",
|
||||
react: "react",
|
||||
redux: "redux",
|
||||
"react-redux": "react-redux",
|
||||
"redux-saga": "redux-saga",
|
||||
"redux-dynamic-modules": "redux-dynamic-modules"
|
||||
"redux-dynamic-modules": "redux-dynamic-modules",
|
||||
},
|
||||
plugins: [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
analyzerMode: "static",
|
||||
reportFilename: `react-redux-module.stats.html`,
|
||||
openAnalyzer: false
|
||||
})
|
||||
]
|
||||
openAnalyzer: false,
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -3,6 +3,6 @@ import { IExtension } from "redux-dynamic-modules";
|
|||
|
||||
export function getThunkExtension(): IExtension {
|
||||
return {
|
||||
middleware: [thunk]
|
||||
}
|
||||
}
|
||||
middleware: [thunk],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015",
|
||||
"es2016"
|
||||
],
|
||||
"lib": ["dom", "es2015", "es2016"],
|
||||
"declaration": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitAny": false,
|
||||
|
@ -13,7 +9,5 @@
|
|||
"jsx": "react",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
{
|
||||
|
||||
}
|
||||
{}
|
||||
|
|
|
@ -1,36 +1,40 @@
|
|||
let webpack = require("webpack");
|
||||
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
let BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
|
||||
.BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
let mode_env = argv.mode || 'development';
|
||||
let mode_env = argv.mode || "development";
|
||||
|
||||
return {
|
||||
mode: mode_env,
|
||||
devtool: 'source-map',
|
||||
devtool: "source-map",
|
||||
entry: {
|
||||
main: './lib/index'
|
||||
main: "./lib/index",
|
||||
},
|
||||
|
||||
output: {
|
||||
library: "redux-dynamic-modules-thunk",
|
||||
libraryTarget: "umd",
|
||||
filename: mode_env === "production" ? "redux-dynamic-modules-thunk.min.js" : "redux-dynamic-modules-thunk.js",
|
||||
path: __dirname + "/dist/"
|
||||
filename:
|
||||
mode_env === "production"
|
||||
? "redux-dynamic-modules-thunk.min.js"
|
||||
: "redux-dynamic-modules-thunk.js",
|
||||
path: __dirname + "/dist/",
|
||||
},
|
||||
|
||||
externals: {
|
||||
"react": "react",
|
||||
"redux": "redux",
|
||||
react: "react",
|
||||
redux: "redux",
|
||||
"react-redux": "react-redux",
|
||||
"redux-thunk": "redux-thunk",
|
||||
"redux-dynamic-modules": "redux-dynamic-modules"
|
||||
"redux-dynamic-modules": "redux-dynamic-modules",
|
||||
},
|
||||
plugins: [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
analyzerMode: "static",
|
||||
reportFilename: `react-redux-module.stats.html`,
|
||||
openAnalyzer: false
|
||||
})
|
||||
]
|
||||
openAnalyzer: false,
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,71 +1,71 @@
|
|||
{
|
||||
"name": "redux-dynamic-modules",
|
||||
"version": "3.0.3",
|
||||
"description": "Modularize the redux app by dynamically loading reducers, state and sagas",
|
||||
"repository": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/Microsoft/redux-dynamic-modules"
|
||||
},
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"scripts": {
|
||||
"prepublish": "npm run build:prod && npm run test",
|
||||
"build": "npm run clean && tsc && webpack --mode development --display-modules --progress --display-error-details",
|
||||
"build:prod": "npm run clean && tsc && webpack --mode development --display-error-details && webpack --mode production --display-modules --progress --display-error-details",
|
||||
"clean": "rimraf lib/ dist/",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"redux",
|
||||
"module",
|
||||
"dynamic",
|
||||
"load"
|
||||
],
|
||||
"author": "Navneet Gupta (Microsoft), Alex Bettadapur (Microsoft)",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^23.3.1",
|
||||
"@types/react": "^16.4.13",
|
||||
"@types/react-redux": "^6.0.6",
|
||||
"@types/redux": "^3.6.0",
|
||||
"jest": "^23.5.0",
|
||||
"react": "^16.4.2",
|
||||
"react-redux": "^5.0.7",
|
||||
"redux": "^4.0.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"ts-jest": "^23.1.4",
|
||||
"tslib": "^1.9.3",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^3.0.3",
|
||||
"uglifyjs-webpack-plugin": "^1.3.0",
|
||||
"webpack": "^4.17.1",
|
||||
"webpack-bundle-analyzer": "^2.13.1",
|
||||
"webpack-cli": "^3.1.0"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
"name": "redux-dynamic-modules",
|
||||
"version": "3.0.3",
|
||||
"description": "Modularize the redux app by dynamically loading reducers, state and sagas",
|
||||
"repository": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/Microsoft/redux-dynamic-modules"
|
||||
},
|
||||
"verbose": false,
|
||||
"testMatch": [
|
||||
"**/src/__tests__/**/(*.)+(spec|test).ts?(x)"
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"scripts": {
|
||||
"prepublish": "npm run build:prod && npm run test",
|
||||
"build": "npm run clean && tsc && webpack --mode development --display-modules --progress --display-error-details",
|
||||
"build:prod": "npm run clean && tsc && webpack --mode development --display-error-details && webpack --mode production --display-modules --progress --display-error-details",
|
||||
"clean": "rimraf lib/ dist/",
|
||||
"test": "jest"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"redux",
|
||||
"module",
|
||||
"dynamic",
|
||||
"load"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 15.0.0",
|
||||
"react-redux": ">= 5.0.0",
|
||||
"redux": ">= 3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"redux-dynamic-middlewares": "^1.0.0"
|
||||
}
|
||||
"author": "Navneet Gupta (Microsoft), Alex Bettadapur (Microsoft)",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/jest": "^23.3.1",
|
||||
"@types/react": "^16.4.13",
|
||||
"@types/react-redux": "^6.0.6",
|
||||
"@types/redux": "^3.6.0",
|
||||
"jest": "^23.5.0",
|
||||
"react": "^16.4.2",
|
||||
"react-redux": "^5.0.7",
|
||||
"redux": "^4.0.0",
|
||||
"rimraf": "^2.6.2",
|
||||
"ts-jest": "^23.1.4",
|
||||
"tslib": "^1.9.3",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^3.0.3",
|
||||
"uglifyjs-webpack-plugin": "^1.3.0",
|
||||
"webpack": "^4.17.1",
|
||||
"webpack-bundle-analyzer": "^2.13.1",
|
||||
"webpack-cli": "^3.1.0"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"verbose": false,
|
||||
"testMatch": [
|
||||
"**/src/__tests__/**/(*.)+(spec|test).ts?(x)"
|
||||
],
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 15.0.0",
|
||||
"react-redux": ">= 5.0.0",
|
||||
"redux": ">= 3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"redux-dynamic-middlewares": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ export interface IModule<State> {
|
|||
*/
|
||||
middlewares?: Middleware[];
|
||||
|
||||
|
||||
/**
|
||||
* These actions are dispatched immediately after adding the module in the store
|
||||
*/
|
||||
|
@ -50,19 +49,20 @@ export interface IModuleManager {
|
|||
/**
|
||||
* Add the given module to the store
|
||||
*/
|
||||
addModule: (module: IModule<any>) => IDynamicallyAddedModule
|
||||
addModule: (module: IModule<any>) => IDynamicallyAddedModule;
|
||||
/**
|
||||
* Adds the given set of modules to the store
|
||||
*/
|
||||
addModules: (modules: IModule<any>[]) => IDynamicallyAddedModule;
|
||||
}
|
||||
|
||||
export type IModuleStore<State> = Store<State> & IModuleManager & {
|
||||
/**
|
||||
* Remove all the modules from the store
|
||||
*/
|
||||
dispose: () => void;
|
||||
};
|
||||
export type IModuleStore<State> = Store<State> &
|
||||
IModuleManager & {
|
||||
/**
|
||||
* Remove all the modules from the store
|
||||
*/
|
||||
dispose: () => void;
|
||||
};
|
||||
|
||||
export interface IItemManager<T> {
|
||||
getItems: () => T[];
|
||||
|
|
|
@ -4,15 +4,15 @@ import { IModule, IModuleStore, IDynamicallyAddedModule } from "./Contracts";
|
|||
import { Provider } from "react-redux";
|
||||
|
||||
export interface IDynamicModuleLoaderProps<OriginalState, AdditionalState> {
|
||||
/** Modules that need to be dynamically registerd */
|
||||
modules: IModule<AdditionalState>[];
|
||||
/** Modules that need to be dynamically registerd */
|
||||
modules: IModule<AdditionalState>[];
|
||||
|
||||
/** Optional callback which returns a store instance. This would be called if no store could be loaded from the context. */
|
||||
createStore?: () => IModuleStore<any>;
|
||||
/** Optional callback which returns a store instance. This would be called if no store could be loaded from the context. */
|
||||
createStore?: () => IModuleStore<any>;
|
||||
}
|
||||
|
||||
export interface IDynamicModuleLoaderContext {
|
||||
store: IModuleStore<any>;
|
||||
store: IModuleStore<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,59 +21,61 @@ export interface IDynamicModuleLoaderContext {
|
|||
* On unmount, they will be unregistered
|
||||
*/
|
||||
export class DynamicModuleLoader<
|
||||
OriginalState,
|
||||
AdditionalState
|
||||
OriginalState,
|
||||
AdditionalState
|
||||
> extends React.Component<
|
||||
IDynamicModuleLoaderProps<OriginalState, AdditionalState>
|
||||
IDynamicModuleLoaderProps<OriginalState, AdditionalState>
|
||||
> {
|
||||
private _addedModules?: IDynamicallyAddedModule;
|
||||
private _store: IModuleStore<any>;
|
||||
private _providerInitializationNeeded: boolean;
|
||||
private _addedModules?: IDynamicallyAddedModule;
|
||||
private _store: IModuleStore<any>;
|
||||
private _providerInitializationNeeded: boolean;
|
||||
|
||||
// @ts-ignore
|
||||
private static contextTypes = {
|
||||
store: PropTypes.object
|
||||
};
|
||||
// @ts-ignore
|
||||
private static contextTypes = {
|
||||
store: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(
|
||||
props: IDynamicModuleLoaderProps<OriginalState, AdditionalState>,
|
||||
context: IDynamicModuleLoaderContext
|
||||
) {
|
||||
super(props, context);
|
||||
const { modules, createStore } = props;
|
||||
this._store = context.store;
|
||||
this._providerInitializationNeeded = false;
|
||||
constructor(
|
||||
props: IDynamicModuleLoaderProps<OriginalState, AdditionalState>,
|
||||
context: IDynamicModuleLoaderContext
|
||||
) {
|
||||
super(props, context);
|
||||
const { modules, createStore } = props;
|
||||
this._store = context.store;
|
||||
this._providerInitializationNeeded = false;
|
||||
|
||||
if (!this._store) {
|
||||
if (createStore) {
|
||||
this._store = createStore();
|
||||
this._providerInitializationNeeded = true;
|
||||
} else {
|
||||
throw new Error("Could not load store from React context.");
|
||||
}
|
||||
if (!this._store) {
|
||||
if (createStore) {
|
||||
this._store = createStore();
|
||||
this._providerInitializationNeeded = true;
|
||||
} else {
|
||||
throw new Error("Could not load store from React context.");
|
||||
}
|
||||
}
|
||||
|
||||
this._addedModules = this._store.addModules(modules);
|
||||
}
|
||||
|
||||
this._addedModules = this._store.addModules(modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister sagas and reducers
|
||||
*/
|
||||
public componentWillUnmount(): void {
|
||||
if (this._addedModules) {
|
||||
this._addedModules.remove();
|
||||
this._addedModules = undefined;
|
||||
/**
|
||||
* Unregister sagas and reducers
|
||||
*/
|
||||
public componentWillUnmount(): void {
|
||||
if (this._addedModules) {
|
||||
this._addedModules.remove();
|
||||
this._addedModules = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a Redux provider
|
||||
*/
|
||||
public render(): React.ReactNode {
|
||||
if (this._providerInitializationNeeded) {
|
||||
return <Provider store={this._store}>{this.props.children}</Provider>;
|
||||
} else {
|
||||
return this.props.children;
|
||||
/**
|
||||
* Render a Redux provider
|
||||
*/
|
||||
public render(): React.ReactNode {
|
||||
if (this._providerInitializationNeeded) {
|
||||
return (
|
||||
<Provider store={this._store}>{this.props.children}</Provider>
|
||||
);
|
||||
} else {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { Middleware } from "redux";
|
||||
import { IItemManager } from "../Contracts";
|
||||
import { createDynamicMiddlewares } from 'redux-dynamic-middlewares'
|
||||
import { createDynamicMiddlewares } from "redux-dynamic-middlewares";
|
||||
|
||||
export interface IDynamicMiddlewareManager extends IItemManager<Middleware> {
|
||||
enhancer: Middleware;
|
||||
|
@ -12,18 +12,20 @@ export const getMiddlewareManager = (): IDynamicMiddlewareManager => {
|
|||
const add = (middlewares: Middleware[]) => {
|
||||
dynamicMiddlewaresInstance.addMiddleware(...middlewares);
|
||||
return middlewares;
|
||||
}
|
||||
};
|
||||
|
||||
const remove = (middlewares: Middleware[]) => {
|
||||
middlewares.forEach(dynamicMiddlewaresInstance.removeMiddleware);
|
||||
return middlewares;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getItems: () => [],
|
||||
enhancer: dynamicMiddlewaresInstance.enhancer,
|
||||
add,
|
||||
remove,
|
||||
dispose: () => { dynamicMiddlewaresInstance.resetMiddlewares() }
|
||||
}
|
||||
}
|
||||
dispose: () => {
|
||||
dynamicMiddlewaresInstance.resetMiddlewares();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,13 +1,26 @@
|
|||
import { IModule, IItemManager, IExtension } from "../Contracts";
|
||||
import { AnyAction, ReducersMapObject, Dispatch, Middleware, Reducer } from "redux";
|
||||
import { getReducerManager, IReducerManager, getRefCountedReducerManager } from "./ReducerManager";
|
||||
import {
|
||||
AnyAction,
|
||||
ReducersMapObject,
|
||||
Dispatch,
|
||||
Middleware,
|
||||
Reducer,
|
||||
} from "redux";
|
||||
import {
|
||||
getReducerManager,
|
||||
IReducerManager,
|
||||
getRefCountedReducerManager,
|
||||
} from "./ReducerManager";
|
||||
|
||||
export interface IModuleManager<State> extends IItemManager<IModule<State>> {
|
||||
setDispatch: (dispatch: Dispatch<AnyAction>) => void;
|
||||
getReducer: (state: State, action: AnyAction) => State;
|
||||
}
|
||||
|
||||
export function getModuleManager<State>(middlewareManager: IItemManager<Middleware>, extensions: IExtension[]): IModuleManager<State> {
|
||||
export function getModuleManager<State>(
|
||||
middlewareManager: IItemManager<Middleware>,
|
||||
extensions: IExtension[]
|
||||
): IModuleManager<State> {
|
||||
let _dispatch = null;
|
||||
let _reducerManager: IReducerManager<State>;
|
||||
let _modules: IModule<any>[] = [];
|
||||
|
@ -19,53 +32,61 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
|
|||
}
|
||||
|
||||
if (!_dispatch) {
|
||||
throw new Error("setDispatch should be called on ModuleManager before adding any modules.");
|
||||
throw new Error(
|
||||
"setDispatch should be called on ModuleManager before adding any modules."
|
||||
);
|
||||
}
|
||||
|
||||
actions.forEach(_dispatch);
|
||||
}
|
||||
};
|
||||
|
||||
const _addMiddlewares = (middlewares: Middleware[]) => {
|
||||
if (!middlewares) {
|
||||
return;
|
||||
}
|
||||
middlewareManager.add(middlewares);
|
||||
}
|
||||
};
|
||||
|
||||
const _removeMiddlewares = (middlewares: Middleware[]) => {
|
||||
if (!middlewares) {
|
||||
return;
|
||||
}
|
||||
middlewareManager.remove(middlewares);
|
||||
}
|
||||
};
|
||||
|
||||
const _addReducers = (reducerMap: ReducersMapObject<Reducer, AnyAction>) => {
|
||||
const _addReducers = (
|
||||
reducerMap: ReducersMapObject<Reducer, AnyAction>
|
||||
) => {
|
||||
if (!reducerMap) {
|
||||
return;
|
||||
}
|
||||
if (!_reducerManager) {
|
||||
_reducerManager = getRefCountedReducerManager(getReducerManager(reducerMap)) as any;
|
||||
_reducerManager = getRefCountedReducerManager(
|
||||
getReducerManager(reducerMap)
|
||||
) as any;
|
||||
} else {
|
||||
for (const key in reducerMap) {
|
||||
_reducerManager.add(key, reducerMap[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _removeReducers = (reducerMap: ReducersMapObject<Reducer, AnyAction>) => {
|
||||
const _removeReducers = (
|
||||
reducerMap: ReducersMapObject<Reducer, AnyAction>
|
||||
) => {
|
||||
if (!reducerMap || !_reducerManager) {
|
||||
return;
|
||||
}
|
||||
for (const key in reducerMap) {
|
||||
_reducerManager.remove(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Create reduce function which redirects to _reducers.reduce
|
||||
const _reduce = (s: State, a: AnyAction) => {
|
||||
if (_reducerManager) {
|
||||
return _reducerManager.reduce(s, a);
|
||||
}
|
||||
return (s || null);
|
||||
return s || null;
|
||||
};
|
||||
|
||||
const moduleManager = {
|
||||
|
@ -93,10 +114,8 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
// add the sagas and dispatch actions at the end so all the reducers are registered
|
||||
justAddedModules.forEach(module => {
|
||||
|
||||
// Let the extensions know we added a module
|
||||
extensions.forEach(p => {
|
||||
if (p.onModuleAdded) {
|
||||
|
@ -105,8 +124,15 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
|
|||
});
|
||||
|
||||
// Dispatch the initial actions
|
||||
const moduleAddedAction = { type: "@@Internal/ModuleManager/ModuleAdded", payload: module.id };
|
||||
_dispatchActions(module.initialActions ? [moduleAddedAction, ...module.initialActions] : [moduleAddedAction]);
|
||||
const moduleAddedAction = {
|
||||
type: "@@Internal/ModuleManager/ModuleAdded",
|
||||
payload: module.id,
|
||||
};
|
||||
_dispatchActions(
|
||||
module.initialActions
|
||||
? [moduleAddedAction, ...module.initialActions]
|
||||
: [moduleAddedAction]
|
||||
);
|
||||
});
|
||||
},
|
||||
remove: (modulesToRemove: IModule<any>[]) => {
|
||||
|
@ -115,7 +141,6 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
|
|||
}
|
||||
modulesToRemove = modulesToRemove.filter(module => module);
|
||||
modulesToRemove.forEach(module => {
|
||||
|
||||
if (_moduleIds.has(module.id)) {
|
||||
_dispatchActions(module.finalActions);
|
||||
|
||||
|
@ -132,13 +157,18 @@ export function getModuleManager<State>(middlewareManager: IItemManager<Middlewa
|
|||
_moduleIds.delete(module.id);
|
||||
_modules = _modules.filter(m => m.id !== module.id);
|
||||
|
||||
_dispatchActions([{ type: "@@Internal/ModuleManager/ModuleRemoved", payload: module.id }]);
|
||||
_dispatchActions([
|
||||
{
|
||||
type: "@@Internal/ModuleManager/ModuleRemoved",
|
||||
payload: module.id,
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
},
|
||||
dispose: () => {
|
||||
moduleManager.remove(_modules);
|
||||
}
|
||||
},
|
||||
};
|
||||
return moduleManager;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
combineReducers,
|
||||
Reducer,
|
||||
ReducersMapObject,
|
||||
AnyAction
|
||||
} from "redux";
|
||||
import { combineReducers, Reducer, ReducersMapObject, AnyAction } from "redux";
|
||||
import { getStringRefCounter } from "../Utils/RefCounter";
|
||||
|
||||
export interface IReducerManager<S> {
|
||||
|
@ -16,7 +11,9 @@ export interface IReducerManager<S> {
|
|||
/**
|
||||
* Adds reference counting to reducer manager and adds/remove keys only when ref count is zero
|
||||
*/
|
||||
export function getRefCountedReducerManager<S>(manager: IReducerManager<S>): IReducerManager<S> {
|
||||
export function getRefCountedReducerManager<S>(
|
||||
manager: IReducerManager<S>
|
||||
): IReducerManager<S> {
|
||||
const reducerKeyRefCounter = getStringRefCounter();
|
||||
for (const key in manager.getReducerMap()) {
|
||||
reducerKeyRefCounter.add(key);
|
||||
|
@ -38,7 +35,7 @@ export function getRefCountedReducerManager<S>(manager: IReducerManager<S>): IRe
|
|||
if (reducerKeyRefCounter.getCount(key) === 0) {
|
||||
manager.remove(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -52,12 +49,14 @@ export function getReducerManager<S extends {}>(
|
|||
initialReducers: ReducersMapObject<S>
|
||||
): IReducerManager<S> {
|
||||
let combinedReducer = combineReducers(initialReducers);
|
||||
const reducers: ReducersMapObject<S> = { ...initialReducers as object } as ReducersMapObject<S>;
|
||||
const reducers: ReducersMapObject<S> = {
|
||||
...(initialReducers as object),
|
||||
} as ReducersMapObject<S>;
|
||||
let keysToRemove = [];
|
||||
|
||||
const reduce = (state: S, action: AnyAction) => {
|
||||
if (keysToRemove.length > 0) {
|
||||
state = { ...state as any };
|
||||
state = { ...(state as any) };
|
||||
for (let key of keysToRemove) {
|
||||
delete state[key];
|
||||
}
|
||||
|
@ -75,7 +74,6 @@ export function getReducerManager<S extends {}>(
|
|||
getReducerMap: () => reducers,
|
||||
reduce,
|
||||
add: <ReducerState>(key: string, reducer: Reducer<ReducerState>) => {
|
||||
|
||||
if (!key || reducers[key]) {
|
||||
return;
|
||||
}
|
||||
|
@ -101,4 +99,3 @@ function getCombinedReducer(reducerMap: ReducersMapObject<any>) {
|
|||
}
|
||||
return combineReducers(reducerMap);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,13 +4,16 @@ import { IItemManager } from "../Contracts";
|
|||
/**
|
||||
* Enhances the given items with ref counting for add remove purposes
|
||||
*/
|
||||
export function getRefCountedManager<IType extends IItemManager<T>, T>(manager: IType, equals: (a: T, b: T) => boolean): IType {
|
||||
export function getRefCountedManager<IType extends IItemManager<T>, T>(
|
||||
manager: IType,
|
||||
equals: (a: T, b: T) => boolean
|
||||
): IType {
|
||||
let refCounter = getObjectRefCounter<T>(equals);
|
||||
const items = manager.getItems();
|
||||
// Set initial ref counting
|
||||
items.forEach(item => refCounter.add(item));
|
||||
|
||||
const ret: IType = { ...manager as object } as IType;
|
||||
const ret: IType = { ...(manager as object) } as IType;
|
||||
|
||||
// Wrap add method
|
||||
ret.add = (items: T[]) => {
|
||||
|
@ -19,14 +22,15 @@ export function getRefCountedManager<IType extends IItemManager<T>, T>(manager:
|
|||
}
|
||||
|
||||
const nonNullItems = items.filter(i => i);
|
||||
const notAddedItems = nonNullItems.filter(i => refCounter.getCount(i) === 0);
|
||||
const notAddedItems = nonNullItems.filter(
|
||||
i => refCounter.getCount(i) === 0
|
||||
);
|
||||
manager.add(notAddedItems);
|
||||
nonNullItems.forEach(refCounter.add);
|
||||
};
|
||||
|
||||
// Wrap remove
|
||||
ret.remove = (items: T[]) => {
|
||||
|
||||
if (!items) {
|
||||
return;
|
||||
}
|
||||
|
@ -38,11 +42,11 @@ export function getRefCountedManager<IType extends IItemManager<T>, T>(manager:
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ret.dispose = () => {
|
||||
manager.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
@ -1,54 +1,139 @@
|
|||
import { applyMiddleware, compose, createStore as createReduxStore, DeepPartial, StoreEnhancer } from "redux";
|
||||
import {
|
||||
applyMiddleware,
|
||||
compose,
|
||||
createStore as createReduxStore,
|
||||
DeepPartial,
|
||||
StoreEnhancer,
|
||||
} from "redux";
|
||||
import { getMiddlewareManager } from ".";
|
||||
import { IExtension, IModule, IModuleStore } from "./Contracts";
|
||||
import { getModuleManager } from "./Managers/ModuleManager";
|
||||
import { getRefCountedManager } from './Managers/RefCountedManager';
|
||||
import { getRefCountedManager } from "./Managers/RefCountedManager";
|
||||
|
||||
/**
|
||||
* Configure the module store
|
||||
*/
|
||||
export function createStore<S1>(initialState: DeepPartial<S1>, enhancers: StoreEnhancer[], extensions: IExtension[], reduxModule: IModule<S1>): IModuleStore<S1>;
|
||||
export function createStore<S1, S2>(initialState: DeepPartial<S1 & S2>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>): IModuleStore<S1 & S2>;
|
||||
export function createStore<S1, S2, S3>(initialState: DeepPartial<S1 & S2 & S3>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>): IModuleStore<S1 & S2 & S3>;
|
||||
export function createStore<S1, S2, S3, S4>(initialState: DeepPartial<S1 & S2 & S3 & S4>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>): IModuleStore<S1 & S2 & S3 & S4>;
|
||||
export function createStore<S1, S2, S3, S4, S5>(initialState: DeepPartial<S1 & S2 & S3 & S4 & S5>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>, m5: IModule<S5>): IModuleStore<S1 & S2 & S3 & S4 & S5>;
|
||||
export function createStore<S1, S2, S3, S4, S5, S6>(initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>, m5: IModule<S5>, m6: IModule<S6>): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6>;
|
||||
export function createStore<S1, S2, S3, S4, S5, S6, S7>(initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6 & S7>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>, m5: IModule<S5>, m6: IModule<S6>, m7: IModule<S7>): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6 & S7>;
|
||||
export function createStore<S1, S2, S3, S4, S5, S6, S7, S8>(initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>, enhancers: StoreEnhancer[], extensions: IExtension[], m1: IModule<S1>, m2: IModule<S2>, m3: IModule<S3>, m4: IModule<S4>, m5: IModule<S5>, m6: IModule<S6>, m7: IModule<S7>, m8: IModule<S8>): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>;
|
||||
export function createStore<State>(initialState: DeepPartial<State>, enhancers: StoreEnhancer[], extensions: IExtension[], ...initialModules: IModule<any>[]): IModuleStore<State>;
|
||||
export function createStore<State>(initialState: DeepPartial<State>, enhancers: StoreEnhancer[], extensions: IExtension[], ...initialModules: IModule<any>[]): IModuleStore<State> {
|
||||
export function createStore<S1>(
|
||||
initialState: DeepPartial<S1>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
reduxModule: IModule<S1>
|
||||
): IModuleStore<S1>;
|
||||
export function createStore<S1, S2>(
|
||||
initialState: DeepPartial<S1 & S2>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
m1: IModule<S1>,
|
||||
m2: IModule<S2>
|
||||
): IModuleStore<S1 & S2>;
|
||||
export function createStore<S1, S2, S3>(
|
||||
initialState: DeepPartial<S1 & S2 & S3>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
m1: IModule<S1>,
|
||||
m2: IModule<S2>,
|
||||
m3: IModule<S3>
|
||||
): IModuleStore<S1 & S2 & S3>;
|
||||
export function createStore<S1, S2, S3, S4>(
|
||||
initialState: DeepPartial<S1 & S2 & S3 & S4>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
m1: IModule<S1>,
|
||||
m2: IModule<S2>,
|
||||
m3: IModule<S3>,
|
||||
m4: IModule<S4>
|
||||
): IModuleStore<S1 & S2 & S3 & S4>;
|
||||
export function createStore<S1, S2, S3, S4, S5>(
|
||||
initialState: DeepPartial<S1 & S2 & S3 & S4 & S5>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
m1: IModule<S1>,
|
||||
m2: IModule<S2>,
|
||||
m3: IModule<S3>,
|
||||
m4: IModule<S4>,
|
||||
m5: IModule<S5>
|
||||
): IModuleStore<S1 & S2 & S3 & S4 & S5>;
|
||||
export function createStore<S1, S2, S3, S4, S5, S6>(
|
||||
initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
m1: IModule<S1>,
|
||||
m2: IModule<S2>,
|
||||
m3: IModule<S3>,
|
||||
m4: IModule<S4>,
|
||||
m5: IModule<S5>,
|
||||
m6: IModule<S6>
|
||||
): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6>;
|
||||
export function createStore<S1, S2, S3, S4, S5, S6, S7>(
|
||||
initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6 & S7>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
m1: IModule<S1>,
|
||||
m2: IModule<S2>,
|
||||
m3: IModule<S3>,
|
||||
m4: IModule<S4>,
|
||||
m5: IModule<S5>,
|
||||
m6: IModule<S6>,
|
||||
m7: IModule<S7>
|
||||
): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6 & S7>;
|
||||
export function createStore<S1, S2, S3, S4, S5, S6, S7, S8>(
|
||||
initialState: DeepPartial<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
m1: IModule<S1>,
|
||||
m2: IModule<S2>,
|
||||
m3: IModule<S3>,
|
||||
m4: IModule<S4>,
|
||||
m5: IModule<S5>,
|
||||
m6: IModule<S6>,
|
||||
m7: IModule<S7>,
|
||||
m8: IModule<S8>
|
||||
): IModuleStore<S1 & S2 & S3 & S4 & S5 & S6 & S7 & S8>;
|
||||
export function createStore<State>(
|
||||
initialState: DeepPartial<State>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
...initialModules: IModule<any>[]
|
||||
): IModuleStore<State>;
|
||||
export function createStore<State>(
|
||||
initialState: DeepPartial<State>,
|
||||
enhancers: StoreEnhancer[],
|
||||
extensions: IExtension[],
|
||||
...initialModules: IModule<any>[]
|
||||
): IModuleStore<State> {
|
||||
if (!extensions) {
|
||||
extensions = [];
|
||||
}
|
||||
|
||||
const extensionMiddleware = extensions.reduce(
|
||||
(mw, p) => {
|
||||
if (p.middleware) {
|
||||
mw.push(...p.middleware)
|
||||
}
|
||||
const extensionMiddleware = extensions.reduce((mw, p) => {
|
||||
if (p.middleware) {
|
||||
mw.push(...p.middleware);
|
||||
}
|
||||
|
||||
return mw;
|
||||
},
|
||||
[]
|
||||
);
|
||||
return mw;
|
||||
}, []);
|
||||
|
||||
let composeEnhancers = compose;
|
||||
|
||||
//@ts-ignore
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
composeEnhancers = window["__REDUX_DEVTOOLS_EXTENSION_COMPOSE__"] || compose;
|
||||
composeEnhancers =
|
||||
window["__REDUX_DEVTOOLS_EXTENSION_COMPOSE__"] || compose;
|
||||
}
|
||||
|
||||
const middlewareManager = getRefCountedManager(getMiddlewareManager(), (a, b) => a === b);
|
||||
const middlewareManager = getRefCountedManager(
|
||||
getMiddlewareManager(),
|
||||
(a, b) => a === b
|
||||
);
|
||||
const enhancer = composeEnhancers(
|
||||
...enhancers,
|
||||
applyMiddleware(
|
||||
...extensionMiddleware,
|
||||
middlewareManager.enhancer));
|
||||
applyMiddleware(...extensionMiddleware, middlewareManager.enhancer)
|
||||
);
|
||||
|
||||
const modules = getRefCountedManager(
|
||||
getModuleManager<State>(middlewareManager, extensions),
|
||||
(a: IModule<any>, b: IModule<any>) => a.id === b.id);
|
||||
(a: IModule<any>, b: IModule<any>) => a.id === b.id
|
||||
);
|
||||
|
||||
// Create store
|
||||
const store: IModuleStore<State> = createReduxStore<State, any, {}, {}>(
|
||||
|
@ -64,9 +149,9 @@ export function createStore<State>(initialState: DeepPartial<State>, enhancers:
|
|||
return {
|
||||
remove: () => {
|
||||
modules.remove(modulesToBeAdded);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const addModule = (moduleToBeAdded: IModule<any>) => {
|
||||
return addModules([moduleToBeAdded]);
|
||||
|
@ -76,7 +161,7 @@ export function createStore<State>(initialState: DeepPartial<State>, enhancers:
|
|||
if (p.onModuleManagerCreated) {
|
||||
p.onModuleManagerCreated({
|
||||
addModule,
|
||||
addModules
|
||||
addModules,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -95,7 +180,6 @@ export function createStore<State>(initialState: DeepPartial<State>, enhancers:
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
store.addModules(initialModules);
|
||||
|
||||
return store as IModuleStore<State>;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
export interface IMap<K, V> {
|
||||
keys: K[];
|
||||
get(key: K): V;
|
||||
|
@ -10,7 +9,9 @@ export interface IMap<K, V> {
|
|||
* We will use it where we can not use the default Map as the Map class do not allow custom compare function
|
||||
* @param equals Optional, a comparer to use
|
||||
*/
|
||||
export function getMap<K, V>(equals: (key1: K, key2: K) => boolean): IMap<K, V> {
|
||||
export function getMap<K, V>(
|
||||
equals: (key1: K, key2: K) => boolean
|
||||
): IMap<K, V> {
|
||||
const keys: K[] = [];
|
||||
const values: { [key: number]: V } = {};
|
||||
|
||||
|
@ -61,6 +62,6 @@ export function getMap<K, V>(equals: (key1: K, key2: K) => boolean): IMap<K, V>
|
|||
const value = values[index];
|
||||
delete values[index];
|
||||
return value;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
export interface IRefCounter<T> {
|
||||
/**
|
||||
* Gets refeerence count for given item
|
||||
|
@ -16,7 +15,9 @@ export interface IRefCounter<T> {
|
|||
}
|
||||
|
||||
/** Ref counts given object */
|
||||
export function getObjectRefCounter<T>(equals: (a: T, b: T) => boolean): IRefCounter<T> {
|
||||
export function getObjectRefCounter<T>(
|
||||
equals: (a: T, b: T) => boolean
|
||||
): IRefCounter<T> {
|
||||
if (!equals) {
|
||||
equals = (a, b) => a === b;
|
||||
}
|
||||
|
@ -72,8 +73,8 @@ export function getObjectRefCounter<T>(equals: (a: T, b: T) => boolean): IRefCou
|
|||
|
||||
counts[index] = counts[index] - 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +83,6 @@ export function getObjectRefCounter<T>(equals: (a: T, b: T) => boolean): IRefCou
|
|||
export function getStringRefCounter(): IRefCounter<string> {
|
||||
const values: { [key: string]: number } = {};
|
||||
return {
|
||||
|
||||
/**
|
||||
* Returns current ref count for the key
|
||||
*/
|
||||
|
@ -126,6 +126,6 @@ export function getStringRefCounter(): IRefCounter<string> {
|
|||
|
||||
values[key]--;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,14 +11,14 @@ describe("Store with extensions", () => {
|
|||
onModuleAdded: jest.fn(),
|
||||
onModuleManagerCreated: jest.fn(),
|
||||
onModuleRemoved: jest.fn(),
|
||||
dispose: jest.fn()
|
||||
}
|
||||
dispose: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
it("Registers additional middleware", () => {
|
||||
const middlewareFunction = jest.fn();
|
||||
|
||||
const middleware: Middleware = (api) => (next) => (action) => {
|
||||
const middleware: Middleware = api => next => action => {
|
||||
middlewareFunction();
|
||||
};
|
||||
testExtension.middleware = [middleware];
|
||||
|
@ -46,4 +46,4 @@ describe("Store with extensions", () => {
|
|||
|
||||
expect(testExtension.onModuleRemoved).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,60 +6,103 @@ it("module manager tests", () => {
|
|||
const moduleManager = getModuleManager(middlewareManager, []);
|
||||
let actionsDispatched = [];
|
||||
|
||||
moduleManager.setDispatch((action) => {
|
||||
moduleManager.setDispatch(action => {
|
||||
// Push the action type to array so we can track it
|
||||
actionsDispatched.push(action.type);
|
||||
return action.payload || null;
|
||||
});
|
||||
|
||||
const reducer = (state) => state || null;
|
||||
const reducer = state => state || null;
|
||||
|
||||
const module1 = {
|
||||
id: "module1",
|
||||
initialActions: [{ type: "initial1" }, { type: "initial11" }],
|
||||
reducerMap: { "duplicateReducer": reducer, "key11": reducer },
|
||||
reducerMap: { duplicateReducer: reducer, key11: reducer },
|
||||
finalActions: [{ type: "final1" }, { type: "final11" }],
|
||||
sagas: [saga1]
|
||||
sagas: [saga1],
|
||||
};
|
||||
|
||||
const module2 = {
|
||||
id: "module2",
|
||||
initialActions: [{ type: "initial2" }, { type: "initial21" }],
|
||||
reducerMap: { "duplicateReducer": reducer, "key2": reducer }, // Keep one reducer duplicated to ensure ref counting of reducer
|
||||
reducerMap: { duplicateReducer: reducer, key2: reducer }, // Keep one reducer duplicated to ensure ref counting of reducer
|
||||
finalActions: [{ type: "final2" }, { type: "final21" }],
|
||||
sagas: [saga1] //Keep the same saga to validate saga ref counting
|
||||
sagas: [saga1], //Keep the same saga to validate saga ref counting
|
||||
};
|
||||
|
||||
// Add first module
|
||||
moduleManager.add([module1]);
|
||||
|
||||
// Test initial actions are dispatched for module1
|
||||
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11"]);
|
||||
expect(actionsDispatched).toEqual([
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial1",
|
||||
"initial11",
|
||||
]);
|
||||
|
||||
// Add second module
|
||||
moduleManager.add([module2]);
|
||||
|
||||
// Test initial actions are dispatched for module2
|
||||
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "@@Internal/ModuleManager/ModuleAdded", "initial2", "initial21"]);
|
||||
expect(actionsDispatched).toEqual([
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial1",
|
||||
"initial11",
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial2",
|
||||
"initial21",
|
||||
]);
|
||||
|
||||
// Remove Module1
|
||||
moduleManager.remove([module1]);
|
||||
|
||||
// Test final actions are dispatched for module1
|
||||
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "@@Internal/ModuleManager/ModuleAdded", "initial2", "initial21", "final1", "final11", "@@Internal/ModuleManager/ModuleRemoved"]);
|
||||
expect(actionsDispatched).toEqual([
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial1",
|
||||
"initial11",
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial2",
|
||||
"initial21",
|
||||
"final1",
|
||||
"final11",
|
||||
"@@Internal/ModuleManager/ModuleRemoved",
|
||||
]);
|
||||
|
||||
// Remove Module1 again
|
||||
moduleManager.remove([module1]);
|
||||
|
||||
// Test no additional actions are dispatched
|
||||
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "@@Internal/ModuleManager/ModuleAdded", "initial2", "initial21", "final1", "final11", "@@Internal/ModuleManager/ModuleRemoved"]);
|
||||
expect(actionsDispatched).toEqual([
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial1",
|
||||
"initial11",
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial2",
|
||||
"initial21",
|
||||
"final1",
|
||||
"final11",
|
||||
"@@Internal/ModuleManager/ModuleRemoved",
|
||||
]);
|
||||
|
||||
// Remove Module2
|
||||
moduleManager.remove([module2]);
|
||||
|
||||
// Test no additional actions are dispatched
|
||||
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "@@Internal/ModuleManager/ModuleAdded", "initial2", "initial21", "final1", "final11", "@@Internal/ModuleManager/ModuleRemoved", "final2", "final21", "@@Internal/ModuleManager/ModuleRemoved"]);
|
||||
|
||||
expect(actionsDispatched).toEqual([
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial1",
|
||||
"initial11",
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial2",
|
||||
"initial21",
|
||||
"final1",
|
||||
"final11",
|
||||
"@@Internal/ModuleManager/ModuleRemoved",
|
||||
"final2",
|
||||
"final21",
|
||||
"@@Internal/ModuleManager/ModuleRemoved",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Dispose disposes all modules", () => {
|
||||
|
@ -67,28 +110,33 @@ it("Dispose disposes all modules", () => {
|
|||
const moduleManager = getModuleManager(middlewareManager, []);
|
||||
let actionsDispatched = [];
|
||||
|
||||
moduleManager.setDispatch((action) => {
|
||||
moduleManager.setDispatch(action => {
|
||||
// Push the action type to array so we can track it
|
||||
actionsDispatched.push(action.type);
|
||||
return action.payload || null;
|
||||
});
|
||||
|
||||
const reducer = (state) => state || null;
|
||||
const reducer = state => state || null;
|
||||
|
||||
const module1 = {
|
||||
id: "module1",
|
||||
initialActions: [{ type: "initial1" }, { type: "initial11" }],
|
||||
reducerMap: { "duplicateReducer": reducer, "key11": reducer },
|
||||
reducerMap: { duplicateReducer: reducer, key11: reducer },
|
||||
finalActions: [{ type: "final1" }, { type: "final11" }],
|
||||
sagas: [saga1]
|
||||
sagas: [saga1],
|
||||
};
|
||||
|
||||
// Add first module
|
||||
moduleManager.add([module1]);
|
||||
moduleManager.dispose();
|
||||
expect(actionsDispatched).toEqual(["@@Internal/ModuleManager/ModuleAdded", "initial1", "initial11", "final1", "final11", "@@Internal/ModuleManager/ModuleRemoved"]);
|
||||
})
|
||||
expect(actionsDispatched).toEqual([
|
||||
"@@Internal/ModuleManager/ModuleAdded",
|
||||
"initial1",
|
||||
"initial11",
|
||||
"final1",
|
||||
"final11",
|
||||
"@@Internal/ModuleManager/ModuleRemoved",
|
||||
]);
|
||||
});
|
||||
|
||||
export function* saga1() {
|
||||
|
||||
}
|
||||
export function* saga1() {}
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
import { getReducerManager, getRefCountedReducerManager } from "../../Managers/ReducerManager";
|
||||
import {
|
||||
getReducerManager,
|
||||
getRefCountedReducerManager,
|
||||
} from "../../Managers/ReducerManager";
|
||||
|
||||
interface ITestState {
|
||||
name: string;
|
||||
age: number
|
||||
};
|
||||
|
||||
age: number;
|
||||
}
|
||||
|
||||
const reduce = (state: any, action: any) => state;
|
||||
|
||||
it("reducer manager tests", () => {
|
||||
const reducerManager = getReducerManager<ITestState>({
|
||||
name: reduce,
|
||||
age: reduce
|
||||
age: reduce,
|
||||
});
|
||||
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "age"]);
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
|
||||
"name",
|
||||
"age",
|
||||
]);
|
||||
|
||||
reducerManager.remove("age");
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name"]);
|
||||
|
@ -26,24 +31,38 @@ it("reducer manager tests", () => {
|
|||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name"]);
|
||||
|
||||
reducerManager.add("city", reduce);
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "city"]);
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
|
||||
"name",
|
||||
"city",
|
||||
]);
|
||||
});
|
||||
|
||||
it("ref counting reducers", () => {
|
||||
const reducerManager = getRefCountedReducerManager(getReducerManager<ITestState>({
|
||||
name: reduce,
|
||||
age: reduce
|
||||
}));
|
||||
const reducerManager = getRefCountedReducerManager(
|
||||
getReducerManager<ITestState>({
|
||||
name: reduce,
|
||||
age: reduce,
|
||||
})
|
||||
);
|
||||
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "age"]);
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
|
||||
"name",
|
||||
"age",
|
||||
]);
|
||||
|
||||
// Increment the ref count
|
||||
reducerManager.add("age", reduce);
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "age"]);
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
|
||||
"name",
|
||||
"age",
|
||||
]);
|
||||
|
||||
// Decrement the ref count
|
||||
reducerManager.remove("age");
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "age"]);
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
|
||||
"name",
|
||||
"age",
|
||||
]);
|
||||
|
||||
// This time it should be removed
|
||||
reducerManager.remove("age");
|
||||
|
@ -53,5 +72,8 @@ it("ref counting reducers", () => {
|
|||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name"]);
|
||||
|
||||
reducerManager.add("city", reduce);
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual(["name", "city"]);
|
||||
});
|
||||
expect(Object.keys(reducerManager.getReducerMap())).toEqual([
|
||||
"name",
|
||||
"city",
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ it("ref counted manager tests", () => {
|
|||
getItems: () => Array.from(items.keys()),
|
||||
add: (s: string[]) => s.length > 0 && s.forEach(s1 => items.add(s1)),
|
||||
remove: (s: string[]) => s.forEach(s1 => items.delete(s1)),
|
||||
dispose: () => { }
|
||||
dispose: () => {},
|
||||
};
|
||||
|
||||
const refCounter = getRefCountedManager(manager, (a, b) => a === b);
|
||||
|
@ -32,4 +32,4 @@ it("ref counted manager tests", () => {
|
|||
|
||||
refCounter.remove(["c"]);
|
||||
expect(refCounter.getItems()).toEqual(["b"]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,4 +11,4 @@ it("comparable map tests", () => {
|
|||
expect(map.remove(1)).toBe(2);
|
||||
expect(map.remove(1)).toBe(undefined);
|
||||
expect(map.remove(2)).toBe(3);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { getStringRefCounter, getObjectRefCounter } from "../../Utils/RefCounter";
|
||||
import {
|
||||
getStringRefCounter,
|
||||
getObjectRefCounter,
|
||||
} from "../../Utils/RefCounter";
|
||||
it("tests string ref counter", () => {
|
||||
const refCounter = getStringRefCounter();
|
||||
expect(refCounter.getCount("foobar")).toBe(0);
|
||||
|
@ -25,7 +28,6 @@ it("tests string ref counter", () => {
|
|||
expect(refCounter.getCount("a")).toBe(0);
|
||||
});
|
||||
|
||||
|
||||
it("tests object ref counter", () => {
|
||||
const refCounter = getObjectRefCounter<Function>((a, b) => a === b);
|
||||
expect(refCounter.getCount(foobar)).toBe(0);
|
||||
|
@ -46,11 +48,6 @@ it("tests object ref counter", () => {
|
|||
expect(refCounter.getCount(a)).toBe(0);
|
||||
});
|
||||
|
||||
function foobar() {
|
||||
|
||||
}
|
||||
|
||||
function a() {
|
||||
|
||||
}
|
||||
function foobar() {}
|
||||
|
||||
function a() {}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015",
|
||||
"es2016"
|
||||
],
|
||||
"lib": ["dom", "es2015", "es2016"],
|
||||
"declaration": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitAny": false,
|
||||
|
@ -14,10 +10,6 @@
|
|||
"rootDir": "src",
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/__tests__/**/*.ts"
|
||||
]
|
||||
}
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["src/__tests__/**/*.ts"]
|
||||
}
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
{
|
||||
|
||||
}
|
||||
{}
|
||||
|
|
|
@ -1,35 +1,39 @@
|
|||
let webpack = require("webpack");
|
||||
let BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
let BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
|
||||
.BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
let mode_env = argv.mode || 'development';
|
||||
let mode_env = argv.mode || "development";
|
||||
|
||||
return {
|
||||
devtool: 'source-map',
|
||||
devtool: "source-map",
|
||||
entry: {
|
||||
main: './lib/index'
|
||||
main: "./lib/index",
|
||||
},
|
||||
|
||||
output: {
|
||||
library: "redux-dynamic-modules",
|
||||
libraryTarget: "umd",
|
||||
filename: mode_env === "production" ? "redux-dynamic-modules.min.js" : "redux-dynamic-modules.js",
|
||||
path: __dirname + "/dist/"
|
||||
filename:
|
||||
mode_env === "production"
|
||||
? "redux-dynamic-modules.min.js"
|
||||
: "redux-dynamic-modules.js",
|
||||
path: __dirname + "/dist/",
|
||||
},
|
||||
|
||||
externals: {
|
||||
"prop-types": "prop-types",
|
||||
"react": "react",
|
||||
"redux": "redux",
|
||||
react: "react",
|
||||
redux: "redux",
|
||||
"react-redux": "react-redux",
|
||||
"redux-saga": "redux-saga"
|
||||
"redux-saga": "redux-saga",
|
||||
},
|
||||
plugins: [
|
||||
new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'static',
|
||||
analyzerMode: "static",
|
||||
reportFilename: `react-redux-module.stats.html`,
|
||||
openAnalyzer: false
|
||||
})
|
||||
]
|
||||
openAnalyzer: false,
|
||||
}),
|
||||
],
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ This project is a modified version of [Redux Todo App](https://github.com/reduxj
|
|||
|
||||
This project template was originally built with [Create React App](https://github.com/facebookincubator/create-react-app), which provides a simple way to start React projects with no build configuration needed.
|
||||
|
||||
Projects built with Create-React-App include support for ES6 syntax, as well as several unofficial / not-yet-final forms of Javascript syntax such as Class Properties and JSX. See the list of [language features and polyfills supported by Create-React-App](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#supported-language-features-and-polyfills) for more information.
|
||||
Projects built with Create-React-App include support for ES6 syntax, as well as several unofficial / not-yet-final forms of Javascript syntax such as Class Properties and JSX. See the list of [language features and polyfills supported by Create-React-App](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#supported-language-features-and-polyfills) for more information.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
|
@ -35,4 +35,3 @@ If you aren’t satisfied with the build tool and configuration choices, you can
|
|||
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,22 +1,22 @@
|
|||
{
|
||||
"name": "redux-dynamic-modules-todos-example",
|
||||
"version": "3.0.3",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"react-scripts": "^1.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"prop-types": "^15.6.1",
|
||||
"react": "^16.3.1",
|
||||
"react-dom": "^16.3.1",
|
||||
"react-redux": "^5.0.7",
|
||||
"redux": "^3.5.2",
|
||||
"redux-dynamic-modules": "^3.0.3",
|
||||
"redux-saga": "^0.16.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build:dev": "react-scripts build",
|
||||
"eject": "react-scripts eject"
|
||||
}
|
||||
"name": "redux-dynamic-modules-todos-example",
|
||||
"version": "3.0.3",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"react-scripts": "^1.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"prop-types": "^15.6.1",
|
||||
"react": "^16.3.1",
|
||||
"react-dom": "^16.3.1",
|
||||
"react-redux": "^5.0.7",
|
||||
"redux": "^3.5.2",
|
||||
"redux-dynamic-modules": "^3.0.3",
|
||||
"redux-saga": "^0.16.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build:dev": "react-scripts build",
|
||||
"eject": "react-scripts eject"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
import React from 'react'
|
||||
import RootView from "../containers/root/rootView"
|
||||
import React from "react";
|
||||
import RootView from "../containers/root/rootView";
|
||||
|
||||
const App = () => (<div>
|
||||
<RootView />
|
||||
const App = () => (
|
||||
<div>
|
||||
<div>This app contains two modules</div>
|
||||
<div>1) Todo</div>
|
||||
<div>2) Shopping list</div>
|
||||
<RootView />
|
||||
<div>
|
||||
<div>This app contains two modules</div>
|
||||
<div>1) Todo</div>
|
||||
<div>2) Shopping list</div>
|
||||
|
||||
<div>By clicking on each button above the modules are loaded and their views are displayed.</div>
|
||||
<div>When you switch between them the previous module is unloaded and its state is cleared.</div>
|
||||
<div>
|
||||
By clicking on each button above the modules are loaded and
|
||||
their views are displayed.
|
||||
</div>
|
||||
<div>
|
||||
When you switch between them the previous module is unloaded and
|
||||
its state is cleared.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
);
|
||||
|
||||
export default App
|
||||
export default App;
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import React from 'react'
|
||||
import FilterLink from '../../containers/root/FilterLink'
|
||||
import { VisibilityFilters } from '../../modules/root/actions'
|
||||
import React from "react";
|
||||
import FilterLink from "../../containers/root/FilterLink";
|
||||
import { VisibilityFilters } from "../../modules/root/actions";
|
||||
|
||||
const Header = () => (
|
||||
<div>
|
||||
<span>Show: </span>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_TOOD}>
|
||||
Todo View
|
||||
</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_SHOPPING_LIST}>
|
||||
Shopping List
|
||||
</FilterLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Header
|
||||
<div>
|
||||
<span>Show: </span>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_TOOD}>Todo View</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_SHOPPING_LIST}>
|
||||
Shopping List
|
||||
</FilterLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Header;
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Link = ({ active, children, onClick }) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={active}
|
||||
style={{
|
||||
marginLeft: '4px',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
onClick={onClick}
|
||||
disabled={active}
|
||||
style={{
|
||||
marginLeft: "4px",
|
||||
}}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
|
||||
Link.propTypes = {
|
||||
active: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
active: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Link
|
||||
export default Link;
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
import React from 'react'
|
||||
import FilterLink from '../../containers/shoppinglist/FilterItemLink'
|
||||
import { VisibilityFilters } from '../../modules/shoppinglist/actions'
|
||||
import React from "react";
|
||||
import FilterLink from "../../containers/shoppinglist/FilterItemLink";
|
||||
import { VisibilityFilters } from "../../modules/shoppinglist/actions";
|
||||
|
||||
const Footer = () => (
|
||||
<div>
|
||||
<div>
|
||||
<span>Show: </span>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_ALL}>
|
||||
All
|
||||
</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>
|
||||
Active
|
||||
</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>
|
||||
Completed
|
||||
Completed
|
||||
</FilterLink>
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Footer
|
||||
export default Footer;
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Item = ({ onClick, completed, text }) => (
|
||||
<li
|
||||
onClick={onClick}
|
||||
style={{
|
||||
textDecoration: completed ? 'line-through' : 'none'
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</li>
|
||||
)
|
||||
<li
|
||||
onClick={onClick}
|
||||
style={{
|
||||
textDecoration: completed ? "line-through" : "none",
|
||||
}}>
|
||||
{text}
|
||||
</li>
|
||||
);
|
||||
|
||||
Item.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
completed: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired
|
||||
}
|
||||
onClick: PropTypes.func.isRequired,
|
||||
completed: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Item
|
||||
export default Item;
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Item from './Item'
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Item from "./Item";
|
||||
|
||||
const ItemList = ({ todos, toggleItem }) => (
|
||||
<ul>
|
||||
{todos.map(todo =>
|
||||
<Item
|
||||
key={todo.id}
|
||||
{...todo}
|
||||
onClick={() => toggleItem(todo.id)}
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
)
|
||||
<ul>
|
||||
{todos.map(todo => (
|
||||
<Item key={todo.id} {...todo} onClick={() => toggleItem(todo.id)} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
||||
ItemList.propTypes = {
|
||||
todos: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
completed: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired
|
||||
}).isRequired).isRequired,
|
||||
toggleItem: PropTypes.func.isRequired
|
||||
}
|
||||
todos: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
completed: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
}).isRequired
|
||||
).isRequired,
|
||||
toggleItem: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ItemList
|
||||
export default ItemList;
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Link = ({ active, children, onClick }) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={active}
|
||||
style={{
|
||||
marginLeft: '4px',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
onClick={onClick}
|
||||
disabled={active}
|
||||
style={{
|
||||
marginLeft: "4px",
|
||||
}}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
|
||||
Link.propTypes = {
|
||||
active: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
active: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Link
|
||||
export default Link;
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
import React from 'react'
|
||||
import FilterLink from '../../containers/todo/FilterLink'
|
||||
import { VisibilityFilters } from '../../modules/todo/actions'
|
||||
import React from "react";
|
||||
import FilterLink from "../../containers/todo/FilterLink";
|
||||
import { VisibilityFilters } from "../../modules/todo/actions";
|
||||
|
||||
const Footer = () => (
|
||||
<div>
|
||||
<span>Show: </span>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_ALL}>
|
||||
All
|
||||
</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>
|
||||
Active
|
||||
</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>
|
||||
Completed
|
||||
</FilterLink>
|
||||
</div>
|
||||
)
|
||||
<div>
|
||||
<span>Show: </span>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
|
||||
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>
|
||||
Completed
|
||||
</FilterLink>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Footer
|
||||
export default Footer;
|
||||
|
|
|
@ -1,22 +1,21 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Link = ({ active, children, onClick }) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={active}
|
||||
style={{
|
||||
marginLeft: '4px',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
onClick={onClick}
|
||||
disabled={active}
|
||||
style={{
|
||||
marginLeft: "4px",
|
||||
}}>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
|
||||
Link.propTypes = {
|
||||
active: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
active: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Link
|
||||
export default Link;
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Todo = ({ onClick, completed, text }) => (
|
||||
<li
|
||||
onClick={onClick}
|
||||
style={{
|
||||
textDecoration: completed ? 'line-through' : 'none'
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</li>
|
||||
)
|
||||
<li
|
||||
onClick={onClick}
|
||||
style={{
|
||||
textDecoration: completed ? "line-through" : "none",
|
||||
}}>
|
||||
{text}
|
||||
</li>
|
||||
);
|
||||
|
||||
Todo.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
completed: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired
|
||||
}
|
||||
onClick: PropTypes.func.isRequired,
|
||||
completed: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Todo
|
||||
export default Todo;
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Todo from './Todo'
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Todo from "./Todo";
|
||||
|
||||
const TodoList = ({ todos, toggleTodo }) => (
|
||||
<ul>
|
||||
{todos.map(todo =>
|
||||
<Todo
|
||||
key={todo.id}
|
||||
{...todo}
|
||||
onClick={() => toggleTodo(todo.id)}
|
||||
/>
|
||||
)}
|
||||
</ul>
|
||||
)
|
||||
<ul>
|
||||
{todos.map(todo => (
|
||||
<Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
||||
TodoList.propTypes = {
|
||||
todos: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
completed: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired
|
||||
}).isRequired).isRequired,
|
||||
toggleTodo: PropTypes.func.isRequired
|
||||
}
|
||||
todos: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.number.isRequired,
|
||||
completed: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
}).isRequired
|
||||
).isRequired,
|
||||
toggleTodo: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default TodoList
|
||||
export default TodoList;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { setVisibilityFilter } from '../../modules/root/actions'
|
||||
import Link from '../../components/root/Link'
|
||||
import { connect } from "react-redux";
|
||||
import { setVisibilityFilter } from "../../modules/root/actions";
|
||||
import Link from "../../components/root/Link";
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
active: ownProps.filter === state.root.visibilityFilter
|
||||
})
|
||||
active: ownProps.filter === state.root.visibilityFilter,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
|
||||
})
|
||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Link)
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Link);
|
||||
|
|
|
@ -1,33 +1,27 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
connect
|
||||
} from 'react-redux'
|
||||
import {
|
||||
VisibilityFilters
|
||||
} from '../../modules/root/actions'
|
||||
import TodoView from '../todo/TodoView';
|
||||
import ShoppingListView from '../shoppinglist/ShoppingListView';
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
import { VisibilityFilters } from "../../modules/root/actions";
|
||||
import TodoView from "../todo/TodoView";
|
||||
import ShoppingListView from "../shoppinglist/ShoppingListView";
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
visibilityFilter: state.root
|
||||
}
|
||||
}
|
||||
|
||||
const Views = ({visibilityFilter}) => {
|
||||
if (visibilityFilter === VisibilityFilters.SHOW_TOOD) {
|
||||
return <TodoView / >
|
||||
}
|
||||
if (visibilityFilter === VisibilityFilters.SHOW_SHOPPING_LIST) {
|
||||
return <ShoppingListView / >
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Views.propTypes = {
|
||||
visibilityFilter: PropTypes.string.isRequired
|
||||
visibilityFilter: state.root,
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(Views)
|
||||
const Views = ({ visibilityFilter }) => {
|
||||
if (visibilityFilter === VisibilityFilters.SHOW_TOOD) {
|
||||
return <TodoView />;
|
||||
}
|
||||
if (visibilityFilter === VisibilityFilters.SHOW_SHOPPING_LIST) {
|
||||
return <ShoppingListView />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
Views.propTypes = {
|
||||
visibilityFilter: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(Views);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React from 'react';
|
||||
import { DynamicModuleLoader } from 'redux-dynamic-modules'
|
||||
import { getRootModule } from '../../modules/root/rootModule';
|
||||
import Header from '../../components/root/Header';
|
||||
import Views from "./Views"
|
||||
import React from "react";
|
||||
import { DynamicModuleLoader } from "redux-dynamic-modules";
|
||||
import { getRootModule } from "../../modules/root/rootModule";
|
||||
import Header from "../../components/root/Header";
|
||||
import Views from "./Views";
|
||||
|
||||
const Rootview = () => (
|
||||
<DynamicModuleLoader modules={[getRootModule()]}>
|
||||
<Header />
|
||||
<Views />
|
||||
</DynamicModuleLoader>
|
||||
);
|
||||
|
||||
export default Rootview
|
||||
<DynamicModuleLoader modules={[getRootModule()]}>
|
||||
<Header />
|
||||
<Views />
|
||||
</DynamicModuleLoader>
|
||||
);
|
||||
|
||||
export default Rootview;
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { addItem } from '../../modules/shoppinglist/actions'
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { addItem } from "../../modules/shoppinglist/actions";
|
||||
|
||||
const AddItem = ({ dispatch }) => {
|
||||
let input
|
||||
let input;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={e => {
|
||||
e.preventDefault()
|
||||
if (!input.value.trim()) {
|
||||
return
|
||||
}
|
||||
dispatch(addItem(input.value))
|
||||
input.value = ''
|
||||
}}>
|
||||
<input ref={node => input = node} />
|
||||
<button type="submit">
|
||||
Add Item
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
if (!input.value.trim()) {
|
||||
return;
|
||||
}
|
||||
dispatch(addItem(input.value));
|
||||
input.value = "";
|
||||
}}>
|
||||
<input ref={node => (input = node)} />
|
||||
<button type="submit">Add Item</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect()(AddItem)
|
||||
export default connect()(AddItem);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { setVisibilityFilter } from '../../modules/shoppinglist/actions'
|
||||
import Link from '../../components/shoppinglist/Link'
|
||||
import { connect } from "react-redux";
|
||||
import { setVisibilityFilter } from "../../modules/shoppinglist/actions";
|
||||
import Link from "../../components/shoppinglist/Link";
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
active: ownProps.filter === state.shoppingList.visibilityFilter
|
||||
})
|
||||
active: ownProps.filter === state.shoppingList.visibilityFilter,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
|
||||
})
|
||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Link)
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Link);
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import AddItem from './AddItem';
|
||||
import React from 'react';
|
||||
import { DynamicModuleLoader } from 'redux-dynamic-modules';
|
||||
import VisibleItemsList from './VisibleItemsList';
|
||||
import Footer from '../../components/shoppinglist/Footer';
|
||||
import { getShoppingListModule } from '../../modules/shoppinglist/shoppingListModule';
|
||||
import AddItem from "./AddItem";
|
||||
import React from "react";
|
||||
import { DynamicModuleLoader } from "redux-dynamic-modules";
|
||||
import VisibleItemsList from "./VisibleItemsList";
|
||||
import Footer from "../../components/shoppinglist/Footer";
|
||||
import { getShoppingListModule } from "../../modules/shoppinglist/shoppingListModule";
|
||||
|
||||
const ShoppingListView = () => (
|
||||
<DynamicModuleLoader modules={[getShoppingListModule()]}>
|
||||
<DynamicModuleLoader modules={[getShoppingListModule()]}>
|
||||
<div>Shopping list view</div>
|
||||
<AddItem />
|
||||
<VisibleItemsList />
|
||||
<Footer />
|
||||
</DynamicModuleLoader>
|
||||
);
|
||||
</DynamicModuleLoader>
|
||||
);
|
||||
|
||||
export default ShoppingListView
|
||||
export default ShoppingListView;
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { toggleItem } from '../../modules/shoppinglist/actions'
|
||||
import ItemList from '../../components/shoppinglist/ItemList'
|
||||
import { VisibilityFilters } from '../../modules/shoppinglist/actions'
|
||||
import { connect } from "react-redux";
|
||||
import { toggleItem } from "../../modules/shoppinglist/actions";
|
||||
import ItemList from "../../components/shoppinglist/ItemList";
|
||||
import { VisibilityFilters } from "../../modules/shoppinglist/actions";
|
||||
|
||||
const getVisibleItems = (items, filter) => {
|
||||
switch (filter) {
|
||||
case VisibilityFilters.SHOW_ALL:
|
||||
return items
|
||||
case VisibilityFilters.SHOW_COMPLETED:
|
||||
return items.filter(t => t.completed)
|
||||
case VisibilityFilters.SHOW_ACTIVE:
|
||||
return items.filter(t => !t.completed)
|
||||
default:
|
||||
throw new Error('Unknown filter: ' + filter)
|
||||
}
|
||||
}
|
||||
switch (filter) {
|
||||
case VisibilityFilters.SHOW_ALL:
|
||||
return items;
|
||||
case VisibilityFilters.SHOW_COMPLETED:
|
||||
return items.filter(t => t.completed);
|
||||
case VisibilityFilters.SHOW_ACTIVE:
|
||||
return items.filter(t => !t.completed);
|
||||
default:
|
||||
throw new Error("Unknown filter: " + filter);
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
todos: getVisibleItems(state.shoppingList.items, state.shoppingList.visibilityFilter)
|
||||
})
|
||||
todos: getVisibleItems(
|
||||
state.shoppingList.items,
|
||||
state.shoppingList.visibilityFilter
|
||||
),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleItem: id => dispatch(toggleItem(id))
|
||||
})
|
||||
toggleItem: id => dispatch(toggleItem(id)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ItemList)
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ItemList);
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { addTodo } from '../../modules/todo/actions'
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { addTodo } from "../../modules/todo/actions";
|
||||
|
||||
const AddTodo = ({ dispatch }) => {
|
||||
let input
|
||||
let input;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={e => {
|
||||
e.preventDefault()
|
||||
if (!input.value.trim()) {
|
||||
return
|
||||
}
|
||||
dispatch(addTodo(input.value))
|
||||
input.value = ''
|
||||
}}>
|
||||
<input ref={node => input = node} />
|
||||
<button type="submit">
|
||||
Add Todo
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
if (!input.value.trim()) {
|
||||
return;
|
||||
}
|
||||
dispatch(addTodo(input.value));
|
||||
input.value = "";
|
||||
}}>
|
||||
<input ref={node => (input = node)} />
|
||||
<button type="submit">Add Todo</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect()(AddTodo)
|
||||
export default connect()(AddTodo);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { setVisibilityFilter } from '../../modules/todo/actions'
|
||||
import Link from '../../components/todo/Link'
|
||||
import { connect } from "react-redux";
|
||||
import { setVisibilityFilter } from "../../modules/todo/actions";
|
||||
import Link from "../../components/todo/Link";
|
||||
|
||||
const mapStateToProps = (state, ownProps) => ({
|
||||
active: ownProps.filter === state.todo.visibilityFilter
|
||||
})
|
||||
active: ownProps.filter === state.todo.visibilityFilter,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, ownProps) => ({
|
||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
|
||||
})
|
||||
onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Link)
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Link);
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import AddTodo from './AddTodo';
|
||||
import React from 'react';
|
||||
import { DynamicModuleLoader } from 'redux-dynamic-modules';
|
||||
import VisibleTodoList from './VisibleTodoList';
|
||||
import Footer from '../../components/todo/Footer';
|
||||
import { getTodoModule } from '../../modules/todo/todoModule';
|
||||
import AddTodo from "./AddTodo";
|
||||
import React from "react";
|
||||
import { DynamicModuleLoader } from "redux-dynamic-modules";
|
||||
import VisibleTodoList from "./VisibleTodoList";
|
||||
import Footer from "../../components/todo/Footer";
|
||||
import { getTodoModule } from "../../modules/todo/todoModule";
|
||||
|
||||
const TodoView = () => (
|
||||
<DynamicModuleLoader modules={[getTodoModule()]}>
|
||||
<div>Todo View</div>
|
||||
<AddTodo />
|
||||
<VisibleTodoList />
|
||||
<Footer />
|
||||
</DynamicModuleLoader>
|
||||
)
|
||||
|
||||
export default TodoView
|
||||
<DynamicModuleLoader modules={[getTodoModule()]}>
|
||||
<div>Todo View</div>
|
||||
<AddTodo />
|
||||
<VisibleTodoList />
|
||||
<Footer />
|
||||
</DynamicModuleLoader>
|
||||
);
|
||||
|
||||
export default TodoView;
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
import { connect } from 'react-redux'
|
||||
import { toggleTodo } from '../../modules/todo/actions'
|
||||
import TodoList from '../../components/todo/TodoList'
|
||||
import { VisibilityFilters } from '../../modules/todo/actions'
|
||||
import { connect } from "react-redux";
|
||||
import { toggleTodo } from "../../modules/todo/actions";
|
||||
import TodoList from "../../components/todo/TodoList";
|
||||
import { VisibilityFilters } from "../../modules/todo/actions";
|
||||
|
||||
const getVisibleTodos = (todos, filter) => {
|
||||
switch (filter) {
|
||||
case VisibilityFilters.SHOW_ALL:
|
||||
return todos
|
||||
case VisibilityFilters.SHOW_COMPLETED:
|
||||
return todos.filter(t => t.completed)
|
||||
case VisibilityFilters.SHOW_ACTIVE:
|
||||
return todos.filter(t => !t.completed)
|
||||
default:
|
||||
throw new Error('Unknown filter: ' + filter)
|
||||
}
|
||||
}
|
||||
switch (filter) {
|
||||
case VisibilityFilters.SHOW_ALL:
|
||||
return todos;
|
||||
case VisibilityFilters.SHOW_COMPLETED:
|
||||
return todos.filter(t => t.completed);
|
||||
case VisibilityFilters.SHOW_ACTIVE:
|
||||
return todos.filter(t => !t.completed);
|
||||
default:
|
||||
throw new Error("Unknown filter: " + filter);
|
||||
}
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
todos: getVisibleTodos(state.todo.todos, state.todo.visibilityFilter)
|
||||
})
|
||||
todos: getVisibleTodos(state.todo.todos, state.todo.visibilityFilter),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
toggleTodo: id => dispatch(toggleTodo(id))
|
||||
})
|
||||
toggleTodo: id => dispatch(toggleTodo(id)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TodoList)
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TodoList);
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
import { createStore } from 'redux-dynamic-modules'
|
||||
import { Provider } from 'react-redux'
|
||||
import App from './components/App'
|
||||
import React from "react";
|
||||
import { render } from "react-dom";
|
||||
import { createStore } from "redux-dynamic-modules";
|
||||
import { Provider } from "react-redux";
|
||||
import App from "./components/App";
|
||||
|
||||
const store = createStore({}, [], []);
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById('root')
|
||||
)
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
export const setVisibilityFilter = text => {
|
||||
debugger;
|
||||
return {
|
||||
type: 'SHOW_VIEW',
|
||||
text
|
||||
}
|
||||
}
|
||||
debugger;
|
||||
return {
|
||||
type: "SHOW_VIEW",
|
||||
text,
|
||||
};
|
||||
};
|
||||
export const VisibilityFilters = {
|
||||
SHOW_TOOD: 'SHOW_TODO',
|
||||
SHOW_SHOPPING_LIST: 'SHOW_SHOPPING_LIST'
|
||||
}
|
||||
SHOW_TOOD: "SHOW_TODO",
|
||||
SHOW_SHOPPING_LIST: "SHOW_SHOPPING_LIST",
|
||||
};
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
const rootReducer = (state = "", action) => {
|
||||
switch (action.type) {
|
||||
case 'SHOW_VIEW':
|
||||
debugger;
|
||||
return action.text;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
switch (action.type) {
|
||||
case "SHOW_VIEW":
|
||||
debugger;
|
||||
return action.text;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default rootReducer;
|
||||
export default rootReducer;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import rootReducer from "./reducers"
|
||||
import rootReducer from "./reducers";
|
||||
export function getRootModule() {
|
||||
return {
|
||||
id: "root",
|
||||
reducerMap: {
|
||||
root: rootReducer
|
||||
}
|
||||
}
|
||||
}
|
||||
root: rootReducer,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
let nextItemId = 0
|
||||
let nextItemId = 0;
|
||||
export const addItem = text => ({
|
||||
type: 'ADD_ITEM',
|
||||
id: nextItemId++,
|
||||
text
|
||||
})
|
||||
type: "ADD_ITEM",
|
||||
id: nextItemId++,
|
||||
text,
|
||||
});
|
||||
|
||||
export const setVisibilityFilter = filter => ({
|
||||
type: 'SET_VISIBILITY_FILTER_ITEMS',
|
||||
filter
|
||||
})
|
||||
type: "SET_VISIBILITY_FILTER_ITEMS",
|
||||
filter,
|
||||
});
|
||||
|
||||
export const toggleItem = id => ({
|
||||
type: 'TOGGLE_ITEM',
|
||||
id
|
||||
})
|
||||
type: "TOGGLE_ITEM",
|
||||
id,
|
||||
});
|
||||
|
||||
export const VisibilityFilters = {
|
||||
SHOW_ALL: 'SHOW_ALL_ITEMS',
|
||||
SHOW_COMPLETED: 'SHOW_COMPLETED_ITEMS',
|
||||
SHOW_ACTIVE: 'SHOW_ACTIVE_ITEMS'
|
||||
}
|
||||
SHOW_ALL: "SHOW_ALL_ITEMS",
|
||||
SHOW_COMPLETED: "SHOW_COMPLETED_ITEMS",
|
||||
SHOW_ACTIVE: "SHOW_ACTIVE_ITEMS",
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { combineReducers } from 'redux'
|
||||
import items from './items'
|
||||
import visibilityFilter from './visibilityFilter'
|
||||
import { combineReducers } from "redux";
|
||||
import items from "./items";
|
||||
import visibilityFilter from "./visibilityFilter";
|
||||
|
||||
export default combineReducers({
|
||||
items,
|
||||
visibilityFilter
|
||||
})
|
||||
items,
|
||||
visibilityFilter,
|
||||
});
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
const items = (state = [], action) => {
|
||||
switch (action.type) {
|
||||
case 'ADD_ITEM':
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
id: action.id,
|
||||
text: action.text,
|
||||
completed: false
|
||||
}
|
||||
]
|
||||
case 'TOGGLE_ITEM':
|
||||
return state.map(item =>
|
||||
(item.id === action.id)
|
||||
? {...item, completed: !item.completed}
|
||||
: item
|
||||
)
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
switch (action.type) {
|
||||
case "ADD_ITEM":
|
||||
return [
|
||||
...state,
|
||||
{
|
||||
id: action.id,
|
||||
text: action.text,
|
||||
completed: false,
|
||||
},
|
||||
];
|
||||
case "TOGGLE_ITEM":
|
||||
return state.map(item =>
|
||||
item.id === action.id
|
||||
? { ...item, completed: !item.completed }
|
||||
: item
|
||||
);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default items
|
||||
export default items;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { VisibilityFilters } from '../actions'
|
||||
import { VisibilityFilters } from "../actions";
|
||||
|
||||
const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
|
||||
switch (action.type) {
|
||||
case 'SET_VISIBILITY_FILTER_ITEMS':
|
||||
return action.filter
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
switch (action.type) {
|
||||
case "SET_VISIBILITY_FILTER_ITEMS":
|
||||
return action.filter;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default visibilityFilter
|
||||
export default visibilityFilter;
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче