Storybook: Cambiar potenciómetros mediante acciones [idea]

Creado en 9 jul. 2018  ·  55Comentarios  ·  Fuente: storybookjs/storybook

Acabo de empezar a usar el libro de cuentos y hasta ahora me encanta. Una cosa con la que he luchado es cómo tratar con componentes puros "sin estado" que requieren que el estado esté contenido en un padre. Por ejemplo, tengo una casilla de verificación que tiene un checked prop. Hacer clic en la casilla de verificación no cambia su estado, sino que dispara un onChange y espera recibir un checked prop actualizado. No parece haber ninguna documentación sobre las mejores prácticas para manejar este tipo de componentes, y las sugerencias en problemas como https://github.com/storybooks/storybook/issues/197 han sido crear un componente contenedor o agregue otro complemento. Prefiero no hacer un contenedor de componentes si puedo evitarlo, porque me gustaría mantener mis historias lo más simples posible.

Una idea que tuve para manejar esto es conectar el complemento actions con knobs , permitiendo que las perillas se alternen programáticamente a través de acciones. Como dije, soy nuevo en el libro de cuentos, así que no tengo idea de si esto es factible, pero al menos quería plantear la sugerencia.

Este es un punto de tropiezo para mí al implementar historias, e imagino que también podría serlo para otros. Si crear un componente contenedor es realmente lo mejor que se puede hacer, tal vez se podría agregar algo de documentación para aclarar esto, y ¿cómo lograrlo?

knobs feature request todo

Comentario más útil

Casi hemos terminado con 5.3 (lanzamiento temprano de enero) y estamos buscando una reescritura de perillas para 6.0 (lanzamiento tardío de marzo) que resolverá un montón de problemas de perillas de larga data. ¡Veré si podemos trabajar en esto! ¡Gracias por su paciencia!

Todos 55 comentarios

Algún tipo de idea similar se introdujo en este PR # 3701, que fue controvertido (y no se fusionó).

Podemos abrir una discusión al respecto nuevamente y escuchar las sugerencias de la API =).

Ah, gracias, no había visto ese PR. Gracias @aherriot por armar eso, parece que tenemos los mismos pensamientos.

Antes de sumergirse en una API, parece que el concepto fundamental debe discutirse y acordarse. Uno de los comentarios en el PR fue este de @Hypnosphi :

No me gusta el hecho de que introduce múltiples fuentes de verdad (devoluciones de llamada de componentes y perillas de interfaz de usuario)

Me parece que la idea no es introducir diferentes fuentes de verdad, sino más bien permitir el mantenimiento de una única fuente de verdad para el estado de los componentes: las perillas. Todos los otros enfoques que he visto sugeridos (componentes de envoltura, complementos de estado, recomposición) de hecho introducirían otra fuente de verdad. En mi ejemplo de casilla de verificación, no podría tener una perilla para checked , y también permitiría que un componente contenedor proporcionara el checked prop. Veo el panel de control de las perillas como el padre del componente, excepto que actualmente no puede recibir ninguna devolución de llamada del componente, que es un poco unilateral y no es la forma en que se construyen normalmente las aplicaciones de reacción.

Al permitir que las perillas se controlen programáticamente, la historia puede aislarse solo del componente que se está demostrando, dejando el mecanismo de administración de estado real opaco, como debería ser para un componente de presentación simple como una casilla de verificación. A la casilla de verificación en sí no le importa cómo obtiene sus accesorios, podría conectarse a redux, su padre podría usar setState , tal vez use withState de recomposición, o tal vez los accesorios estén controlados por perillas de libro de cuentos.

De todos modos, como dije, soy muy nuevo en esto, así que solo estoy compartiendo mis pensamientos intuitivos. Si estoy fuera de lugar y existe una "mejor práctica" generalmente aceptada para tratar con este tipo de componentes puros sin estado, ¿tal vez alguien pueda señalarme un buen ejemplo que pueda seguir?

Hola :)

