@ackee/redux-worker 中文文档教程
[WIP] redux-worker
Redux 的 React 绑定,其中 Redux 放置在 Web Worker 中。
Main features
⚡️ Always responsive UI
所有业务逻辑(reducer、sagas、selectors)都放在工作线程中,因此主线程不会被这些操作阻塞,并为 UI 释放。
⏲ Handling of long-runnning tasks
如果工作线程在一定时间内没有响应主线程,则会触发
TASK_DURATION_TIMEOUT
事件。 然后可以终止或重新启动工作线程。 无需重新加载页面。#### 至少在 worker 上下文中限制对
window
对象的访问 如果您需要在工作线程中访问窗口对象,您可以使用executeInWindow
async。 方法,例如:const innerHeight = await executeInWindow('innerHeight')
;
Knowledge requirements
Current Limitations
- Import files (container selectors in our case) with
require.context
is really easy, but really slow. Particularly, when we need to search for those files also innode_modules
. This may be solved with a Webpack plugin.
Table of contents
Installing
使用纱线:
$ yarn add @ackee/redux-worker
使用 npm:
$ npm i -S @ackee/redux-worker
Core Concepts
TODO
Usage
Implement redux-worker
to your project
添加
worker-plugin
到您的 Webpack 配置。使用以下代码创建名为
getSelectors.js
的export default function getContainerSelectors() { // import all files that match the regex pattern (e.g. 'Counter.selector.js') // The path must also include node_modules (if any of them uses `connectWorker` HOC)
}// require.context(pathToRoot, deepLookUp, regexPattern) return require.context('../../../', true, /\.selector\.js$/);
文件: 创建名为
configureStore.js
的文件,其中包含返回 Redux Store 的函数:import { createStore } from 'redux'; const rootReducer = (state, action) => state; export default function configureStore() { // The simplest Redux store. return createStore(rootReducer); }
创建名为
Store.worker.js 的文件
使用以下代码:使用以下代码import { configureStoreWorker } from '@ackee/redux-worker/worker'; import configureStore from './createStore'; import getSelectors from './getSelectors'; configureStoreWorker({ configureStore, getSelectors, });
创建名为
configureReduxWorker.js
的文件:import * as ReduxWorker from '@ackee/redux-worker/main'; const createStoreWorker = () => { // NOTE: Path to the web worker we've created earlier. return new Worker('./Store.worker.js', { type: 'module', name: 'Store.worker', // optional, for easier debugging }); }; ReduxWorker.initialize(createStoreWorker);
Examples
Connecting to Redux Store with connectWorker
HOC
为此容器创建新的桥 ID:
// --------------------------------------- // modules/counter/constants/index.js // --------------------------------------- import { uniqueId } from '@ackee/redux-worker'; export const bridgeIds = { COUNTER_BRIDGE: uniqueId('COUNTER_BRIDGE'), };
使用该 ID 创建容器组件:
// --------------------------------------- // modules/counter/containers/Counter.js // --------------------------------------- import { connectWorker } from '@ackee/redux-worker/main'; import { bridgeIds } from '../constants'; import Counter from '../components/Counter'; const mapDispatchToProps = dispatch => ({ // ... }); export default connectWorker(bridgeIds.COUNTER_BRIDGE, mapDispatchToProps)(Counter);
为
mapStateToProps 创建特殊文件
并使用该 ID:// --------------------------------------- // containers/Counter.selector.js // --------------------------------------- import { registerSelector } from '@ackee/redux-worker/worker'; import { bridgeIds } from '../constants'; const mapStateToProps = state => { return { // ... }; }; registerSelector(bridgeIds.COUNTER_BRIDGE, mapStateToProps);
Rebooting unresponding store worker
// ---------------------------------------
// config/redux-worker/index.js
// ---------------------------------------
import * as ReduxWorker from '@ackee/redux-worker/main';
const createStoreWorker = () => {
return new Worker('./Store.worker.js', {
type: 'module',
name: 'Store.worker',
});
};
ReduxWorker.on(ReduxWorker.eventTypes.TASK_DURATION_TIMEOUT, async () => {
// worker is terminated, then immediately booted again, new redux store is created
await ReduxWorker.storeWorker.reboot();
});
ReduxWorker.initialize(createStoreWorker, {
taskDurationWatcher: {
enabled: true,
},
});
API - Window context
async initialize(createStoreWorker, [config]): void
它的作用:
- Store worker is booted (Redux Store is created).
- An configuration object is created and sent to the worker.
- The task duration watcher is started.
- The window bridge is initialized (see
executeInWindow
section).
Parameters
createStoreWorker: Function
- 返回新商店工作人员的函数。config: Object
- 可选,具有以下默认值:{ /* const logLevelEnum = { development: logLevels.INFO, production: logLevels.SILENT, [undefined]: logLevels.ERROR, }; */ logLevel: logLevelEnum[process.env.NODE],
}taskDurationWatcher: { enabled: process.env.NODE_ENV !== 'development', /* If the store worker doesn't report itself in 4s to the main thread, then the worker is considered to be non-responding and the ReduxWorker.eventTypes.TASK_DURATION_TIMEOUT event is fired. */ unrespondingTimeout: 1000 * 4, // ms /* How often should the store worker report itself to the tasksDurationWatcher module. Note that each report resets the unrespondingTimeout. So reportStatusInPeriod + messageHopDelay < unrespondingTimeout. */ reportStatusInPeriod: 1000 * 3, // ms }
Example
// ...
ReduxWorker.initialize(createStoreWorker).then(() => {
console.log('Store worker is ready');
});
Notes
- This method can be called only once, otherwise an error is thrown.
- The
connectWorker
HOC can be safely used before this method is called. But since the store worker isn't ready at that moment, nothing will be rendered and all incoming messages to the worker are going to be queued.
connectWorker(bridgeId, mapDispatchToProps?, ownPropsSelector?): (ConnectedComponent, Loader?): React.Component
它的作用:
- It connects to Redux store which is placed at a Web Worker. This happens on
componentDidMount
. - It disconnects from Redux store on
componentWillUnmount
.
Parameters
bridgeId: String
- An unique ID among other instances ofconnectWorker
. See helper utilityuniqueId
mapDispatchToProps: Function|Object
Function
: 1st argument is dispatch method that send Redux actions to the store worker. 2nd argument isownProps
object which is returned fromownPropSelector
.Object
: Properties are Redux action creators.
ownPropsSelector: Function
- Function that receives all component props and returns props that are only required in themapDispatchToProps
andmapStateToProps
(e.g.userId
).
Returns
由 connectWorker
HOC 包装的 React 组件。
Example
// --------------------
// Foo.js (main thread context)
// --------------------
import { connectWorker } from '@ackee/redux-worker/main';
import Foo from '../components/Foo';
const mapDispatchToProps = (dispatch, selectedOwnProps) => ({
// ...
});
const ownPropSelector = componentProps => ({
// ...
});
// The mapStateToProps selector is placed at `Foo.selector.js` file
export default connectWorker('FOO_BRIDGE', mapDispatchToProps, ownPropsSelector)(Foo);
// --------------------
// Foo.selector.js (worker context)
// --------------------
import { registerSelector } from '@ackee/redux-worker/worker';
const mapStateToProps = (state, selectedOwnProps) => ({
// ...
});
registerSelector('FOO_BRIDGE', mapStateToProps);
API - Worker context
registerSelector(bridgeId: String, mapStateToProps: Function): void
将容器选择器添加到全局选择器寄存器。
import { registerSelector } from '@ackee/redux-worker';
const mapStateToProps = state => ({
// ...
});
registerSelector('BRIDGE_ID', mapStateToProps);
async executeInWindow(pathToProperty: String, args:Array?): any
import { executeInWindow } from '@ackee/redux-worker/worker';
async function accessWindowObjectInWorker() {
const innerWidth = await executeInWindow('innerWidth');
console.log(innerWidth);
}
async function goBack() {
await executeInWindow('history.back');
}
async function storeToSessionStorage() {
await executeInWindow('sessionStorage.setItem', ['message', 'hello world']);
const message = await executeInWindow('sessionStorage.getItem', ['message']);
console.log(message); // > 'hello world'
}
API - shared context
unqiueId(prefix?): String
获取唯一的字符串 ID,可以选择使用自定义前缀。 在相同的上下文下保证唯一性。
此实用程序的目的是为
connectWorker
HOC 和registerSelector
方法生成桥 ID。
Example
import { uniqueId } from '@ackee/redux-worker';
uniqueId(); // > 'rs'
uniqueId('COUNTER_BRIDGE'); // > 'COUNTER_BRIDGE_rt'
uniqueId('COUNTER_BRIDGE'); // > 'COUNTER_BRIDGE_ru'
[WIP] redux-worker
React bindings for Redux, where Redux is placed at Web Worker.
Main features
⚡️ Always responsive UI
All business logic (reducers, sagas, selectors) is placed at worker thread, so the main thread isn't blocked by those opererations and is freed for UI.
⏲ Handling of long-runnning tasks
If worker thread isn't responding to the main thread for certain amount time, the
TASK_DURATION_TIMEOUT
event is fired. The worker thread can be then terminated or rebooted. No need for page reload.#### ???? At least limited access to the
window
object in worker context If you need access the window object in worker thread, you can useexecuteInWindow
async. method, e.g.:const innerHeight = await executeInWindow('innerHeight')
;
Knowledge requirements
You should known React and Redux. But you don't need to know anything about Web Workers.
Current Limitations
- Import files (container selectors in our case) with
require.context
is really easy, but really slow. Particularly, when we need to search for those files also innode_modules
. This may be solved with a Webpack plugin.
Table of contents
Installing
Using yarn:
$ yarn add @ackee/redux-worker
Using npm:
$ npm i -S @ackee/redux-worker
Core Concepts
TODO
Usage
Implement redux-worker
to your project
Add
worker-plugin
to your Webpack configuration.Create filed called
getSelectors.js
with the code below:export default function getContainerSelectors() { // import all files that match the regex pattern (e.g. 'Counter.selector.js') // The path must also include node_modules (if any of them uses `connectWorker` HOC)
}// require.context(pathToRoot, deepLookUp, regexPattern) return require.context('../../../', true, /\.selector\.js$/);
Create file called
configureStore.js
with function that returns the Redux Store:import { createStore } from 'redux'; const rootReducer = (state, action) => state; export default function configureStore() { // The simplest Redux store. return createStore(rootReducer); }
Create file called
Store.worker.js
with the code below:import { configureStoreWorker } from '@ackee/redux-worker/worker'; import configureStore from './createStore'; import getSelectors from './getSelectors'; configureStoreWorker({ configureStore, getSelectors, });
Create file called
configureReduxWorker.js
with the code below:import * as ReduxWorker from '@ackee/redux-worker/main'; const createStoreWorker = () => { // NOTE: Path to the web worker we've created earlier. return new Worker('./Store.worker.js', { type: 'module', name: 'Store.worker', // optional, for easier debugging }); }; ReduxWorker.initialize(createStoreWorker);
Examples
Connecting to Redux Store with connectWorker
HOC
Create new bridge ID for this container:
// --------------------------------------- // modules/counter/constants/index.js // --------------------------------------- import { uniqueId } from '@ackee/redux-worker'; export const bridgeIds = { COUNTER_BRIDGE: uniqueId('COUNTER_BRIDGE'), };
Create container component with that ID:
// --------------------------------------- // modules/counter/containers/Counter.js // --------------------------------------- import { connectWorker } from '@ackee/redux-worker/main'; import { bridgeIds } from '../constants'; import Counter from '../components/Counter'; const mapDispatchToProps = dispatch => ({ // ... }); export default connectWorker(bridgeIds.COUNTER_BRIDGE, mapDispatchToProps)(Counter);
Create special file for
mapStateToProps
and use that ID:// --------------------------------------- // containers/Counter.selector.js // --------------------------------------- import { registerSelector } from '@ackee/redux-worker/worker'; import { bridgeIds } from '../constants'; const mapStateToProps = state => { return { // ... }; }; registerSelector(bridgeIds.COUNTER_BRIDGE, mapStateToProps);
Rebooting unresponding store worker
// ---------------------------------------
// config/redux-worker/index.js
// ---------------------------------------
import * as ReduxWorker from '@ackee/redux-worker/main';
const createStoreWorker = () => {
return new Worker('./Store.worker.js', {
type: 'module',
name: 'Store.worker',
});
};
ReduxWorker.on(ReduxWorker.eventTypes.TASK_DURATION_TIMEOUT, async () => {
// worker is terminated, then immediately booted again, new redux store is created
await ReduxWorker.storeWorker.reboot();
});
ReduxWorker.initialize(createStoreWorker, {
taskDurationWatcher: {
enabled: true,
},
});
API - Window context
async initialize(createStoreWorker, [config]): void
What does it do:
- Store worker is booted (Redux Store is created).
- An configuration object is created and sent to the worker.
- The task duration watcher is started.
- The window bridge is initialized (see
executeInWindow
section).
Parameters
createStoreWorker: Function
- Function that returns new store worker.config: Object
- Optional, with following defaults:{ /* const logLevelEnum = { development: logLevels.INFO, production: logLevels.SILENT, [undefined]: logLevels.ERROR, }; */ logLevel: logLevelEnum[process.env.NODE],
}taskDurationWatcher: { enabled: process.env.NODE_ENV !== 'development', /* If the store worker doesn't report itself in 4s to the main thread, then the worker is considered to be non-responding and the ReduxWorker.eventTypes.TASK_DURATION_TIMEOUT event is fired. */ unrespondingTimeout: 1000 * 4, // ms /* How often should the store worker report itself to the tasksDurationWatcher module. Note that each report resets the unrespondingTimeout. So reportStatusInPeriod + messageHopDelay < unrespondingTimeout. */ reportStatusInPeriod: 1000 * 3, // ms }
Example
// ...
ReduxWorker.initialize(createStoreWorker).then(() => {
console.log('Store worker is ready');
});
Notes
- This method can be called only once, otherwise an error is thrown.
- The
connectWorker
HOC can be safely used before this method is called. But since the store worker isn't ready at that moment, nothing will be rendered and all incoming messages to the worker are going to be queued.
connectWorker(bridgeId, mapDispatchToProps?, ownPropsSelector?): (ConnectedComponent, Loader?): React.Component
What does it do:
- It connects to Redux store which is placed at a Web Worker. This happens on
componentDidMount
. - It disconnects from Redux store on
componentWillUnmount
.
Parameters
bridgeId: String
- An unique ID among other instances ofconnectWorker
. See helper utilityuniqueId
mapDispatchToProps: Function|Object
Function
: 1st argument is dispatch method that send Redux actions to the store worker. 2nd argument isownProps
object which is returned fromownPropSelector
.Object
: Properties are Redux action creators.
ownPropsSelector: Function
- Function that receives all component props and returns props that are only required in themapDispatchToProps
andmapStateToProps
(e.g.userId
).
Returns
A React component wrapped by connectWorker
HOC.
Example
// --------------------
// Foo.js (main thread context)
// --------------------
import { connectWorker } from '@ackee/redux-worker/main';
import Foo from '../components/Foo';
const mapDispatchToProps = (dispatch, selectedOwnProps) => ({
// ...
});
const ownPropSelector = componentProps => ({
// ...
});
// The mapStateToProps selector is placed at `Foo.selector.js` file
export default connectWorker('FOO_BRIDGE', mapDispatchToProps, ownPropsSelector)(Foo);
// --------------------
// Foo.selector.js (worker context)
// --------------------
import { registerSelector } from '@ackee/redux-worker/worker';
const mapStateToProps = (state, selectedOwnProps) => ({
// ...
});
registerSelector('FOO_BRIDGE', mapStateToProps);
API - Worker context
registerSelector(bridgeId: String, mapStateToProps: Function): void
Add a container selector to global selectors register.
import { registerSelector } from '@ackee/redux-worker';
const mapStateToProps = state => ({
// ...
});
registerSelector('BRIDGE_ID', mapStateToProps);
async executeInWindow(pathToProperty: String, args:Array?): any
import { executeInWindow } from '@ackee/redux-worker/worker';
async function accessWindowObjectInWorker() {
const innerWidth = await executeInWindow('innerWidth');
console.log(innerWidth);
}
async function goBack() {
await executeInWindow('history.back');
}
async function storeToSessionStorage() {
await executeInWindow('sessionStorage.setItem', ['message', 'hello world']);
const message = await executeInWindow('sessionStorage.getItem', ['message']);
console.log(message); // > 'hello world'
}
API - shared context
unqiueId(prefix?): String
Get unique string ID, optionally with custom prefix. The uniqueness is guaranteed under the same context.
The purpose of this utility is to generate bridge IDs for
connectWorker
HOC andregisterSelector
method.
Example
import { uniqueId } from '@ackee/redux-worker';
uniqueId(); // > 'rs'
uniqueId('COUNTER_BRIDGE'); // > 'COUNTER_BRIDGE_rt'
uniqueId('COUNTER_BRIDGE'); // > 'COUNTER_BRIDGE_ru'