如何实现基于SVG表情的React可复用用户反馈反应组件
React 面部表情反馈组件实现方案
原始问题
我一直在尝试实现附图设计的功能:使用预定义的SVG面部图标开发React反馈反应组件,但始终不知道该从何入手。目前我已经准备好全套可用的Icon资源,但是不清楚如何实现对应功能,也不知道如何将这些SVG图标封装为可复用的React组件,需要调整全部现有功能来适配问题顶部的附图设计。
参考设计图


现有代码
import React, { useEffect, useRef, useState, useCallback } from "react"; import Button from "../../elements/buttons/Button"; import Icon from "../../elements/icons/Icon"; function useDynamicSVGImport(name, options = {}) { const ImportedIconRef = useRef(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); const { onCompleted, onError } = options; useEffect(() => { setLoading(true); const importIcon = async () => { try { ImportedIconRef.current = ( await import(`./${name}.svg`) ).ReactComponent; if (onCompleted) { onCompleted(name, ImportedIconRef.current); } } catch (err) { if (onError) { onError(err); } setError(err); } finally { setLoading(false); } }; importIcon(); }, [name, onCompleted, onError]); return { error, loading, SvgIcon: ImportedIconRef.current }; } /** * 动态SVG导入Hook的简单封装,你也可以实现自己的封装逻辑,甚至直接在组件中使用该Hook。 */ /** const Icon = ({ name, onCompleted, onError, ...rest }) => { const { error, loading, SvgIcon } = useDynamicSVGImport(name, { onCompleted, onError }); if (error) { return error.message; } if (loading) { return "Loading..."; } if (SvgIcon) { return <SvgIcon {...rest} />; } return null; }; */ export default function FacialReactions() { const [name, setName] = useState("svg1"); const handleOnCompleted = useCallback( (iconName) => console.log(`${iconName} successfully loaded`), [] ); const handleIconError = useCallback((err) => console.error(err.message), []); return ( <div className="App"> <button onClick={() => setName((prevName) => (prevName === "svg1" ? "svg2" : "svg1")) } > 切换图标 </button> <section> <Icon icon="mood-vgood" variant="horizontal" /> <Icon icon="mood-good" variant="horizontal" /> <Icon icon="mood-neutral" variant="horizontal" /> <Icon icon="mood-bad" variant="horizontal" /> <Icon icon="mood-vbad" variant="horizontal" /> /** 现在我需要调整全部功能来适配问题顶部的附图设计! */ /** <Icon name={name} fill="gray" onCompleted={handleOnCompleted} onError={handleIconError} /> <Icon name="svg1" fill="gray" width="300" onCompleted={handleOnCompleted} onError={handleIconError} /> <Icon name="svg2" fill="darkblue" height="100" onCompleted={handleOnCompleted} onError={handleIconError} /> */ </section> </div> ); }
实现方案
你已经完成了核心的动态SVG导入Hook,只需要放开注释的Icon组件并补充交互逻辑即可匹配设计需求,完整可运行代码如下:
import React, { useEffect, useRef, useState, useCallback } from "react"; // 保留原有动态SVG导入逻辑 function useDynamicSVGImport(name, options = {}) { const ImportedIconRef = useRef(); const [loading, setLoading] = useState(false); const [error, setError] = useState(); const { onCompleted, onError } = options; useEffect(() => { setLoading(true); const importIcon = async () => { try { ImportedIconRef.current = ( await import(`./${name}.svg`) ).ReactComponent; if (onCompleted) { onCompleted(name, ImportedIconRef.current); } } catch (err) { if (onError) { onError(err); } setError(err); } finally { setLoading(false); } }; importIcon(); }, [name, onCompleted, onError]); return { error, loading, SvgIcon: ImportedIconRef.current }; } // 放开并优化Icon组件封装,新增选中态样式控制 const Icon = ({ name, isActive, onCompleted, onError, ...rest }) => { const { error, loading, SvgIcon } = useDynamicSVGImport(name, { onCompleted, onError }); if (error) return null; if (loading) return <div style={{width: isActive ? '80px' : '48px', height: isActive ? '80px' : '48px'}} />; if (SvgIcon) { return <SvgIcon {...rest} width={isActive ? 80 : 48} height={isActive ? 80 : 48} fill={isActive ? '#165DFF' : '#8C8C8C'} />; } return null; }; // 表情配置,可根据实际SVG文件名、显示文字调整 const reactionList = [ { id: 'vgood', label: '非常满意', svgName: 'mood-vgood' }, { id: 'good', label: '满意', svgName: 'mood-good' }, { id: 'neutral', label: '一般', svgName: 'mood-neutral' }, { id: 'bad', label: '不满意', svgName: 'mood-bad' }, { id: 'vbad', label: '非常不满意', svgName: 'mood-vbad' }, ] export default function FacialReactions() { const [selectedId, setSelectedId] = useState(null); const handleOnCompleted = useCallback( (iconName) => console.log(`${iconName} 加载成功`), [] ); const handleIconError = useCallback((err) => console.error(err.message), []); return ( <div style={{padding: '24px', maxWidth: '700px', margin: '0 auto'}}> <h3>请给出您的评价</h3> <div style={{display: 'flex', justifyContent: 'space-around', alignItems: 'flex-end', gap: '12px', margin: '40px 0'}}> {reactionList.map(item => ( <div key={item.id} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', cursor: 'pointer', transform: selectedId === item.id ? 'translateY(-10px)' : 'none', transition: 'all 0.2s ease' }} onClick={() => setSelectedId(item.id)} > <Icon name={item.svgName} isActive={selectedId === item.id} onCompleted={handleOnCompleted} onError={handleIconError} /> {selectedId === item.id && ( <p style={{margin: '12px 0 0 0', color: '#165DFF', fontWeight: 500, fontSize: '14px'}}>{item.label}</p> )} </div> ))} </div> <button disabled={!selectedId} style={{ padding: '10px 32px', backgroundColor: selectedId ? '#165DFF' : '#E5E6EB', color: '#fff', border: 'none', borderRadius: '4px', cursor: selectedId ? 'pointer' : 'not-allowed', fontSize: '14px' }} > 提交 </button> </div> ); }
功能说明
- 完整匹配设计效果:选中的表情会放大、变色、向上浮动,同时底部显示对应评价文案
- 保留了原有的动态SVG导入逻辑,按需加载,性能更优
- 内置了可配置的表情列表,可根据实际资源情况快速调整
- 增加了提交按钮的禁用逻辑,未选择评价时无法提交
内容的提问来源于stack exchange,提问作者Shehabd Dwedar
相关产品推荐
相关产品推荐

