Я хочу поделиться опытом разработки плагинов под современные js-фреймворки для своей ванильной библиотеки маскирования imaskjs.
Я опишу некоторые нестандартные моменты и свои эмоции, возникшие в ходе разработки. Я не претендую на полноценный гайд с нуля, к тому же разработка велась несколько месяцев назад, что-то уже могло поменяться.
Сразу стоит отметить, что библиотека не имеет визуальной составляющей, т.е. вопросы верстки и стилей рассматриваться не будут.
Введение
Суть разработки плагинов в том, чтобы сделать надежный механизм синхронизации состояний данных и компонента, т.е. подключение, отключение функционала и изменение настроек.
У многих библиотек, которые я встречал, встречаются сложности с сочетанием реактивного подхода и обновлением данных. Реактивный подход на практике означает, что данные могут поменяться в любой момент и компонент должен среагировать на изменения. Чтобы облегчить синхронизацию, удобно иметь возможность обновлять данные скопом, а логика по поиску измененных значений и их обновлению должна быть в ядре библиотеки.
Мой пример:
mask.update(options)
А там внутри уже библиотека сама разберется что и как обновлять. Естественно, можно передавать подмножество параметров. Также в настройках желательно не иметь вложенных объектов, чтобы избежать глубокого сравнения.
И пожалуй, самое главное, что надо учитывать при разработке плагинов — это возможность их использования с существующими компонентами. Например в моем случае нужно было не забыть про всякие datepicker, number-spinner, StyledComponents и пр. При этом для простых случаев также желательно иметь готовый к использованию компонент маскированного ввода.
Также при разработке плагинов для указания зависимостей от фреймворков в npm-пакете используем peerDependencies вместо dependencies/devDependencies.
React
Первый плагин react-imask был для React. С ним все прошло довольно гладко: у React хорошая документация и много живых примеров.
Тем не менее, есть несколько моментов, о которых следует помнить.
В React принято передавать параметры в виде отдельных свойств. Например в моем случае с маской:
<IMaskInput
// свойства маски
mask=”00-00”
value="123"
unmask={true}
onAccept={(value, mask) => console.log(value)}
// свойства обычного input
placeholder='Enter code here'
/>
В этом случае происходит смешение свойств маски со свойствами вложенного HTML-элемента. Задача состоит в том, чтобы выцепить свойства маски, а остальные передать дальше. Это неплохо решается в совокупности с объявлением propTypes и итерации по его ключам. Важно не забыть про некоторые особенные свойства, например в моем случае value, которые должны быть обработаны специальным образом. Отдельный случай представляют колбеки, хотя в React они передаются также как и обычные свойства. Я обернул внутренние колбеки библиотеки в методы компонента, и принудительно подключил при инициализации. Таким образом они будут вызываться всегда, а там уже если что-то передано в props, то будет вызвано дальше. Это позволяет избежать явного управления подключения/отключения колбеков.
Мы хотим чтобы наши компоненты можно было расширять и использовать совместно с другими. Для расширения компонентов в React используется идея High Order Components (HOC), что на практике реализуется как обертка-декоратор. Нет времени объяснять что это, посмотрите гайд и пример.
Желательно не забыть объявить Component.displayName, чтобы ваши компоненты были узнаваемы при отладке.
Vue
Следующим был плагин на Angular, но это долгая история, сначала про vue-imask. До написания плагина я практически не был знаком с Vue, и учился в процессе работы. Несмотря на то, что Vue показался хипстерским фреймворком, сообщество оставило очень приятные впечатления — люди открыты и небезразличны.
Итак, в Vue есть два способа реализации плагина: через директиву и через компонент. Компоненты — предпочитаемый способ, я реализовал оба — выбирайте сами.
Начнем с более простой директивы. Мне показалось, директива — это наиболее подходящий подход: есть все нужные методы bind, unbind, update. Что же еще надо? А нужен state, которого у директивы нет. Куда же класть свои данные? — прямо в html-element, а колбеки реализуются через эмуляцию DOM-событий руками. И вы еще спрашиваете причём тут хипстеры.
Ну да ладно, директива просто предназначена не для тупых плагинов, а для полностью тупых.
<template>
<input
:value="value"
v-imask="mask"
@accept="onAccept"
@complete="onComplete">
</template>
<script>
import {IMaskDirective} from 'vue-imask';
export default {
data () {
return {
value: '',
mask: {
mask: '{8}000000',
lazy: false
},
onAccept (e) {
const maskRef = e.detail;
console.log('accept', maskRef.value);
},
onComplete (e) {
const maskRef = e.detail;
console.log('complete', maskRef.unmaskedValue);
}
}
},
directives: {
imask: IMaskDirective
}
}
</script>
Давайте попробуем через компонент.
Здесь, как и в React, параметры принято передавать через свойства. Поэтому чтобы отследить изменения, подписываемся на изменения всех $props, выковыриваем наши и обновляемся.
Важный момент, который надо учитывать — это модели Vue, которые реагируют на событие input. И тут такая история. Моя библиотека живет себе тихо-мирно и не глушит стандартные HTML-события, а добавляет пару своих — accept и complete, кому что надо сам выбирает. И получается, что модель обновляется на событие input, когда значение еще не было обработано маской. Но это неверно, ожидается такое поведение: маска подключена — реагируем на accept, маска отключена — на input. Решил вопрос ручным управлением событиями, что не очень удобно. Кто знает способ получше?
Поскольку в результате получился полноценный компонент с HTML-input под капотом, возникает вопрос: а как же его расширять или использовать параллельно с другими компонентами? — как-то так.
<template>
<imask-input
v-model="numberModel"
:mask="Number"
radix="."
:unmask="true"
@accept="onAccept" // first argument will be `value` or `unmaskedValue` depending on prop above
// ...and more mask props in a guide
// other input props
placeholder='Enter number here'
/>
</template>
<script>
import {IMaskComponent} from 'vue-imask';
export default {
data () {
return {
numberModel: '',
onAccept (value) {
console.log(value);
}
}
},
components: {
'imask-input': IMaskComponent
}
}
</script>
Angular
С Angular было больно, хотя с ним было больше всего опыта и самые лучшие ожидания.
Многие критикуют Angular за отсутствие толковой документации, и видимо небезосновательно. У ангуляровцев грандиозные планы и много пиара, но у меня сложилось чувство, что они позиционируют свой фреймворк исключительно для пользователей, но не для разработчиков. Для использования в приложениях средней сложности документация вполне сносная, но на момент разработки плагина не было практически никакой официальной информации о том, как разрабатывать плагины/библиотеки для Angular. У Angular-cli есть небольшая заметка, которую еще найти надо, а в остальном — догадывайтесь сами. При этом особенностей сборки очень много, чего только стоит один AOT. А Angular-cli видимо еще не скоро поможет. Но решение есть.
Итак, нам нужна директива и модуль, которые можно использовать с Angular>=4 с JIT и AOT компиляцией. Я нашел несколько шаблонов проектов (1, 2, 3, 4, 5, 6), примеров библиотек (1, 2, 3, 4, 5), статей (1, 2, 3, 4, 5), но честно говоря, мало помогло. Мне для плагина, как и во всей остальной работе, нужен понятный, минимально рабочий и максимально простой вариант.
В общем виде процесс сборки выглядит следующим образом:
На входе имеем исходный код библиотеки на typescript, а на выходе получаем различные javascript-сборки. Минимальная сборка обычно включает umd версию, а также esm5 версию (модули es6, но код es5). Самый полный комплект сборки, который я видел, называется Angular Package Format и включает много других дополнительных вариантов скомпилированного кода. В большинстве случаев достаточно umd-версии, ей и ограничимся.
Помимо скомпилированного кода в придачу также публикуются тайпинги (.d.ts файлы) и пачка метаданных. В моем случае метаданные ограничиваются только файлами .metadata.json, т.к. не используются стили и шаблоны. Иначе также залетают фабрики ngfactory.ts и файлы .ngsummary.json.
Вся эта макулатура обязательна в основном ради фичи Angular под названием AOT. Если коротко, AOT в Angular — дополнительная оптимизация во время компиляции с примесью json-магии, пакующая шаблоны и стили. Для разработчиков плагинов все было бы замечательно, если бы AOT остался опциональным вариантом. Но фактически AOT принудили использовать, и множество ранее написанных библиотек оказались несовместимыми. В результате, в репозитории Angular десятки, если не сотни багов на эту тему, закрытых без объяснения, много недовольных, но по-прежнему нет внятной документации как же нужно делать. Ситуация объясняется в красивой сказке о том, какой теперь Angular крутой и быстрый. Стало ли реально быстрее? — да, но доверие потеряно. Что ж, давайте делать компиляцию ради компиляции.
Angular использует свой компилятор Angular Compiler (ngc), который использует компилятор typescript (tsc). Angular Compiler используется для упрощения генерации макулатуры, и с документацией у него совсем все плохо. Покажу основные моменты при настройке сборки:
{
...
"main": "dist/angular-imask.umd.js",
"module": "dist/index.js", // esm5
"typings": "dist/index.d.ts", // обяз. поле, иначе AOT умрет
...
}
{
...
"target": "es5", // версия для кода
"module": "es2015", // версия для импортов
"moduleResolution": "node", // мы используем npm-зависимости
"experimentalDecorators": true, // декораторы по-прежнему экспериментальные
"stripInternal": true, // если используете @internal
"declaration": true, // генерировать описания типов .d.ts
"emitDecoratorMetadata": true, // какая-то магия чтобы сгенерировать метаданные
// еще немного непонятных букв для укрощения беса, чтобы оставил в покое ваши peer-зависимости
"baseUrl": ".",
"paths": {
"@angular/*": ["node_modules/@angular/*"],
"rxjs/*": ["node_modules/rxjs/*"]
},
// дальше тайная магия
"angularCompilerOptions": {
"skipTemplateCodegen": true, // ставим true, если не используем шаблоны/стили, выкинет много лишнего
"skipMetadataEmit": false, // мне нужны метаданные, напомню ему на всякий случай, бывает забывал
"strictMetadataEmit" : true, // скажи мне сразу про ошибки
"annotationsAs": "decorators", // особенное тайное заклинание для укрощения Angular 5, который иначе просто вырежет ваши декораторы. Оптимизация, сэр. По хорошему надо использовать только для JIT версии, для aot не нужно. После включения этого флага tsc для каждого файла закинет свои хелперы. Почему их нельзя было положить в одно место? Зачем подсовывать для каждого файла? Адепты предлагают либо использовать флаг noEmitHelpers, и тогда будьте добры руками их подключить, либо используйте флаг importHelpers и получаете tslib в рантайме. Выбирайте сами как будете страдать.
...
}
С ngc есть еще одна проблема. На примере кода:
export class A {
myVar = 1;
}
tsc корректно скомпилирует и добавит присвоение переменной в конструктор, но ngc просто вырежет присвоение, и неожиданно имеем undefined в рантайме. Поэтому пришлось всю инициализацию переносить в конструктор. Пожалуйста, поделитесь, если кто-то знает как бороть.
После преодоления компиляции ts > js можно колбасить js как хотите. Я ограничился umd-сборкой.
Если вдруг все развалилось, есть еще много бесполезных советов:
- использовать export {… явное перечисление} from … вместо export * from ...
- вместо import IMask from 'imask'; использовать import * as IMask from 'imask'; Не делайте так. При использовании в Аngular-cli приложении вас ждет сюрприз в рантайме.
- удалять вложенный node_modules
- вручную испортировать полифилы
- использовать --preserve-symlinks для сборки
И не пытайтесь заставить работать плагин, скомпилированный с Angular 5, на Angular 4.
Итого для Angular получили рабочую директиву на выходе, про которую писать особо нечего. Главное — побороть сборку.
Вообще всех этих ребят из Angular-тусовки отличает любовь к оберткам вокруг своей тайной магии, которая играет злую шутку в самые неподходящие моменты. Любителям магической Angular-капусты подойдет ng-packagr — очередная обертка над оберткой над оберткой, чтобы вы никогда в жизни не пытались больше понять суть происходящего, но таки стали JSON-экспертом. Из плюсов — выдает модный Angular Package Format, из минусов — инлайнит зависимости, не нашел как отключить, настроек практически нет.
Управление пакетами
Плагинов стало много, и возник вопрос с обновлением версий. По хорошему, при обновлении core-библиотеки, должны обновляться и версии плагинов, чтобы вы узнали об обновлении. Но при обновлении framework-плагина имеет смысл обновлять только его версию, а саму библиотеку и другие плагины не трогать. На практике я не встречал таких схем, либо они выполняются вручную и требуют к себе слишком много внимания. Я остановился на варианте “одна версия на все”, а плагины, поскольку они уже достаточно стабильны, обновляться будут редко.
Первое что я сделал — стал использовать lerna, и второе — вынес общие зависимости в верхний пакет. Это также решило проблему обновления зависимостей для разных плагинах, т.к. теперь они в одном месте.
В заключении
Я потратил много времени на разработку библиотеки, плагинов, документации, статей и пр. И что же? Меня поблагодарили несколько человек, один сделал пожертвование (ему вообще респект) и еще пара ребят поддержали кодом и советом. За это им большое спасибо. Благодаря вам разработка продолжалась, и я чувствовал, что делаю что-то полезное. В целом Open-Source принес мне интересный опыт и стало понятно, что от него ждать на практике. Но на этом наверно все.
Комментарии (38)
redyuf
01.05.2018 20:09А почему решили с нуля написать, а не взять например text-mask? С нуля пилить такое под кучу фреймворков и платформ — неблагодарное занятие. Вроде кажется легко, но есть множество подводных камней, как например совместимость с мобилками, из-за которых что-нибудь, да сработает не так.
Почему такое грустное заключение, какие ожидания у вас не оправдались? Вы хорошую работу проделали, хотя бы ради собственного опыта.burfee Автор
01.05.2018 22:24По долгу службы я в свое время перебрал множество вариантов масок. У нас были сложные, динамические маски в перемешку с фиксированными и местами опциональными символами. Я думаю любую библиотеку можно взять и допилить до нужной кондиции, мы вначале так делали с jquery-inputmask, потом с text-mask. text-mask пожалуй действительно был самый лучший вариант для допиливания. Не помню уже точно почему не подошел. Кажется нам нужна была возможность считать фиксированные символы частью размаскированного значения. Также покопавшись в кишках этих библиотек, я понял, что можно сделать намного лучше.
Сейчас imaskjs покрывает полностью все наши нужды, в т.ч. числовая маска и маска для дат из коробки. Маску просто расширять, хотя наверно я не сильно старался описывать это в гайде. Пожалуй уникальность маски в том, что я использую только событие input для обработки ввода, без хаков с keydown, paste и прочим. Это сделало ее достаточно стабильной для большинства задач и вместе с тем кому надо без особых проблем дохачит. Но да, на абсолютную универсальность не претендую.
Основной сложностью было грамотно сделать ванильную версию, плагины это совсем небольшая обвязка в сумме. Да и сообщество тут реально помогло.
Спасибо за вопрос про ожидания, думаю отдельно может быть напишу. Но если коротко, то дело в том, что фактически любые ожидания не оправдываются. Помимо опыта я получил очень много предложений о работе из самых разных, в т.ч. весьма крупных, компаний. Но мне оно как-то мимо. Но пожалуй самое главное что я получил — это опыт не просто разработки, а доведение продукта с нуля до готовности, и затем представление общественности. Поэтому хотя бы ради этого я всем советую попробовать open-source. Но тут есть много нюансов. Я свой опыт получил, теперь надо взять этот опыт, и идти другие задачи решать. Не вижу смысла повторяться.
AMorgun
01.05.2018 21:41Большинство библиотек для маскирования нужно жечь огнем. Не работают в мобилке, плохо работают в selenium, у юзеров вылязят баги в разных местах. Особенно маски для телефонов. Боль
burfee Автор
01.05.2018 21:48Судя по моему опыту, это не проблема библиотек, а проблема стандартов. Точнее их отсутствие, либо реализация как в голову пришло.
Я честно говоря практически не тестил на мобилах, но вроде более менее работает. На самом деле ситуация сейчас такова, что идеально работающей маски просто нет. Мой вариант — просто очередной со своими тараканами. Я старался делать упор на удобство использования и расширяемость. В костылестроении для мобил не упражнялся, этим и так заняты все вокруг заняты.apapacy
01.05.2018 21:58Да действительно у maskedinput была и осталась большая проблема с мобильными Андроидами (телефонами на планшетах как ни странно нормально все было). Библиотека эта 2015 года и не развивается. Аналогичная проблема была до недавнего времени у jquery mask. Но в последних версиях пофиксили работает нормально. Та насколько я понял что-то с фокусом происходит при активации виртуальной клавиатуры. Проблема конечно стандартов но с учетом распространенности андроидов решать ее приходится библиотекам. Сам как-то был просто удивлен реализацией андроид. Там например событие touchend не отрабатывает как предполагается. stackoverflow.com/questions/19088117/touchend-not-firing-after-touchmove
Так это же практически базовый функционал.
MikeKosulin
02.05.2018 01:30Ну помимо всего прочего стоит наверное заменять первую 8ку на 7ку, особенно это актуально для пейста.
А на мобайле вроде бы прилично работает)
А для ограничений по диапазонам дат, все же подставлять максимальную, если пытаются ввести другую. Но это только с подсказкой:)
IvanNochnoy
02.05.2018 11:40Я пару лет назад выбирал библиотеку масок для Angular 2. Перепробовав 5 вариантов, написал свою, так как существующие не удовлетворяли по тем или иным параметрам — либо глючные, либо бестолковые. Выбор большой, а выбрать нечего. Только время зря потратил.
lega
02.05.2018 00:29Как я и преполагал — авторы Ангуляра сделали болото, к тому же не «едят» то, что сами готовят.
AxisPod
02.05.2018 06:46Angular по сути написан для энтерпрайз бэкендеров, стиль разработки очень похож.
React для JS программеров.
Вот и вся разница.lega
02.05.2018 14:18+1Если стиль похож, то это не значит что он для них написан. В первую очередь он для фронтендеров (что очевидно), или посмотрите кого они приглашают на конференции.
Angular по сути написан для энтерпрайз
Что по вашему «энтерпрайз» и чем он не отличается от не «энтерпрайз»?IvanNochnoy
02.05.2018 15:37Можно я отвечу? Если бы он был для фронта, то у них на первой странице не было бы написано: One framework. Mobile & desktop. Все эти Реакты, Ангуляры и Вью позиционируются как фулл-стек.
Когда я впервые увидел Angular, я подумал: ну как же похож наяйцоWPF! Ну в самом деле: DI есть, привязка данных есть, роутинг есть, MVVM есть (да, я знаю, что они говорят о MVW, но на практике W === VM), директивы это тоже самое, что behaivors, pipes === конверторы значений, HTML + CSS === XAML. Даже ReactiveX используется и там и там. Так что все очень похоже. Поэтому Angular более популярен у .NET и Java программеров, так как при его использовании у них не происходит сдвига парадигмы.
А Энтерпрайз это варповый корабль такой.justboris
02.05.2018 16:49+1Если бы он был для фронта, то у них на первой странице не было бы написано: One framework. Mobile & desktop.
Сейчас бы фреймворк по маркетинговым слоганам оценивать.
AxisPod
03.05.2018 04:39Но только я почему-то гораздо чаще вижу в требованиях Angular для фуллстек разрабов.
panda-madness
02.05.2018 08:21Важный момент, который надо учитывать — это модели Vue, которые реагируют на событие input. И тут такая история. Моя библиотека живет себе тихо-мирно и не глушит стандартные HTML-события, а добавляет пару своих — accept и complete, кому что надо сам выбирает. И получается, что модель обновляется на событие input, когда значение еще не было обработано маской. Но это неверно, ожидается такое поведение: маска подключена — реагируем на accept, маска отключена — на input. Решил вопрос ручным управлением событиями, что не очень удобно. Кто знает способ получше?
Событие, по которому обновляется модель компоненты можно кастомить.burfee Автор
02.05.2018 08:26да, я вначале так и сделал через model. Но как в таком случае динамически менять event?
panda-madness
02.05.2018 08:32Зачем регистрировать два разных ивента? Почему нельзя через один ивент передавать значение поля, не важно с маской оно или нет? Условно если компонент сверху переключает маску он знает, какое именно значение он получил.
burfee Автор
02.05.2018 12:48Событие accept заложено внутри функционала ванильной библиотеки. Думаю понятно зачем так сделано. При этом библиотека не переопределяет и не глушит стандартное событие input. Получается есть 2 события обновления. Но в ванильной версии понятно что имеет смысл только accept, как вы и написали.
А вот в плагине все было бы просто, если бы маску нельзя было отключать. Поэтому внутри плагина приходится иметь дело с двумя событиями: input при отключенной маске и accept при подключенной.panda-madness
02.05.2018 14:32Что-то вроде такого не подходит?
if(this.maskRef) { this.maskRef.on('accept', this.$emit('input', this.maskRef.value)) } else { this.inputRef.on('input', this.$emit('input', this.inputRef.value)) }
Чтобы обновлять динамически можно в watch смотреть за пропом mask и вешать/снимать ивенты когда в нем что-то меняется.
alex6636
02.05.2018 09:23к тому же разработка велась несколько месяцев назад, что-то уже могло поменяться
И это печально. Какой вообще смысл учить и использовать технологию, когда через несколько месяцев эти знания и наработки могут превратиться в пыль?
burfee Автор
02.05.2018 12:41Это вряд ли, скорее всего технология останется. Но проблемы, которые были актуальны, могут решиться.
Kuorell
03.05.2018 21:37Тут не раз писали, что для vue это не идеоматично
Примерно обернул imask как это делают с vue так codesandbox.io/s/jlq5wr73
(очень грубая наметка, как обернуть imask под vue, но она дает нам: возможность привязываться к кастомным инпутам, простую работу с v-model, чистые шаблоны, точно работающую со всем остальным во vue реактивность vue, минимум бойлерплейта)
Т.к. imask по сути изменяет представление данных нет смысла лезть в шаблоны (скорее всего это желание появилось т.к. в Реакте у вас скорее шаблоны как сахар в JS, а во vue шаблоны как… ммм, шаблоны тупые и простые)
В идеале можно избавиться и от хранения состояния, оставив чисто вычисления в маску и назад.burfee Автор
03.05.2018 21:47Спасибо, интересный вариант. Но есть несколько проблем из-за того, что фактически UI-часть маски была выброшена. А там были такие важные штуки как, например, позиционирование курсора на клик/фокус, возможность подписки на события accept/complete и др. Это все придется делать руками. Хотя я считаю это не совсем правильным, я думаю как пример можно добавить в readme плагина, если кому-то поможет.
bjornd
Создание сторонних компонентов для современных JS-фреймворков (inb4 React — не фреймворк):
React — OK
Vue — на дворе 2018, а у них миксины, пользовательские стратегии слияния опций, события не композируются
Angular — ?\_(?)_/?
Focushift
А что не так с Vue?
bjornd
Вроде и в статье и в комменте написано:
— как нам переиспользовать функциональность?
— так наследование же
— наследование не походит, там только один предок
— тогда миксины
— а что если два миксина определят одно и то же свойство
— да пофиг, потом придумаем что-нибудь
Focushift
Так понятней, после прочтения самой статьи не дошло.
JSmitty
Vue.js несколько другой, но отдельные вещи можно заимствовать из реакта. Так, есть хорошо развитая и документированная концепция слотов, которые по большей части замещают как HOC, так и render props. Код, который автор предлагает как образец использования — не идеоматичный для vue.js. Так что возможно стоило бы потратить чуть побольше времени на гуглинг и чтение официальной документации.
Миксины во Vue — да, боль и страдания. Часть задач по структурированию приложений (но не плагинов) решает vuex, еще часть — строгое отделение бизнес логики от логики компонентов. Многие про слоты забывают (т.к. в том же реакте так не принято, ну и сложно).
bjornd
Render props и HOC — более мощные и сложные концепции, слоты vue в React реализуются с помощью this.props.children (одиночный слот) и передачи компонентов в качестве свойств (именованные слоты во Vue)
JSmitty
На Vue.js можно делать HOC, хоть это и проблематично. Насчет render props — насколько я понимаю, полный аналог по решаемым задачам — scoped slots.
Paul_Smith
Для React не совсем ок — итерация по propTypes — не лучшая из идей, на моей практике не редко (но не всегда) используется сборка с вырезанием propTypes из production кода, что может привести к весьма неуловимым багам
burfee Автор
Я нашел открытый вопрос в репе prop-types и обсуждение в create-react-app. Насколько я понимаю сейчас это не коснется моей библиотеки, т.к. в npm пакете лежит только umd версия, которую по идее никто не будет кормить бабелю.
Но согласен, что идея использовать propTypes наверно не самая лучшая. Но как не хочется копипастить…
bjornd
Деструктуризация же:
burfee Автор
Та не, не вариант. Мне надо разбирать props на свойства маски для передачи дальше и остальные свойства. По отдельности каждое выцеплять неудобно. А в propTypes сейчас как раз и лежат все нужные ключи.
bjornd
Так просто положить их в другую переменную, использовать эту переменную для PropTypes и для фильтрации props.
burfee Автор
Вариант!
TheShock