пятница, 2 июня 2017 г.

React, Redux, React-Redux, TypeScript, Webpack

Порядок расположения папок и файлов:

/@types/index.d.ts
/@types/systemjs/index.d.ts
/webpack/config.js
/tsconfig.json
/package.json
/index.html
/src/index.tsx
/src/index-redux.tsx

Файл /@types/index.d.ts

/// <reference path="../node_modules/ufs-ui/typings/globals/EventListener/index.d.ts" />
/// <reference path="../node_modules/ufs-ui/typings/globals/react-dom/index.d.ts" />
/// <reference path="../node_modules/ufs-ui/typings/globals/react-redux/index.d.ts" />
/// <reference path="../node_modules/ufs-ui/typings/globals/react/index.d.ts" />
/// <reference path="../node_modules/ufs-ui/typings/globals/redux-logger/index.d.ts" />
/// <reference path="../node_modules/ufs-ui/typings/globals/redux-thunk/index.d.ts" />
/// <reference path="../node_modules/ufs-ui/typings/globals/redux/index.d.ts" />
/// <reference path="../node_modules/typings/modules/react-router/index.d.ts" />

Файл /@types/systemjs/index.d.ts

// Type definitions for SystemJS 0.19.29
// Project: https://github.com/systemjs/systemjs
// Definitions by: Ludovic HENIN <https://github.com/ludohenin/>, Nathan Walker <https://github.com/NathanWalker/>, Giedrius Grabauskas <https://github.com/GiedriusGrabauskas>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped


declare namespace SystemJSLoader {

    type ModulesList = { [bundleName: string]: Array<string> };

    type PackageList<T> = { [packageName: string]: T };

    /**
     * The following module formats are supported:
     *
     * - esm: ECMAScript Module (previously referred to as es6)
     * - cjs: CommonJS
     * - amd: Asynchronous Module Definition
     * - global: Global shim module format
     * - register: System.register or System.registerDynamic compatibility module format
     *
     */
    type ModuleFormat = "esm" | "cjs" | "amd" | "global" | "register";

    /**
     * Sets the module name of the transpiler to be used for loading ES6 modules.
     * Represents a module name for System.import that must resolve to either Traceur, Babel or TypeScript.
     * When set to traceur, babel or typescript, loading will be automatically configured as far as possible.
     */
    type Transpiler = "plugin-traceur" | "plugin-babel" | "plugin-typescript" | "traceur" | "babel" | "typescript" | boolean;

    type ConfigMap = PackageList<string>;

    type ConfigMeta = PackageList<MetaConfig>;

    interface MetaConfig {
        /**
         * Sets in what format the module is loaded.
         */
        format?: ModuleFormat;

        /**
         * For the global format, when automatic detection of exports is not enough, a custom exports meta value can be set.
         * This tells the loader what global name to use as the module's export value.
         */
        exports?: string;

        /**
         * Dependencies to load before this module. Goes through regular paths and map normalization.
         * Only supported for the cjs, amd and global formats.
         */
        deps?: Array<string>;

        /**
         * A map of global names to module names that should be defined only for the execution of this module.
         * Enables use of legacy code that expects certain globals to be present.
         * Referenced modules automatically becomes dependencies. Only supported for the cjs and global formats.
         */
        globals?: string;

        /**
         * Set a loader for this meta path.
         */
        loader?: string;

        /**
         * For plugin transpilers to set the source map of their transpilation.
         */
        sourceMap?: any;

        /**
         * Load the module using <script> tag injection.
         */
        scriptLoad?: boolean;

        /**
         * The nonce attribute to use when loading the script as a way to enable CSP.
         * This should correspond to the "nonce-" attribute set in the Content-Security-Policy header.
         */
        nonce?: string;

        /**
         * The subresource integrity attribute corresponding to the script integrity,
         * describing the expected hash of the final code to be executed.
         * For example, System.config({ meta: { 'src/example.js': { integrity: 'sha256-e3b0c44...' }});
         * would throw an error if the translated source of src/example.js doesn't match the expected hash.
         */
        integrity?: string;

