07. Factory模式 - 动态组件创建
概述
Factory 模式是一种根据条件动态创建和返回不同组件的设计模式。它允许在运行时根据配置、类型或状态决定渲染哪个组件,提高代码的灵活性和可维护性。
| 维度 | 内容 |
|---|---|
| What | 根据条件动态创建和返回不同组件的模式 |
| Why | 根据配置或状态动态渲染不同组件 |
| When | 根据类型渲染不同 UI、动态表单 |
| Where | 组件工厂函数、动态渲染 |
| Who | 需要条件渲染的开发者 |
| How | `const Component = componentMap[type] |
1. 什么是 Factory 模式
1.1 基本概念
Factory 模式使用一个工厂函数或对象映射来动态决定创建哪个组件。
// 基础工厂模式 function getComponent(type) { const components = { text: TextComponent, image: ImageComponent, video: VideoComponent, }; const Component = components[type] || DefaultComponent; return <Component />; } // 使用 function DynamicContent({ type }) { return getComponent(type); }1.2 为什么需要 Factory 模式
// ❌ 传统方式:大量的条件判断 function Message({ type, data }) { if (type === 'success') { return <SuccessMessage data={data} />; } else if (type === 'error') { return <ErrorMessage data={data} />; } else if (type === 'warning') { return <WarningMessage data={data} />; } else if (type === 'info') { return <InfoMessage data={data} />; } else { return <DefaultMessage data={data} />; } } // ✅ Factory 模式:使用映射 const messageComponents = { success: SuccessMessage, error: ErrorMessage, warning: WarningMessage, info: InfoMessage, }; function Message({ type, data }) { const Component = messageComponents[type] || DefaultMessage; return <Component data={data} />; }2. 基础实现
2.1 组件映射
// 定义组件映射 const cardComponents = { user: UserCard, product: ProductCard, post: PostCard, comment: CommentCard, }; // 工厂组件 function Card({ type, data }) { const Component = cardComponents[type]; if (!Component) { return <div>未知的卡片类型: {type}</div>; } return <Component data={data} />; } // 使用 function Dashboard() { const items = [ { type: 'user', data: { name: '张三', email: 'zhang@example.com' } }, { type: 'product', data: { name: '手机', price: 5999 } }, { type: 'post', data: { title: 'React 19', content: '...' } }, ]; return ( <div> {items.map((item, index) => ( <Card key={index} type={item.type} data={item.data} /> ))} </div> ); }2.2 带默认组件
const buttonVariants = { primary: PrimaryButton, secondary: SecondaryButton, danger: DangerButton, success: SuccessButton, }; function Button({ variant, children, ...props }) { const Component = buttonVariants[variant] || DefaultButton; return <Component {...props}>{children}</Component>; } // 使用 <Button variant="primary">主要按钮</Button> <Button variant="danger">危险按钮</Button> <Button variant="custom">默认按钮</Button>3. 高级实现
3.1 动态组件注册
// 组件注册表 class ComponentRegistry { constructor() { this.components = new Map(); } register(name, component) { this.components.set(name, component); } get(name) { return this.components.get(name); } has(name) { return this.components.has(name); } } const registry = new ComponentRegistry(); // 注册组件 registry.register('text', TextWidget); registry.register('image', ImageWidget); registry.register('chart', ChartWidget); // 工厂组件 function Widget({ type, props }) { const Component = registry.get(type); if (!Component) { return <div>未知组件类型: {type}</div>; } return <Component {...props} />; }3.2 懒加载工厂
// 懒加载组件映射 const lazyComponents = { dashboard: () => import('./Dashboard'), profile: () => import('./Profile'), settings: () => import('./Settings'), }; function LazyPage({ name }) { const [Component, setComponent] = useState(null); useEffect(() => { const loader = lazyComponents[name]; if (loader) { loader().then(module => setComponent(() => module.default)); } }, [name]); if (!Component) return <div>加载中...</div>; return <Component />; } // 或使用 React.lazy const pageComponents = { dashboard: lazy(() => import('./Dashboard')), profile: lazy(() => import('./Profile')), settings: lazy(() => import('./Settings')), }; function Page({ name }) { const Component = pageComponents[name]; if (!Component) return <div>页面不存在</div>; return ( <Suspense fallback={<div>加载中...</div>}> <Component /> </Suspense> ); }3.3 带参数的工厂
// 工厂函数返回带配置的组件 function createField(type, config = {}) { const fieldComponents = { text: (props) => <Input type="text" {...config} {...props} />, email: (props) => <Input type="email" {...config} {...props} />, select: (props) => <Select options={config.options} {...props} />, checkbox: (props) => <Checkbox {...config} {...props} />, date: (props) => <DatePicker {...config} {...props} />, }; return fieldComponents[type] || fieldComponents.text; } // 使用 function DynamicForm({ fields }) { return ( <form> {fields.map(field => { const FieldComponent = createField(field.type, field.config); return <FieldComponent key={field.name} name={field.name} />; })} </form> ); } // 配置 const formConfig = [ { name: 'username', type: 'text', config: { placeholder: '用户名' } }, { name: 'email', type: 'email', config: { placeholder: '邮箱' } }, { name: 'role', type: 'select', config: { options: ['admin', 'user'] } }, ];4. 实际应用场景
4.1 动态表单
// 字段类型映射 const fieldTypes = { text: ({ label, value, onChange }) => ( <div> <label>{label}</label> <input type="text" value={value} onChange={onChange} /> </div> ), number: ({ label, value, onChange }) => ( <div> <label>{label}</label> <input type="number" value={value} onChange={onChange} /> </div> ), select: ({ label, value, onChange, options }) => ( <div> <label>{label}</label> <select value={value} onChange={onChange}> {options.map(opt => ( <option key={opt.value} value={opt.value}>{opt.label}</option> ))} </select> </div> ), checkbox: ({ label, checked, onChange }) => ( <div> <label> <input type="checkbox" checked={checked} onChange={onChange} /> {label} </label> </div> ), textarea: ({ label, value, onChange }) => ( <div> <label>{label}</label> <textarea value={value} onChange={onChange} rows={4} /> </div> ), }; function DynamicField({ field, value, onChange }) { const FieldComponent = fieldTypes[field.type]; if (!FieldComponent) { return <div>未知字段类型: {field.type}</div>; } return ( <FieldComponent label={field.label} value={value} checked={value} onChange={(e) => onChange(field.name, e.target.value)} options={field.options} /> ); } function DynamicForm({ schema, onSubmit }) { const [formData, setFormData] = useState({}); const handleChange = (name, value) => { setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = (e) => { e.preventDefault(); onSubmit(formData); }; return ( <form onSubmit={handleSubmit}> {schema.fields.map(field => ( <DynamicField key={field.name} field={field} value={formData[field.name]} onChange={handleChange} /> ))} <button type="submit">提交</button> </form> ); } // 使用 const formSchema = { fields: [ { name: 'name', type: 'text', label: '姓名' }, { name: 'age', type: 'number', label: '年龄' }, { name: 'gender', type: 'select', label: '性别', options: [ { value: 'male', label: '男' }, { value: 'female', label: '女' }, ]}, { name: 'agree', type: 'checkbox', label: '同意条款' }, { name: 'bio', type: 'textarea', label: '个人简介' }, ], }; <DynamicForm schema={formSchema} onSubmit={(data) => console.log(data)} />4.2 动态图表
const chartComponents = { line: LineChart, bar: BarChart, pie: PieChart, scatter: ScatterChart, area: AreaChart, }; function Chart({ type, data, options }) { const ChartComponent = chartComponents[type]; if (!ChartComponent) { return <div>不支持的图表类型: {type}</div>; } return <ChartComponent data={data} options={options} />; } // 使用 const charts = [ { type: 'line', data: lineData }, { type: 'bar', data: barData }, { type: 'pie', data: pieData }, ]; function Dashboard() { return ( <div className="dashboard"> {charts.map((chart, i) => ( <Chart key={i} type={chart.type} data={chart.data} /> ))} </div> ); }4.3 动态通知
const notificationComponents = { success: ({ message, onClose }) => ( <div className="notification success"> <span>✅ {message}</span> <button onClick={onClose}>×</button> </div> ), error: ({ message, onClose }) => ( <div className="notification error"> <span>❌ {message}</span> <button onClick={onClose}>×</button> </div> ), warning: ({ message, onClose }) => ( <div className="notification warning"> <span>⚠️ {message}</span> <button onClick={onClose}>×</button> </div> ), info: ({ message, onClose }) => ( <div className="notification info"> <span>ℹ️ {message}</span> <button onClick={onClose}>×</button> </div> ), }; function Notification({ type, message, onClose }) { const Component = notificationComponents[type] || notificationComponents.info; return <Component message={message} onClose={onClose} />; }5. 完整示例:CMS 页面构建器
// 组件库 const componentLibrary = { hero: ({ title, subtitle, image }) => ( <div className="hero"> <img src={image} alt={title} /> <h1>{title}</h1> <p>{subtitle}</p> </div> ), features: ({ items }) => ( <div className="features"> {items.map((item, i) => ( <div key={i} className="feature"> <h3>{item.title}</h3> <p>{item.description}</p> </div> ))} </div> ), testimonial: ({ quote, author, role }) => ( <div className="testimonial"> <p>"{quote}"</p> <div className="author"> <strong>{author}</strong> <span>{role}</span> </div> </div> ), pricing: ({ plans }) => ( <div className="pricing"> {plans.map(plan => ( <div key={plan.name} className="plan"> <h3>{plan.name}</h3> <div className="price">¥{plan.price}</div> <ul> {plan.features.map(feature => ( <li key={feature}>{feature}</li> ))} </ul> </div> ))} </div> ), cta: ({ title, buttonText, buttonLink }) => ( <div className="cta"> <h2>{title}</h2> <a href={buttonLink} className="button">{buttonText}</a> </div> ), }; // 页面组件 function PageBuilder({ sections }) { return ( <div className="page-builder"> {sections.map((section, index) => { const Component = componentLibrary[section.type]; if (!Component) { return <div key={index}>未知组件类型: {section.type}</div>; } return <Component key={index} {...section.props} />; })} </div> ); } // 页面配置 const pageConfig = { sections: [ { type: 'hero', props: { title: '欢迎来到我们的网站', subtitle: '最好的产品和服务', image: '/hero.jpg', }, }, { type: 'features', props: { items: [ { title: '功能1', description: '描述1' }, { title: '功能2', description: '描述2' }, { title: '功能3', description: '描述3' }, ], }, }, { type: 'testimonial', props: { quote: '非常棒的产品!', author: '张三', role: 'CEO', }, }, { type: 'pricing', props: { plans: [ { name: '基础版', price: 99, features: ['功能1', '功能2'] }, { name: '专业版', price: 199, features: ['功能1', '功能2', '功能3'] }, ], }, }, { type: 'cta', props: { title: '开始使用', buttonText: '立即购买', buttonLink: '/pricing', }, }, ], }; // 使用 function HomePage() { return <PageBuilder sections={pageConfig.sections} />; }6. 总结
核心要点
| 要点 | 说明 |
|---|---|
| 核心价值 | 动态选择组件,避免条件判断 |
| 实现方式 | 对象映射、工厂函数、注册表 |
| 适用场景 | 动态表单、CMS、多类型组件 |
| 优势 | 可扩展、易维护 |
记忆口诀
工厂模式动态选,映射对象最方便
类型配置决定 UI,扩展新类不用愁
7. 相关资源
- 工厂模式
- React 动态组件