1.0.9 • Published 6 years ago

istore-react v1.0.9

Weekly downloads
3
License
ISC
Repository
github
Last release
6 years ago

istore-react

Библиотека для связывания react компонентов с istore доменами.

Данная библиотека позволяет "замкнуть" домен и получить функцию с помощью которой можно сформировать обертку над компонентом. Обертка позволяет будет брать часть данных и/или действий из домена и прокидывать их в оборачиваемый компонент в виде пропертей. Так же такие обертки будут автоматически перерисовывать компонент при изменении используемых данных домена. Пример применения данной библиотеки в связке с библиотекой istore можно посмотреть по ссылке https://github.com/Curnull/todo-istore

Пример

import { domain, value } from 'istore';
import { lockDomain } from 'istore-react';
import { Input } from './components';

const createUserDomain = domain({ // описываем модель данных
  name: value(''),
  age: value(0),
  email: value(''),
});

const wrap = lockDomain('createUserDomain', createUserDomain); // замыкаем домен с уникальным именем 'createUserDomain' и получаем функцию для оборачивания компонентов

const NameInput = wrap // начинаем цепочку оборачивания
  .withProps((state, methods) => ({ // описываем какие пропсы буду передаваться оборачиваемому компоненту
    value: state.name, // в качестве значения берем имя из состояния домена
    onChange: methods.name.set, // в качестве метода изменения имени, метод из методов домена для изменения имени
  }))
  .component(Input); // замыкаем цепочку компонентом

const AgeInput = wrap // повторяем операцию для поля возраст
  .withProps((state, methods) => ({
    value: state.age,
    onChange: methods.age.set,
  }))
  .component(Input);

const EmailInput = wrap //  повторяем операцию для поля почтовый ящик
  .withProps((state, methods) => ({
    value: state.email,
    onChange: methods.email.set,
  }))
  .component(Input);

const CreateUserForm = () => ( // создаем компонент который помещает все обернутые инпуты на форму
  <div>
    <div>name: <NameInput /></div>
    <div>age: <AgeInput /></div>
    <div> email: <EmailInput /><div>
  </div>
);

export default wrap.rootComponent(CreateUserForm); // оборачиваем компонент CreateUserForm как "главный", что бы при его маунте и анмаунте на страницу, домен помещал и удалял свое состояние из/в хранилище.

API

lockDomain(prefixProvider, domainProvider, extra)

Функция для создания экземпляра класса WrapChain который будет связан с определенным доменом.

Параметры

  • prefixProvider required, string | (props: any) => string - строка либо функция возвращающая строку и принимающая проперти "главного" компонента. Возвращенная строка будет использоваться как префикс при помещении состояния домена в хранилище (domain.mount(prefix)). Префикс должен быть уникальным в рамках всего приложения.

  • domainProvider required, Domain | (props: any) => Domain - экземпляр домена, либо функция возвращающая экземпляр домена на основе пропертей главного компонента.

  • extra optional, Object - объект вида (все свойства опциональны):

    • onMount: (domain, props) => void - функция которая будет вызвана при помещении главного компонента на страницу, в нее будет передан экземпляр домена и проперти главного компонента. Может быть использована для первичной инициализации состояния домена.
    • onChangeProps: (domain, nextProps, prevProps) => void - функция которая будет вызываться каждый раз при изменении "внешних" (т.е. пропертей приходящих из родительского компонента, но не из оберток) пропертей. Первый аргумент функции это экземпляр домена, второй объект с новыми пропертями, третий объект со старыми пропертями. Может использоваться для обновления состояния домена при изменении пропертей главного компонента.

Возвращаемое значение Экземпляр класса WrapChain связанный с переданным доменом.

WrapChain

Класс позволяющий с помощью набора методов вызывающихся последовательно друг за другом расширить react компонент, предоставляя для него определенные проперти, функции жизненного цикла и другие расширения. Так-же связывает react компоненты с какой-либо доменной моделью. Сформированный компонент автоматически отслеживает изменения доменной модели и автоматически перерисовывается когда это нужно (реагирует только на изменения тех полей в состоянии домена, который он использует для расчета своих пропертей). Все методы класса генерируют и возвращают новый экземпляр WrapChain на основе текущей конфигурации, что позволяет делать частично завершенные цепочки, который потом можно продолжить или объеденить.

Свойства отсутсвуют

Методы

  • withProps(propsProvider: (state, methods, extra) => Object): WrapChain - метод для предоставления пропертей для компонента который будет обернут цепочкой. Принимает функцию, в которую будет передано состояние домена, методы домена, и объект с вспомогательными функциями. propsProvider должен возвращать объект сформированный из полей параметров state и/или methods. Функция propsProvider будет вызываться каждый раз при обновлении используемых в ней полей объекта state. Входной объект extra имеет вид
    • getProps: () => any - функция, возвращающая текущие проперти оборачиваемого компонента.
    • or: (...args: any) => boolean - функция для выполнения логической операции "или" (см. Маппинг пропертей из состояния и методов домена)
    • and: (...args: any) => boolean - функция для выполнения логической операции "и" (см. Маппинг пропертей из состояния и методов домена) Пример
