Result Summary Components

Leo
Leo
Cover Image for Result Summary Components
  1. 需求
  2. 实现步骤
  3. 关于svg 图标的使用
  4. 小结

今天我们来完成一个 React 的入门练习,参考的是frontendmentor 的设计文档和文字描述。

需求

这个练习要求实现一个结果展示组件,显示四个不同测试类别的得分以及一个综合得分。每个测试类别都有一个图标和背景样式。组件还需要显示用户排名和一些说明文字。

此外,该组件需要适应不同的屏幕尺寸,以在移动端和桌面端呈现不同的布局。

实现步骤

首先将组件分为两部分:综合得分和测试类别得分。这两部分在不同的屏幕尺寸下采用不同的布局方式:


<div className="flex flex-col md:flex-row md:w-[450px] md:shadow-xl md:rounded-xl">
<div className="flex flex-col ...">
...
</div>
<div className="md:ml-4 w-[200px]">
...
</div>
</div>

在屏幕宽度达到中等屏幕(md)尺寸后,将采用左右排列方式,否则将采用上下排列方式。根据设计图,两种布局方式的不同点在于圆角和阴影效果,使用 "md:shadow-xl" 和 "md:rounded-xl" 属性进行设置。

综合得分部分:


<div className="flex flex-col w-[200px] justify-center items-center py-4 bg-gradient-to-b from-[#7857ff] to-[#2e2be9] xl:rounded-t-2xl rounded-t-lg rounded-b-2xl text-white">
<div className="text-sm">Your Result</div>
<div className="mt-4 w-[120px] h-[120px] flex flex-col justify-center items-center bg-gradient-to-b from-[#4e21caff] to-[#2421ca00] rounded-full">
<div className="text-[40px]">76</div>
<div className="text-sm text-gray-400">of 100</div>
</div>
<div className="mt-4">Great</div>
<p className="mx-4 mt-2 text-gray-400 text-[6px] text-center">
You scored higher than 65% of the people who have taken these tests.
</p>
</div>