        /**
         * When scripts are loaded from a different domain (e.g. CDN) the global error handler (window.onerror)
         * has very limited information about errors to prevent unintended leaking. In order to mitigate this,
         * the <script> tags need to set crossorigin attribute and the server needs to enable CORS.
         * The valid values are "anonymous" and "use-credentials".
         */
        crossOrigin?: string;

        /**
         * When loading a module that is not an ECMAScript Module, we set the module as the default export,
         * but then also iterate the module object and copy named exports for it a well.
         * Use this option to disable this iteration and copying of the exports.
         */
        esmExports?: boolean;
     
        /**
         * To ignore resources that shouldn't be traced as part of the build.
         * Use with the SystemJS Builder. (https://github.com/systemjs/builder#ignore-resources)
         */
        build?: boolean;
    }

    interface PackageConfig {
        /**
         * The main entry point of the package (so import 'local/package' is equivalent to import 'local/package/index.js')
         */
        main?: string;

        /**
         * The module format of the package. See Module Formats.
         */
        format?: ModuleFormat;

        /**
         * The default extension to add to modules requested within the package. Takes preference over defaultJSExtensions.
         * Can be set to defaultExtension: false to optionally opt-out of extension-adding when defaultJSExtensions is enabled.
         */
        defaultExtension?: boolean | string;

        /**
         * Local and relative map configurations scoped to the package. Apply for subpaths as well.
         */
        map?: ConfigMap;

        /**
         * Module meta provides an API for SystemJS to understand how to load modules correctly.
         * Package-scoped meta configuration with wildcard support. Modules are subpaths within the package path.
         * This also provides an opt-out mechanism for defaultExtension, by adding modules here that should skip extension adding.
         */
        meta?: ConfigMeta;
    }

    interface TraceurOptions {
        properTailCalls?: boolean;
        symbols?: boolean;
        arrayComprehension?: boolean;
        asyncFunctions?: boolean;
        asyncGenerators?: any;
        forOn?: boolean;
        generatorComprehension?: boolean;
    }

    interface Config {
        /**
         * For custom config names
         */
        [customName: string]: any;

        /**
         * The baseURL provides a special mechanism for loading modules relative to a standard reference URL.
         */
        baseURL?: string;

        /**
         * Set the Babel transpiler options when System.transpiler is set to babel.
         */
        //TODO: Import BabelCore.TransformOptions
        babelOptions?: any;

        /**
         * undles allow a collection of modules to be downloaded together as a package whenever any module from that collection is requested.
         * Useful for splitting an application into sub-modules for production. Use with the SystemJS Builder.
         */
        bundles?: ModulesList;

        /**
         * Backwards-compatibility mode for the loader to automatically add '.js' extensions when not present to module requests.
         * This allows code written for SystemJS 0.16 or less to work easily in the latest version:
         */
        defaultJSExtensions?: boolean;

        /**
         * An alternative to bundling providing a solution to the latency issue of progressively loading dependencies.
         * When a module specified in depCache is loaded, asynchronous loading of its pre-cached dependency list begins in parallel.
         */
        depCache?: ModulesList;

        /**
         * The map option is similar to paths, but acts very early in the normalization process.
         * It allows you to map a module alias to a location or package:
         */
        map?: ConfigMap;

        /**
         * Module meta provides an API for SystemJS to understand how to load modules correctly.
         * Meta is how we set the module format of a module, or know how to shim dependencies of a global script.
         */
        meta?: ConfigMeta;

        /**
         * Packages provide a convenience for setting meta and map configuration that is specific to a common path.
         * In addition packages allow for setting contextual map configuration which only applies within the package itself.
         * This allows for full dependency encapsulation without always needing to have all dependencies in a global namespace.
         */
        packages?: PackageList<PackageConfig>;

        /**
         * The ES6 Module Loader paths implementation, applied after normalization and supporting subpaths via wildcards.
         * It is usually advisable to use map configuration over paths unless you need strict control over normalized module names.
         */
        paths?: PackageList<string>;

        /**
         * Set the Traceur compilation options.
         */
        traceurOptions?: TraceurOptions;

        /**
         * Sets the module name of the transpiler to be used for loading ES6 modules.
         */
        transpiler?: Transpiler;
     
        trace?: boolean;

        /**
         * Sets the TypeScript transpiler options.
         */
        //TODO: Import Typescript.CompilerOptions
        typescriptOptions?: any;
    }

    interface SystemJSSystemFields {
        env: string;
        loaderErrorStack: boolean;
        packageConfigPaths: Array<string>;
        pluginFirst: boolean;
        version: string;
        warnings: boolean;
    }

    interface System extends Config, SystemJSSystemFields {
        /**
         * For backwards-compatibility with AMD environments, set window.define = System.amdDefine.
         */
        amdDefine: Function;

        /**
         * For backwards-compatibility with AMD environments, set window.require = System.amdRequire.
         */
        amdRequire: Function;

        /**
         * SystemJS configuration helper function.
         * Once SystemJS has loaded, configuration can be set on SystemJS by using the configuration function System.config.
         */
        config(config: Config): void;

        /**
         * This represents the System base class, which can be extended or reinstantiated to create a custom System instance.
         */
        constructor(): System;

        /**
         * Deletes a module from the registry by normalized name.
         */
        delete(moduleName: string): void;

        /**
         * Returns a module from the registry by normalized name.
         */
        get(moduleName: string): any;
        get<TModule>(moduleName: string): TModule;

        /**
         * Returns whether a given module exists in the registry by normalized module name.
         */
        has(moduleName: string): boolean;

        /**
         * Loads a module by name taking an optional normalized parent name argument.
         * Promise resolves to the module value.
         */
        import(moduleName: string, normalizedParentName?: string): Promise<any>;
        import<TModule>(moduleName: string, normalizedParentName?: string): Promise<TModule>;

        /**
         * Given a plain JavaScript object, return an equivalent Module object.
         * Useful when writing a custom instantiate hook or using System.set.
         */
        newModule(object: any): any;
        newModule<TModule>(object: any): TModule;

        /**
         * Declaration function for defining modules of the System.register polyfill module format.
         */
        register(name: string, deps: Array<string>, declare: Function): void;
        register(deps: Array<string>, declare: Function): void;

        /**
         * Companion module format to System.register for non-ES6 modules.
         * Provides a <script>-injection-compatible module format that any CommonJS or Global module can be converted into for CSP compatibility.
         */
        registerDynamic(name: string, deps: Array<string>, executingRequire: boolean, declare: Function): void;
        registerDynamic(deps: Array<string>, executingRequire: boolean, declare: Function): void;

        /**
         * Sets a module into the registry directly and synchronously.
         * Typically used along with System.newModule to create a valid Module object:
         */
        set(moduleName: string, module: any): void;

        /**
         * In CommonJS environments, SystemJS will substitute the global require as needed by the module format being
         * loaded to ensure the correct detection paths in loaded code.
         * The CommonJS require can be recovered within these modules from System._nodeRequire.
         */
        _nodeRequire: Function;

        /**
         * Modules list available only with trace=true
         */
        loads: PackageList<any>;
    }

}

declare var SystemJS: SystemJSLoader.System;

/**
 * @deprecated use SystemJS https://github.com/systemjs/systemjs/releases/tag/0.19.10
 */
declare var System: SystemJSLoader.System;

declare module "systemjs" {
    import systemJSLoader = SystemJSLoader;
    let system: systemJSLoader.System;
    export = system;
}

Файл /webpack/config.js

'use strict';

const path = require('path');

