Storybook: Измените ручки с помощью действий [идея]

Созданный на 9 июл. 2018  ·  55Комментарии  ·  Источник: storybookjs/storybook

Я только начал использовать сборник рассказов, и пока мне он нравится. Одна вещь, с которой я боролся, - это как работать с чистыми компонентами без состояния, которые требуют, чтобы состояние содержалось в родительском элементе. Например, у меня есть флажок, который принимает опору checked . Щелчок по флажку не переключает его состояние, а скорее запускает onChange и ждет, чтобы получить обратно обновленную опору checked . Похоже, что нет какой-либо документации по передовым методам работы с этими видами компонентов, а предложения в таких вопросах, как https://github.com/storybooks/storybook/issues/197 , касались создания компонента-оболочки или добавить еще одно дополнение. Я бы предпочел не делать обертку для компонентов, если я могу ей помочь, потому что я хотел бы, чтобы мои истории были как можно более простыми.

Одна идея, с которой мне пришлось справиться, - это связать надстройку actions с knobs , позволяющую программно переключать ручки с помощью действий. Как я уже сказал, я новичок в сборнике рассказов, поэтому я понятия не имею, возможно ли это вообще, но я хотел, по крайней мере, высказать свое мнение.

Это камень преткновения для меня при реализации историй, и я полагаю, что это может быть и для других. Если создание компонента-оболочки действительно лучше всего, возможно, можно было бы добавить некоторую документацию, чтобы прояснить это, и как это сделать?

knobs feature request todo

Самый полезный комментарий

Мы почти закончили с 5.3 (выпуск в начале января) и изучаем возможность переписывания регуляторов для 6.0 (выпуск в конце марта), которая решит кучу давних проблем с регуляторами. Я посмотрю, сможем ли мы с этим поработать! Спасибо за ваше терпение!

Все 55 Комментарий

Какая-то похожая идея была представлена ​​в этом 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 являются объектами и, следовательно, могут иметь свойства.
Объект можно деструктурировать.

screen shot 2018-09-19 at 00 08 35

@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 - это функция, которая также может иметь свойства.

screen shot 2018-09-23 at 14 12 28

Я говорил о 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 });
}

Возможно, я пропустил некоторые детали своей настройки:

  • Мы используем Vue.js с Storybook
  • Код, изменяющий значение и запускающий повторную визуализацию, был выполнен внутри метода, если компонент обертывания истории.

Не знаю, сработает ли такой подход для всех.

Я полностью согласен с @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 для этой цели, но он должен работать. Пример в монорепозитории:

https://github.com/storybookjs/storybook/blob/next/examples/official-storybook/stories/core/args.stories.js#L34 -L43

Спасибо, @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' },
};

cb-arg

@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

Я еще не пробовал это (пока застрял на более старой версии сборника рассказов), но похоже, что теперь эту проблему можно закрыть. Спасибо за отличную работу над аргументами / элементами управления!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги