Storybook: μŠ€ν† λ¦¬ λ‚΄μ—μ„œ 직접 React 후크λ₯Ό μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€.

에 λ§Œλ“  2019λ…„ 02μ›” 22일  Β·  53μ½”λ©˜νŠΈ  Β·  좜처: storybookjs/storybook

버그 μ„€λͺ…

μŠ€ν† λ¦¬μ—μ„œ 직접 후크λ₯Ό μ‚¬μš©ν•  수 μ—†μœΌλ©° Hooks can only be called inside the body of a function component μ‹€νŒ¨ν•©λ‹ˆλ‹€.

μž¬ν˜„ν•˜λ €λ©΄

예제 μ½”λ“œ :

import React from 'react'

import { storiesOf } from '@storybook/react'

const stories = storiesOf('Hooks test', module)

const TestComponent: React.FC = () => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
}

stories.add('this story works', () => <TestComponent />)

stories.add('this story fails', () => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
})

μ˜ˆμƒλ˜λŠ” 행동
첫 번째 μŠ€ν† λ¦¬λŠ” μ •μƒμ μœΌλ‘œ μž‘λ™ν•˜κ³  두 번째 μŠ€ν† λ¦¬λŠ” 초기 λ Œλ”λ§μ—μ„œ μ‹€νŒ¨ν•©λ‹ˆλ‹€.

버전
@storybook/[email protected]
[email protected]
[email protected]

react has workaround question / support

κ°€μž₯ μœ μš©ν•œ λŒ“κΈ€

μš°λ¦¬λŠ” 같은 문제λ₯Ό κ²½ν—˜ν–ˆμœΌλ©° 이것은 μš°λ¦¬κ°€ ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μˆ˜ν–‰ν•˜λŠ” κ°„λ‹¨ν•œ λ°©λ²•μž…λ‹ˆλ‹€.

stories.add('this story fails', () => React.createElement(() => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
}))

해킹없이 기본적으둜 μ§€μ›λ˜λŠ” 것이 훨씬 λ‚«λ‹€λŠ” 데 λ™μ˜ν•©λ‹ˆλ‹€. λ©”μΈν…Œμ΄λ„ˆλ“€λ„ λ™μ˜ν•œλ‹€λ©΄ PR을 생각 ν•΄λ‚Ό μ‹œκ°„μ„ 찾을 μˆ˜μžˆμ„ κ²ƒμž…λ‹ˆλ‹€.

λͺ¨λ“  53 λŒ“κΈ€

이것이 버그인지 ν™•μ‹€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. stories.add 의 두 번째 μΈμˆ˜λŠ” μ‹€μ œ React ꡬ성 μš”μ†Œκ°€ μ•„λ‹Œ ꡬ성 μš”μ†Œλ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λ₯Ό κΈ°λŒ€ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. ν•¨μˆ˜ ꡬ성 μš”μ†Œλ₯Ό μ™ΈλΆ€λ‘œ μ΄λ™ν•΄λ³΄μ‹­μ‹œμ˜€. μ„±κ³΅ν•΄μ•Όν•©λ‹ˆλ‹€.

예

function SomeComponent() {
    const [blah] = React.useState('blah');
    return <div> {blah}</div>;
}
stories.add('BlahComponent', () => <SomeComponent />);

stories.add 의 두 번째 μΈμˆ˜λŠ” μ‹€μ œ React ꡬ성 μš”μ†Œκ°€ μ•„λ‹Œ ꡬ성 μš”μ†Œλ₯Ό λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λ₯Ό κΈ°λŒ€ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

AFAIKλŠ” μ€‘μš”ν•˜μ§€ μ•Šμ§€λ§Œ 항상 μ‹œλ„ν•΄ λ³Ό κ°€μΉ˜κ°€ μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ @sargant κ°€ 였λ₯˜λ₯Ό λ°œμƒ

@Keraito μ•„λ‹ˆμš” 였λ₯˜κ°€ μ •ν™•ν•˜λ‹€κ³  ν™•μ‹ ν•©λ‹ˆλ‹€.

λ°˜μ‘μ˜ λ§₯λ½μ—μ„œ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€. 일λͺ… μŠ€ν† λ¦¬ 뢁은 λ‹€μŒκ³Ό 같이 ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€.

const element = storyFn();

μ•„λ‹ˆ

const element = <StoryFn />

μš°λ¦¬κ°€ κ·Έλ ‡κ²Œ μ‹œμž‘ν•œλ‹€λ©΄ νš¨κ³Όκ°€μžˆμ„ κ²ƒμž…λ‹ˆλ‹€.

μ§€κΈˆ @gabefromutah 의 쑰언은 κ±΄μ „ν•©λ‹ˆλ‹€.

μ‹€μ œ μ½”λ“œ 쀄은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
https://github.com/storybooks/storybook/blob/next/app/react/src/client/preview/render.js#L24

λˆ„κ΅°κ°€μ΄ μž‘ν’ˆμ„ λ§Œλ“€κΈ° μœ„ν•΄ μ‹€ν—˜μ„ν•˜κ³  μ‹Άλ‹€λ©΄ μ—¬κΈ°μ„œλΆ€ν„° μ‹œμž‘ν•΄μ•Όν•œλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.

@sargant @gabefromutah 의 μ œμ•ˆμ΄ 당신을 μœ„ν•΄ μž‘λ™ν•©λ‹ˆκΉŒ?

@ndelangen @gabefromutah 의 μ œμ•ˆμ€ λ‚΄κ°€ λ―ΏλŠ” 초기 "이 이야기가 μž‘λ™ν•˜λŠ”"μ˜ˆμ œμ™€ λ™μΌν•©λ‹ˆκΉŒ?