const webpack = require('webpack');
const failPlugin = require('webpack-fail-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const precss = require('precss');

const autoprefixer = require('autoprefixer');

const OUTPUT_PATH = 'dist';

let hotEntries = [
    'webpack/hot/dev-server'
];

let entry = {
    'index': [].concat(hotEntries, path.resolve(__dirname, '../src/index-redux.tsx'))
};

let plugins = [
    failPlugin,
    new webpack.DefinePlugin({
        'buildEnv': JSON.stringify(process.env.NODE_ENV)
    })
];

plugins = plugins.concat([
    new webpack.HotModuleReplacementPlugin(),
    new CleanWebpackPlugin([OUTPUT_PATH], {
        root: path.resolve(__dirname, '../'),
        verbose: false
    }),
    new CopyWebpackPlugin([
        {from: 'index.html'},

        // Зависимости
        {from: 'node_modules/systemjs/dist/system.js'},
        {from: 'node_modules/systemjs/dist/system-polyfills.js'},
        {from: 'node_modules/systemjs/dist/system-polyfills.js.map'},
        {from: 'node_modules/systemjs/dist/system.js.map'}

    ])
]);

module.exports = {
    context: path.resolve(__dirname, '../'),
    entry: entry,
    output: {
        filename: '[name].bundle.js',
        path: path.resolve(__dirname, '../' + OUTPUT_PATH),
        libraryTarget: 'umd'
    },
    module: {
        loaders: [
            {
                test: /\.tsx?$/,
                exclude: /node_modules/,
                loaders: ['react-hot-loader/webpack', 'es3ify', 'ts']
            },
            {
                test: /\.s?css$/,
                loaders: ['style', 'css', 'postcss']
            },
            {test: /\.json$/, loader: 'json'},
            {test: /\.svg|ico|png|gif|jpg($|\?)/, loader: 'file?name=images/[hash].[ext]'},
            {test: /\.eot|ttf|woff|woff2($|\?)/, loader: 'file?name=fonts/[hash].[ext]'}
        ]
    },
    postcss: function () {
        return [precss, autoprefixer];
    },
    ts: {
        compilerOptions: {
            noEmit: false
        }
    },
    resolve: {
        root: [
            path.resolve(__dirname, '../src')
        ],
        extensions: ['', '.js', '.ts', '.tsx', '.css', '.scss', '.png']
    },
    plugins: plugins,
    devServer: {
        contentBase: './' + OUTPUT_PATH,
        hot: true,
        inline: true,
        noInfo: false,
        stats: {
            hash: false,
            version: false,
            chunks: false,
            publicPath: false,
            errors: true,
            warnings: true,
            children: false
        }
    }
};

Файл /tsconfig.json

{
  "compilerOptions": {
    "lib": ["DOM","ES6","ScriptHost"],
    "target": "es5",
    "noImplicitAny": false,
    "sourceMap": true,
    "typeRoots": ["./@types"],
    "types": ["systemjs"],
    "jsx": "react"
  },
  "files": [
    "@types/index.d.ts"
  ]
}

Файл /package.json

{
  "name": "test",
  "version": "1.0.0",
  "description": "Test",
  "scripts": {
    "start": "webpack-dev-server --config webpack/config.js --host localhost --port 8080 --inline --colors"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^0.1.16",
    "copy-webpack-plugin": "^4.0.1",
    "css-loader": "0.23.1",
    "es3ify-loader": "0.2.0",
    "extract-text-webpack-plugin": "1.0.1",
    "file-loader": "0.8.5",
    "json-loader": "^0.5.4",
    "postcss-loader": "^1.1.1",
    "postcss-scss": "^0.3.1",
    "precss": "1.4.0",
    "react-addons-test-utils": "0.14.0",
    "react-hot-loader": "3.0.0-beta.6",
    "source-map-loader": "0.1.5",
    "style-loader": "0.13.1",
    "ts-loader": "0.8.2",
    "typescript": "2.2.1",
    "url-loader": "0.5.7",
    "webpack": "1.14.0",
    "webpack-dev-server": "1.16.2",
    "webpack-fail-plugin": "1.0.4",
    "zip-folder": "^1.0.0"
  },
  "dependencies": {
    "core-js": "1.2.7",
    "es6-promise": "3.0.2",
    "react": "0.14.0",
    "react-dom": "0.14.0",
    "redux": "3.6.0",
    "react-redux": "5.0.5",
    "redux-logger": "3.0.6",
    "redux-thunk": "2.2.0",
    "systemjs": "0.19.35"
  }
}

Файл /index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
    <div id="root"></div>
    <script type="text/javascript" src="index.bundle.js" charset="utf-8"></script>
</body>
</html>

Файл /src/index.tsx

import * as React from 'react';
import {Component, PropTypes} from 'react';
import * as ReactDOM from 'react-dom';

// React Events
// onClick onMouseDown onMouseUp onMouseEnter onMouseLeave
// onKeyDown onKeyPress onKeyUp
// onChange onInput onSubmit onSelect
// onFocus onBlur
// onScroll
// onWheel
// image onLoad onError

interface IHeader {
    identificator: string;
    func(): void;
}

class Header extends Component<IHeader, {}> {
    constructor (props) {
        super(props);
    }
    render (): JSX.Element {
        this.props.func();
        return (
            <h2
                    className={this.props.identificator === 'header' ? 'bold' : 'thin'}
                    style={{backgroundColor: 'blue', color: 'yellow'}}
                    data-custom-attribute="foo"
            >{this.props.children}{/* Через children можно передать и функцию {this.props.children('a', 'b', 'c')} так <Header>{(a, b, c) => <p>{a} {b} {c}</p>}</Header> */}</h2>
        );
    }
}

class DangerouslyHTML extends Component<{}, {}> {
    constructor (props) {
        super(props);
    }
    render (): JSX.Element {
        return (
            <div dangerouslySetInnerHTML={{__html: '<span>&middot;Это опасный HTML-код</span>'}}></div>
        );
    }
}

interface IList {
    elementsArray: string[][];
}

class List extends Component<IList, {}> {
    constructor(props) {
        super(props);
    }
    render (): JSX.Element {
        return (
            <ul>
            {
                this.props.elementsArray.map((element: string[], index: number): JSX.Element => {
                    return <li key={index} onClick={this.handleClick.bind(this)}>{element[0]}</li>
                })
            }
            </ul>
        );
    }
    handleClick (event): void {
        event.preventDefault();
        console.log(event.target.innerHTML);
    }
}


interface IFormState {
    inputValue: string;
    textareaValue: string;
    selectValue: string;
    checkboxValue: boolean;
}

class Form extends Component<{},  IFormState> {
    constructor (props) {
        super(props);
        this.state = {
            inputValue: 'Анна',
            textareaValue: 'Пришла на работу',
            selectValue: 'one',
            checkboxValue: true
        };
    }
    render (): JSX.Element {
        return (
            <form method="get" action ="/" onSubmit={this.handleSubmit.bind(this)}>
                <label htmlFor="input">Имя</label>
                <input ref="inputRef" type="text" disabled={false} defaultValue="Борис" value={this.state.inputValue} onChange={this.handleInputChange.bind(this)} />
                <textarea ref="textareaRef" disabled={false} defaultValue="Идет домой" value={this.state.textareaValue} onChange={this.handleTextareaChange.bind(this)} />
                <select ref="selectRef" disabled={false} multiple={true} value={[this.state.selectValue, 'two']} onChange={this.handleSelectChange.bind(this)}>
                    <option value="one">один</option>
                    <option value="two">два</option>
                    <option value="three">три</option>
                </select>
                <input ref="checkboxRef" type="checkbox" disabled={false} checked={this.state.checkboxValue} onChange={this.handleCheckboxChange.bind(this)} />
                <input type="submit" value="Отправить" / >
                <input type="reset" value="Очистить" onClick={this.handleReset.bind(this)} />
            </form>
        );
    }
    handleSubmit (event) {
        event.preventDefault();
        event.stopPropagation();
        console.log({
            input: (this.refs.inputRef as HTMLInputElement).value,
            textarea: (this.refs.textareaRef as HTMLInputElement).value,
            select: (this.refs.selectRef as HTMLInputElement).value,
            checkbox: (this.refs.checkboxRef as HTMLInputElement).checked
        });
    }
    handleInputChange (event) {
        this.setState(
            Object.assign({}, this.state, {inputValue: event.target.value.length > 5 ? event.target.value.slice(0, 5) : event.target.value}),
            function () {console.log('Input changed');}
        );
    }
    handleTextareaChange (event) {
        this.setState(Object.assign({}, this.state, {textareaValue: event.targer.value}));
    }
    handleSelectChange (event) {
        console.log(event.target.value);
    }
    handleCheckboxChange (event) {
        this.setState(Object.assign({}, this.state, {checkboxValue: event.target.checked === true ? true : false}));
    }
    handleReset (event) {
        event.preventDefault();
        this.setState({
            inputValue: '',
            textareaValue: '',
            selectValue: '',
            checkboxValue: true
        });
    }
}

class Menu extends Component<{}, {}> {
    constructor (props) {
        super(props);
    }
    render (): JSX.Element {
        const elements: string[][] = [
            ['главная', '/'],
            ['отчеты', '/reports'],
            ['контакты', '/contacts']
        ];
        return (
            <List elementsArray={elements} />
        );
    }
}

interface IPropsValidation {
    optionalBoolean: boolean;

    optionalNumber: number;

    optionalString: string;
    optionalOneOf: string;

    optionalArray: string[];
    optionalArrayOf: number[];

    optionalObject: {};
    optionalObjectOf: {};
    optionalObjectWithShape: {
        color: string;
        fontSize: number;
    };

    optionalFunction: () => number;
    requiredFunction: () => number,

// optionalSymbol: symbol;

    optionalReactElement: JSX.Element;

    anythingOptionalThatCanBeRendered: number | string | JSX.Element;

    optionalInstanceOfClass: number[];

    optionalOneOfType: number | string;

    requiredAny: any;

    customProp: string;
}

class PropsValidation extends Component <IPropsValidation, {}> {
    static contextTypes = {
        color: PropTypes.string
    };
    static childContextTypes = {
        color: PropTypes.string
    };
    static defaultProps = {
        optionalBoolean: true,
        optionalNumber: 1,
        optionalString: 'text'
    };
    static propTypes = {
        optionalBoolean: PropTypes.bool,

        optionalNumber: PropTypes.number,

        optionalString: PropTypes.string,
        optionalOneOf: PropTypes.oneOf(['News', 'Photos']),

        optionalArray:  PropTypes.array,
        optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

        optionalObject: PropTypes.object,
        optionalObjectOf: PropTypes.objectOf(PropTypes.number),
        optionalObjectWithShape: PropTypes.shape({
            color: PropTypes.string,
            fontSize: PropTypes.number
        }),

        optionalFunction: PropTypes.func,
        requiredFunction: PropTypes.func.isRequired,

    // optionalSymbol: PropTypes.symbol,

        optionalReactElement: PropTypes.element,

        anythingOptionalThatCanBeRendered: PropTypes.node, // number, string, element

        optionalInstanceOfClass: PropTypes.instanceOf(Array),

        optionalOneOfType: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),

        requiredAny: PropTypes.any.isRequired,

        customProp: function (props, propName, componentName) {
            if (!/matchme/.test(props[propName])) {
                return new Error('Invalid prop "' + propName + '" supplied to "' + componentName + '". Validation failed.');
            }
        }
    };
    constructor (props) {
        super(props);
    }
    render (): JSX.Element {
        console.log(this.props.optionalFunction());
        console.log(this.props.requiredFunction());
        return (
            <div>
                {this.props.optionalBoolean}
                {this.props.optionalNumber}
                {this.props.optionalString}
                {this.props.optionalOneOf}
                {this.props.optionalArray[0]}
                {this.props.optionalArrayOf[0]}
                {(this.props.optionalObject as any).one}
                {(this.props.optionalObjectOf as any).three}
                {this.props.optionalObjectWithShape.color}
                {/* this.props.optionalSymbol */}
                {this.props.optionalReactElement}
                {this.props.anythingOptionalThatCanBeRendered}
                {this.props.optionalInstanceOfClass}
                {this.props.optionalOneOfType}
                {this.props.requiredAny}
                {this.props.customProp}
            </div>
        );
    }
}