Solo quiero agregar que me encontré con eso cuando mis componentes están recibiendo si deberían mostrar sus diseños móviles (o no) de los accesorios, y mi objetivo era vincular el cambio de la ventana gráfica a mi perilla, y hubiera sido realmente bueno;)

Solo quería agregar mi caso de uso aquí, y como @IanVS, incluso una devolución de llamada hubiera sido agradable (así que si alterno mis accesorios de isMobile, podría activar un cambio de vista)

He escuchado a varias personas expresar interés en un método para actualizar el estado de la perilla. Si podemos ponernos de acuerdo sobre una buena arquitectura, creo que esto será valioso para muchas personas. Especialmente porque se trata de una funcionalidad adicional que las personas pueden optar por usar o no y porque no afecta negativamente a quienes no quieren usarla.

@Hypnosphi , tenías objeciones sobre la implementación anterior, ¿WDYT?

Comentando aquí para mantener este problema abierto. Todavía tengo curiosidad por lo que @Hypnosphi tiene que decir sobre @ igor-dv propuesto anteriormente aquí.

Me parece que la idea no es introducir diferentes fuentes de verdad, sino más bien permitir mantener una única fuente de verdad para el estado de los componentes: las perillas. Todos los otros enfoques que he visto sugeridos (componentes de envoltura, complementos de estado, recomposición) de hecho introducirían otra fuente de verdad. En mi ejemplo de casilla de verificación, no podría tener una perilla para marcar, y también permitiría que un componente de envoltura proporcione el accesorio marcado. Veo el panel de control de las perillas como el padre del componente, excepto que actualmente no puede recibir ninguna devolución de llamada del componente, que es un poco unilateral y no es la forma en que se construyen normalmente las aplicaciones de reacción.

Me suena razonable. Discutamos la API

Esta será una gran característica. Acabo de enfrentar esta misma necesidad aquí con un componente controlado muy básico y también lo había enfrentado antes en otros componentes. Gracias por mirarlo.

Mantenerse al día con la sintaxis actual parece un pequeño desafío. Quizás podrías usar algo como:

const {value: name, change: setName} = text('Name', 'Kent');

@brunoreis , me preocuparía que cambiar la firma de retorno de las perillas fuera un cambio

import {boolean, changeBoolean} from '@storybook/addon-knobs/react';

stories.add('custom checkbox', () => (
    <MyCheckbox
        checked={boolean('checked', false)}
        onChange={(isChecked) => changeBoolean('checked', isChecked)} />
));

Donde la devolución onChange llamada

onChange={(isChecked) => changeBoolean('checked')(isChecked)}

// which of course simplifies down to
onChange={changeBoolean('checked')}

La parte importante es que el primer argumento debe ser el mismo que la etiqueta de la perilla que debe cambiarse. Creo que esto permitiría a los usuarios optar por este comportamiento sin cambiar nada sobre la forma en que se utilizan las perillas actualmente. (A menos que tal vez no se requiera que las etiquetas sean únicas, supongo que lo son…)

@IanVS , eso es bueno. Estoy de acuerdo contigo en no cambiar la forma en que funcionan las cosas. No pude encontrar la manera de hacerlo porque no pensé en usar la etiqueta como clave. Aquello podría funcionar. Veamos qué tiene en mente @Hypnosphi .

cambiar la firma de retorno de las perillas sería un cambio rotundo

Técnicamente, eso no es un problema en este momento. Tenemos un lanzamiento importante próximo. Pero de hecho sería mejor permitir cierta compatibilidad con versiones anteriores.

Me gusta la idea de conseguir apoyo.

A menos que tal vez no se requiera que las etiquetas sean únicas.

Sí, lo son, y en realidad son únicos en diferentes tipos. Por lo tanto, no debería haber necesidad de exportar change<Type> por separado, solo change debería ser suficiente. Eso es básicamente lo que se hizo en https://github.com/storybooks/storybook/pull/3701
Creo que reabriré ese PR para que

Podemos tener esto:

const {value: name, change: setName} = text('Name', 'Kent');

