描述问题
在Create React App TypeScript项目中,我有一个FunctionComponent,它具有以下prop接口:
type PickedAntButtonProps = Pick<
ComponentProps<typeof AntButton>,
'size' | 'disabled' | 'icon' | 'loading' | 'onClick'
>;
export interface DefaultButtonProps extends PickedAntButtonProps {
/**
* Set primary style
*/
primary?: boolean;
/**
* Fit button to parent width
*/
stretch?: boolean;
}
Storybook无法正确识别这些props,只生成了 primary
、 stretch
和 onClick
的Controls & Doc Table条目,而控件似乎是Object定义( OnClick: {}
)。
我不认为这是一个 react-docgen-typescript
的bug,因为我直接在组件上运行了parse,它似乎生成了正确的文档。
重现问题
前提条件:
- CRA 4.0.3
- 将
antd
添加到项目并设置(将antd作为依赖项并将css导入添加到index.ts) - 将Storybook添加到项目中
出现问题的组件:
import { ComponentProps, FunctionComponent } from 'react';
import { Button as AntButton } from 'antd';
type PickedAntButtonProps = Pick<
ComponentProps<typeof AntButton>,
'size' | 'disabled' | 'icon' | 'loading' | 'onClick'
>;
export interface DefaultButtonProps extends PickedAntButtonProps {
/**
* Set primary style
*/
primary?: boolean;
/**
* Fit button to parent width
*/
stretch?: boolean;
}
/**
* Default button
*/
const DefaultButton: FunctionComponent<DefaultButtonProps> = ({
children,
primary = true,
stretch = false,
...props
}) => {
return (
<AntButton block={stretch} type={primary ? 'primary' : undefined} {...props} data-testid="default-button">
{children}
</AntButton>
);
};
export default DefaultButton;
该组件返回并接受Ant Design按钮的一部分属性,并定义/重命名其中的一些属性。
运行storybook将显示以下Control和Doc Table中的props:primary
, stretch
, onClick
,而 onClick
具有一个JSON控件,其中包含 onClick: {}
。
我尝试直接在文件上运行 react-docgen-typescript
,结果如下:
const docgen = require('react-docgen-typescript');
const options = {
savePropValueAsString: true,
};
// Parse a file for docgen info
const docs = docgen.parse('./src/components/button/DefaultButton/DefaultButton.tsx', options);
console.log(JSON.stringify(docs, null, '\t'));
这看起来在我的眼中是正确的(-ish?),因此所有props都检测并正确解析,但Storybook似乎没有正确使用它们。
这可能是我这边的一个配置问题,或者是Storybook没有正确检测某些东西。我在下面进一步添加了我的 main.js
。也可能问题完全不同,与这个特殊的类型无关。
系统
System:
OS: macOS 10.15.7
CPU: (8) x64 Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
Binaries:
Node: 14.16.1 - ~/.nvm/versions/node/v14.16.1/bin/node
Yarn: 1.22.10 - ~/.nvm/versions/node/v14.16.1/bin/yarn
npm: 6.14.12 - ~/.nvm/versions/node/v14.16.1/bin/npm
Browsers:
Chrome: 90.0.4430.93
Safari: 14.0.3
npmPackages:
@storybook/addon-actions: ^6.2.9 => 6.2.9
@storybook/addon-essentials: ^6.2.9 => 6.2.9
@storybook/addon-links: ^6.2.9 => 6.2.9
@storybook/node-logger: ^6.2.9 => 6.2.9
@storybook/preset-ant-design: 0.0.2 => 0.0.2
@storybook/preset-create-react-app: ^3.1.7 => 3.1.7
@storybook/react: ^6.2.9 => 6.2.9
附加上下文
Storybook启动命令: start-storybook -p 6006 -s public
.storybook/main.js
:
我有各种配置来添加导入别名,基于口味的不同ant主题(这是一个白标签应用程序),以及ant design。我已经包含了几乎所有的东西,所以如果只是一个配置问题,那么找到问题应该会更容易。
const path = require('path');
/*
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["./src/components/*"]
}
}
}
*/
/*
and in tsconfig.json:
{
"extends": "./tsconfig.paths.json",
"compilerOptions": {
...
*/
const aliasPaths = require(path.join(__dirname, '/../tsconfig.paths.json'));
/*
const path = require('path');
const antThemes = {
a: require(path.join(__dirname, '/a/theme.ant.json')),
b: require(path.join(__dirname, '/b/theme.ant.json')),
c: require(path.join(__dirname, '/c/theme.ant.json')),
};
module.exports = {
antThemes,
};
*/
/*
{
"@primary-color": "#ff3131",
"@text-color": "rgba(0, 0, 0, 0.85)",
"@btn-primary-color": "#ffffff"
}
*/
const antThemes = require(path.join(__dirname, '/../src/settings/themes.js')).antThemes;
// SNIP - Removed some logic to determine which flavor to build as this app uses different themes for whitelabling
/*
* Auto Generate Path Aliases
* Using the tsconfig.paths.json config file, generate WebPack path aliases
*/
const matchAliasName = /(.*)\/\*/;
const matchAliasPath = /\.\/(.*)\/\*/;
const webpackPathAliases = Object.entries(aliasPaths.compilerOptions.paths)
.map((entry) => {
const aliasNameMatch = entry[0].match(matchAliasName);
const aliasPathMatch = entry[1][0].match(matchAliasPath);
return [aliasNameMatch[1], aliasPathMatch[1]];
})
.reduce((acc, entry) => {
acc[entry[0]] = path.join(__dirname, `/../${entry[1]}`);
return acc;
}, {});
/*
* Main Storybook Config
*/
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
{
name: '@storybook/preset-create-react-app',
options: {
craOverrides: {
fileLoaderExcludes: ['less'],
},
},
},
{
name: '@storybook/preset-ant-design',
options: {
lessOptions: {
modifyVars: antThemes[targetFlavor],
},
},
},
],
webpackFinal: (config) => {
/*
* Push REACT_APP_ environment variables into storybook webpack config.
* Otherwise only STORYBOOK_ variables would be available.
*/
const plugin = config.plugins.find((plugin) => plugin.definitions?.['process.env']);
const reactAppEnv = Object.keys(process.env)
.filter((name) => /^REACT_APP_/.test(name))
.reduce((acc, name) => {
acc[name] = `"${process.env[name]}"`;
return acc;
}, {});
plugin.definitions['process.env'] = {
...plugin.definitions['process.env'],
...reactAppEnv,
};
return {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
...webpackPathAliases,
},
},
};
},
};
preview.js
只进行了轻微修改,有两个装饰器用于Suspense和i18n Providers,以及一个globalTypes导出用于更改Locale的按钮:
import { Suspense, useEffect } from 'react';
import { I18nextProvider } from 'react-i18next';
import '../src/index.css'; // Load global styles
import AppConfig from '../src/settings/config'; // Load app flavor theme
import i18n from '../src/services/i18n/i18next';
/*
* Define Storybook globals
*/
export const globalTypes = {
locale: {
name: 'Locale',
description: 'i18n locale',
defaultValue: 'de',
toolbar: {
icon: 'globe',
items: [
{ value: 'en', right: '🇺🇸', title: 'English' },
{ value: 'de', right: '🇩🇪', title: 'Deutsch' },
],
},
},
};
/*
* react-i18next wrapper to change locale based on storybook global
*/
const I18nProviderWrapper = ({ children, i18n, locale }) => {
useEffect(() => {
i18n.changeLanguage(locale);
}, [i18n, locale]);
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
};
export const parameters = {
appFlavor: AppConfig.flavor,
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
/*
* Story wrappers
*/
export const decorators = [
(Story, { globals }) => (
<I18nProviderWrapper locale={globals.locale} i18n={i18n}>
<Story />
</I18nProviderWrapper>
),
(Story) => (
<Suspense fallback="loading">
<Story />
</Suspense>
),
];
6条答案
按热度按时间1sbrub3j1#
看起来我可能说得太早了,这(至少部分上)是一个
react-docgen-typescript
的问题,因为它似乎与:styleguidist/react-docgen-typescript#68和styleguidist/react-docgen-typescript#56有关。在上面的示例中,一个可能的修复/解决方法是将接口更改为:
这将生成正确的控件,但要冗长得多。
走更直接的
Pick<AntButtonProps, 'size' | 'disabled' | 'icon' | 'loading' | 'onClick' | 'className'>
的方法也不起作用。agyaoht72#
遇到相同的问题。如果我们不必重新创建仅链接到现有类型的类型,那就太好了。
idfiyjo83#
在这里也遇到了同样的问题。
tvokkenx4#
经过一些挖掘,这个对我有用:
zy1mlcev5#
遇到了类似的问题。
最后通过以下
filterProp
函数解决了:kkbh8khc6#
经过一些挖掘,这个方法对我有效:
我尝试了这个方法,但它并不适用于所有组件,例如按钮组件,但对于自动完成功能来说效果很好。