npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-singleton-state

v1.0.4

Published

The library helps you providing easy data sharing between components by using a principle of angular singleton services

Downloads

2

Readme

react-singleton-state 1.0.4

Author: Oleg Mukhov

Description:

Библиотека помогает быстро внедрять данные в React-компоненты путем записи и чтения объектов-синглтонов. Библиотека для тех, кто хочет быстро "пересесть" с Angular 1.x на React

Launch v1.0.0:

Так как при установке в node_modules приходит исходный каталог, необходимо добавить в webpack.config Вашего проекта следующую настройку js-модуля:

{
    test: /\.js$/,
    loader: 'babel-loader',
    query: {
        presets: ['es2017', 'react'],
        plugins: ['transform-object-rest-spread', 'transform-class-properties']
    }
}

дополнительно установите: npm install --save-dev babel-preset-es2017 babel-plugin-transform-class-properties babel-plugin-transform-object-rest-spread Проблема решена с версии 1.0.1.

Launch Sample App (since v1.0.2)

start index.html in browser node_modules/react-singleton-state/public/index.html You also need material-ui package to run the sample app.

There you can compare react-singleton-state and redux and choose which one is easier and more flexible for you.

Использование:

Библиотека состоит из следующих исполняемых классов и функций:

  1. Класс Provider - синглтон, основное хранилище приложения.
import { Provider, providerExporter } from 'react-singleton-state';
  1. Класс Service - от данного класса наследуются все сервисы приложения. После агрегации в Provider, они также становятся синглтонами.
import { Service } from 'react-singleton-state';
  1. Класс Component - обертка библиотеки для React-компонентов, связывает state-компонентов с инстансами Service.
import { Component } from 'react-singleton-state';

Provider

Класс Provider является синглтоном.

Использование Provider в приложении:

  • наследуем класс, например, AppProvider от Provider;
class AppProvider extends Provider { }
  • описываем метод defineServices() класса AppProvider, где просто агрегируем сервисы нашего приложения;
defineServices() {
    this.UserService = new UserService('UserService');
    this.TaskService = new TaskService('TaskService');
}
  • в случае необходимости можно описать необязательный метод defineUrls(), который сохранит все url'ы в константы по каскадному принципу;
defineUrls() {
    return {
        ROOT: {
            url: 'http://localhost:8090',
            REST: {
                url: '/rest',
                TASKS: {
                    url: '/tasks'
                },
                FOLLOWERS: {
                    url: '/followers'
                }
            },
            CREDENTIALS: {
                url: '/security/v1'
            }
        }
    };
}
/*  
 *  this.URLS = {
 *    ROOT:        'http://localhost:8090',
 *    REST:        'http://localhost:8090/rest',
 *    TASKS:       'http://localhost:8090/rest/tasks',
 *    FOLLOWERS:   'http://localhost:8090/rest/followers',
 *    CREDENTIALS: 'http://localhost:8090/security/v1'
 *  }
 */
  • Последним шагом экспортируем AppProvider в проект используя библиотечную функцию providerExporter(). Во вермя импорта произойдет вызов функции, которая вернет new AppProvider(), в результате чего результат такого импорта можно сразу представить в виде объекта URLS и необходимых сервисов.
export default providerExporter(AppProvider);
// ==========================================
import AppProvider from 'src/AppProvider';
const { URLS, UserService, TaskService } = AppProvider;

Методы, которые должны или могут быть описаны у наследника класса Provider:

  • Обязательный метод defineServices(). Не ожидает никаких аргументов. В теле метода необходимо присвоить полям класса, которые в последствии станут сервисами-синглтонами приложения, экземпляры классов-наследников класса Service. Метод вызывается в конструкторе класса Provider.
  • Необязательный метод defineUrls(). Не ожидает никаких аргументов. В теле метода необходимо вернуть объект, представляющий собой каскадируемые урлы. Поля url являются конечными полями в итерациях.

Принцип работы класса Provider

Provider является самовызывающейся функцией, которая возвращает класс. В конструкторе этого класса, сперва, происходит проверка на существование экземпляра этого класса, если такой имеется, то конструктор всегда вернет этот экземпляр. Такая проверка возможна, так как экземпляр класса и сам класс всегда находятся в одном замыкании. Если же экземпляра класса не обнаруживается, то последовательно вызываются методы defineServices() и defineUrls(). Затем происходит присвоение экземпляра класса в переменную внутри замыкания.

Service

Service - это бин. В сервисе есть только его поля, а также геттеры и сеттеры для обращения к ним.

Использование Service в приложении

  1. Наследуем новый бин от Service, ~~определяем его поля~~, геттеры и сеттеры. (Начиная с версии 1.0.3, приватные поля определяются автоматически из defaultValues. Доступ к ним осуществляется через Symbol.for()).
export default class UserService extends Service {
    static defaultValues = {
        userName: 'DefaultName'
    };
    
    get userName() { return this[Symbol.for('userName')]; }
    set userName(val) { this[Symbol.for('userName')] = val; } 
}

~~2. Определяем значение полей по умолчанию через статическую переменную defaultValues, и сохраняем ссылку на класс используя метод getClass(). Ключи defaultValues должны совпадать с названиями геттеров и сеттеров.~~ Метод getClass() перестал поддерживаться с версии 1.0.1 и будет полностью отменен с версии 1.1.0

/*
 *  Шаг не имеет смысла после обновлений 1.0.2 и 1.0.3
 */
export default class UserService extends Service {
    static defaultValues = {
        userName: 'admin'
    };
    constructor(sn) {
        super(sn);
        this.getClass(UserService);
        //other variables
    }
    //getters and setters
}
  1. Агрегируем UserService в AppProvider'e.
class AppProvider extends Provider {
    defineServices() {
        this.UserService = new UserService('UserService');
    }
}
  1. Дальнейшее использование Service тесно связано с использованием класса ComponentService

Методы класса Service:

  • getClass(classType: class) [DEPRECATED] - метод принимает в качестве аргумента класс и записывает его в поле this.classType. В каждом наследнике класса Service необходимо вызывать этот метод в конструкторе, передавая в него ссылку на себя. Данная процедура необходима для доступа к статической переменной, описанного в классе Service.
  • toDefault(prop: string) - метод переводит указанное поле (по имени сеттера) к значению данного поля в статической переменной defaultValues.
  • defaultAll() - переводит все сеттеры к их значениям в defaultValues.

Принцип работы класса Service

Класс Service содержит только одно приватное поле serviceName. Поле заполняется при создании экземпляра класса. Поле необходимо для заполнения this.state React-компонента. Далее поле доступно только через геттер. Автор настоятельно рекомендует передавать в конструктор Service'ов такое же строковое значение как и название класса этого сервиса! Поскольку экземпляры всех сервисов в приложении агрегированы в синглтон-Provider, то все они сами выступают синглтонами.

Component

Класс Component связывает наши React-компоненты с экземплярами Service'ов.

Использование Component в приложении:

  1. Наследуем новый statefull-компонент от Component'a и инжектим Service'ы в его this.state через метод this._injectServices(). Метод не помешает использовать компонентный (локальный) state.
export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            localStateField1: 'default',
            localStateField2: undefined
        };
        this._injectServices([RouteService, UserService], InjectMerging.AFTER);
    }
}
  1. Для чтения данных из Service'ов нам абсолютно не нужен this.state, поэтому данные также легко вставлять и в stateless-компоненты.
const Stateless = props => <p>{props.paragraph}</p>;

//render of some class
render() {
    return (
        <div>
            <Stateless paragraph={TextService.paragraph} />
        </div>
    );
}
  1. Для записи данных в Service есть два способа:
Первый способ необходим для изменения данных в Service без ререндеринга компонента. Для этого нужно использовать сеттер самого Service'a
export default class TaskItem extends Component {
    constructor(props) {
        super(props);
        this.state = {
            TaskService           
        };
    }
    onTaskChange(e) {
        TaskService.taskText = e.target.value;
    }
    render() {
        return (
            <div>
                <p>{TaskService.taskText}</p>
                <input value={TaskService.taskText} onChange={this.onTaskChange} />
            </div>
        );
    }
} 

В данном примере значение внутри Service'a будет изменяться, однако ни в input, ни в p, новое значение появляться не будет. Данный код можно оптимизировать, отображая значение в input, но не изменяя его в p. Для этого добавим поле компонентного state и метод lifecycle - componentWillUpdate() и componentWillUnmount. И поменяем данные в input.props.value.

