使用TypeScript+Storybook开发React库时stories文件编译报错求助
React组件库开发:Storybook故事文件编译报错
问题背景
使用TypeScript+Storybook开发React组件库,组件编译完成后导入使用正常,但编译过程中*.stories.tsx文件出现两处类型错误。
相关文件代码
Button.tsx
import React, {forwardRef, useEffect, useImperativeHandle, useState} from 'react' import ButtonPropTypes from './ButtonPropTypes' const Button = forwardRef((props: InferProps<typeof ButtonPropTypes>, buttonRef) => {...}
ButtonPropTypes.ts
import PropTypes from 'prop-types' export default { to: PropTypes.string, label: PropTypes.any, children: PropTypes.any, disabled: PropTypes.bool, loading: PropTypes.bool, onClick: PropTypes.func, state: PropTypes.object, type: PropTypes.string.isRequired, use: PropTypes.string.isRequired }
(为简化内容,省略了部分propTypes)
Button.stories.tsx
import React from 'react' import {Story, ComponentMeta} from '@storybook/react' import Button from './Button' import ButtonPropTypes from './ButtonPropTypes' import {InferProps} from 'prop-types' type ButtonPropsInterface = InferProps<typeof ButtonPropTypes> export default { title: 'RecylinkReactComponents/Button', component: Button } as ComponentMeta<typeof Button> const Template: Story<ButtonPropsInterface> = args => <Button {...args} /> // 错误1:类型'(args: any) => Element'与类型'Story<ButtonPropsInterface>'无共同属性 export const TestButton = Template.bind({}) // 错误2:类型'Story<ButtonPropsInterface>'上不存在属性'bind' TestButton.args = { label: 'Label', onClick: () => console.log('Clicked'), type: 'button', use: 'function' }
rollup.config.js
import resolve from '@rollup/plugin-node-resolve' import commonjs from '@rollup/plugin-commonjs' import typescript from '@rollup/plugin-typescript' import postcss from 'rollup-plugin-postcss' import dts from 'rollup-plugin-dts' import {terser} from 'rollup-plugin-terser' import peerDepsExternal from 'rollup-plugin-peer-deps-external' const packageJson = require('./package.json') export default [ { input: 'src/index.ts', output: [ { file: packageJson.main, format: 'cjs', sourcemap: true }, { file: packageJson.module, format: 'esm', sourcemap: true } ], plugins: [ peerDepsExternal(), resolve(), commonjs(), typescript({ tsconfig: './tsconfig.json', exclude: [ /\.test.((js|jsx|ts|tsx))$/, /\.stories.((js|jsx|ts|tsx|mdx))$/ ] }), postcss(), terser() ], context: 'window' }, { input: 'dist/esm/index.d.ts', output: [{file: 'dist/index.d.ts', format: 'esm'}], plugins: [dts()], external: [/\.css$/], context: 'window' } ]
tsconfig.json
{ "compilerOptions": { "target": "es5", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "jsx": "react", "module": "ESNext", "declaration": true, "declarationDir": "types", "sourceMap": true, "outDir": "dist", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "emitDeclarationOnly": true, "allowJs": true, "noImplicitAny": false } }
.storybook/main.js
module.exports = { "stories": [ "../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)" ], "addons": [ "@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-interactions" ], "framework": "@storybook/react", "core": { "builder": "@storybook/builder-webpack5" } }
错误信息
- 模板函数赋值行:类型
'(args: any) => Element'与类型'Story<ButtonPropsInterface>'无共同属性 Template.bind({})行:类型'Story<ButtonPropsInterface>'上不存在属性bind
解决方案
原因
Story是Storybook提供的包含属性和方法的对象类型,并非单纯的函数类型;而bind是JS函数的原生方法,Story对象并不具备该方法,因此直接赋值函数会触发类型不匹配错误。
修复方案
方案1:替换为StoryFn类型
StoryFn是Storybook专为故事模板函数设计的类型,直接替换即可匹配:
import React from 'react' // 替换Story为StoryFn import {StoryFn, ComponentMeta} from '@storybook/react' import Button from './Button' import ButtonPropTypes from './ButtonPropTypes' import {InferProps} from 'prop-types' type ButtonPropsInterface = InferProps<typeof ButtonPropTypes> export default { title: 'RecylinkReactComponents/Button', component: Button } as ComponentMeta<typeof Button> // 用StoryFn定义模板类型 const Template: StoryFn<ButtonPropsInterface> = args => <Button {...args} /> export const TestButton = Template.bind({}) TestButton.args = { label: 'Label', onClick: () => console.log('Clicked'), type: 'button', use: 'function' }
方案2:直接从组件导出Props类型(更推荐)
先修改Button.tsx,导出Props类型:
import React, {forwardRef, useEffect, useImperativeHandle, useState} from 'react' import ButtonPropTypes from './ButtonPropTypes' import {InferProps} from 'prop-types' // 导出组件Props类型 export type ButtonProps = InferProps<typeof ButtonPropTypes> const Button = forwardRef((props: ButtonProps, buttonRef) => {...} export default Button
再修改stories文件,直接导入使用:
import React from 'react' import {StoryFn, ComponentMeta} from '@storybook/react' // 导入组件及Props类型 import Button, {ButtonProps} from './Button' export default { title: 'RecylinkReactComponents/Button', component: Button } as ComponentMeta<typeof Button> const Template: StoryFn<ButtonProps> = args => <Button {...args} /> export const TestButton = Template.bind({}) TestButton.args = { label: 'Label', onClick: () => console.log('Clicked'), type: 'button', use: 'function' }
额外优化建议
- 避免同时使用prop-types和TypeScript,直接用TS接口定义组件Props,减少冗余且类型检查更严格
- 确保Storybook类型依赖完整,执行
npm install @types/storybook-react --save-dev或yarn add @types/storybook-react --dev
内容的提问来源于stack exchange,提问作者diegoolavarriau
相关产品推荐
相关产品推荐