interface ILifecycleState {
    comments: string[];
}

class Lifecycle extends Component<{}, ILifecycleState> {
    // 1. Установка первоначальных значений.
    // Выполняется только 1 раз перед вставкой компонента на страниц.
    /* componentWillMount (props, context) { */ constructor (props, context) {
        super(props);
        /* getInitialState () {return {comments: []};} */ this.state = {comments: []};
        console.log('1. Установка первоначальных значений.');
    }
    // 2. Рендеринг компонента.
    // Выполняется при вставке компонента на страницу.
    // А также каждый раз при изменении свойств this.props или состояния компонента чере this.setState()
    render (): JSX.Element {
        console.log('2. Рендеринг компонента.');
        if (this.state.comments.length > 1) {
            return (
                <ul className="comments">
                    {
                        this.state.comments.map((comments) => {
                            return (
                                <li>comment</li>
                            );
                        })
                    }
                </ul>
            );
        } else {
            return (
                <div onClick={this.forceUpdateHandler.bind(this)}>Нет данных [Обновить]</div>
            );
        }
    }
    forceUpdateHandler () {
        this.forceUpdate(); // Принудительная перерисовка компонента
    }
    // 3. Выполнение действий после вставки компонента на страницу.
    // Выполняется только 1 раз после вставки компонента на страницу.
    // Здесь можно изменять размеры элементов после вставки компонента или получать данные через AJAX.
    componentDidMount () {
        // getData().then((data) => {
        //     this.setState(Object.assign({}, this.state, {comments: data}));
        // });
        // resizeComponent();
        // this.refs.textInput.focus();
        // this.timerId = setInterval(() => {this.tick();}, 1000);
        console.log('3. Выполнение действий после вставки компонента на страницу.');
    }
    // 4. Получение новых свойств props.
    // Выполняется каждый раз, когда уже вставленный на страницу компонент перезагружается и получает новые свойства props.
    // Обычно не нужно менять.
    componentWillReceiveProps (nextProps, nextContext) {
        this.setState(Object.assign({}, this.state, {comments: nextProps.comments}));
        console.log('4. Получение новых свойств при перерисовке компонента.');
    }
    // 5. Проверка необходимости делать ререндеринг компонента
    // Выполняется каждый раз, когда уже вставленный на страницу компонент перезагружается.
    // Здесь можно сделать проверку нужно ли делать ререндеринг компонента в зависимости от переданных ему свойств props.
    // Данный метод обязательно должен вернуть true или false.
    // Обычно не нужно менять.
    shouldComponentUpdate (nextProps, nextState, nextContext) {
        console.log('5. Проверка необходимости делать ререндеринг компонента');
        // return this.props.id !== nextProps.id;
        // return this.state.comments !== nextState.comments;
        if (this.state.comments.length > 0) {
            return true;
        } else {
            return false;
        }
    }
    // 6. Выполнение действий перед перерисовкой компонента.
    // Выполняется каждый раз перед перерисовкой компонента.
    // Здесь можно устанавливать слушателей событий.
    // Обычно не нужно менять.
    componentWillUpdate (nextProps, nextStatem, nextContext) {
        console.log('6. Выполнение действий перед перерисовкой компонента.');
    }
    // 7. Выполнение действий после перерисовкой компонента.
    // Выполняется каждый раз после перерисовки компонента.
    // Здесь можно изменять размеры элементов после вставки компонента.
    // Обычно не нужно менять.
    componentDidUpdate (prevProps, prevState, prevContext) {
        // resizeComponent();
        console.log('7. Выполнение действий после перерисовкой компонента.');
    }
    // 8. Выполнение действий перед удалением компонента со страницы.
    // Выполняется только 1 раз перед удалением компонента со страницы.
    // Здесь можно удалять слушателей событий.
    componentWillUnmount () {
        // document.body.removeEventListener(resize);
        // clearInterval(this.timerId);
        console.log('8. Выполнение действий перед удалением компонента со страницы.');
    }
}