sin tener que cambiar el tipo de devolución de text .

Las funciones de Javascript son objetos y, por tanto, pueden tener propiedades.
El objeto se puede desestructurar.

screen shot 2018-09-19 at 00 08 35

@ndelangen la función text es de hecho un objeto, pero su valor de retorno no lo es. Esto no funcionará en su ejemplo:

const { foo, bar } = x() // note the parens

Sin embargo, podríamos tener algo como esto (el nombre es discutible):

const {value: name, change: setName} = text.mutable('Name', 'Kent');

¿Por qué no funciona esto?

const { foo, bar } = x() // note the parens

El retorno de x es una función que también puede tener propiedades.

screen shot 2018-09-23 at 14 12 28

Estaba hablando de x = () => {} de su ejemplo original.

Si hacemos que text devuelva una función, los usuarios deberán cambiar su código:

// Before
<Foo bar={text('Bar', '')}>

// After
<Foo bar={text('Bar', '')()}>
                         ^^ this

veo

¿Qué pasa con la sugerencia de

Sería genial tener esta característica.

¿qué pasa con "destruir", como con "react hooks"?:

const [foo, setFoo] = useString('foo', 'default');

Vine a decir lo mismo que @DimaRGB
Sería capaz de preservar la sintaxis existente y agregar llamadas en forma de gancho, por ejemplo:

.add('example', () => {
  const [selected, setSelected] = useBool(false);

  return <SomeComponent selected={selected} onSelected={setSelected} />
})

La necesidad ocasional es que no se supone que los componentes de reacción actualicen sus propios props , sino que le digan a sus padres que necesitan un cambio. A veces, un componente contiene algún elemento que se supone que debe alternar sus accesorios (me encontré con esto porque a veces un menú de la barra de navegación que tengo debe abrirse si se hace clic en un elemento secundario).

@IanVS Tengo exactamente la misma situación en la que tengo un componente de alternancia que solo se actualiza a través de los accesorios de los padres y una vez que el usuario de una historia de libro de cuentos lo cambia, el estado de la interfaz de usuario es inconsistente con lo que se muestra en el panel de perillas. Lo pirateé usando un método privado de @storybook/addons-knobs ; una sugerencia más simple sería proporcionar una API oficial para hacer algo como:

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 Probé su enfoque, pero eso nunca pareció actualizar la forma de la perilla (incluso cuando actualizó los accesorios proporcionados al componente).

EDITAR:

rasca eso; Simplemente no vi esas actualizaciones porque estaba usando options () con casillas de verificación que aparentemente se operan de manera 'incontrolada'. Al cambiar a selección múltiple y usar este método en una devolución de llamada, obtuve los valores actualizados en la forma de la perilla:

// Ditch when https://github.com/storybooks/storybook/issues/3855 gets resolved properly.
function FixMeKnobUpdate(name, value) {
    addons.getChannel().emit(CHANGE, { name, value });
}

Podría haber omitido algunos detalles de mi configuración:

  • Estamos usando Vue.js con Storybook
  • El código que muta el valor y activa la re-renderización se realizó dentro de un método si el componente de envoltura de la historia.

No sé si ese enfoque funcionaría para todos.

Estoy completamente de acuerdo con @IanVS , me encanta el libro de cuentos, pero realmente extraño la capacidad de actualizar las perillas a través de acciones. En mi caso, tengo dos componentes individuales A y B pero en una de mis historias quiero mostrar una composición usando ambos, para que siempre que cambie AI emita una acción que modificaría B y viceversa.

Probé el truco de

Error: se espera que no esté en la zona angular, ¡pero lo está!

Intenté ejecutar esto de alguna manera fuera de Angular pero no pude hacerlo ... En el peor de los casos, crearé un tercer componente C que será un contenedor de A y B.

La solución temporal de @davidolivefarga de

Trabajó para nosotros usando React de la misma manera que se mencionó anteriormente

+1 para agregar un método visible públicamente para activar, que contiene el nombre de la perilla como una cadena para actualizar y un nuevo valor, por ejemplo:

