You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现基于SVG表情的React可复用用户反馈反应组件

React 面部表情反馈组件实现方案

原始问题

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

参考设计图

设计参考1
设计参考2

现有代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 01:12:00