Я только начал использовать сборник рассказов, и пока мне он нравится. Одна вещь, с которой я боролся, - это как работать с чистыми компонентами без состояния, которые требуют, чтобы состояние содержалось в родительском элементе. Например, у меня есть флажок, который принимает опору checked
. Щелчок по флажку не переключает его состояние, а скорее запускает onChange
и ждет, чтобы получить обратно обновленную опору checked
. Похоже, что нет какой-либо документации по передовым методам работы с этими видами компонентов, а предложения в таких вопросах, как https://github.com/storybooks/storybook/issues/197 , касались создания компонента-оболочки или добавить еще одно дополнение. Я бы предпочел не делать обертку для компонентов, если я могу ей помочь, потому что я хотел бы, чтобы мои истории были как можно более простыми.
Одна идея, с которой мне пришлось справиться, - это связать надстройку actions
с knobs
, позволяющую программно переключать ручки с помощью действий. Как я уже сказал, я новичок в сборнике рассказов, поэтому я понятия не имею, возможно ли это вообще, но я хотел, по крайней мере, высказать свое мнение.
Это камень преткновения для меня при реализации историй, и я полагаю, что это может быть и для других. Если создание компонента-оболочки действительно лучше всего, возможно, можно было бы добавить некоторую документацию, чтобы прояснить это, и как это сделать?
Какая-то похожая идея была представлена в этом PR # 3701, что было спорно (и не было объединено).
Мы можем снова начать обсуждение этого вопроса и выслушать предложения API =).
Ах, спасибо, я не видел этого пиара. Спасибо @aherriot за то, что собрал все вместе, кажется, у нас одни и те же мысли.
Прежде чем погрузиться в API, кажется, что необходимо обсудить и согласовать фундаментальную концепцию. Один из комментариев в PR был от @Hypnosphi :
Мне не нравится тот факт, что он вводит несколько источников истины (обратные вызовы компонентов и ручки пользовательского интерфейса)
Мне кажется, что идея состоит не в том, чтобы вводить разные источники истины, а, скорее, в том, чтобы позволить поддерживать единственный источник истины для состояния компонента - ручки. Все другие предложенные мной подходы (компоненты оболочки, надстройки состояния, перекомпоновка) на самом деле представляют еще один источник истины. В моем примере с флажком я не смогу иметь ручку для checked
, а также позволить компоненту оболочки предоставлять опору checked
. Я вижу панель управления ручками как родительский компонент, за исключением того, что в настоящее время он не может получать какие-либо обратные вызовы от компонента, что является своего рода односторонним, а не тем, как обычно создаются реагирующие приложения.
Позволяя программно управлять ручками, история может быть изолирована только от демонстрируемого компонента, оставляя фактический механизм управления состоянием непрозрачным, как и должно быть для простого презентационного компонента, такого как флажок. Самому флажку не важно, как он получает свои реквизиты, он может подключаться к redux, его родительский элемент может использовать setState
, может быть, он использует withState
из перекомпоновки, или, может быть, реквизиты контролируются ручки сборника рассказов.
В любом случае, как я уже сказал, я новичок в этом, поэтому я делюсь только своими интуитивными мыслями. Если я за пределами базы и есть общепринятые «лучшие практики» для работы с такими чистыми компонентами без сохранения состояния, может быть, кто-нибудь подскажет мне хороший пример, которому я могу следовать?
Привет :)
Я просто хочу добавить, что наткнулся на это, поскольку мои компоненты получают, должны ли они отображать свои мобильные макеты (или нет) из реквизита, и моей целью было привязать изменение области просмотра к моей ручке, и это было бы действительно хорошо;)
Просто хотел добавить здесь свой вариант использования, и, поскольку @IanVS, даже обратный вызов был бы хорош (поэтому, если я переключу свои реквизиты isMobile, я мог бы вызвать изменение области просмотра)
Я слышал, что несколько человек проявили интерес к способу обновления состояния ручки. Если мы сможем договориться о хорошей архитектуре, я думаю, это будет полезно для многих. Тем более, что это дополнительная функция, которую люди могут использовать или нет, и потому, что она не влияет отрицательно на тех, кто не хочет ее использовать.
@Hypnosphi , у вас были возражения по поводу предыдущей реализации WDYT?
Комментируем здесь, чтобы оставить эту проблему открытой. Мне все еще любопытно, что @Hypnosphi может сказать о ранее предложенном здесь @ igor-dv.
Мне кажется, что идея состоит не в том, чтобы вводить разные источники истины, а, скорее, в том, чтобы позволить поддерживать единственный источник истины для состояния компонента - ручки. Все другие предложенные мной подходы (компоненты оболочки, надстройки состояния, перекомпоновка) на самом деле представляют еще один источник истины. В моем примере с флажком я не смогу иметь ручку для проверки, а также позволить компоненту оболочки предоставлять проверяемую опору. Я вижу панель управления ручками как родительский компонент, за исключением того, что в настоящее время он не может получать какие-либо обратные вызовы от компонента, что является своего рода односторонним, а не тем, как обычно создаются реагирующие приложения.
Для меня это звучит разумно. Обсудим API
Это будет отличная возможность. Я столкнулся с такой же потребностью здесь с очень простым управляемым компонентом и сталкивался с этим раньше и в других компонентах. Спасибо, что посмотрели на это.
Идти в ногу с текущим синтаксисом кажется небольшой проблемой. Возможно, вы могли бы использовать что-то вроде:
const {value: name, change: setName} = text('Name', 'Kent');
@brunoreis , я бы побеспокоился, что изменение обратной подписи ручек будет
import {boolean, changeBoolean} from '@storybook/addon-knobs/react';
stories.add('custom checkbox', () => (
<MyCheckbox
checked={boolean('checked', false)}
onChange={(isChecked) => changeBoolean('checked', isChecked)} />
));
Если обратный вызов onChange
может даже разрешить каррирование, чтобы это тоже работало:
onChange={(isChecked) => changeBoolean('checked')(isChecked)}
// which of course simplifies down to
onChange={changeBoolean('checked')}
Важная часть состоит в том, что первый аргумент должен совпадать с меткой ручки, которую следует изменить. Я считаю, что это позволит пользователям выбрать такое поведение, ничего не меняя в том, как в настоящее время используются ручки. (Если, может быть, в настоящее время не требуется, чтобы ярлыки были уникальными? Я предполагаю, что они…)
@IanVS , это мило. Я согласен с вами в том, что нельзя менять способ работы. Я не мог найти способ сделать это, потому что не думал об использовании метки в качестве ключа. Это может сработать. Посмотрим, что имеет в виду @Hypnosphi .
изменение обратной подписи ручек было бы критическим изменением
Технически сейчас это не проблема. У нас скоро будет крупный релиз. Но действительно было бы лучше разрешить некоторую обратную совместимость.
Мне нравится идея заручиться поддержкой.
Если, может быть, в настоящее время не требуется, чтобы ярлыки были уникальными?
Да, они есть, и на самом деле они уникальны для разных типов. Таким образом, не должно быть необходимости в отдельном экспорте change<Type>
, достаточно всего change
. Это в основном то, что было сделано в https://github.com/storybooks/storybook/pull/3701
Думаю, я просто открою этот PR, чтобы
У нас может быть это:
const {value: name, change: setName} = text('Name', 'Kent');
без изменения возвращаемого типа text
.
Функции Javascript являются объектами и, следовательно, могут иметь свойства.
Объект можно деструктурировать.
@ndelangen функция text
действительно является объектом, но ее возвращаемое значение - нет. В вашем примере это не сработает:
const { foo, bar } = x() // note the parens
Но у нас могло бы быть что-то вроде этого (название спорное):
const {value: name, change: setName} = text.mutable('Name', 'Kent');
почему это не сработает?
const { foo, bar } = x() // note the parens
Возврат x - это функция, которая также может иметь свойства.
Я говорил о x = () => {}
из вашего исходного примера.
Если мы заставим text
возвращать функцию, пользователям нужно будет изменить свой код:
// Before
<Foo bar={text('Bar', '')}>
// After
<Foo bar={text('Bar', '')()}>
^^ this
я понимаю
Что насчет предложения @IanVS ?
было бы здорово иметь эту функцию.
как насчет "destruct", например, "react hooks" ?:
const [foo, setFoo] = useString('foo', 'default');
Пришел сказать то же, что и @DimaRGB
Сможет сохранить существующий синтаксис и добавить вызовы типа крючка, например:
.add('example', () => {
const [selected, setSelected] = useBool(false);
return <SomeComponent selected={selected} onSelected={setSelected} />
})
Иногда возникает необходимость в том, что реагирующие компоненты не должны обновлять свои собственные props
, а вместо этого сообщают своим родителям, что их нужно изменить. Иногда компонент содержит какой-то элемент, который должен переключать его свойства (я столкнулся с этим, потому что иногда меню панели навигации, которое у меня есть, должно открываться само, если щелкнуть дочерний элемент).
@IanVS У меня точно такая же ситуация, когда у меня есть переключаемый компонент, который обновляется только через родительские реквизиты, и как только пользователь истории из сборника рассказов переключает его, состояние пользовательского интерфейса несовместимо с тем, что показано на панели ручек. Я обошел его, используя частный метод из @storybook/addons-knobs
- более простым предложением было бы предоставить официальный API, чтобы делать что-то вроде:
import { manager } from '@storybook/addons-knob/dist/registerKnobs.js'
const { knobStore } = manager
// The name given to your knob - i.e: `select("Checked", options, defaultValue)`
knobStore.store['Checked'].value = newValue
// Danger zone! _mayCallChannel() triggers a re-render of the _whole_ knobs form.
manager._mayCallChannel()
@erickwilder Я пробовал ваш подход, но, похоже, он никогда не обновлял форму ручки (даже когда он обновлял реквизиты, предоставленные компоненту).
РЕДАКТИРОВАТЬ:
сотрите это; Я просто не видел этих обновлений, потому что я использовал options () с флажками, которые, по-видимому, работают «неконтролируемым» образом. Переключившись на множественный выбор и используя этот метод в обратном вызове, я получил обновленные значения в форме ручки:
// Ditch when https://github.com/storybooks/storybook/issues/3855 gets resolved properly.
function FixMeKnobUpdate(name, value) {
addons.getChannel().emit(CHANGE, { name, value });
}
Возможно, я пропустил некоторые детали своей настройки:
Не знаю, сработает ли такой подход для всех.
Я полностью согласен с @IanVS , я люблю сборник рассказов, но мне очень не хватает возможности обновлять ручки с помощью действий. В моем случае у меня есть два отдельных компонента A и B, но в одной из моих историй я хочу показать композицию, использующую их оба, чтобы всякий раз, когда я меняю AI, испускал действие, которое изменило бы B, и наоборот.
Я попробовал взлом @erickwilder с моей установкой (Angular 7), но всякий раз, когда я пытаюсь обновить хранилище кнопок, я получаю следующую ошибку:
Ошибка: ожидается, что не будет в Angular Zone, но это так!
Я попытался как-то запустить это вне Angular, но мне это не удалось ... В худшем случае я создам третий компонент C, который будет оберткой для A и B.
@davidolivefarga временное решение от @erickwilder должно работать для любых рамок , поскольку она не связана с рамочной / библиотекой (среагировать, угловая, вя, все остальное) , но к отрисовкам аддона Ручки.
Работал для нас с использованием React так же, как указано выше.
+1, чтобы добавить общедоступный метод для запуска, содержащий имя регулятора в виде строки для обновления и новое значение, например:
import { manager } from "@storybook/addon-knobs"
manager.updateKnob(
propName, // knobs property, example from above "Checked"
newValue, // new value to set programmatically, ex. true
)
Очень бы хотелось, чтобы это было официально поддержано.
А пока с Storybook v5 мне пришлось пойти с этим ужасно хакерским решением:
window.__STORYBOOK_ADDONS.channel.emit('storybookjs/knobs/change', {
name: 'The name of my knob',
value: 'the_new_value'
})
🙈
🙈
Связанный: # 6916
Это было бы круто ... Тем более, что модалы.
Думаю, это было бы полезно.
Пример:
storiesOf('input', module)
.addDecorator(withKnobs)
.add('default', () => <input type={text('type', 'text')} value={text('value', '')} disabled={boolean('disabled', false)} placeholder={text('placeholder', '')} onChange={action('onChange')} />)
В настоящее время это работает, но кажется естественным, что мы хотим связать целевое значение события onChange со значением, установленным в props тестируемого элемента. Это лучше продемонстрировало бы применение элемента.
Это было бы полезно
+1 к @ mathieuk / @raspo за указание на решение здесь.
Мы потенциально можем получить доступ к этому и другим способом. Создание нескольких общих методов в модуле helpers
?
import addons from "@storybook/addons";
export const emitter = (type, options) => addons.getChannel().emit(type, options);
export const updateKnob = (name, value) => (
emitter("storybookjs/knobs/change", { name, value })
);
И звонит по необходимости в рассказах ...
import { text } from "@storybook/addon-knobs";
import { updateKnob } from 'helpers';
// ...
const value = text("value", "Initial value");
<select
value={value}
onChange={({ target }) => updateKnob("value", target.value)}
>
... но по-прежнему кажется каким-то забавным обходным путем с двусторонней привязкой к чему-то, что может быть встроено в API кнопок
Есть какие-нибудь обновления по вышеизложенному? Было бы очень полезно
Эта реализованная функция позволит нам довольно легко создавать действительно мощные перекрестно-зависимые регуляторы. Представьте себе создание истории компонентов разбиения на страницы, если бы у вас могла быть одна ручка, динамически обновляемая в зависимости от другой, с чем-то вроде этого в вашей истории:
const resultsCount = number(
'Results count',
currentPage, {
max: 100,
min: 0,
range: true
}
);
const resultsArray: React.ReactNode[] = new Array(resultsCount)
.fill(true)
.map((_, idx) => <div key={idx + 1}>Test Pagination Result #{idx + 1}</div>);
const childrenPerPage = 10;
const currentPage = number(
'Current page index',
0, {
max: Math.ceil(resultsCount / childrenPerPage) - 1,
min: 0,
range: true
}
);
Я попытался сделать это по существу, надеясь, что максимальный диапазон на моей ручке currentPage
будет динамически обновляться при увеличении ручки resultsCount
с шагом 10, однако кажется, что начальное значение каждой ручки кэшируется во время создания и не привыкает переопределять значения внутреннего состояния при последующих отрисовках. С помощью приведенного выше кода, когда я увеличиваю resultsCount
на 10+, я ожидаю, что max
из currentPage
также увеличится на 1, однако его значение останется прежним, несмотря на то, что базовые значения используется для расчета его изменения (регистрация Math.ceil(resultsCount / childrenPerPage) - 1
показывает ожидаемое новое значение).
Мы почти закончили с 5.3 (выпуск в начале января) и изучаем возможность переписывания регуляторов для 6.0 (выпуск в конце марта), которая решит кучу давних проблем с регуляторами. Я посмотрю, сможем ли мы с этим поработать! Спасибо за ваше терпение!
@shilman Мне очень понравилась эта функция 😄
Я, @atanasster и @ PlayMa256 какое- то время работаю над фундаментом для этого. Потребуется еще несколько итераций, чтобы все получилось, но я очень уверен, что мы сможем достичь 100% в 6.0.0 и действительно произвести революцию в данных и реактивности для сборника рассказов.
Произвести революцию? Чертовски круто. Прекрати меня дразнить, мое тело готово.
+1 для модальных окон :)
Не могу поверить, что это невозможно с самого начала. Жду обновлений ...
Привет народ!
В качестве временного решения я использовал в своем приложении следующий код:
import { addons } from '@storybook/addons';
import { CHANGE } from '@storybook/addon-knobs';
const channel = addons.getChannel();
channel.emit(CHANGE, {
name: 'prop_name',
value: prop_value,
});
Все еще ждем, что эта функция будет реализована изначально.
Я решил эту проблему с помощью наблюдаемого. Я использую сборник рассказов с угловым
`
.add ('обновление данных диаграммы', () => {
const myObservable= new BehaviorSubject([{a: 'a', b: 'b'}]);
return {
template: '
<my-component
myInput: myData,
(myEvent)="myEventProp($event)"
></my-component>
',
props: {
myData: myObservable,
myEventProp: $event => {
myObservable.next([]);
action('(myEvent)')($event);
}
}
};
})
`
@ norbert-doofus спасибо, что твой пример мне помог 👍
Привет, банда! Мы только что выпустили бета-версию 6.0!
Элементы управления представляют собой портативные, автоматически генерируемые ручки, которые предназначены для замены дополнительных ручек в долгосрочной перспективе.
Обновите и попробуйте их сегодня. Спасибо за вашу помощь и поддержку в выпуске этой стабильной версии!
Замечательно! Я не могу точно сказать, читая README (возможно, я его пропустил), можно ли изменить эти новые controls
программно, каков был запрос в этом выпуске?
Они могут быть, хотя я не уверен, что API еще официально поддерживается (хотя мы делаем именно это для элементов управления в addon-docs). Я буду работать с @tmeasday, чтобы выяснить, как лучше всего это сделать после того, как в первом раунде исправления ошибок управления.
updateArgs
в контекст истории?this.context?.updateArgs(....)
)Для всех, кто интересуется элементами управления, но не знает, с чего начать, я создал быстрое и грязное пошаговое руководство, чтобы перейти от свежего проекта CRA к рабочей демонстрации. Проверить это:
=> Элементы управления Storybook с CRA и TypeScript
Также есть несколько "ручек для управления" документами по миграции в README Controls:
Привет народ!
В качестве временного решения я использовал в своем приложении следующий код:import { addons } from '@storybook/addons'; import { CHANGE } from '@storybook/addon-knobs'; const channel = addons.getChannel(); channel.emit(CHANGE, { name: 'prop_name', value: prop_value, });
Все еще ждем, что эта функция будет реализована изначально.
Имейте в виду, что при использовании groupId
вам нужно будет добавить идентификатор группы к name
следующим образом:
const show = boolean('Show Something', true, 'Group')
channel.emit(CHANGE, {
name: 'Show Something_Group',
value: prop_value,
});
Кроме того, channel
является EventEmitter, поэтому вы можете addListener
к нему, чтобы проверить, какие параметры принимаются.
channel.addListener(CHANGE, console.log)
Вот фрагмент кода для всех, кто интересуется, как это сделать, используя addon-controls
в v6.
import { useArgs } from '@storybook/client-api';
// Inside a story
export const Basic = ({ label, counter }) => {
const [args, updateArgs] = useArgs();
return <Button onClick={() => updateArgs({ counter: counter+1 })>{label}: {counter}<Button>;
}
Я не знаю, лучший ли это API для этой цели, но он должен работать. Пример в монорепозитории:
Спасибо, @shilman! Это помогло.
Для заинтересованных людей у нас есть точная Checkbox
story checked
prop, с которой началась эта цепочка. Вот наша недавно созданная история с использованием Storybook 6.0.0-rc.9:
export const checkbox = (args) => {
const [{ checked }, updateArgs] = useArgs();
const toggleChecked = () => updateArgs({ checked: !checked });
return <Checkbox {...args} onChange={toggleChecked} />;
};
checkbox.args = {
checked: false,
label: 'hello checkbox!',
};
checkbox.argTypes = {
checked: { control: 'boolean' },
};
@shilman Я попытался использовать useArgs
в истории для управляемого ввода текста (случай, когда мы обычно можем использовать хук useState
для обновления value
prop компонента через его событие onChange
). Однако я столкнулся с проблемой, когда каждый раз, когда пользователь вводит символ, компонент теряет фокус. Возможно, это вызвано обновлением / повторным рендерингом истории каждый раз, когда мы обновляем аргументы?
Есть ли другой рекомендуемый метод использования аргументов / элементов управления для компонента с управляемым вводом текста?
Это было с 6.0.0-rc.13
@jcq Можете ли вы создать новую useArgs
, но, безусловно, тот, который мы хотели бы поддержать, поэтому мы были бы счастливы вникнуть в него.
@shilman Нет проблем - вот новый выпуск:
https://github.com/storybookjs/storybook/issues/11657
Я также должен был пояснить, что ошибочное поведение отображается в Документах, хотя оно работает правильно в обычном режиме холста.
Я решил эту проблему с помощью наблюдаемого. Я использую сборник рассказов с угловым
`
.add ('обновление данных диаграммы', () => {const myObservable= new BehaviorSubject([{a: 'a', b: 'b'}]); return { template: ' <my-component myInput: myData, (myEvent)="myEventProp($event)" ></my-component> ', props: { myData: myObservable, myEventProp: $event => { myObservable.next([]); action('(myEvent)')($event); } } };
})
`
Это сработало для меня с использованием Angular, но изменилось на myData.value
в строке 5
Я еще не пробовал это (пока застрял на более старой версии сборника рассказов), но похоже, что теперь эту проблему можно закрыть. Спасибо за отличную работу над аргументами / элементами управления!
Самый полезный комментарий
Мы почти закончили с 5.3 (выпуск в начале января) и изучаем возможность переписывания регуляторов для 6.0 (выпуск в конце марта), которая решит кучу давних проблем с регуляторами. Я посмотрю, сможем ли мы с этим поработать! Спасибо за ваше терпение!