import { manager } from "@storybook/addon-knobs"

manager.updateKnob(
  propName, // knobs property, example from above "Checked"
  newValue, // new value to set programmatically, ex. true
)

Realmente me gustaría que esto fuera apoyado oficialmente.

Mientras tanto, con Storybook v5 tuve que ir con esta solución terriblemente hacky:

window.__STORYBOOK_ADDONS.channel.emit('storybookjs/knobs/change', {
  name: 'The name of my knob',
  value: 'the_new_value'
})

🙈

🙈

Relacionado: # 6916

Esto sería genial ... Especialmente porque los modales.

Creo que esto sería útil.

Ejemplo:

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')} />)

Esto funciona actualmente, pero parece natural que queramos conectar el valor objetivo del evento onChange al valor establecido en los accesorios del elemento probado. Esto demostraría mejor la aplicación del elemento.

Esto sería útil

+1 a @ mathieuk / @raspo por apuntar hacia una solución aquí.

Potencialmente, también podemos tener acceso a esto de otra manera. ¿Creando algunos métodos genéricos en un módulo 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 })
);

Y llamando según sea necesario en las historias ...

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)}
>

... pero todavía se siente como una solución de enlace bidireccional funky para algo que podría integrarse en la API de perillas

¿Alguna actualización sobre lo anterior? Sería una característica muy útil 👍

Esta característica que se está implementando nos permitiría hacer con bastante facilidad algunos potenciómetros dependientes cruzados realmente potentes. Imagine hacer una historia de componentes de paginación si pudiera tener una perilla actualizándose dinámicamente dependiendo de otra, con algo como esto en su historia:

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
    }
);

He intentado hacer esencialmente esto, esperando que el rango máximo en mi perilla currentPage se actualice dinámicamente al aumentar la perilla resultsCount en un incremento de 10, sin embargo, parece que el valor inicial de cada perilla se almacena en caché en el momento de la creación y no se acostumbra a anular los valores de estado interno en las representaciones posteriores. Con el código anterior, cuando aumento resultsCount en 10+, esperaría que max de currentPage también aumente en 1, sin embargo, su valor permanece igual a pesar de que los valores subyacentes son utilizado para calcular que ha cambiado (el registro Math.ceil(resultsCount / childrenPerPage) - 1 muestra el nuevo valor esperado).

Casi hemos terminado con 5.3 (lanzamiento temprano de enero) y estamos buscando una reescritura de perillas para 6.0 (lanzamiento tardío de marzo) que resolverá un montón de problemas de perillas de larga data. ¡Veré si podemos trabajar en esto! ¡Gracias por su paciencia!

@shilman Estoy muy emocionado con esta función 😄

Yo, @atanasster y @ PlayMa256 hemos estado trabajando en la base de esto por un tiempo. Se necesitarán algunas iteraciones más para hacerlo bien, pero estoy muy seguro de que podemos llegar al 100% en 6.0.0 y realmente revolucionar los datos y la reactividad para el libro de cuentos.

¿Revolucionar? Maldita diggity caliente. Deja de molestarme, mi cuerpo está listo.

+1 para modales :)

No puedo creer que sea imposible desde el principio. Esperando actualizaciones ...

¡Hola amigos!
Como solución temporal que he usado en el siguiente código de mi aplicación:

import { addons } from '@storybook/addons';
import { CHANGE } from '@storybook/addon-knobs';

const channel = addons.getChannel();

channel.emit(CHANGE, {
  name: 'prop_name',
  value: prop_value,
});

Aún esperando, esta función se implementará de forma nativa.

Resolví ese problema usando observable. Estoy usando un libro de cuentos con angular