import { domain, value } from 'istore';
import { lockDomain } from 'istore-react';

const someDomain = domain({ ... })
const wrap = lockDomain('someDomain', someDomain);

const wrapChain = wrap.withProps((state, methods, { getProps }) => ({
  prop1: state.someValue,
  prop2: state.someObj.someValue,
  onSomething: methods.someValue.set,
  onSomethingElse: () => methods.someObj.someValue.delete(getProps().someId),
}))
  • onChangeProps(callback: (domain: Domain, nextProps: any, prevProps: any, isInit: boolean) => void): WrapChain - функция которая принимает коллбэк который будет вызываться при инициализации оборачиваемого компонента и при изменении его пропертей (внешник, т.е. не предоставляемых цепочкой с помощью .withProps). Описание параметров коллбэка:
    • domain Domain - экземпляр домена
    • nextProps any - объект с новыми пропертями компонента (будет опредено всегда, в случае с инициализацией компонента тут будут его изначальные проперти)
    • prevProps any - объект со старыми пропертями компонента (будет опредено только при обновлении пропертей, в случае с инициализацией компонента тут будет пустой объект)
    • isInit boolean - флаг который равен true если этот вызов был спровацированн инициализацией компонента (componentWillMount), false если произошло обновление пропертей (componentWillReceiveProps)
import { domain, value } from 'istore';
import { lockDomain } from 'istore-react';

const someDomain = domain({ ... })
const wrap = lockDomain('someDomain', someDomain);

const wrapChain = wrap.onChangeProps((domain, nextProps, prevProps, isInit) => {
  if (isInit) {
    console.log('component initialized')
  } else if (nextProps.someProp !== prevProps.someProp) {
    console.log('some prop changed');
    domain.methods.someValue.set(nextProps.someProp);
  }
})
  • withHOC(hoc: (component: Component) => Component): WrapChain - метод для дополнительного оборачивания компонента каким-либо HOC'ом.
import { domain, value } from 'istore';
import { lockDomain } from 'istore-react';

const someDomain = domain({ ... })
const wrap = lockDomain('someDomain', someDomain);

const myHoc = (value) => (Component) => (props) => <Component {...props} someProps={value} />

const wrapChain = wrap.withHOC(myHoc('example'))
  • lockSubDomain(stateProvider, methodsProveider: ): WrapChain - метод для создания нового экземпляра WrapChain, при этом домен этого экземпляра будет сформирован из текущего домена. Принимает 2 параметра:
    • stateProvider required, (state: any) => any - метод получающий состояние текущего домена и возвращающий состояние которое будет использоваться в новой цепочки в качестве состояния домена
    • stateProvider required, (methods: any) => any - метод получающий методы текущего домена и возвращающий методы которые будет использоваться в новой цепочки в качестве методов домена.

Замечание Цепочке полученной в резльтате функции lockSubDomain необходимо так-же указать главный компонент с помощью метода .rootComponent

import { domain, value } from 'istore';
import { lockDomain } from 'istore-react';
import { Input } from './components';

const someDomain = domain({ 
  someObj: {
    someObj: {
      someObj: {
        name: value('Example'),
      }
    }
  }
})
const wrap = lockDomain('someDomain', someDomain);

const subWrap = wrap.lockSubDomain(s => s.someObj.someObj.someObj, m => m.someObj.someObj.someObj);

