Совместная работа записей ImmutableJS и потока
Примечание. Этот блог был написан 6 мая 2017 г. Из-за того, что информация зависит от времени, часть содержимого может быть устаревшей.
Если вы используете ImmutableJS и Flow, возможно, вы столкнулись с трудностями, заставляя записи работать. Последний крупный выпуск ImmutableJS, v3.8.1, очень мало поддерживает типы записей Flow. Версия 4.0.0 содержит гораздо лучшую поддержку, но, поскольку это все еще RC-2, вы можете не захотеть использовать последнюю версию для производственного кода. Кроме того, даже код RC-2 все еще имеет некоторые проблемы. Поскольку записи очень полезны, вот способ заставить их работать в вашем проекте.
Короче говоря, временным решением является создание настраиваемого типа неизменяемого потока на основе определений потока RC-2 и патча из одного из запросов на вытягивание.
Создание пользовательского неизменяемого типа потока
Сначала скопируйте файл immutable.js.flow из версии RC-2 в папку с типизированным потоком, предполагая, что вы используете инструмент типизированный поток для переноса типов потока в папку с версиями. Назовите файл как-то вроде immutable_v4.x.x.js, чтобы следовать соглашению о потоковом типе. Зарегистрируйте этот файл в системе контроля версий.
Теперь давайте внесем некоторые изменения в файл, чтобы сделать его совместимым как модуль.
Примечание: если вы хотите пропустить следующие шаги, вы можете скопировать / вставить эту суть. Я на всякий случай включил внизу лицензию. Вам все равно нужно будет обновить .flowconfig, как показано ниже.
Сначала оберните весь код в объявление модуля:
declare module 'immutable' { }
Затем удалите все экспорты внизу файла. Типы, объявленные в определении модуля, не нужно явно экспортировать, и они вызывают ошибки, если выполняются в определении с потоковым типом. Я не совсем уверен, почему файлы Flow в пакетах node_module обрабатываются иначе, чем определения Flow в библиотеке потокового типа. Если кто-нибудь знает, дайте мне знать.
Затем закомментируйте следующее в строке 232, поскольку оно вызывает ошибку потока и в настоящее время является просто заполнителем:
// Collection.Keyed = KeyedCollection
Последнее необходимое изменение - это применение патча к определению записи. Замените все определения типов записей (строки 1056–1116) следующим фрагментом:
declare function isRecord(maybeRecord: any): boolean %checks(maybeRecord instanceof RecordClass); declare class Record { static <Values>(spec: Values, name?: string): Class<RecordClass<Values>>; constructor<Values>(spec: Values, name?: string): Class<RecordClass<Values>>; static isRecord: typeof isRecord; static getDescriptiveName(record: RecordClass<*>): string; } declare class RecordClass<T: Object> { static (values?: $Shape<T> | Iterable<[string, any]>): RecordClass<T> & T; constructor(values?: $Shape<T> | Iterable<[string, any]>): RecordClass<T> & T; size: number; has(key: string): boolean; get<K: $Keys<T>>(key: K): /*T[K]*/any; equals(other: any): boolean; hashCode(): number; set<K: $Keys<T>>(key: K, value: /*T[K]*/any): this; update<K: $Keys<T>>(key: K, updater: (value: /*T[K]*/any) => /*T[K]*/any): this; merge(...collections: Array<$Shape<T> | Iterable<[string, any]>>): this; mergeDeep(...collections: Array<$Shape<T> | Iterable<[string, any]>>): this; mergeWith( merger: (oldVal: any, newVal: any, key: $Keys<T>) => any, ...collections: Array<$Shape<T> | Iterable<[string, any]>> ): this; mergeDeepWith( merger: (oldVal: any, newVal: any, key: any) => any, ...collections: Array<$Shape<T> | Iterable<[string, any]>> ): this; delete<K: $Keys<T>>(key: K): this; remove<K: $Keys<T>>(key: K): this; clear(): this; setIn(keyPath: Iterable<any>, value: any): this; updateIn(keyPath: Iterable<any>, updater: (value: any) => any): this; mergeIn(keyPath: Iterable<any>, ...collections: Array<any>): this; mergeDeepIn(keyPath: Iterable<any>, ...collections: Array<any>): this; deleteIn(keyPath: Iterable<any>): this; removeIn(keyPath: Iterable<any>): this; toSeq(): KeyedSeq<$Keys<T>, any>; toJS(): { [key: $Keys<T>]: mixed }; toJSON(): T; toObject(): T; withMutations(mutator: (mutable: this) => mixed): this; asMutable(): this; asImmutable(): this; @@iterator(): Iterator<[$Keys<T>, any]>; }
По сути, патч избавляется от промежуточного представления интерфейса записи, в результате чего остаются «класс» записи и «класс» записи. Семантика класса для Flow и то, как на самом деле работает javascript, не совсем совпадают, но это имеет смысл, когда вы начинаете его использовать.
Последнее необходимое изменение - это изменение .flowconfig для игнорирования файла Flow, поставляемого с ImmutableJS. Добавьте в свой конфиг следующую строку:
.*/node_modules/immutable/*
Использование новых записей потока
Теперь вы должны быть настроены для аннотирования записей. Вот практический пример того, как это сделать:
/* @flow */ // file: ExampleComponent.jsx import React, { Component } from 'react'; import { List, Record } from 'immutable'; const exampleRecord = Record(({ displayValue: '', id: '', }: { displayValue: string, id: string, })); // In this example, exampleRecordInstance is only used for type // annotation const exampleRecordInstance = exampleRecord(); type Props = {| listOfRecords: List<typeof exampleRecordInstance>, |}; class ExampleComponent extends Component<void, Props, void> { // {...} } export { exampleRecord }; export default ExampleComponent;
Главное помнить, что код обычно экспортирует «класс» записи, чтобы другие модули могли использовать его для создания записи. Но в данном случае для аннотаций используется тип typeof exampleRecordInstance
, что создает линию шаблона, но это самое чистое решение, которое я когда-либо видел.
Примечание. К сожалению, здесь есть скрытая ошибка. Flow выдаст ошибку, если вы воспользуетесь вызовом «нового» конструктора перед созданием экземпляра записи. Сообщение об ошибке: «Тип вызова конструктора несовместим с каким-либо элементом пересечения типа пересечения». Если в этом нет большого смысла, вы не одиноки. Тип пересечения позволяет динамически определять свойства класса, но у Flow есть ограничения на использование типов пересечений (я думаю, поправьте меня, если я ошибаюсь). Поскольку использование слова «новый» здесь не работает, и поскольку большинство линтеров жалуются на переменные, начинающиеся с заглавных букв, мы используем обычные переменные в верблюжьем регистре, чтобы удовлетворить Flow и линтер.
Если у вас есть родительский компонент, который отображает ExampleComponent
, он может выглядеть примерно так:
/* @flow */ import React, { Component } from 'react'; import { List } from 'immutable'; import ExampleComponent, { exampleRecord, } './ExampleComponent.jsx'; const UseExampleComponent = () => { const aNewInstance = exampleRecord({ id: 'jim', displayValue: 'Jim', }); const aList = List(); return ( <ExampleComponent listOfRecords={aList.push(aNewInstance)}/> ); } export default UseExampleComponent;
Если вам нужно аннотировать aList
- не специально в этом случае, но если у вас есть переменные состояния одного и того же типа, например, - вы можете использовать экзистенциальный тип для типа экземпляра записи, потому что Flow может определить, что он должен соответствовать определенному в Компонент ExampleComponent. Эта строка будет выглядеть так:
const aList: List<*> = List();
Или, если вы хотите быть явным, вы можете сделать что-то вроде этого:
const aList: List<typeof aNewInstance> = List();
Что хорошо в аннотировании записей, так это то, что теперь будет проверяться множество вещей, таких как доступ к свойствам с использованием точечной нотации и использование методов, которые принимают ключи в виде строк - что наиболее важно, сами строки будут проверены с разрешенными свойствами в записи. Итак, используя приведенные выше примеры, aList.get(0).ids
и aList.get(0).get('ids')
будут генерировать ошибки Flow - при условии, что в списке есть действительная запись.
Кроме того, использование Flow уменьшает путаницу при использовании неизменяемых структур данных по сравнению с собственными, поскольку Flow знает контекст.
Как только ImmutableJS v4.0.0 будет официально выпущен, и PR будет объединен с исправлением ошибок, первая часть этой статьи будет устаревшей, но она будет работать до тех пор, пока вы не будете готовы к обновлению.