'
.add ('actualizando los datos del gráfico', () => {

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 gracias tu ejemplo me ayudó 👍

Hola pandilla, ¡Acabamos de lanzar controles adicionales en 6.0-beta!

Los controles son perillas portátiles generadas automáticamente que están destinadas a reemplazar las perillas adicionales a largo plazo.

Actualícelos y pruébelos hoy. ¡Gracias por su ayuda y apoyo para obtener este estable para su lanzamiento!

¡Eso es genial! No puedo decir al leer el README (es posible que me lo haya perdido), ¿se pueden cambiar estos nuevos controls programáticamente, cuál fue la solicitud que se hizo en este número?

Pueden serlo, aunque no estoy seguro de que la API sea oficialmente compatible todavía (aunque estamos haciendo exactamente eso para los controles en addon-docs). Trabajaré con @tmeasday para descubrir la mejor manera de hacerlo después de que se estabilice la primera ronda de errores de Controles.

  • [] agregar updateArgs al contexto de la historia?
  • [] hacer que el contexto de la historia esté disponible para una devolución de llamada que se llama desde la historia? ( this.context?.updateArgs(....) )

Para cualquiera que esté interesado en los controles pero no sepa por dónde empezar, he creado un tutorial paso a paso rápido y sucio para pasar de un nuevo proyecto de CRA a una demostración funcional. Echale un vistazo:

=> Controles de libros de cuentos con CRA y TypeScript

También hay algunos documentos de migración de "botones para controles" en el archivo README de controles:

=> ¿Cómo migro desde addon-knobs?

¡Hola amigos!
Como solución temporal que he usado en el siguiente código de mi aplicación:

import { addons } from '@storybook/addons';
import { CHANGE } from '@storybook/addon-knobs';

const channel = addons.getChannel();

channel.emit(CHANGE, {
  name: 'prop_name',
  value: prop_value,
});

Aún esperando, esta función se implementará de forma nativa.

Tenga en cuenta que cuando use groupId deberá agregar la identificación del grupo al name siguiente manera:

 const show = boolean('Show Something', true, 'Group')

channel.emit(CHANGE, {
  name: 'Show Something_Group',
  value: prop_value,
});

Además, channel es un EventEmitter, por lo que puede addListener para verificar cuáles son los parámetros que se reciben.

channel.addListener(CHANGE, console.log)

Aquí hay un fragmento de código para cualquiera que esté interesado en cómo hacer esto usando addon-controls en 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>;
}

No sé si esta es la mejor API para este propósito, pero debería funcionar. Ejemplo en el monorepo:

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

¡Gracias, @shilman! Eso hizo el truco.

Para las personas interesadas, tenemos exactamente el Checkbox story checked prop que inició todo este hilo. Aquí está nuestra historia recién conectada usando 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 Intenté utilizar useArgs en una historia para una entrada de texto controlada (un caso en el que normalmente podríamos usar el gancho useState para actualizar el value prop del componente a través de su evento onChange ). Sin embargo, encontré un problema en el que cada vez que el usuario escribe un carácter, el componente pierde el enfoque. ¿Quizás esto se deba a que actualiza / vuelve a renderizar la historia cada vez que actualizamos los argumentos?

¿Existe un método recomendado diferente para utilizar argumentos / controles para un componente con entrada de texto controlada?

Esto fue con 6.0.0-rc.13

@jcq ¿Puedes crear un nuevo problema con una reproducción? Este no fue el caso de uso principal de useArgs , pero ciertamente uno que nos gustaría apoyar, por lo que estaremos encantados de profundizar en él.

@shilman No hay problema, aquí está el nuevo problema:
https://github.com/storybookjs/storybook/issues/11657

También debería haber aclarado que el comportamiento errante se muestra en Docs, mientras que funciona correctamente en el modo Canvas normal.

Resolví ese problema usando observable. Estoy usando un libro de cuentos con angular

'
.add ('actualizando los datos del gráfico', () => {

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);
    }
  }
};

})
'

Esto funcionó para mí usando Angular pero cambiando a myData.value en la línea 5

No he probado esto todavía (atascado en una versión anterior del libro de cuentos por ahora) pero parece que este problema se puede cerrar ahora. ¡Gracias por el gran trabajo en argumentos / controles!

¿Fue útil esta página
0 / 5 - 0 calificaciones