export default class TaskItem extends Component {
    constructor(props) {
        super(props);
        this.state = {
            TaskService,
            taskText: TaskService.taskText           
        };
    }
    componentWillUpdate(nextProps, nextState) {
        this.state.taskText = TaskService.taskText;
    }
    componentWillUnmount() {
        TaskService.taskText = this.state.taskText;
    }
    onTaskChange(e) {
        this.setState({taskText: e.target.value});
    }
    render() {
        return (
            <div>
                <p>{TaskService.taskText}</p>
                <input value={this.state.taskText} onChange={this.onTaskChange.bind(this)} />
            </div>
        );
    }
} 

Из примера видно, каким мощным эффектом оптимизации без использования shouldComponentUpdate() обладают сервисы-синглтоны.

Второй способ изменения данных в сервисе влечет за собой ререндеринг компонента. Для его осуществления необходимо вызвать метод reRender(), который унаследован от Component
export default class TaskItem extends Component {
    constructor(props) {
        super(props);
        this.state = {
            TaskService           
        };
    }
    onTaskChange(e) {
        this.reRender(TaskService)('taskText').set(e.target.value);
    }
    render() {
        return (
            <div>
                <p>{TaskService.taskText}</p>
                <input value={TaskService.taskText} onChange={this.onTaskChange.bind(this)} />
            </div>
        );
    }
} 

Теперь при изменении данных внутри input будет происходить ререндеринг компонента, в результате чего в p и input будут отображаться актуальные значения.

Методы класса Component

  • _injectService() внедряет Service'ы в state компонента. Принимает два аргумента:
    1. Массив сервисов, которые будут внедрены в state компонента;
    2. enum InjectMerging с одним из значений: InjectMerging.BEFORE - внедрит сервисы перед значениями локального state, _InjectMerging.AFTER - после. Данный аргумент необязательный, по умолчанию используется InjectMerging.BEFORE
//InjectMerging.BEFORE
this.state = {
    //your services here
    first: 'first',
    second: 'second'
};

///InjectMerging.AFTER
this.state = {
    first: 'first',
    second: 'second',
    //your services here
};

Кроме того, можно обойтись и без метода _injectServices() при внедрении сервисов в state

constructor(props) {
    super(props);
    this.state = {
        UserService,
        first: 'first',
        second: 'second',
        RouteService
    };
}
  • _bindMethods() - метод принимает строковые наименования методов компонента, к которым применится .bind(this). Кроме того, каждый аргумент может быть массивом: ['имя_метода','аргумент1','аргумент2'].
constructor(props) {
    super(props);
    this._bindMethods('onTextChange', 'onSelectChange', 'onButtonClick');
    //======or=======
    this._bindMethods(
        ['onTextChange', 'newValue', SomeFilter.filter('Val')], 
        ['onSelectChange', 1]
    );
}
  • reRender() - метод необходим для изменения значения Service'a с последующим ререндеринго компонента. Метод устроен довольно непросто, так что разберем его подробно.
  1. reRender принимает один аргумент - экземпляр сервиса либо его поле this.serviceName и возвращает функцию...
this.reRender(UserService)   // return serviceProps => { ... }
this.reRender('UserService') // or this.reRender(UserService.serviceName) - return serviceProps => { ... }
  1. возвращаемая функция принимает один необязаетльный аргумент - массив строковых названий полей сервиса или строковое название одного поля сервиса, и возвращает объект методов.
this.reRender(UserService)('login') //return { set: val => {...}, setDefault: () => {...}  }
this.reRender(UserService)(['login', 'followers', 'dateOfBirth']) //return { set: values => {...}, setDefault: () => {...} }
this.reRender(UserService)() // return { set: obj => {...}, setDefault: () => {...} }
  1. Метод set() вызывает сеттер Service'a и делает forceUpdate: * Если возвращен по одному переданному имени поля, то принимает одно значение - новое значение этого поля в Servic'e; * Если возвращен по массиву имен полей, то принимает массив новых значений этих полей по соответствию индексов; * Если возвращен по пустому значению, то принимает объект c ключами - именами полей Service'a, и значениями - новыми значениями этих полей.
this.reRender(UserService)('login').set('MyName');
this.reRender(UserService)(['login', 'dateOfBirth']).set(['MyName', '22.06.1941']);
this.reRender(UserService)().set({login: 'MyName', dateOfBirth: '22.06.1941'});
  1. Метод setDefault() вызывает Service.toDefault() в первых двух случаях и Service.defaultAll() в третьем, после чего вызывает метод forceUpdate.