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.
Использование:
Библиотека состоит из следующих исполняемых классов и функций:
- Класс Provider - синглтон, основное хранилище приложения.
;
- Класс Service - от данного класса наследуются все сервисы приложения. После агрегации в Provider, они также становятся синглтонами.
;
- Класс Component - обертка библиотеки для React-компонентов, связывает state-компонентов с инстансами Service.
;
Provider
Класс Provider является синглтоном.
Использование Provider в приложении:
- наследуем класс, например, AppProvider от Provider;
- описываем метод defineServices() класса AppProvider, где просто агрегируем сервисы нашего приложения;
{ thisUserService = 'UserService'; thisTaskService = 'TaskService';}
- в случае необходимости можно описать необязательный метод defineUrls(), который сохранит все url'ы в константы по каскадному принципу;
{ 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 и необходимых сервисов.
AppProvider;// ==========================================;const URLS UserService TaskService = AppProvider;
Методы, которые должны или могут быть описаны у наследника класса Provider:
- Обязательный метод defineServices(). Не ожидает никаких аргументов. В теле метода необходимо присвоить полям класса, которые в последствии станут сервисами-синглтонами приложения, экземпляры классов-наследников класса Service. Метод вызывается в конструкторе класса Provider.
- Необязательный метод defineUrls(). Не ожидает никаких аргументов. В теле метода необходимо вернуть объект, представляющий собой каскадируемые урлы. Поля url являются конечными полями в итерациях.
Принцип работы класса Provider
Provider является самовызывающейся функцией, которая возвращает класс. В конструкторе этого класса, сперва, происходит проверка на существование экземпляра этого класса, если такой имеется, то конструктор всегда вернет этот экземпляр. Такая проверка возможна, так как экземпляр класса и сам класс всегда находятся в одном замыкании. Если же экземпляра класса не обнаруживается, то последовательно вызываются методы defineServices() и defineUrls(). Затем происходит присвоение экземпляра класса в переменную внутри замыкания.
Service
Service - это бин. В сервисе есть только его поля, а также геттеры и сеттеры для обращения к ним.
Использование Service в приложении
- Наследуем новый бин от Service,
определяем его поля, геттеры и сеттеры. (Начиная с версии 1.0.3, приватные поля определяются автоматически из defaultValues. Доступ к ним осуществляется через Symbol.for()).
static defaultValues = userName: 'DefaultName' ; { return thisSymbol; } { thisSymbol = val; }
2. Определяем значение полей по умолчанию через статическую переменную defaultValues, и сохраняем ссылку на класс используя метод getClass(). Ключи defaultValues должны совпадать с названиями геттеров и сеттеров.
Метод getClass() перестал поддерживаться с версии 1.0.1 и будет полностью отменен с версии 1.1.0
/* * Шаг не имеет смысла после обновлений 1.0.2 и 1.0.3 */ static defaultValues = userName: 'admin' ; { supersn; this; //other variables } //getters and setters
- Агрегируем UserService в AppProvider'e.
{ thisUserService = 'UserService'; }
- Дальнейшее использование 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 в приложении:
- Наследуем новый statefull-компонент от Component'a и инжектим Service'ы в его this.state через метод this._injectServices(). Метод не помешает использовать компонентный (локальный) state.
{ superprops; thisstate = localStateField1: 'default' localStateField2: undefined ; this; }
- Для чтения данных из Service'ов нам абсолютно не нужен this.state, поэтому данные также легко вставлять и в stateless-компоненты.
const Stateless = <p>propsparagraph</p>; //render of some class { return <div> <Stateless = /> </div> ;}
- Для записи данных в Service есть два способа:
Первый способ необходим для изменения данных в Service без ререндеринга компонента. Для этого нужно использовать сеттер самого Service'a
{ ; thisstate = TaskService ; } { TaskServicetaskText = etargetvalue; } { return <div> <p>TaskServicetaskText</p> <input = = /> </div> ; }
В данном примере значение внутри Service'a будет изменяться, однако ни в input, ни в p, новое значение появляться не будет. Данный код можно оптимизировать, отображая значение в input, но не изменяя его в p. Для этого добавим поле компонентного state и метод lifecycle - componentWillUpdate() и componentWillUnmount. И поменяем данные в input.props.value.
{ ; thisstate = TaskService taskText: TaskServicetaskText ; } { thisstatetaskText = TaskServicetaskText; } { TaskServicetaskText = thisstatetaskText; } { this; } { return <div> <p>TaskServicetaskText</p> <input = = /> </div> ; }
Из примера видно, каким мощным эффектом оптимизации без использования shouldComponentUpdate() обладают сервисы-синглтоны.
Второй способ изменения данных в сервисе влечет за собой ререндеринг компонента. Для его осуществления необходимо вызвать метод reRender(), который унаследован от Component
{ ; thisstate = TaskService ; } { this'taskText'; } { return <div> <p>TaskServicetaskText</p> <input = = /> </div> ; }
Теперь при изменении данных внутри input будет происходить ререндеринг компонента, в результате чего в p и input будут отображаться актуальные значения.
Методы класса Component
- _injectService() внедряет Service'ы в state компонента. Принимает два аргумента:
- Массив сервисов, которые будут внедрены в state компонента;
- enum InjectMerging с одним из значений: InjectMerging.BEFORE - внедрит сервисы перед значениями локального state, _InjectMerging.AFTER - после. Данный аргумент необязательный, по умолчанию используется InjectMerging.BEFORE
//InjectMerging.BEFOREthisstate = //your services here first: 'first' second: 'second'; ///InjectMerging.AFTERthisstate = first: 'first' second: 'second' //your services here;
Кроме того, можно обойтись и без метода _injectServices() при внедрении сервисов в state
{ superprops; thisstate = UserService first: 'first' second: 'second' RouteService ;}
- _bindMethods() - метод принимает строковые наименования методов компонента, к которым применится .bind(this). Кроме того, каждый аргумент может быть массивом: ['имя_метода','аргумент1','аргумент2'].
{ superprops; this; //======or======= this;}
- reRender() - метод необходим для изменения значения Service'a с последующим ререндеринго компонента. Метод устроен довольно непросто, так что разберем его подробно.
- reRender принимает один аргумент - экземпляр сервиса либо его поле this.serviceName и возвращает функцию...
this // return serviceProps => { ... }this // or this.reRender(UserService.serviceName) - return serviceProps => { ... }
- возвращаемая функция принимает один необязаетльный аргумент - массив строковых названий полей сервиса или строковое название одного поля сервиса, и возвращает объект методов.
this'login' //return { set: val => {...}, setDefault: () => {...} }this'login' 'followers' 'dateOfBirth' //return { set: values => {...}, setDefault: () => {...} }this // return { set: obj => {...}, setDefault: () => {...} }
- Метод set() вызывает сеттер Service'a и делает forceUpdate: * Если возвращен по одному переданному имени поля, то принимает одно значение - новое значение этого поля в Servic'e; * Если возвращен по массиву имен полей, то принимает массив новых значений этих полей по соответствию индексов; * Если возвращен по пустому значению, то принимает объект c ключами - именами полей Service'a, и значениями - новыми значениями этих полей.
this'login';this'login' 'dateOfBirth';this;
- Метод setDefault() вызывает Service.toDefault() в первых двух случаях и Service.defaultAll() в третьем, после чего вызывает метод forceUpdate.