React Storybook如何创建支持Knob切换图标的输入组件
实现方案
核心原因是组件图标容器位置硬编码了<Wallet />组件,完全没有读取传入的icon prop做匹配渲染,无论Storybook的knobs控件切换到什么值,这里永远固定渲染钱包图标。另外原代码的输入宽度自适应方法存在变量名笔误,输入时会触发JS报错,以下是完整可运行的修正方案:
1. 改造组件,新增图标映射逻辑
不要在JSX里写多分支判断选图标,维护一个图标名和组件的映射对象是成本最低的方案,后续加新图标也不用改渲染逻辑。修正后的组件代码如下:
import React, { useState, useEffect } from "react"; import "./input-withIcon.css"; import PropTypes from "prop-types"; import Wallet from "../../icons-jsx/Wallet"; import Account from "../../icons-jsx/Account"; // 后续新增图标先在这里导入 import Home from "../../icons-jsx/Home"; import Facebook from "../../icons-jsx/Facebook"; // *映射表的key必须和prop传入值、knobs选项值完全一致* const ICON_MAP = { wallet: Wallet, account: Account, home: Home, facebook: Facebook }; const InputWithIcon = ({ children, icon }) => { const [input, setInput] = useState(""); useEffect(() => { adjustInputSize(); }, [input]); const handleChange = (e) => { setInput(e.target.value); }; const adjustInputSize = () => { const inputEl = document.getElementById("iconInput"); // 修复原代码笔误:之前错误使用input.length、Input.style导致报错 if (inputEl.value.length < 1) { inputEl.style.width = "28px"; } else { inputEl.style.width = `${inputEl.value.length + 8}ch`; } }; // 从映射表匹配当前要渲染的图标,匹配不到返回null const CurrentIcon = ICON_MAP[icon] || null; return ( <div className="input-container1"> {/* 替换原硬编码的<Wallet />,动态渲染匹配到的图标 */} {CurrentIcon && ( <div className="icon-input__icon"> <CurrentIcon /> </div> )} <div className="input-container-withIcon"> <input type="text" required className={`iicon-input ${CurrentIcon ? "icon-input__with-icon" : ""}`} id="iconInput" value={input} onChange={handleChange} /> <label className="ibl-icon-label">{children}</label> </div> </div> ); }; InputWithIcon.propTypes = { children: PropTypes.node, // 新增图标后同步更新这里的枚举值 icon: PropTypes.oneOf(["wallet", "account", "home", "facebook"]), }; export default InputWithIcon;
2. 更新Storybook的knobs配置
把新增的图标选项补充到knobs的select配置里,保证选项值和映射表key一致即可:
import IconInput from "../components/fields/input-with-icon/InputWithIcon"; import { withKnobs, text, select } from "@storybook/addon-knobs"; import { withInfo } from "@storybook/addon-info"; export default { title: "Input Fields", parameters: { component: IconInput, decorators: [withKnobs, withInfo], info: { text: "带前置图标的输入框组件,支持切换图标类型、自定义标签和提示文案", }, "in-dsm": { id: "", componentPath: "../components/fields/input-with-icon/InputWithIcon", }, }, }; const iconOptions = { none: null, wallet: "wallet", account: "account", home: "home", facebook: "facebook" }; export const InputIcon = () => ( <div> <IconInput icon={select("icon", iconOptions, iconOptions.wallet)}> {text("label", "Label")} </IconInput> <small className="hint-with-icon">{text("hint text", "Hint text")}</small> </div> );
后续扩展说明
- 新增图标只需要三步:导入对应图标组件、在
ICON_MAP中添加键值对、同步更新PropTypes枚举和story的iconOptions,不需要修改核心渲染逻辑 - 不要用多层三元表达式、if/else判断渲染图标,图标数量多了之后代码会非常难维护,映射表方案的可读性和扩展性最好
- 如果需要支持无图标状态,保持
icon传null即可,逻辑已经做了兼容
内容的提问来源于stack exchange,提问作者TEbogo
相关产品推荐
相关产品推荐