이것이 버그가 μ•„λ‹Œ κ²½μš°μ—λ„ μƒνƒœμ— λŒ€ν•΄ 타사 ν”ŒλŸ¬κ·ΈμΈμ„ μ‚¬μš©ν•˜μ§€ μ•Šμ•„λ„λ˜λŠ” μœ μš©ν•œ κ°œμ„  사항 일 수 μžˆμŠ΅λ‹ˆλ‹€.

μš°λ¦¬κ°€ κ·Έλ ‡κ²Œ μ‹œμž‘ν•œλ‹€λ©΄ νš¨κ³Όκ°€μžˆμ„ κ²ƒμž…λ‹ˆλ‹€.

@ndelangen 은 λ‹€λ₯Έ νŠΉμ • μ• λ“œμ˜¨, 특히 κΈ°λ³Έ λ Œλ”λ§ ꡬ성 μš”μ†Œμ˜ μ†Œν’ˆκ³Ό 같은 정보가 ν•„μš”ν•œ addon-info 와 μƒν˜Έ μž‘μš©ν•˜λŠ” 방법을 μ™„μ „νžˆ ν™•μ‹ ν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€.

μš°λ¦¬λŠ” 같은 문제λ₯Ό κ²½ν—˜ν–ˆμœΌλ©° 이것은 μš°λ¦¬κ°€ ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μˆ˜ν–‰ν•˜λŠ” κ°„λ‹¨ν•œ λ°©λ²•μž…λ‹ˆλ‹€.

stories.add('this story fails', () => React.createElement(() => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
}))

해킹없이 기본적으둜 μ§€μ›λ˜λŠ” 것이 훨씬 λ‚«λ‹€λŠ” 데 λ™μ˜ν•©λ‹ˆλ‹€. λ©”μΈν…Œμ΄λ„ˆλ“€λ„ λ™μ˜ν•œλ‹€λ©΄ PR을 생각 ν•΄λ‚Ό μ‹œκ°„μ„ 찾을 μˆ˜μžˆμ„ κ²ƒμž…λ‹ˆλ‹€.

@ kevin940726 ꡉμž₯ ν•  κ²ƒμž…λ‹ˆλ‹€ πŸ‘

λ‚΄ .storybook / config.js에 λ‹€μŒμ„ μΆ”κ°€ν•˜λ©΄ λ‚˜λ₯Ό μœ„ν•΄ μΌν–ˆμŠ΅λ‹ˆλ‹€.

addDecorator((Story) => <Story />)

λ‚˜λŠ” λΉ„μŠ·ν•œ 것을 κ΅¬ν˜„ν–ˆμ§€λ§Œ λ§Žμ€ μ• λ“œμ˜¨, 특히 prop λ¬Έμ„œλ₯Ό 좜λ ₯ν•˜κΈ°μœ„ν•œ addon-infoλ₯Ό 망가 λœ¨λ¦°λ‹€ κ³  μƒκ°ν•©λ‹ˆλ‹€. λ‚˜λŠ” 후크λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 κ·Έ 정보보닀 더 μ€‘μš”ν•˜λ‹€κ³  κ²°μ •ν–ˆμ§€λ§Œ (μ–΄μ¨Œλ“  λ³„λ„λ‘œ μƒμ„±ν•˜λ―€λ‘œ) 일반적으둜 λͺ¨λ“  μŠ€ν† λ¦¬ 뢁에 적용될 것 같지 μ•ŠμŠ΅λ‹ˆλ‹€. λ°˜μ‘ μ™ΈλΆ€μ—μ„œ api와 같은 후크λ₯Ό μ–΄λ–»κ²Œ μ²˜λ¦¬ν• μ§€ λͺ¨λ¦…λ‹ˆλ‹€.

μ†ŒμŠ€ μ½”λ“œμ—μ„œ κ΅¬ν˜„ν•˜λ €κ³ ν–ˆμ§€λ§Œ storyshot-addon으둜 μž‘λ™ν•˜λ„λ‘ λ§Œλ“œλŠ” 데 어렀움을 κ²ͺκ³  μžˆμŠ΅λ‹ˆλ‹€. λͺ¨λ“  μŠ€λƒ… μƒ·μ—μ„œ μƒˆλ‘œμš΄ λΆ€λͺ¨ λ…Έλ“œλ₯Ό μƒμ„±ν•˜λ―€λ‘œ 이것이 큰 변화라고 μƒκ°ν•©λ‹ˆλ‹€. μ‚¬μš©μžκ°€ μ„ νƒν•œ λ Œλ”λŸ¬λ₯Ό μ œμ–΄ ν•  수 μ—†κΈ° λ•Œλ¬Έμ— ν•œ μˆ˜μ€€ 깊이둜 λ›°μ–΄λ“€ 수 μ—†μŠ΅λ‹ˆλ‹€. μ†”λ£¨μ…˜μ„ λ¬Έμ„œν™”ν•˜κ³  μ‚¬μš©μžκ°€ 옡트 인 ν•  μˆ˜μžˆλŠ” λ„μš°λ―Έ APIλ₯Ό 제곡 ν•  μˆ˜μžˆλŠ” λ“± λ‹€λ₯Έ λŒ€μ•ˆμ„ 생각해야 ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

λ‚΄ .storybook / config.js에 λ‹€μŒμ„ μΆ”κ°€ν•˜λ©΄ λ‚˜λ₯Ό μœ„ν•΄ μΌν–ˆμŠ΅λ‹ˆλ‹€.

addDecorator((Story) => <Story />)

@emjaksa μ΄κ²ƒμ˜ 일뢀λ₯Ό μ œκ³΅ν•΄ μ£Όμ‹œκ² μŠ΅λ‹ˆκΉŒ?

이 λ¬Έμ œμ— λŒ€ν•œ ν•΄κ²° 방법은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