class App extends Component<{}, {}> {
    constructor (props) {
        super(props);
    }
    render (): JSX.Element {
        return (
            <div>
                {/* Это комментарий к компоненту */}
                {
                    // Это комментарий в конце строки
                }
                {true ? (
                                <div>Ура!</div>
                             ) : (
                                <div>Ха-ха!</div>
                             )}
                {[1, 2, 3].length > 0 && <div>Ура!</div>}
                <h1>Hello</h1>
                <p>
                    <span>Так</span>{'   '}
                    <span>вставляется</span>{'   '}
                    <span>множество</span>{'   '}
                    <span>пробелов</span>
                </p>
                {[
                    <p key="1">4</p>,
                    <p key="2">5</p>,
                    <p key="3">6</p>
                ]}
                <ul>
                    {[1, 2, 3].map((number, index) => {
                        return (<li key={index}>{number}</li>);
                    })}
                </ul>
                <Header identificator="header" func={function () {console.log('OK Header');}}>Заголовок</Header>
                <DangerouslyHTML />
                <Menu />
                <Form />
                <PropsValidation
                    optionalBoolean={true}

                    optionalNumber={1}

                    optionalString="text"
                    optionalOneOf="News"

                    optionalArray={['one', 'two', 'three']}
                    optionalArrayOf={[1, 2, 3]}

                    optionalObject={{one: 1, two: 2}}
                    optionalObjectOf={{three: 3, four: 4}}
                    optionalObjectWithShape={{
                        color: 'red',
                        fontSize: 10
                    }}

                    optionalFunction={function () {return 1;}}
                    requiredFunction={function () {return 2;}}

                    optionalReactElement={<Header identificator="header2" func={function () {console.log('OK Header 2');}}>Заголовок 2</Header>}

                    anythingOptionalThatCanBeRendered={123}

                    optionalInstanceOfClass={new Array(4, 5, 6)}

                    optionalOneOfType={15}

                    requiredAny="string"

                    customProp="matchme"
                />
                <Lifecycle />
            </div>
        );
    }
}