const SomeInput = subWrap.withProps((state, methods) => ({
  value: state.name,
  onChange: methods.name.set,
}).component(Input);

// в случае если бы мы не делали lockSubDomain то обертка выглядела бы вот так
const SomeInput = wrap.withProps((state, methods) => ({
  value: state.someObj.someObj.someObj.name,
  onChange: methods.someObj.someObj.someObj.name.set,
}).component(Input);
  • join(chain: WrapChain): WrapChain - метод для объединения двух цепочек в одну. Принимает на входе цепочку для мержа и возвращает новый экземпляр WrapChain который сомещает конфигурацию из обоих цепочек.
import { domain, value } from 'istore';
import { lockDomain } from 'istore-react';
import { Input } from './components';

const someDomain = domain({ 
  name: value(),
})
const wrap = lockDomain('someDomain', someDomain);

const commonChain = wrap.withProps((state) => ({
  disabled: state.disabled,
}))

const NameInput = subWrap.join(commonChain).withProps((state, methods) => ({
  value: state.name,
  onChange: methods.name.set,
}).component(Input); // в итоге в компоннт Input попадут 3 проперти: value, onChange, disabled
  • сomponent(component: Component): Component - метод для для замыкания цепочки и получения обернутого компонента. Принимает на входе react компонент для обертки.

  • rootComponent(component: Component): Component - метод для оборачивания главного компонента домена, т.е. компонента при помещении которого на страницу домен должен поместить свое состояние в хранилищие, и удалить его из хранилища если компонент был удален со страницы.

wrap

Экземпляр класса WrapChain без привязки к какому либо домену. Можно использовать для добавления в компоннты статических пропертей

import { domain, value } from 'istore';
import { wrap } from 'istore-react';
import { Input } from './components';

const SomeReadonlyInput = wrap.withProps((state, methods) => ({
  value: 'static text',
  disabled: true,
}).component(Input);

Детали работы

Маппинг пропертей из состояния и методов домена

Ключевой функциональностью данной библиотеки является маппинг пропертей для компонентов на основе состояния и методов домена. При этом данный мапинг будет происходить при помещении компонента на страницу (для получения начальных значений пропертей) и затем при любых изменениях в полях состояния домена, которые используется в функции мапинга. Как это работает: 1. При помещении обернутого компонента на форму происходит вызов всех маппероов (.withProps может быть сколько угодно в рамках одной цепочки) 2. При первом вызове маппера происходит подписка на все экземпляры юнитов, состояния которых были использованы при формаировании пропертей. 3. При изменения стейта любого из юнитов, состояния которых использовались для формарования пропертей, происходит повторный вызов всех мапперов в которых использовалось его состояние (таких мапперов может быть нескольких в рамках одной цепочки). В итге формируется новый объект с пропертями. 4. Все вновь полученные проперти проверяются на предмет того, изменились ли они по сравнению с прошлым разом, и есть хотя бы она пропертя изменилась происходит перерисовка обернутого компонента уже с новыми пропертями.

Нюансы

Подписка

Подписка на юниты происходит только после первого вызова маппера, поэтому если обращения к state не произошло, то и подписки к юниту не будет, пример:

import { domainm value } from 'istore';
import { lockDomain } from 'istore-react'

const myDomain = domain({
    isActive: value(true),
    showName: value(false),
    name: value('John'),
})

const wrap = lockDomain('myDomain', myDomain);

const WrappedComponent = wrap
    .withProps((state) => ({
        name: state.showName && state.isActive ? state.name : ''
    }))
    .component(SomeComponent);

в примере выше если на момент первого вызова маппера в state.showName будет false, то WrappedComponent будет реагировать только на изменение стейта юнита который хранит значение state.isActive, но не будет реагировать на изменения состояния юнита state.name и state.isActive, что не верно. Что произошла подписка на все юниты нужно переписать маппер следующим образом:

const WrappedComponent = wrap
    .withProps(({ showName, isActive, name}) => ({
        name: showName && isActive ? name : ''
    }))
    .component(SomeComponent);

Как мы видим теперь происходит безусловное обращение к состояниям трех юнитов (тут имеется ввиду деструктуризация входного объкта state, которая фактически является обращением к свойству объекта и сохранением его в локальную переменную), что приведет к подписке ни все 3 юнита.

Если способ с деструктуризацией вам не походит, то есть еще 2 способа: 1. Достать из объекта state все нужные для вычисления свойства в локальные перменные, и только затем произвести вычисления. Фактически это то же самое что и деструктуризация, только более наглядно. 2. Использовать вспомогательные фукнции для логических операций or и and, пример:

const WrappedComponent = wrap
    .withProps((state, methods, { and }) => ({
        name: and(state.showName, state.isActive) ? state.name : ''
    }))
    .component(SomeComponent);

Передача функций в .withProps

Для удобства^ поля функции с одинаковыми названиями не перетирают друг друга, а объединяются в одну функцию, пример

import { wrap } from 'istore-react';

const WrappedComponent = wrap
    .withProps(() => ({
        onChange: () => console.log('on change')
    }))
    .withProps(() => ({
        onChange: () => alert('on change')
    }))
    .component(SomeComponent);

В данном примере мы имеем функцию onChange в двух мапперах переданных в .withProps, что вводит в ступор. С помощью внутреннего миханизма обединения функций с одинковыми названиями когда компонент SomeComponent вызовет функцию onChange из своих props, будут вызваны обе функции из .withProps, что даст строку в консоли и браузерный алерт. В то же время данный вариант приведет к ошибке:

import { wrap } from 'istore-react';
const WrappedComponent = wrap
    .withProps(() => ({
        onChange: () => console.log('on change')
    }))
    .withProps(() => ({
        onChange: 15
    }))
    .component(SomeComponent);

В примере выше мы пытаемся обернуть компонент используя два маппера, один из которыз возвращает функцию по ключу onChange а второй возвращает значение по тому же ключу. В таких случая в библиотеке будет выброшено исключени с описание проблемы. Более того, если компонент из мапперов получит несколько функций под одним ключем и хотя бы одна из них вернет значение отличное от undefined после ее вызова то будет выбрашено исключение. Таким образом под одним ключем могут объединяться только функции возвращающие undefined. Данная функциональность позволяет вешать несколько обработчиков на одно и то же событие (например onChange, onBlur, onFocus).

1.0.9

6 years ago

1.0.8

6 years ago

1.0.7

6 years ago

1.0.6

6 years ago

1.0.5

6 years ago

1.0.4

6 years ago

1.0.3

6 years ago

1.0.2

6 years ago

1.0.1

6 years ago

1.0.0

6 years ago