import React, { useState } from 'react';

import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withInfo } from '@storybook/addon-info';

import SelectField from 'component-folder/SelectField';

/**
 * special wrapper that replaces the `value` and `onChange` properties to make
 * the component work hooks
 */
const SelectFieldWrapper = props => {
  const [selectValue, setValue] = useState('');

  return (
    <SelectField
      {...props}
      value={selectValue}
      onChange={e => {
        setValue(e.target.value);
        action('onChange')(e.target.value);
      }}
    />
  );
};
SelectFieldWrapper.displayName = 'SelectField';

const info = {
  text: SelectField.__docgenInfo.description,
  propTables: [SelectField],
  propTablesExclude: [SelectFieldWrapper]
};

storiesOf('Controls/SelectField', module)
  .addDecorator(withInfo)

  // ... some stories

  // this example uses a wrapper component to handle the `value` and `onChange` props, but it should
  // be interpreted as a <SelectField> component
  .add('change handler', () => 
    <SelectFieldWrapper
      id="employment-status"
      placeholder="some placeholder"
      value={//selectValue}
      onChange={e => {
          // setValue(e.target.value);
      }}
    />, { info });

λ‚΄κ°€ μ–ΈκΈ‰ν–ˆλ“―μ΄ μ—¬μ „νžˆ ν•΄κ²° λ°©λ²•μ΄μ§€λ§Œ μž‘λ™ν•˜λ©° info μ• λ“œμ˜¨μ„ μ€‘λ‹¨ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. (λ‹€λ₯Έ μ• λ“œμ˜¨μ„ ν…ŒμŠ€νŠΈν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€)

μŠ€ν† λ¦¬ λ°μ½”λ ˆμ΄ν„°λ₯Ό λ§ˆμ§€λ§‰μ— μΆ”κ°€ ν•œ 경우 λ‚΄ 정보 μ• λ“œμ˜¨μ΄ μ€‘λ‹¨λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

import React from 'react'

import { configure, addDecorator } from '@storybook/react'
import { withInfo } from '@storybook/addon-info'
import { withKnobs } from '@storybook/addon-knobs'

const req = require.context('../src', true, /\.stories\.js$/)

function loadStories() {
  req.keys().forEach(filename => req(filename))
}

addDecorator(
  withInfo({
    header: false,
  }),
)
addDecorator(withKnobs)
addDecorator((Story) => (
    <Story />
))

configure(loadStories, module)

또 λ‹€λ₯Έ ν•΄κ²° 방법 :

UseState λΌλŠ” μœ ν‹Έλ¦¬ν‹° ꡬ성 μš”μ†Œκ°€ λ‹€μŒκ³Ό 같이 μ •μ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

export const UseState = ({ render, initialValue }) => {
    const [ variable, setVariable ] = useState(initialValue)
    return render(variable, setVariable)
}

그리고 λ‚˜λŠ” 이것을 λ‹€μŒκ³Ό 같은 μ΄μ•ΌκΈ°μ—μ„œ μ‚¬μš©ν•©λ‹ˆλ‹€.

.add('use state example', () => (
    <UseState
        initialValue={0}
        render={(counter, setCounter) => (            
            <button onClick={() => setCounter(counter + 1)} >Clicked {counter} times</button>
        )}
    />
)

ν•˜μ§€λ§Œ @ kevin940726 의 ν•΄κ²° 방법이 κ°€μž₯ μ’‹μŠ΅λ‹ˆλ‹€.

λ‚˜λŠ” μƒνƒœ 변화에 λŒ€ν•œ 이야기λ₯Ό λ‹€μ‹œ λ Œλ”λ§ ν•  수 μ—†μŠ΅λ‹ˆλ‹€. κ°•μ œ λ‹€μ‹œ λ Œλ”λ§λ„ μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

이 μ½”λ“œλŠ” React Hooksμ—μ„œ 잘 μž‘λ™ν•˜μ§€λ§Œ

storiesOf("Dropdowns", module).add("Basic", () => <DropdownBasicStory />);

@storybook/addon-info μ—μ„œλŠ” 잘 μž‘λ™ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
Screenshot 2019-05-13 14 35 10

이둜 인해이 ν•΄κ²° 방법을 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€. μ–΄λ–€ 아이디어? μŠ€ν† λ¦¬ 뢁 5.1.0- 베타 .0

@artyomtrityak addon-info μ—μ„œ propTables μ˜΅μ…˜μ„ μž¬μ •μ˜ ν•  수 μžˆμŠ΅λ‹ˆκΉŒ? λ‹€κ°€μ˜€λŠ” addon-docs μ—μ„œμ΄ 문제λ₯Ό μ œλŒ€λ‘œ ν•΄κ²°ν•  κ²ƒμž…λ‹ˆλ‹€ : https://medium.com/storybookjs/storybook-docs-sneak-peak-5be78445094a

@shilman μ—λŠ” <DropdownBasicStory /> μ†ŒμŠ€λ„ ν¬ν•¨λ©λ‹ˆκΉŒ?

@artyomtrityak λ‚΄κ°€ 무엇을 ν•  수 μžˆλŠ”μ§€ λ³Όκ²Œμš” πŸ˜†

μ•ˆλ…•ν•˜μ„Έμš” μ—¬λŸ¬λΆ„! 졜근이 λ¬Έμ œμ—μ„œ λ§Žμ€ 일이 μΌμ–΄λ‚˜μ§€ μ•Šμ€ 것 κ°™μŠ΅λ‹ˆλ‹€. μ—¬μ „νžˆ 질문, 의견 λ˜λŠ” 버그가 있으면 토둠을 κ³„μ†ν•˜μ‹­μ‹œμ˜€. μ•ˆνƒ€κΉκ²Œλ„ λͺ¨λ“  문제λ₯Ό λ‹€λ£° μ‹œκ°„μ΄ μ—†μŠ΅λ‹ˆλ‹€. μš°λ¦¬λŠ” 항상 기여에 μ—΄λ € μžˆμœΌλ―€λ‘œ 도움이 ν•„μš”ν•˜λ©΄ ν’€ μš”μ²­μ„ λ³΄λ‚΄μ£Όμ‹­μ‹œμ˜€. λΉ„ν™œμ„± λ¬Έμ œλŠ” 30 일 후에 λ§ˆκ°λ©λ‹ˆλ‹€. 감사!

λ‹€λ₯Έ μ‚¬λžŒμ΄ μ—¬μ „νžˆ 이것을 μ–»λŠ”λ‹€λ©΄ λ¬Έμ œκ°€ κΈ°λŠ₯ ꡬ성 μš”μ†Œμ— μ†Œν’ˆ μœ ν˜•μ„ μΆ”κ°€ν•˜λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€.

const Dropdown = () => (
  // component content
);

Dropdown.propTypes = {
  ...
}

μ–΄λ–€ 이유둜 .propTypes 주석을 달면 μ œλŒ€λ‘œ μž‘λ™ν•˜λŠ” 것 κ°™μŠ΅λ‹ˆλ‹€. λ¬Έμ„œ λ˜λŠ” λ‹€λ₯Έ 것을 μœ„ν•΄ νŒŒμ‹±ν•˜λŠ” μ†Œν’ˆ μœ ν˜•κ³Ό κ΄€λ ¨λœ λ¬Έμ œμΈμ§€ ν™•μ‹€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

ν›„ν¬κ°€μžˆλŠ” ꡬ성 μš”μ†Œμ˜ μ†ŒμŠ€ μ½”λ“œλ₯Ό ν‘œμ‹œν•˜λ €λ©΄

function WithState({ children }) {
  const [value, setValue] = React.useState([]);

  return React.cloneElement(children, {
    value,
    onChange: event => setValue(event.target.value),
  });
}

storiesOf(`${__dirname}`, module).add('Basic', () => (
  <WithState>
    <select value="[parent state]" onChange="[parent func]">
      <option value="Australia">Australia</option>
      <option value="Cambodia">Cambodia</option>
    </select>
  </WithState>
));

μ•ˆλ…•ν•˜μ„Έμš” μ—¬λŸ¬λΆ„! 졜근이 λ¬Έμ œμ—μ„œ λ§Žμ€ 일이 μΌμ–΄λ‚˜μ§€ μ•Šμ€ 것 κ°™μŠ΅λ‹ˆλ‹€. μ—¬μ „νžˆ 질문, 의견 λ˜λŠ” 버그가 있으면 토둠을 κ³„μ†ν•˜μ‹­μ‹œμ˜€. μ•ˆνƒ€κΉκ²Œλ„ λͺ¨λ“  문제λ₯Ό λ‹€λ£° μ‹œκ°„μ΄ μ—†μŠ΅λ‹ˆλ‹€. μš°λ¦¬λŠ” 항상 기여에 μ—΄λ € μžˆμœΌλ―€λ‘œ 도움이 ν•„μš”ν•˜λ©΄ ν’€ μš”μ²­μ„ λ³΄λ‚΄μ£Όμ‹­μ‹œμ˜€. λΉ„ν™œμ„± λ¬Έμ œλŠ” 30 일 후에 λ§ˆκ°λ©λ‹ˆλ‹€. 감사!

μœ„μ˜ React.createElement 및 <Story /> μ ‘κ·Ό λ°©μ‹μ—μ£Όμ˜ν•˜μ‹­μ‹œμ˜€. μ–΄λ–€ κ²½μš°μ—λŠ” μ˜³μ§€ μ•Šλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€. μ‹€μ œλ‘œ λ°˜μ‘ ꡬ성 μš”μ†Œ (μŠ€ν† λ¦¬ ꡬ성 μš”μ†Œ)λ₯Ό λ°˜ν™˜ν•˜λŠ” λŒ€μ‹  λ‹€μ‹œ λ§Œλ“€κ³  있기 λ•Œλ¬Έμž…λ‹ˆλ‹€. λ Œλ”λ§ 된 μš”μ†Œ (λ¬Έμ„œμ— ν‘œμ‹œλœλŒ€λ‘œ config.js μŠ€ν† λ¦¬ λΆμ—μ„œ story() 쀑)

예λ₯Ό λ“€μ–΄ μŠ€ν† λ¦¬μ— ꡬ성 μš”μ†Œ 둜컬 μƒνƒœ μ—…λ°μ΄νŠΈλ₯Ό νŠΈλ¦¬κ±°ν•˜λŠ” Knobs.button κ°€μžˆλŠ” 경우 λ²„νŠΌμ„ 클릭 ν•œ ν›„ ν˜„μž¬ ꡬ성 μš”μ†Œμ˜ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•˜λŠ” λŒ€μ‹  μƒˆ μŠ€ν† λ¦¬ ꡬ성 μš”μ†Œλ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

λ…ΈλΈŒ λ²„νŠΌμ„ 클릭 ν•œ ν›„ 값이 μ—…λ°μ΄νŠΈλ˜μ§€ μ•ŠλŠ”μ΄ κ°„λ‹¨ν•œ μ½”λ“œ 쑰각으둜 λ‚΄ 가정을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

storiesOf('Test', module).add('with text', () => {
  return React.createElement(() => {
    const [value, setValue] = React.useState(1);

    Knobs.button('Increase', () => setValue(prev => prev + 1));

    return <span>{value}</span>;
  });
});

μž‘λ™ν•˜λ„λ‘ν•˜λ €λ©΄ λ‹€μŒμ„ μˆ˜ν–‰ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

function MyStory() {
  const [value, setValue] = React.useState(1);

  Knobs.button('Increase', () => setValue(prev => prev + 1));

  return <span>{value}</span>;
}

storiesOf('Test', module).add('with text', () => <MyStory />);

λ”°λΌμ„œ ν•΄κ²° λ°©λ²•μœΌλ‘œ 래퍼λ₯Ό λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€.

import {
  DecoratorParameters,
  Story,
  StoryDecorator,
  storiesOf as origStoriesOf,
} from '@storybook/react';

class ReactStory {
  private readonly story: Story;

  constructor(name: string, module: NodeModule) {
    this.story = origStoriesOf(name, module);
  }

  public add(
    storyName: string,
    Component: React.ComponentType,
    parameters?: DecoratorParameters
  ): this {
    this.story.add(storyName, () => <Component />, parameters);
    return this;
  }

  public addDecorator(decorator: StoryDecorator): this {
    this.story.addDecorator(decorator);
    return this;
  }

  public addParameters(parameters: DecoratorParameters): this {
    this.story.addParameters(parameters);
    return this;
  }
}

new ReactStory('Test', module).add('with text', () => {
  const [value, setValue] = React.useState(1);

  Knobs.button('Increase', () => setValue(prev => prev + 1));

  return <span>{value}</span>;
});

μ„ νƒμ μœΌλ‘œ μŠ€ν† λ¦¬ 뢁 APIλ₯Ό λͺ¨λ°©ν•˜μ—¬ λ¦¬νŒ©ν† λ§ μ˜€λ²„ ν—€λ“œλ₯Ό 쀄이고 λ‚˜μ€‘μ— λ¬Έμ œκ°€ ν•΄κ²°λ˜λ©΄ λ‹€μ‹œ μ „ν™˜ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

export function storiesOf(name: string, module: NodeModule) {
  return new ReactStory(name, module);
}

λ‚΄κ°€ 틀렸을 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. κ·Έλ ‡λ‹€λ©΄ μ•Œλ €μ£Όμ„Έμš”. 맀우 감사!

λ”°λ‹€ !! 방금이 문제λ₯Ό μ°Έμ‘°ν•˜λŠ” PR # 7571이 포함 된 https://github.com/storybookjs/storybook/releases/tag/v5.2.0-beta.10 을 λ¦΄λ¦¬μŠ€ν–ˆμŠ΅λ‹ˆλ‹€. μ§€κΈˆ μ—…κ·Έλ ˆμ΄λ“œν•˜μ—¬ μ‚¬μš©ν•΄λ³΄μ„Έμš”!

이 프리 λ¦΄λ¦¬μ¦ˆλŠ” @next NPM νƒœκ·Έμ—μ„œ 찾을 수 μžˆμŠ΅λ‹ˆλ‹€.

이 문제λ₯Ό λ§ˆλ¬΄λ¦¬ν•©λ‹ˆλ‹€. ν•  일이 더 λ§Žλ‹€κ³  μƒκ°λ˜λ©΄ λ‹€μ‹œμ—¬μ‹­μ‹œμ˜€.

@shilman κ°μ‚¬ν•©λ‹ˆλ‹€ !! μ‚¬μš© 방법에 λŒ€ν•œ μ½”λ“œ 슀 λ‹ˆνŽ« μ˜ˆμ œκ°€ μžˆμŠ΅λ‹ˆκΉŒ?

@shilman κ°μ‚¬ν•©λ‹ˆλ‹€!
ν•˜μ§€λ§Œ μ—¬μ „νžˆ 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€.
React Hook "useState"λŠ” React ν•¨μˆ˜ μ»΄ν¬λ„ŒνŠΈ λ‚˜ μ»€μŠ€ν…€ React Hook ν•¨μˆ˜κ°€ μ•„λ‹Œ ν•¨μˆ˜ "component"μ—μ„œ ν˜ΈμΆœλ©λ‹ˆλ‹€.

@shiranZe 졜근 5.2 베타λ₯Ό μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆκΉŒ?

@shilman λ§žμ•„μš” ...

stories / components / Menu / index.js의 λ‚΄ μ½”λ“œ :

const ꡬ성 μš”μ†Œ = () => {
const [buttonEl, setButtonEl] = useState (null)

const handleClick = (event) => {
    console.log('the event', event)
    setButtonEl(event.currentTarget)
}

return (
    <>
        <IconButton iconName={"menu-hamburger"} size="s" onClick={handleClick} color={"midGray"}></IconButton>

내보내기 κΈ°λ³Έκ°’ [readme, ꡬ성 μš”μ†Œ];

그리고 stories / components / index.jsμ—μ„œ :
storiesOf('Components', module) .addDecorator(withKnobs) .add('Text', withReadme(...Menu))

@shiranZe λ‚˜λŠ” 그것이 addon-readme 의 문제라고 μƒκ°ν•©λ‹ˆλ‹€.

λ‹΅μž₯을 보내 μ£Όμ‹  @shilman μ—κ²Œ κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€. 그게 λ¬Έμ œμΈμ§€ λͺ¨λ₯΄κ² μ–΄μš”. addon-readme 없이 μ‹œλ„ν–ˆμ§€λ§Œ μ—¬μ „νžˆ λ™μΌν•œ 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€. 5.2 베타 μŠ€ν† λ¦¬ 뢁의 URL이 μžˆμŠ΅λ‹ˆκΉŒ?
https://storybooks-official.netlify.com/ (other | demo / Button)을 μ‚΄νŽ΄ λ³΄λ €κ³ ν–ˆμŠ΅λ‹ˆλ‹€.
ν•˜μ§€λ§Œ λ°˜μ‘ ν›„ν¬κ°€μžˆλŠ” 예제λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.

λ‚˜λŠ” μ—¬μ „νžˆ 5.2.0-beta.30 에 λ¬Έμ œκ°€ 있으며 λ…ΈλΈŒ μ• λ“œμ˜¨μ„ μ‚¬μš©ν•˜κ³  있기 λ•Œλ¬Έμ— ν•΄κ²° 방법이 λ‚˜μ—κ²Œ νš¨κ³Όκ°€ μ—†λ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

@shiranZe 우리의 netlify 배포가 κΊΌμ Έ μžˆμ§€λ§Œ (cc @ndelangen) next 브랜치의 μ €μž₯μ†Œλ₯Ό ν™•μΈν•˜κ³  κ±°κΈ°μ—μ„œ μ‹œλ„ν•΄ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

@sourcesoft μ†μž‘μ΄ κ΄€λ ¨ "미리보기 후크"문제 (cc @Hypnosphi)λŠ”μ΄ λ¬Έμ œμ™€ 관련이 μ—†λ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€. 곡톡점은 후크 κ°œλ…λΏμž…λ‹ˆλ‹€.

@shilman κ°μ‚¬ν•©λ‹ˆλ‹€, λ‚˜λŠ” 당신이 μ˜³λ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€. Btw μ‹œν–‰ 착였둜 μˆ˜μ •ν•˜λŠ” 방법을 μ•Œμ•„ λ‚΄κ³  μ‚¬μš©μž μ •μ˜ 후크λ₯Ό λ³€κ²½ν–ˆμŠ΅λ‹ˆλ‹€.

export const useField = (id, updateField) => {
  const onChange = useCallback((event) => {
    const {
      target: { value },
    } = e;
    updateField(id, value);
  };

  return {
    onChange,
  };
}, []);

...에

export const useField = (id, updateField) => {
  const onChange = (event) => {
    const {
      target: { value },
    } = e;
    updateField(id, value);
  };

  return {
    onChange,
  };
};

기본적으둜 μ—¬κΈ°μ„œ useCallback μ‚¬μš©μ„ μ œκ±°ν–ˆμŠ΅λ‹ˆλ‹€. 첫 번째 버전이 μœ νš¨ν•œ 후크인지 ν™•μ‹€ν•˜μ§€ μ•Šμ§€λ§Œ μ΄μ „μ—λŠ” μž‘λ™ν–ˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ 였λ₯˜κ°€ Hooks can only be called inside the body of a function component λ˜κ±°λ‚˜ μ—¬λŸ¬ λ²„μ „μ˜ Reactκ°€μžˆλŠ” 경우 μ•½κ°„ ν˜Όλž€ μŠ€λŸ½μŠ΅λ‹ˆλ‹€.

μœ„μ˜ μ˜ˆμ—μ„œ useCallback μ œκ±°ν•œ ν›„ μ‹€μ œλ‘œ useField 에 후크 등둝을 μ‚¬μš© ν•˜κ³  μžˆμŠ΅λ‹ˆκΉŒ? πŸ€”

μ†μž‘μ΄κ°€μžˆλŠ” 후크λ₯Ό μ‚¬μš©ν•˜λŠ” 데 λ¬Έμ œκ°€μžˆλŠ” 것 κ°™μŠ΅λ‹ˆλ‹€. λˆ„κ΅¬λ‚˜ κ°„λ‹¨ν•œ μž¬ν˜„μ„ 제곡 ν•  수 μžˆλ‹€λ©΄ 기꺼이 μ‚΄νŽ΄ λ³΄κ² μŠ΅λ‹ˆλ‹€.

@shilman 이전에 κ²Œμ‹œ ν•œ 예제λ₯Ό μ‚¬μš©ν•΄ λ³΄μ…¨μŠ΅λ‹ˆκΉŒ? λ‹€μŒμ€ 슀 λ‹ˆνŽ«μž…λ‹ˆλ‹€.

storiesOf('Test', module).add('with text', () => {
  return React.createElement(() => {
    const [value, setValue] = React.useState(1);

    Knobs.button('Increase', () => setValue(prev => prev + 1));

    return <span>{value}</span>;
  });
});

이전 APIλ₯Ό μ‚¬μš©ν•˜μ§€λ§Œ ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ μ΅œμ‹  API둜 μ‰½κ²Œ λ³€ν™˜ ν•  수 μžˆμ–΄μ•Όν•©λ‹ˆλ‹€. μ›λž˜ κ²Œμ‹œλ¬Ό μ—μ„œ μ˜ˆμƒλ˜λŠ” λ™μž‘μ„ 찾을 수 μžˆμŠ΅λ‹ˆλ‹€.

@zhenwenc 참고둜 μ½”λ“œλŠ” μž‘λ™ν•˜μ§€λ§Œ react-docgen-typescript-webpack-plugin λ Œλ”λ§ 된 λ¬Έμ„œ μ‚¬μš©μ΄ μ€‘λ‹¨λ©λ‹ˆλ‹€.

ν•΄κ²° 방법은 μ•½κ°„ μ·¨μ•½ν•˜κ³  λ‹€λ₯Έ λΌμ΄λΈŒλŸ¬λ¦¬μ™€ μΆ©λŒν•˜λŠ” κ²ƒμ²˜λŸΌ λ³΄μž…λ‹ˆλ‹€. λŒ€μ•ˆμœΌλ‘œ λˆ„κ΅°κ°€ μ‚¬μš© κ²½ν—˜μ΄ μžˆμŠ΅λ‹ˆκΉŒ? : https://github.com/Sambego/storybook-state

μ—¬κΈ°μ—μ„œ 였λ₯˜ λ©”μ‹œμ§€λ₯Ό κ²€μƒ‰ν•˜λŠ” 경우 :

클래슀 ꡬ성 μš”μ†Œλ₯Ό ν›„ν¬κ°€μžˆλŠ” κΈ°λŠ₯ ꡬ성 μš”μ†Œλ‘œ λ³€κ²½ν•˜κ³  useState μ—μ„œ @storybook/addons μ—μ„œ 잘λͺ» κ°€μ Έμ˜¬ λ•Œμ΄ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‚˜λŠ”μ—μ„œ 온 그것을 ν•„μš”λ‘œ react λ³΄λ‹€λŠ” @storybook/addons ... μžλ™ κ°€μ Έ μ˜€κΈ°κ°€ μ‹€νŒ¨ν•©λ‹ˆλ‹€.

슀 λ‹ˆνŽ«μ— λŒ€ν•œ @orpheus 의 μš”μ²­μ— 응닡

λ‚΄ .storybook / config.js에 λ‹€μŒμ„ μΆ”κ°€ν•˜λ©΄ λ‚˜λ₯Ό μœ„ν•΄ μΌν–ˆμŠ΅λ‹ˆλ‹€.
addDecorator((Story) => <Story />)

@emjaksa μ΄κ²ƒμ˜ 일뢀λ₯Ό μ œκ³΅ν•΄ μ£Όμ‹œκ² μŠ΅λ‹ˆκΉŒ?

diff --git a/.storybook/config.js b/.storybook/config.js
--- a/.storybook/config.js
+++ b/.storybook/config.js
@@ -1,6 +1,9 @@
-import { configure } from '@storybook/react';
+import { configure, addDecorator } from '@storybook/react';
+import React from 'react';

 // automatically import all files ending in *.stories.js
 configure(require.context('../stories', true, /\.stories\.js$/), module);
+
+addDecorator((Story) => <Story />);

κ²°κ³Ό ( .storybook/config.js μ™„λ£Œ) :

import { configure, addDecorator } from '@storybook/react';
import React from 'react';

// automatically import all files ending in *.stories.js
configure(require.context('../stories', true, /\.stories\.js$/), module);

addDecorator((Story) => <Story />);

이것이 μŠ€ν† λ¦¬ 뢁 λ‚΄μ—μ„œ μž‘λ™ν•˜λŠ” λ°˜μ‘ 후크λ₯Ό μ–»λŠ” κ°€μž₯ 쒋은 방법인지 ν™•μ‹€ν•˜μ§€ μ•Šμ§€λ§Œ 자체 ꡬ성 μš”μ†Œ <Story /> ꡬ성 μš”μ†Œλ‘œ μŠ€ν† λ¦¬ 뢁을 λž˜ν•‘ν•˜λŠ” 것은 μ—¬κΈ°μ„œ "@storybook/react": "^5.2.6", 와 ν•¨κ»˜ μž‘λ™ν•©λ‹ˆλ‹€.

이 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κΈ° 전에 λ…ΈλΈŒ (예 : λΆ€μšΈ)λ₯Ό μ—…λ°μ΄νŠΈ ν•  λ•Œλ§ˆλ‹€ 첫 번째 λ Œλ”λ§μ—μ„œ μž‘λ™ν–ˆμ§€λ§Œ λ‚˜μ€‘μ— λ Œλ”λ§μ„ μ€‘μ§€ν–ˆμŠ΅λ‹ˆλ‹€. μœ„μ˜ μ†”λ£¨μ…˜μ΄ μˆ˜μ •λ˜μ—ˆμŠ΅λ‹ˆλ‹€. Btw, μŠ€ν† λ¦¬ λΆμ—μ„œ 리 μ•‘νŠΈ 훅을 μ‚¬μš©ν•˜λŠ” 것이 쒋은 방법인지 λͺ¨λ₯΄κ² μŠ΅λ‹ˆλ‹€. κ°€λŠ₯ν•˜λ©΄ λͺ¨λ“  것을 μ‘°λ‘±ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.이 방법이 더 μ’‹μŠ΅λ‹ˆλ‹€.

버그 μ„€λͺ…

μŠ€ν† λ¦¬μ—μ„œ 직접 후크λ₯Ό μ‚¬μš©ν•  수 μ—†μœΌλ©° Hooks can only be called inside the body of a function component μ‹€νŒ¨ν•©λ‹ˆλ‹€.

μž¬ν˜„ν•˜λ €λ©΄

예제 μ½”λ“œ :

import React from 'react'

import { storiesOf } from '@storybook/react'

const stories = storiesOf('Hooks test', module)

const TestComponent: React.FC = () => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
}

stories.add('this story works', () => <TestComponent />)

stories.add('this story fails', () => {
  const [state, setState] = React.useState(5)
  return (
    <button onClick={() => setState(state + 1)}>
      {state}
    </button>
  )
})

μ˜ˆμƒλ˜λŠ” 행동
첫 번째 μŠ€ν† λ¦¬λŠ” μ •μƒμ μœΌλ‘œ μž‘λ™ν•˜κ³  두 번째 μŠ€ν† λ¦¬λŠ” 초기 λ Œλ”λ§μ—μ„œ μ‹€νŒ¨ν•©λ‹ˆλ‹€.

버전
@storybook/[email protected]
[email protected]
[email protected]

=====================================

κΈ°λŠ₯적 ꡬ성 μš”μ†Œλ₯Ό λ§Œλ“œλŠ” 데 ν™”μ‚΄ν‘œ κΈ°λŠ₯을 μ‚¬μš©ν•˜μ§€ λ§ˆμ‹­μ‹œμ˜€.
μ•„λž˜ 예 쀑 ν•˜λ‚˜λ₯Ό μˆ˜ν–‰ν•˜μ‹­μ‹œμ˜€.

function MyComponent(props) {
  const [states, setStates] = React.useState({ value: '' });

  return (
    <input
      type="text"
      value={states.value}
      onChange={(event) => setStates({ value: event.target.value })}
    />
  );
}

λ˜λŠ”

//IMPORTANT: Repeat the function name

const MyComponent = function MyComponent(props) { 
  const [states, setStates] = React.useState({ value: '' });

  return (
    <input
      type="text"
      value={states.value}
      onChange={(event) => setStates({ value: event.target.value })}
    />
  );
};

"ref"(μ•„λ§ˆ λ£¨ν”„μ—μ„œ)에 λ¬Έμ œκ°€μžˆλŠ” 경우 해결책은 forwardRef ()λ₯Ό μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

// IMPORTANT: Repeat the function name
// Add the "ref" argument to the function, in case you need to use it.

const MyComponent = React.forwardRef( function MyComponent(props, ref) {
  const [states, setStates] = React.useState({ value: '' });

  return (
    <input
      type="text"
      value={states.value}
      onChange={(event) => setStates({ value: event.target.value })}
    />
  );
});

MDXμ—μ„œ μ–΄λ–»κ²Œμ΄ μž‘μ—…μ„ μˆ˜ν–‰ ν•  수 μžˆμŠ΅λ‹ˆκΉŒ?

ν•˜μ§€λ§Œ μ—¬μ „νžˆ 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€.
React Hook "useState"λŠ” React ν•¨μˆ˜ μ»΄ν¬λ„ŒνŠΈ λ‚˜ μ»€μŠ€ν…€ React Hook ν•¨μˆ˜κ°€ μ•„λ‹Œ ν•¨μˆ˜ "component"μ—μ„œ ν˜ΈμΆœλ©λ‹ˆλ‹€.
https://github.com/storybookjs/storybook/issues/5721#issuecomment -518225880

λ‚˜λŠ”μ΄ λ˜‘κ°™μ€ 문제λ₯Ό κ²ͺκ³ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. μˆ˜μ • 사항은 λͺ…λͺ… 된 μŠ€ν† λ¦¬ 내보내기λ₯Ό λŒ€λ¬Έμžλ‘œ μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

 'react'μ—μ„œ React κ°€μ Έ 였기;
 './Foo'μ—μ„œ Foo κ°€μ Έ 였기;

 export default {
 제λͺ© : 'Foo';
 };

 내보내기 const κΈ°λ³Έ = () => <Foo />

λ¬Έμ„œμ— λ”°λ₯΄λ©΄ λŒ€λ¬Έμž μ‚¬μš©μ΄ ꢌμž₯ λ˜μ§€λ§Œ κ²½κ³ λ₯Ό μ—†μ• λ €λ©΄ ν•„μš”ν•©λ‹ˆλ‹€.

제 경우 λ¬Έμ œλŠ” μ œκ°€ μ‚¬μš©ν•˜κ³ μžˆλŠ” 후크λ₯Ό κ°€μ Έ μ˜€λŠ” 것을 μžŠμ—ˆλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.

λ‚΄ .storybook / config.js에 λ‹€μŒμ„ μΆ”κ°€ν•˜λ©΄ λ‚˜λ₯Ό μœ„ν•΄ μΌν–ˆμŠ΅λ‹ˆλ‹€.

addDecorator((Story) => <Story />)

@emjaksa κ³ λ§™μŠ΅λ‹ˆλ‹€! Storybook v5.3μ—μ„œ ν…ŒμŠ€νŠΈ 및 잘 μž‘λ™ν•©λ‹ˆλ‹€.

λ™μΌν•œ λ¬Έμ œκ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. κΈ°λŠ₯적 λ°˜μ‘ ꡬ성 μš”μ†Œμ— useState(value) 있고 value κ°€ Knobsμ—μ„œ μƒˆ 값을 λ‹€μ‹œ λ Œλ”λ§ν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.이 λ¬Έμ œλŠ” useState둜 인해 λ°œμƒν•©λ‹ˆλ‹€.

React λ¬Έμ„œμ—μ„œ :
Screen Shot 2020-08-07 at 19 00 04

μž‘μ—… μ†”λ£¨μ…˜ Storybook v5.3 :

preview.js μ—…λ°μ΄νŠΈ

.storybook/preview.js
import React from 'react'; // Important to render the story
import { withKnobs } from '@storybook/addon-knobs';
import { addDecorator } from '@storybook/react';

addDecorator(withKnobs);
addDecorator(Story => <Story />); // This guy will re-render the story

λ˜ν•œ main.js μ—…λ°μ΄νŠΈ

.storybook/main.js
module.exports = {
  stories: ['../src/**/*.stories.jsx'],
  addons: [
    '@storybook/preset-create-react-app',
    '@storybook/addon-knobs/register', // Attention to this guy
    '@storybook/addon-actions',
    '@storybook/addon-links',
  ],
  webpackFinal: async config => {
    return config;
  },
};


❓
이 호좜 λ°μ½”λ ˆμ΄ν„°κ°€ 후크λ₯Ό μž‘λ™μ‹œν‚€λŠ” 이유λ₯Ό μ•„λŠ” μ‚¬λžŒμ΄ μžˆμŠ΅λ‹ˆκΉŒ?

```tsx
SomeComponent.decorators = [(μŠ€ν† λ¦¬) => ]

@tmikeschu 그것은 <Story /> κ°€ 후크에 ν•„μš”ν•œ React.createElement μ½”λ“œλ₯Ό λž˜ν•‘ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

@shilman κ°μ‚¬ν•©λ‹ˆλ‹€!

이 νŽ˜μ΄μ§€κ°€ 도움이 λ˜μ—ˆλ‚˜μš”?
0 / 5 - 0 λ“±κΈ‰