λ²κ·Έ μ€λͺ
μ€ν 리μμ μ§μ νν¬λ₯Ό μ¬μ©ν μ μμΌλ©° 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]
μ΄κ²μ΄ λ²κ·ΈμΈμ§ νμ€νμ§ μμ΅λλ€. 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
μμλ μ μλνμ§ μμ΅λλ€.
μ΄λ‘ μΈν΄μ΄ ν΄κ²° λ°©λ²μ μ¬μ©ν μ μμ΅λλ€. μ΄λ€ μμ΄λμ΄? μ€ν 리 λΆ 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 λ¬Έμμμ :
μμ μ루μ 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 κ°μ¬ν©λλ€!
κ°μ₯ μ μ©ν λκΈ
μ°λ¦¬λ κ°μ λ¬Έμ λ₯Ό κ²½ννμΌλ©° μ΄κ²μ μ°λ¦¬κ° ν΄κ²°νκΈ° μν΄ μννλ κ°λ¨ν λ°©λ²μ λλ€.
ν΄νΉμμ΄ κΈ°λ³Έμ μΌλ‘ μ§μλλ κ²μ΄ ν¨μ¬ λ«λ€λ λ° λμν©λλ€. λ©μΈν μ΄λλ€λ λμνλ€λ©΄ PRμ μκ° ν΄λΌ μκ°μ μ°Ύμ μμμ κ²μ λλ€.