主要是分别为综合得分的背景设置一个渐变的背景颜色,bg-gradient-to-b from-[#7857ff] to-[#2e2be9] 同样的思路为中间的圆形区域设置一个渐变背景 bg-gradient-to-b from-[#4e21caff] to-[#2421ca00],此外,还需要设置元素之间的间距和对齐方式。对于子元素的对齐,使用 "flex justify-center items-center" 属性即可。

测试类别得分

测试类别部分分为三部分

  • 文字提示
  • 分类列表
  • 继续按钮

<div className="md:ml-4 w-[200px]">
<div className="mt-2 text-slate-600">Summary</div>
<div className="mt-4">
{results.map((result) => (
<ScoreItem key={result.category} {...result} />
))}
</div>
<div className="mt-4 w-full rounded-full bg-slate-600">
<div className="py-1 flex justify-center items-center text-white">Continue</div>
</div>
</div>

这里重点看一下中间的map 函数,我们直接数据列表转换成了一个组件列表,


import reactionIcon from './icons/icon-reaction.svg';
import memoryIcon from './icons/icon-memory.svg';
import verbalIcon from './icons/icon-verbal.svg';
import visualIcon from './icons/icon-visual.svg';
const results = [
{
category: 'Reaction',
score: 80,
icon: reactionIcon,
style: 'bg-red-50 text-red-300',
},
{
category: 'Memory',
score: 92,
icon: memoryIcon,
style: 'bg-yellow-50 text-yellow-300',
},
{
category: 'Verbal',
score: 61,
icon: verbalIcon,
style: 'bg-green-50 text-green-300',
},
{
category: 'Visual',
score: 72,
icon: visualIcon,
style: 'bg-blue-50 text-blue-300',
},
];
function ScoreItem({ category, score, icon, style }) {
return (
<div className={`mt-2 px-2 py-2 ${style} text-[12px] rounded-md flex flex-row justify-between`}>
<div className="flex flex-row items-center">
<img src={icon.src} alt={category} className="mr-2" />
<span>{category}</span>
</div>
<div className="flex flex-row items-center">
<span className="text-slate-600">{score}</span>
<span className="text-gray-400">/100</span>
</div>
</div>
);
}

results 是一个列表,存储了分类测试数据的各类信息,ScoreItem 是我们用于渲染分类信息的一个组件,他接收外部传入的各种属性参数进行渲染。

关于svg 图标的使用

如果要使用存储在本地的svg图标,我们可以使用import语句来将SVG图标导入React组件中。例如,我们可以使用以下代码导入reactionIcon图标:


import reactionIcon from './icons/icon-reaction.svg'
...

接下来,我们可以将此图标传递给ScoreItem组件,然后使用img标记将其呈现在UI中。例如,在ScoreItem组件中,我们使用以下代码将图标呈现在UI中:


<img src={icon.src} alt={category} className="mr-2" />

小结

在这个练习主要涉及到以下知识点:

  • 组件的定义与使用 React 的核心是组件化开发,组件是将 UI 划分为独立的、可复用的部分。在这个代码中,ScoreItem 和 ResultSummary 就是两个组件。其中,ResultSummary 组件是默认导出的,也是这个代码中渲染的顶层组件。我们可以通过 JSX 语法来使用组件,例如在 ResultSummary 中,我们可以通过 {results.map((prop) => (<ScoreItem key={prop.category} {...prop} />))} 的方式来渲染 ScoreItem 组件。

  • 属性传递与解构赋值 在 React 中,组件的数据来源可以是组件自身的状态,也可以是父组件传递下来的属性。在这个代码中,ScoreItem 组件通过 prop 对象获取了父组件传递过来的属性,例如 prop.style、prop.category、prop.score 等。同时,为了简化代码,我们使用了解构赋值的方式将 prop 中的属性提取出来, { style, icon, category, score };。

  • CSS 样式类的动态绑定 在 React 中,我们可以通过条件判断、函数调用等方式来动态生成样式类,从而实现样式的动态绑定。在这个代码中,我们使用了模板字符串和模板文字的方式来生成样式类,例如 className={mt-2 px-2 py-2 ${prop.style} text-[12px] rounded-md flex flex-row justify-between}。同时,我们可以看到,在 ScoreItem 组件中,使用了 Tailwind 的类名来定义样式类,例如 bg-red-50、text-red-300 等。

  • 类名的组合使用 Tailwind 是一款基于类名的 CSS 工具库,我们可以通过组合不同的类名来实现样式的定义。在这个代码中,我们可以看到,一个元素通常会同时使用多个类名来定义样式,例如 className={mt-2 px-2 py-2 ${prop.style} text-[12px] rounded-md flex flex-row justify-between} 中就使用了 6 个类名。

  • 响应式设计 Tailwind 提供了一系列的响应式类名,可以根据不同的屏幕尺寸来定义样式。在这个代码中,我们可以看到,ResultSummary 组件使用了 md:w-[450px]md:flex-row 等类名来指定在中等屏幕尺寸以上的样式,而 ScoreItem 组件使用了 w-[200px]md:ml-4 等类名来指定在中等尺寸屏幕上的布局。

最后附上控件的渲染效果以及完整源码。你可以通过改变浏览器宽度来查看控件在不同宽度屏幕上的展示效果。

Your Result
76
of 100
Great

You scored higher than 65% of the people who have taken these tests.

Summary
ReactionReaction
80/100
MemoryMemory
92/100
VerbalVerbal
61/100
VisualVisual
72/100
Continue
result-summary.jsx
Copy

import reactionIcon from './icons/icon-reaction.svg';
import memoryIcon from './icons/icon-memory.svg';
import verbalIcon from './icons/icon-verbal.svg';
import visualIcon from './icons/icon-visual.svg';
const results = [
{
category: 'Reaction',
score: 80,
icon: reactionIcon,
style: 'bg-red-50 text-red-300',
},
{
category: 'Memory',
score: 92,
icon: memoryIcon,
style: 'bg-yellow-50 text-yellow-300',
},
{
category: 'Verbal',
score: 61,
icon: verbalIcon,
style: 'bg-green-50 text-green-300',
},
{
category: 'Visual',
score: 72,
icon: visualIcon,
style: 'bg-blue-50 text-blue-300',
},
];
function ScoreItem({ category, score, icon, style }) {
return (
<div className={`mt-2 px-2 py-2 ${style} text-[12px] rounded-md flex flex-row justify-between`}>
<div className="flex flex-row items-center">
<img src={icon.src} alt={category} className="mr-2" />
<span>{category}</span>
</div>
<div className="flex flex-row items-center">
<span className="text-slate-600">{score}</span>
<span className="text-gray-400">/100</span>
</div>
</div>
);
}
export default function ResultSummary() {
return (
<div className="flex flex-col md:w-[450px] md:flex-row md:shadow-xl md:rounded-xl">
<div className="flex flex-col w-[200px] justify-center items-center py-4 bg-gradient-to-b from-[#7857ff] to-[#2e2be9] xl:rounded-t-2xl rounded-t-lg rounded-b-2xl text-white">
<div className="text-sm">Your Result</div>
<div className="mt-4 w-[120px] h-[120px] flex flex-col justify-center items-center bg-gradient-to-b from-[#4e21caff] to-[#2421ca00] rounded-full">
<div className="text-[40px]">76</div>
<div className="text-sm text-gray-400">of 100</div>
</div>
<div className="mt-4">Great</div>
<p className="mx-4 mt-2 text-gray-400 text-[6px] text-center">
You scored higher than 65% of the people who have taken these tests.
</p>
</div>
<div className="md:ml-4 w-[200px]">
<div className="mt-2 text-slate-600">Summary</div>
<div className="mt-4">
{results.map((result) => (
<ScoreItem key={result.category} {...result} />
))}
</div>
<div className="mt-4 w-full rounded-full bg-slate-600">
<div className="py-1 flex justify-center items-center text-white">Continue</div>
</div>
</div>
</div>
);
}