ReactDOM.render(<App />, document.getElementById('root'));

Файл /src/index-redux.tsx

import * as React from 'react';
import {Component} from 'react';
import * as ReactDOM from 'react-dom';

import {createStore, combineReducers, applyMiddleware, bindActionCreators} from 'redux';

import * as logger from 'redux-logger';
import thunk from 'redux-thunk';

import {Provider, connect} from 'react-redux';

const couterUpAction = (value) => {
    return {
        type: 'COUNTER_UP',
        value: value
    };
};

const couterDownAction = (value) => {
    return {
        type: 'COUNTER_DOWN',
        value: value
    };
};

const couterDownAsync = (value) => {
    return function (dispatch) {
       setTimeout(() => {
           dispatch(couterDownAction(value));
       }, 1000);
    };
};

const counterReducer = (state = 0, action) => {
    switch (action.type) {
        case 'COUNTER_UP': return state + action.value;
        case 'COUNTER_DOWN': return state - action.value;
        default: return state;
    }
};


const outterReducer = combineReducers({
    inner: counterReducer
});

const rootReducer = combineReducers({
    counter: outterReducer
});

const initialState = {
    counter: {
        inner: 0
    }
};

const store = createStore(rootReducer, initialState, applyMiddleware((logger as any).createLogger(), thunk));

