@ackee/redux-worker 中文文档教程

发布于 4年前 浏览 18 项目主页 更新于 3年前

ackee|redux-worker

GitHub licenseCI StatusPRs WelcomeDependency Status

[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

你应该知道 ReactRedux。 但是您不需要了解 Web Worker 的任何知识。


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 in node_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

  1. 为此容器创建新的桥 ID:

    // ---------------------------------------
    //  modules/counter/constants/index.js
    // ---------------------------------------
    import { uniqueId } from '@ackee/redux-worker';
    
    export const bridgeIds = {
        COUNTER_BRIDGE: uniqueId('COUNTER_BRIDGE'),
    };
    
  2. 使用该 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);
    
  3. 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 of connectWorker. See helper utility uniqueId
  • mapDispatchToProps: Function|Object
    • Function: 1st argument is dispatch method that send Redux actions to the store worker. 2nd argument is ownProps object which is returned from ownPropSelector.
    • Object: Properties are Redux action creators.
  • ownPropsSelector: Function - Function that receives all component props and returns props that are only required in the mapDispatchToProps and mapStateToProps (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'

ackee|redux-worker

GitHub licenseCI StatusPRs WelcomeDependency Status

[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 use executeInWindow 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 in node_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

  1. 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'),
    };
    
  2. 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);
    
  3. 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 of connectWorker. See helper utility uniqueId
  • mapDispatchToProps: Function|Object
    • Function: 1st argument is dispatch method that send Redux actions to the store worker. 2nd argument is ownProps object which is returned from ownPropSelector.
    • Object: Properties are Redux action creators.
  • ownPropsSelector: Function - Function that receives all component props and returns props that are only required in the mapDispatchToProps and mapStateToProps (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 and registerSelector method.

Example

import { uniqueId } from '@ackee/redux-worker';

uniqueId(); // > 'rs'
uniqueId('COUNTER_BRIDGE'); // > 'COUNTER_BRIDGE_rt'
uniqueId('COUNTER_BRIDGE'); // > 'COUNTER_BRIDGE_ru'
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文