安装Styled-components
npm install styled-components
Styled-components的一大好处就是可以使用主题,可以将主题的背景颜色,字体大小设置在主题中:
theme.js
export default {
primaryColor:"#4F9DDE"
}
main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import {ThemeProvider} from "styled-components"
import theme from './theme.js'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<ThemeProvider theme={theme}></ThemeProvider>
<App />
</React.StrictMode>,
)
安装StoryBook
Storybook 是一个开源工具,用于构建 UI 组件并创建组件库。它提供了一个沙盒环境,开发者可以在其中独立地开发和测试 UI 组件,无需担心应用程序的其他部分。
npx -p @storybook/cli sb init
注意:storybook需要node.js版本在18及以上。
可以使用nvm管理node版本:
在终端中运行以下命令来安装最新版本的 Node.js:
nvm install node
然后,你可以使用以下命令来切换到新安装的版本:
nvm use node
启动storybook:
npm run storybook
配置主题样式到storybook中:
创建一个全局装饰器,这个装饰器是一个函数,接收一个组件 Story
作为参数,返回一个 ThemeProvider
组件。
Story
组件的渲染结果会作为 ThemeProvider
的子组件,这样所有的故事都会被 ThemeProvider
包裹,从而能访问到主题样式。
CHAT-UI\.storybook\preview.jsx
import React from 'react';
import { ThemeProvider } from 'styled-components';
import theme from '../src/theme';
export const decorators = [(Story) => <ThemeProvider theme={theme}><Story /></ThemeProvider>];
此项目设计稿原作者:https://dribbble.com/shots/5262706-Daily-UI-13-Direct-Messaging
头像组件
共有四种头像类型,需要动态设计的地方:
- 头像图片
- 有无在线状态
- 在线状态的颜色
- 头像图片的尺寸
注意:组件文件夹下一般包括以下几个文件:
index.js
—— 组件主体代码style.js
—— Styled-components 样式组件代码[组件名].stories.js
—— 组件 storieshook.js
—— 自定义的hooks
index.jsx
import React from "react";
import PropTypes from "prop-types";
import { StatusIcon, StyledAvatar, AvatarClip, AvatarImage } from "./style";
// 定义 Avatar 组件。它接收几个 props:src(头像的图片地址),size(头像的尺寸),status(用户的在线状态),statusIconSize(状态图标的尺寸),以及其他的 props(...rest)。
function Avatar({
src,
size = "48px",
status,
statusIconSize = "8px",
...rest
}) {
// 返回一个 JSX 元素。这个元素是一个 StyledAvatar 组件,它接收所有的 rest props。
return (
<StyledAvatar {...rest}>
{/* 如果 status prop 存在,就渲染一个 StatusIcon 组件。这个组件接收 status 和 size props。 */}
{status && (
<StatusIcon $status={status} size={statusIconSize}></StatusIcon>
)}
{/* 渲染一个 AvatarClip 组件,它接收 size prop。AvatarClip 组件的子元素是一个 AvatarImage 组件,它接收 src 和 alt props。 */}
<AvatarClip size={size}>
<AvatarImage src={src} alt="avatar" />
</AvatarClip>
</StyledAvatar>
);
}
// 使用 PropTypes 对 Avatar 组件的 props 进行类型检查。src 是必需的,size 和 statusIconSize 是可选的,status 必须是 "online" 或 "offline"。
Avatar.propTypes = {
src: PropTypes.string.isRequired,
size: PropTypes.string,
status: PropTypes.oneOf(["online", "offline"]),
statusIconSize: PropTypes.string,
};
// 导出 Avatar 组件,使得其他文件可以导入和使用它。
export default Avatar;
重点 | 描述 |
---|---|
PropTypes | 使用 PropTypes 对 props 进行类型检查,可以帮助开发者理解组件期望的输入,并且在开发过程中提供警告。 |
默认参数 | 在函数参数中使用默认值,可以使得函数在没有提供某些参数的情况下仍然可以正常工作。 |
解构赋值 | 使用解构赋值来获取 props,可以使得代码更加清晰和简洁。 |
条件渲染 | 使用条件渲染来决定是否渲染 StatusIcon,可以使得组件更加灵活。 |
rest 参数 | 使用 rest 参数来获取所有未被单独获取的 props,然后将它们传递给 StyledAvatar。这是一个好的习惯,可以使得 Avatar 组件可以接收并使用任意数量的 props。 |
需要注意的点 | 描述 |
---|---|
未知的 props | 当你将一个未知的 prop(如 "status")传递给 styled-components 时,它会被传递给 DOM,从而触发一个警告。你可以使用 transient props(如 "$status")来避免这个警告。 |
有效的 HTML 属性 | 并非所有的 props 都是有效的 HTML 属性。当你将一个无效的 prop(如 "status")传递给 DOM 元素时,React 会在控制台中打印出警告。你需要确保只有有效的 HTML 属性被传递给 DOM 元素。 |
PropTypes 的限制 | PropTypes 只能在开发模式下进行类型检查。在生产模式下,为了提高性能,PropTypes 会被自动忽略。因此,你不能依赖 PropTypes 来阻止错误的 props 被传递给组件。 |
在React中,当你向一个组件传递props时,这些props会被传递到组件的DOM元素上。然而,并非所有的props都是有效的DOM属性。
例如,"status"就不是一个有效的DOM属性。当无效的props被传递到DOM元素上时,React会在控制台中打印出警告。
style.js
import styled, { css } from "styled-components";
// circleMixinFunc 是一个函数,它接收颜色和尺寸作为参数,并返回一个 css 模板字符串。这个字符串定义了一个绝对定位的块级元素,它的宽度、高度和背景颜色都由参数指定,形状为圆形。
const circleMixinFunc = (color, size = "8px") => css`
content: "";
display: block;
position: absolute;
width: ${size};
height: ${size};
border-radius: 50%;
background-color: ${color};
`;
const StyledAvatar = styled.div`
width: 48px;
height: 48px;
position: relative;
`;
const StatusIcon = styled.div`
position: absolute;
left: 2px;
top: 4px;
// ::before 伪元素的样式由 circleMixinFunc 生成,颜色被设置为白色,尺寸由 size prop 指定,然后被放大两倍。
&::before {
${({size}) => circleMixinFunc("white", size)}
transform: scale(2);
}
// ::after 伪元素的样式也由 circleMixinFunc 生成,颜色和尺寸由 props 指定。如果 status prop 是 "online",颜色就被设置为绿色;如果 status prop 是 "offline",颜色就被设置为灰色。
&::after {
${({ theme, $status, size }) => { // 此处的size是statusIconSize,并非AvatarClip的size
if ($status === "online") {
return circleMixinFunc(theme.green, size);
} else if ($status === "offline") {
return circleMixinFunc(theme.gray, size);
}
}}
}
`;
const AvatarClip = styled.div`
width: ${({ size }) => size};
height: ${({ size }) => size};
border-radius: 50%;
overflow: hidden;
`;
const AvatarImage = styled.img`
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
`;
export { StyledAvatar, StatusIcon, AvatarClip, AvatarImage };
重点 | 描述 |
---|---|
样式封装 | 使用 circleMixinFunc 封装了圆形元素的公共样式,这是一个好的习惯,可以使得代码更加清晰和简洁。 |
伪元素 | 使用 ::before 和 ::after 伪元素来创建额外的视觉效果。 |
动态样式 | 使用函数来动态生成样式,这是一个好的习惯,可以使得你的组件更加灵活。 |
主题 | 使用 theme prop 来获取主题颜色,这是一个好的习惯,可以使得你的组件更加易于定制和维护。 |
需要注意的点 | 描述 |
---|---|
伪元素的内容 | ::before 和 ::after 伪元素需要 content 属性,否则它们不会被显示。 |
伪元素的层叠顺序 | ::before 和 ::after 伪元素的默认层叠顺序是根据它们的出现顺序来决定的。如果你需要改变它们的层叠顺序,你可以使用 z-index 属性。 |
transient props | 当你使用 transient props(如 $status )时,你需要确保你在所有使用这个 prop 的地方都使用了新的 prop 名。例如,你需要在 Avatar 组件中将 status 改为 $status 。 |
prop 类型 | 当你使用函数来动态生成样式时,你需要确保你的 props 是正确的类型。例如,size prop 应该是一个字符串,而 $status prop 应该是 "online" 或 "offline"。你可以使用 PropTypes 来检查 prop 的类型。 |
Avatar.stories.jsx
// 导入 React 和 Avatar 组件,以及一些图片和样式文件。
import React from "react";
import Avatar from ".";
import "../../story.css";
import face1 from "../../assets/images/face-male-1.jpg";
import face2 from "../../assets/images/face-male-2.jpg";
import face4 from "../../assets/images/face-male-4.jpg";
// 定义一个默认的导出对象,它包含了故事的标题和组件。
export default {
title: "Avatar",
component: Avatar,
};
// 定义一个名为 "Default" 的故事,它返回一个 Avatar 组件,src prop 被设置为 face1。
export const Default = () => {
return <Avatar src={face1} />;
};
// 定义一个名为 "Sizes" 的故事,它返回一个包含四个 Avatar 组件的 div 元素,这四个 Avatar 组件的 size prop 分别被设置为 "48px"、"56px"、"64px" 和 "72px"。
export const Sizes = () => {
return (
<div className="row-elements">
<Avatar size="48px" src={face1} />
<Avatar size="56px" src={face2} />
<Avatar size="64px" src={face4} />
<Avatar size="72px" src={face1} />
</div>
);
};
// 定义一个名为 "WithStatus" 的故事,它返回一个包含三个 Avatar 组件的 div 元素,这三个 Avatar 组件的 status prop 分别被设置为 "online"、"offline" 和 "offline",最后一个 Avatar 组件的 size prop 被设置为 "72px",statusIconSize prop 被设置为 "12px"。
export const WithStatus = () => {
return (
<div className="row-elements">
<Avatar src={face1} status="online" />
<Avatar src={face2} status="offline" />
<Avatar src={face4} status="offline" size="72px" statusIconSize="12px" />
</div>
);
};
安装Hygen
Hygen 是一个快速、简单的代码生成器,它可以帮助你自动化创建新的代码文件或代码片段的过程。Hygen 的目标是让你能够快速地创建新的组件、测试、模块等,而不需要手动复制和粘贴代码。
安装hygen:
npm i -g hygen
初始化hygen:
hygen init self
可以看到在根目录下创建了一些文件:
Loaded templates: D:\nvm\v22.2.0\node_modules\hygen\src\templates
added: _templates/generator/help/index.ejs.t
added: _templates/generator/with-prompt/hello.ejs.t
added: _templates/generator/with-prompt/prompt.ejs.t
added: _templates/generator/new/hello.ejs.t
added: _templates/init/repo/new-repo.ejs.t
生成自定义的模板文件夹:
hygen generator new component
在new文件夹下创建好模板文件后:
hygen component new Icon
hygen
:这是 Hygen 代码生成器的命令行工具。component
:这是你在 Hygen 中定义的生成器的名称。在这个例子中,它表示你想要生成一个新的组件。new
:这是生成器的动作。在这个例子中,它表示你想要创建一个新的组件。Icon
:这是你要生成的新组件的名称。
图标组件
$(({ color }) => color &&
fill: ${color};)
是一个箭头函数,它接收一个对象作为参数,并从中解构出 color
属性。如果 color
属性存在(即不是 null
、undefined
或 false
),那么它就会返回一个字符串 fill: ${color};
,这是一个 CSS 声明,用于设置元素的填充颜色。
同样,$(({ opacity }) => opacity &&
opacity: ${opacity};)
也是一个箭头函数,它接收一个对象作为参数,并从中解构出 opacity
属性。如果 opacity
属性存在,那么它就会返回一个字符串 opacity: ${opacity};
,这是一个 CSS 声明,用于设置元素的透明度。
svg,
svg * {
${({ color }) => color && `fill: ${color};`}
${({ opacity }) => opacity && `opacity: ${opacity};`}
}
引入svg图标作为React组件
在 Create React App 和一些其他的项目模板中,你可以使用 ReactComponent
来导入 SVG 文件作为 React 组件。
但是在Vite中,需要引入插件完成转换操作:React + Vite 项目中如何使用 SVG - 掘金 (juejin.cn)
引入FontAwesomeIcon
安装相关图标库:
npm install --save @fortawesome/react-fontawesome @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/free-brands-svg-icons @fortawesome/free-regular-svg-icons
引入到story文件中:
import React from "react";
import Icon from ".";
import SmileIcon from "../../assets/icon/smile.svg?react"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faCommentDots } from '@fortawesome/free-solid-svg-icons'
export default {
title: "UI 组件/Icon",
component: Icon,
};
export const Default = () => <Icon icon={SmileIcon}></Icon>;
export const CustomColor = () => <Icon icon={SmileIcon} color="#cccccc"></Icon>;
export const CustomSize = () => <Icon icon={SmileIcon} width={48} height={48}></Icon>;
export const FontAwesome = () => <FontAwesomeIcon icon={faCommentDots}></FontAwesomeIcon>;