store.subscribe(() => {console.log(store.getState());});

interface IStateProps {
    counter: number;
}
interface IDispatchProps {
    handleUpClick: () => void;
    handleDownClick: () => void;
}

class Counter extends Component<IStateProps & IDispatchProps, {}> {
    constructor (props) {
        super(props);
        // this.state = {counter: store.getState()};
    }
    render (): JSX.Element {
        const {
            counter,
            handleUpClick,
            handleDownClick
        } = this.props;
        return (
            <div>
                <span>{counter}</span>{' '}
                <span onClick={handleUpClick}>Вверх</span>{' '}
                <span onClick={handleDownClick}>Вниз</span>
            </div>
        );
    }
}

const mapStateToProps = (state): IStateProps => {
    return {
        counter: state.counter.inner
    };
};

const mapDispatchToProps = (dispatch): IDispatchProps => {
    return {
        handleUpClick: function () {
            dispatch(couterUpAction(1));
            // this.setState(Object.assign({}, this.state, {counter: store.getState().counter}));
        },
        handleDownClick: function () {
            dispatch(couterDownAsync(1));
            // this.setState(Object.assign({}, this.state, {counter: store.getState().counter}));
        }
    };
};

const ConnectedCounter = connect<IStateProps, IDispatchProps, {}>(mapStateToProps, mapDispatchToProps)(Counter);

class App extends Component<{}, {}> {
    render (): JSX.Element {
        return (
            <ConnectedCounter />
        );
    }
}

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

Комментариев нет:

Отправить комментарий