понедельник, 28 ноября 2016 г.

Custom React-Redux Provider

Файл custom-provider.js, позволяющий помимо доступа к store вызывать функцию setAppState(), обновляющую state всего приложения и перерисовывающая его.

import React, {Component, PropTypes, Children} from 'react';

let didWarnAboutReceivingStore = false;
function warnAboutReceivingStore () {
    if (didWarnAboutReceivingStore) {return;}
    didWarnAboutReceivingStore = true;
    warning(
            '<Provider> does not support changing `store` on the fly. '
        + 'It is most likely that you see this error because you updated to '
        + 'Redux 2.x and React Redux 2.x which no longer hot reload reducers '
        + 'automatically. See https://github.com/reactjs/react-redux/releases/'
        + 'tag/v2.0.0 for the migration instructions.'
    );
}

// Prints a warning in the console if it exists.
// @param {String} message The warning message.
// @returns {void}
function warning (message) {
    /* eslint-disable no-console */
    if (typeof console !== 'undefined' && typeof console.error === 'function') {
        console.error(message);
    }
    /* eslint-enable no-console */
    try {
        // This error was thrown as a convenience so that if you enable
        // "break on all exceptions" in your console,
        // it would pause the execution at this line.
        throw new Error(message);
    /* eslint-disable no-empty */
    } catch (e) {}
    /* eslint-enable no-empty */
}

export default class Provider extends Component {
    getChildContext () {
        return {
              store: this.store
            , setAppState: this.setState.bind(this)
        };
    }
    constructor (props, context) {
        super(props, context)
        this.store = props.store;
    }
    render () {
        return Children.only(this.props.children);
    }
}

if (process.env.NODE_ENV !== 'production') {
    Provider.prototype.componentWillReceiveProps = function (nextProps) {
        const store = this.store
                , nextStore = nextProps.store;
        if (store !== nextStore) {warnAboutReceivingStore();}
    }
}

const storeShape = PropTypes.shape({
      subscribe: PropTypes.func.isRequired
    , dispatch: PropTypes.func.isRequired
    , getState: PropTypes.func.isRequired
});

Provider.propTypes = {
      store: storeShape.isRequired
    , children: PropTypes.element.isRequired
};

Provider.childContextTypes = {
      store: storeShape.isRequired
    , setAppState: React.PropTypes.func
};

Файл index.js с примером кода приложения.

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers} from 'redux';
import Provider from './custom-provider.js';

class RandomNumberGenerator extends Component {
    constructor (props, context) {
        super(props, context);
        this.store = this.context.store;
        this.setAppState = this.context.setAppState;
    }
    render () {
        return (
            <div>
                <p>Текущее случайное число = {this.store.getState().randomNumber}</p>
                <div style={{display: 'inline-block', border: '1px solid black'}} onClick={this.handleClick.bind(this)}>Сгенерировать новое случайное число</div>
            </div>
        );
    }
    handleClick () {
        this.store.dispatch({type: 'GENERATE_RANDOM_NUMBER'});
        this.setAppState({});
    }
    shouldComponentUpdate () {
        console.log('RandomNumberGenerator updated');
        return true;
    }
}

RandomNumberGenerator.contextTypes = {
      store: React.PropTypes.object
    , setAppState: React.PropTypes.func
};

class Hello extends Component {
    constructor (props, context) {
        super(props, context);
        this.store = this.context.store;
    }
    render () {
        return (
            <div>
                <h1>Привет! Ваше случайное число = {this.store.getState().randomNumber}</h1>
                <RandomNumberGenerator />
            </div>
        );
    }
    shouldComponentUpdate () {
        console.log('Hello updated');
        return true;
    }
}

Hello.contextTypes = {
    store: React.PropTypes.object
};

class App extends Component {
    render () {
        return <Hello />;
    }
}

var initialState = {randomNumber: 0.2841463617514819};

function randomNumberReducer (state = initialState.randomNumber, action) {
    switch (action.type) {
        case 'GENERATE_RANDOM_NUMBER': return Math.random();
        default: return state;
    }
}

var rootReducer = combineReducers({
    randomNumber: randomNumberReducer
});

const store = createStore(rootReducer, initialState);

ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('app'));

Файл index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>React Redux Webpack</title>
</head>
<body>
    <div id="app"></div>
    <script type="text/javascript" src="../dist/bundle.js"></script>
</body>
</html>