30-seconds-of-react 项目的中文翻译版本,使用 umi.js 对所有案例进行分析、注释、上线。
共 25 个组件,目前已全部翻译完成 。

React 30 秒速学
精选有用的 React 片段,你可以在30秒或更短的时间内理解。
- 使用 Ctrl + F 或command + F 搜索片段。
- 欢迎贡献(原项目),请阅读30-seconds-of-react 贡献指南。
- 片段用React 16.8+编写,充分使用 hooks 特性。
先决条件
要将代码段导入项目,必须导入React并复制粘贴组件的JavaScript代码,如下所示:
import React from 'react';function MyComponent(props) {/* ... */}
如果有任何与您的组件相关的CSS,请将其复制粘贴到具有相同名称和相应扩展名的新文件,然后将其导入如下:
import './MyComponent.css';
要渲染组件,请确保在元素中存在一个名为“root”的节点 ( 最好是<div> ) 并且导入了ReactDOM,如下所示:
import ReactDOM from 'react-dom';
umi.js 的应用
在 React 30 秒速学这个中文翻译项目里,我使用 umi.js 进行代码的学习。优势是无需定义路由,新建文件即可访问。
把示例代码跑起来:
npm installnpm run dev
相关产品
目录
Array渲染数组
Input输入
- Input基础输入框
- LimitedTextarea限制字符数的多行文本
- LimitedWordTextarea限制单词数的多行文本
- MultiselectCheckbox复选框
- PasswordRevealer密码可见
- Select下拉选择器
- Slider滑块元素
- TextArea多行文本
Object对象渲染
String字符串处理
Visual视觉效果渲染
- Accordion手风琴组件
- Carousel轮播组件
- Collapse折叠面板
- CountDown倒计时
- FileDrop文件拖放组件
- Mailto发送电子邮件
- Modal模态框组件
- StarRating星级评分
- Tabs选项卡组件
- Ticker时间控制组件
- Toggle开关组件
- Tooltip提示
Array渲染数组
DataList渲染为列表
通过数组渲染元素列表。
- 使用
isOrderedprop 的值有条件地渲染<ol>或<ul>列表。 - 使用
Array.prototype.map将data中的每个项目渲染为<li>元素,给它一个由其索引和值的串联产生的key。 - 默认情况下,省略
isOrderedprop 以渲染<ul>列表。
function DataList({ isOrdered, data }) {const list = data.map((val, i) => <li key={`${i}_${val}`}>{val}</li>);return isOrdered ? <ol>{list}</ol> : <ul>{list}</ul>;}
例子
export default function() {const names = ['John', 'Paul', 'Mary'];return (<div>无序列表:<DataList data={names} />有序列表:<DataList data={names} isOrdered /></div>);}
ps:
DataTable渲染为表格
通过数组渲染表格,动态创建每一行。
渲染一个带有两列(ID和Value)的<table>元素。
使用Array.prototype.map将data中的每个项目渲染为<tr>元素,由其索引和值组成,给它一个由两者串联产生的key。
function DataTable({ data }) {return (<table><thead><tr><th>ID</th><th>Value</th></tr></thead><tbody>{data.map((val, i) => (<tr key={`${i}_${val}`}><td>{i}</td><td>{val}</td></tr>))}</tbody></table>);}
例子
export default function() {const people = ['John', 'Jesse'];return <DataTable data={people} />;}
ps:
MappedTable渲染为映射表格
通过对象数组渲染表格,属性名称与列对应,动态创建每一行。
- 使用
Object.keys(),Array.prototype.filter(),Array.prototype.includes()和Array.prototype.reduce()生成一个filteredData数组,包含所有对象 使用propertyNames中指定的键。 - 渲染一个
<table>元素,其中一组列等于propertyNames中的值。 - 使用
Array.prototype.map将propertyNames数组中的每个值渲染为<th>元素。 - 使用
Array.prototype.map将filteredData数组中的每个对象渲染为<tr>元素,对象中的每个键包含一个<td>。
function MappedTable({ data, propertyNames }) {let filteredData = data.map(v =>Object.keys(v).filter(k => propertyNames.includes(k))// 使用 reduce 迭代,为 acc 对象赋值:// 回调函数为 (acc, key) => ((acc[key] = v[key]), acc) 初始值为 {}// ((操作), 返回值) 语法解读:括号里进行任意操作,并指定返回值.reduce(( acc, key) => ((acc[key] = v[key]), acc), {}),);return (<table><thead><tr>{propertyNames.map(val => (<th key={`h_${val}`}>{val}</th>))}</tr></thead><tbody>{filteredData.map((val, i) => (<tr key={`i_${i}`}>{propertyNames.map(p => (<td key={`i_${i}_${p}`}>{val[p]}</td>))}</tr>))}</tbody></table>);}
Notes
此组件不适用于嵌套对象,如果在propertyNames中指定的任何属性中有嵌套对象,则会中断。
例子
export default function() {const people = [{ name: 'John', surname: 'Smith', age: 42 },{ name: 'Adam', surname: 'Smith', gender: 'male' },];const propertyNames = ['name', 'surname', 'age'];return <MappedTable data={people} propertyNames={propertyNames} />;}
ps:
Input输入
Input基础输入框
输入框组件,使用回调函数将其值传递给父组件。
- 使用对象解构来设置
<input>元素的某些属性的默认值。 - 使用适当的属性渲染一个
<input>元素,并使用onChange事件中的callback函数将输入值传递给父元素。
function Input({ callback, type = 'text', disabled = false, readOnly = false, placeholder = '' }) {return (<inputtype={type}disabled={disabled}readOnly={readOnly}placeholder={placeholder}// event.target.valueonChange={({ target: { value } }) => callback(value)}/>);}
例子
export default function() {return <Input type="text" placeholder="Insert some text here..." callback={val => console.log(val)} />;}
ps:
LimitedTextarea限制字符数的多行文本
呈现限制字符数的多行文本组件。
- 使用
React.useState()hook 创建content状态变量并将其值设置为value。 创建一个方法setFormattedContent,如果它比limit长,它会修剪输入的内容。 - 使用
React.useEffect()hook 来调用content状态变量值的setFormattedContent方法。 - 使用
<div>来包装<textarea>和<p>元素,它显示字符数并绑定<textarea>的onChange事件来调用setFormattedContent处理event.target.value的值。
参考: hook文档
import React from "react";function LimitedTextarea({ rows, cols, value, limit }) {// React.useState(初始值) 通过在函数组件里调用它,来给组件添加一些内部 state// 返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。const [content, setContent] = React.useState(value);const setFormattedContent = text => {console.log("setFormattedContent");// 符合长度才允许修改text.length > limit ? setContent(text.slice(0, limit)) : setContent(text);};// useEffect 就是一个 Effect Hook ,在组件挂载和更新时执行// 可以看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个生命周期函数的组合React.useEffect(() => {console.log("useEffect");setFormattedContent(content);}, []);return (<div><textarearows={rows}cols={cols}onChange={event => setFormattedContent(event.target.value)}value={content}/><p>{content.length}/{limit}</p></div>);}
例子
export default function() {return <LimitedTextarea limit={32} value="Hello!" />;}
ps:
LimitedWordTextarea限制单词数的多行文本
呈现限制单词数的多行文本组件。
- 使用
React.useState()hook 创建content和wordCount状态变量,并将它们的值分别设置为value和0。 - 创建一个方法
setFormattedContent,它使用String.prototype.split(' ')将输入转换为单词数组,并使用Array.prototype.filter(Boolean)检查length是否比limit长。 - 如果上述
length超过limit,则修剪输入,否则返回原始输入,在两种情况下都相应地更新content和wordCount。 - 使用
React.useEffect()hook 来调用content状态变量值的setFormattedContent方法。 - 使用
<div>来包装<textarea>和<p>元素,它显示字符数并绑定<textarea>的onChange事件来调用setFormattedContent处理event.target.value的值。
function LimitedWordTextarea({ rows, cols, value, limit }) {const [content, setContent] = React.useState(value);const [wordCount, setWordCount] = React.useState(0);const setFormattedContent = text => {let words = text.split(" ");// words.filter(Boolean).length 获取数组长度// .filter(Boolean) 等价于 .filter((item) => {return Boolean(item)})// 也就是说这样写的意思就是去除数组中为 “假” 的元素if (words.filter(Boolean).length > limit) {setContent(text.split(" ").slice(0, limit).join(" "));setWordCount(limit);} else {setContent(text);setWordCount(words.filter(Boolean).length);}};React.useEffect(() => {setFormattedContent(content);}, []);return (<div><textarearows={rows}cols={cols}onChange={event => setFormattedContent(event.target.value)}value={content}/><p>{wordCount}/{limit}</p></div>);}
例子
export default function() {return <LimitedWordTextarea limit={5} value="Hello there!" />;}
ps:
MultiselectCheckbox复选框
呈现一个复选框列表,该列表使用回调函数将其选定的值/值传递给父组件。
- 使用
React.setState()创建一个data状态变量,并将其初始值设置为等于options。 - 创建一个函数
toggle,用于切换checked以更新data状态变量,并调用通过组件的props传递的onChange回调。 - 渲染一个
<ul>元素并使用Array.prototype.map()将data状态变量映射到单独的<li>元素,其中<input>元素作为它们的子元素。 - 每个
<input>元素都有type ='checkbox'属性并被标记为readOnly,因为它的click事件由父<li>元素的onClick处理程序处理。
const style = {listContainer: {listStyle: 'none',paddingLeft: 0},itemStyle: {cursor: 'pointer',padding: 5}};function MultiselectCheckbox({ options, onChange }) {const [data, setData] = React.useState(options);const toggle = item => {data.map((_, key) => {if (data[key].label === item.label) data[key].checked = !item.checked;});setData([...data]);onChange(data);};return (<ul style={style.listContainer}>{data.map(item => {return (<li key={item.label} style={style.itemStyle} onClick={() => toggle(item)}><input readOnly type="checkbox" checked={item.checked || false} />{item.label}</li>);})}</ul>);}
例子
export default function() {const options = [{ label: "Item One" }, { label: "Item Two" }];return (<MultiselectCheckboxoptions={options}onChange={data => {console.log(data);}}/>);}
ps:
PasswordRevealer密码可见
使用“显示”按钮呈现密码输入字段。
- 使用
React.useState()钩子创建shown状态变量并将其值设置为false。 - 使用
div>包装<input>和<button>元素,用于切换text和password之间输入字段的类型。
function PasswordRevealer({ value }) {const [shown, setShown] = React.useState(false);return (<div><input type={shown ? 'text' : 'password'} value={value} onChange={() => {}} /><button onClick={() => setShown(!shown)}>显示/隐藏</button></div>);}
例子
export default function() {return <PasswordRevealer />;}
ps:
Select下拉选择器
呈现一个<select>元素,该元素使用回调函数将其值传递给父组件。
- 使用对象解构来设置
<select>元素的某些属性的默认值。 - 使用适当的属性渲染一个
<select>元素,并使用onChange事件中的callback函数将textarea的值传递给父元素。 - 在
values数组上使用destructuring来传递value和text元素的数组以及selected属性来定义<select>元素的初始value。
function Select({values,callback,disabled = false,readonly = false,selected}) {const [current, setCurrent] = React.useState(selected);const handleChange = ({ target: { value } }) => {setCurrent(value);callback(value);};return (<selectvalue={current}disabled={disabled}readOnly={readonly}onChange={handleChange}>{values.map(([value, text]) => (<option value={value} key={value}>{text}</option>))}</select>);}
例子
export default function() {let choices = [["grapefruit", "Grapefruit"],["lime", "Lime"],["coconut", "Coconut"],["mango", "Mango"]];return (<Selectvalues={choices}selected={"lime"}callback={val => {console.log(val);}}/>);}
ps: 这里的实现跟官方不同,官方使用 option 的 selected 属性,但浏览器报错说不应使用,故更改为 select 的 value 属性。
Slider滑块元素
呈现滑块元素,使用回调函数将其值传递给父组件。
- 使用对象解构来设置
<input>元素的某些属性的默认值。 - 渲染一个类型为
range的<input>元素和相应的属性,使用onChange事件中的callback函数将输入值传递给父元素。
function Slider({ callback, disabled = false, readOnly = false }) {return (<inputtype="range"disabled={disabled}readOnly={readOnly}onChange={({ target: { value } }) => callback(value)}/>);}
例子
export default function() {return <Slider callback={val => console.log(val)} />;}
ps:
TextArea多行文本
呈现一个<textarea>元素,该元素使用回调函数将其值传递给父组件。
- 使用对象解构来设置
<textarea>元素的某些属性的默认值。 - 使用适当的属性渲染
<textarea>元素,并使用onChange事件中的callback函数将textarea的值传递给父元素。
function TextArea({callback,cols = 20,rows = 2,disabled = false,readOnly = false,placeholder = ''}) {return (<textareacols={cols}rows={rows}disabled={disabled}readOnly={readOnly}placeholder={placeholder}onChange={({ target: { value } }) => callback(value)}/>);}
例子
export default function() {return (<TextAreaplaceholder="Insert some text here..."callback={val => console.log(val)}/>);}
ps:
Object对象渲染
TreeView可折叠无限层级树组件
可折叠、无限层级、支持数组和对象的树组件。
- 使用对象解构来设置某些传入属性的默认值。
- 使用传入的
toggled属性来确定内容的初始状态(折叠/展开)。 - 使用
React.setState()hook 来创建isToggled状态变量,并在最初为它赋予传入的toggled的值。 - 返回一个
<div>来包装组件的内容和用于改变组件的isToggled状态的<span>元素。 - 根据
data上的isParentToggled,isToggled,name和Array.isArray()确定组件的外观。 - 对于
data中的每个子节点,确定它是对象还是数组,并递归渲染子树。 - 否则,使用适当的样式渲染一个
<p>元素。
样式:
/* 树节点的基本样式 */.tree-element {margin: 0;position: relative;}div.tree-element:before {content: '';position: absolute;top: 24px;left: 1px;height: calc(100% - 48px);border-left: 1px solid gray;}/* 切换显示、隐藏的按钮元素 */.toggler {position: absolute;top: 10px;left: 0px;width: 0;height: 0;border-top: 4px solid transparent;border-bottom: 4px solid transparent;border-left: 5px solid gray;cursor: pointer;}.toggler.closed {transform: rotate(90deg);}/* 隐藏节点内容 */.collapsed {display: none;}
树组件:
import styles from "./TreeView.css";function TreeView({// 使用对象解构来设置某些传入属性的默认值data,toggled = true, // 折叠按钮,是否处于折叠状态name = null, // 当前属性名,如果子元素是对象显示isLast = true, // 是否最后一个isChildElement = false, // 是否子元素isParentToggled = true // 是否被父节点折叠}) {const [isToggled, setIsToggled] = React.useState(toggled);return (<divstyle={{ marginLeft: isChildElement ? 16 : 4 + "px" }}// 如果父折叠就隐藏className={isParentToggled ? styles["tree-element"] : styles.collapsed}>{/* 折叠按钮,点击设置反状态 */}<spanclassName={isToggled ? styles.toggler : `${styles.toggler} ${styles.closed}`}onClick={() => setIsToggled(!isToggled)}/>{name ? <strong> {name}: </strong> : <span> </span>}{/* 开始符 */}{Array.isArray(data) ? "[" : "{"}{/* 子元素被折叠 */}{!isToggled && "..."}{/* 渲染对象的子元素 */}{Object.keys(data).map((v, i, a) =>// 是对象,递归调用自身typeof data[v] == "object" ? (<TreeViewdata={data[v]}key={i}isLast={i === a.length - 1}// 子元素的属性名,对象需要显示属性名,数组不显示name={Array.isArray(data) ? null : v}isChildElementisParentToggled={isParentToggled && isToggled}/>) : ( // 不是对象,显示内容即可<pkey={i}style={{ marginLeft: 16 + "px" }}className={isToggled ? styles["tree-element"] : styles.collapsed}>{Array.isArray(data) ? "" : <strong>{v}: </strong>}{data[v]}{i === a.length - 1 ? "" : ","}</p>))}{/* 结束符 */}{Array.isArray(data) ? "]" : "}"}{/* 不是最后元素,加个逗号 */}{!isLast ? "," : ""}</div>);}
例子
export default function() {let data = {lorem: {ipsum: "dolor sit",amet: {consectetur: "adipiscing",elit: ["duis","vitae",{semper: "orci"},{est: "sed ornare"},"etiam",["laoreet", "tincidunt"],["vestibulum", "ante"]]},ipsum: "primis"}};return <TreeView data={data} name="data" />;}
ps:
String字符串处理
AutoLink自动识别文本中的链接
将字符串中的URL转换为适当的 <a> 元素。
- 使用正则表达式配合
String.prototype.split()和String.prototype.match()来查找字符串中的URL。 - 返回一个
<React.Fragment>不产生多余的元素;其匹配的URL呈现为<a>元素,必要时需要处理丢失的协议前缀补充为http://,并将其余字符串呈现为明文。
import React from "react";function AutoLink({ text }) {// 用于找 url 的正则表达式const delimiter = /((?:https?:\/\/)?(?:(?:[a-z0-9]?(?:[a-z0-9\-]{1,61}[a-z0-9])?\.[^\.|\s])+[a-z\.]*[a-z]+|(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})(?::\d{1,5})*[a-z0-9.,_\/~#&=;%+?\-\\(\\)]*)/gi;return (<React.Fragment>{/* 按正则分割为数组,最终渲染时被显示在了一起 */}{text.split(delimiter).map(word => {// foo bar baz// http://example.org// barconsole.log(word);// 从以上分割的内容中找到具体的url的部分// 进行超链接的处理let match = word.match(delimiter);if (match) {let url = match[0];return (<a href={url.startsWith("http") ? url : `http://${url}`} key={url} target="_blank">{url}</a>);}return word;})}</React.Fragment>);}
例子
export default function() {return <AutoLink text="foo bar baz http://example.org barhttp://baidu.com 123" />;}
Visual视觉效果渲染
Accordion手风琴组件
手风琴效果组件,包含多个可折叠内容。
- 定义一个
AccordionItem组件,将它传递给Accordion。并通过在props.children中识别函数的名称来删除AccordionItem所需的不必要的节点。 - 每个
AccordionItem组有一个<button>,用于通过props.handleClick回调更新Accordion和组件的内容,通过props.children向下传递,它的折叠状态由props.isCollapsed确定。 - 在
Accordion组件中,使用React.useState()钩子将bindIndex状态变量的值初始化为props.defaultIndex。 - 在收集的节点上使用
Array.prototype.map来渲染单个可折叠的元素。 - 定义
changeItem,它将在单击AccordionItem的<button>时执行。changeItem执行传递的回调,onItemClick并根据点击的元素更新bindIndex。
AccordionItem 组件:
import React from "react";function AccordionItem(props) {const style = {collapsed: {display: "none"},expanded: {display: "block"},buttonStyle: {display: "block",width: "100%"}};return (<div>{/* 按钮,点击传入的 handleClick */}<button style={style.buttonStyle} onClick={() => props.handleClick()}>{props.label}</button>{/* 控制显示、隐藏状态 */}<divclassName="collapse-content"style={props.isCollapsed ? style.collapsed : style.expanded}aria-expanded={props.isCollapsed}>{/* 内容 */}{props.children}</div></div>);}
Accordion 组件:
function Accordion(props) {// 目前显示的 indexconst [bindIndex, setBindIndex] = React.useState(props.defaultIndex);// 点击即把 bindIndex 设置为自己const changeItem = itemIndex => {if (typeof props.onItemClick === "function") props.onItemClick(itemIndex);if (itemIndex !== bindIndex) setBindIndex(itemIndex);};// 筛选出传入的 AccordionItem 组件,忽略其他// item.type 对应 react 组件自身的函数, 函数.name 是函数名// 在本地环境,没有压缩,函数名就是组件名 AccordionItem// 线上压缩后,函数名没改为了单个字母,自然就无法判断了// 解决方案:跟它本身 .name 比较即可const items = props.children.filter(item => item.type.name === AccordionItem.name);return (<div className="wrapper">{items.map(({ props }) => (<AccordionItemisCollapsed={bindIndex === props.index}label={props.label}handleClick={() => changeItem(props.index)}children={props.children}/>))}</div>);}
例子
export default function() {return (<Accordion defaultIndex="1" onItemClick={console.log}><AccordionItem label="A" index="1">Lorem ipsum</AccordionItem><AccordionItem label="B" index="2">Dolor sit amet</AccordionItem></Accordion>);}
Carousel轮播组件
轮播组件。
- 使用
React.setState()hook 来创建active状态变量,并给它一个值’0’(第一项的索引)。 - 使用
style对象来保存各个组件的样式。 - 使用
React.setEffect()hook 使用setTimeout将active的值更新为下一个项的索引。 - 构造
props,计算是否应将可见性样式设置为“可见”或不对每个轮播项目进行映射,并相应地将组合样式应用于轮播项目组件。 - 使用
React.cloneElement()渲染轮播项目,并将其余的props与计算出的样式一起传递下来。
function Carousel(props) {// active 当前轮播激活的索引const [active, setActive] = React.useState(0);const style = {carousel: {position: "relative"},carouselItem: {position: "absolute",visibility: "hidden"},visible: {visibility: "visible"}};React.useEffect(() => {// 将 active 的值更新为下一个项的索引setTimeout(() => {const { carouselItems } = props;// 因为 active 在 render 中使用了, active 改变会影响视图而重新渲染,所以也会再次触发 useEffectsetActive((active + 1) % carouselItems.length);}, 1000);});const { carouselItems, ...rest } = props;return (<div style={style.carousel}>{carouselItems.map((item, index) => {// 激活就显示,否则隐藏const activeStyle = active === index ? style.visible : {};// 克隆出一个组件来渲染return React.cloneElement(item, {...rest,style: {...style.carouselItem,...activeStyle},key: index});})}</div>);}
例子
export default function() {return (<CarouselcarouselItems={[<div>carousel item 1</div>,<div>carousel item 2</div>,<div>carousel item 3</div>]}/>);}
Collapse折叠面板
折叠面板组件。
- 使用
React.setState()hook 创建isCollapsed状态变量,初始值为props.collapsed。 - 使用一个对象
style来保存单个组件及其状态的样式。 - 使用
<div>来包装改变组件的isCollapsed状态的<button>和组件的内容,通过props.children传递。 - 根据
isCollapsed确定内容的外观,并从style对象应用适当的CSS规则。 - 最后,根据
isCollapsed更新aria-expanded属性的值。
function Collapse(props) {const [isCollapsed, setIsCollapsed] = React.useState(props.collapsed);const style = {collapsed: {display: "none"},expanded: {display: "block"},buttonStyle: {display: "block",width: "100%"}};return (<div><buttonstyle={style.buttonStyle}onClick={() => setIsCollapsed(!isCollapsed)}>{isCollapsed ? "显示" : "隐藏"} 内容</button><divclassName="collapse-content"// 决定显示和折叠style={isCollapsed ? style.collapsed : style.expanded}// aria-expanded 是给 Screen Reader 用来 判断当前元素状态的辅助属性aria-expanded={isCollapsed}>{props.children}</div></div>);}
例子
export default function() {return (<Collapse><h1>This is a collapse</h1><p>Hello world!</p></Collapse>);}
CountDown倒计时
渲染倒数计时器,在达到零时打印消息。
- 使用对象解构来设置
hours,minutes和secondsprop 的默认值。 - 使用
React.useState()钩子来创建time,paused和over状态变量,并将它们的值分别设置为传递的props,false和false的值。 - 创建一个方法
tick,它根据当前值更新time的值(即将时间减少一秒)。 - 如果
paused或over是true,tick将立即返回。 - 创建一个方法
reset,将所有状态变量重置为其初始状态。 - 使用
React.useEffect()钩子通过使用setInterval()每秒调用tick方法,并在卸载组件时使用clearInterval()进行清理。 - 使用
<div>用time状态变量的文本表示形式包装<p>元素,以及分别暂停/取消暂停和重启计时器的两个<button>元素。 - 如果
over为true,计时器将显示一条消息,而不是time的值。
import React from "react";function CountDown({ hours = 0, minutes = 0, seconds = 0 }) {const [paused, setPaused] = React.useState(false);const [over, setOver] = React.useState(false);// time 默认值是一个 objectconst [time, setTime] = React.useState({hours: parseInt(hours),minutes: parseInt(minutes),seconds: parseInt(seconds)});const tick = () => {// 暂停,或已结束if (paused || over) return;if (time.hours === 0 && time.minutes === 0 && time.seconds === 0)setOver(true);else if (time.minutes === 0 && time.seconds === 0)setTime({hours: time.hours - 1,minutes: 59,seconds: 59});else if (time.seconds === 0)setTime({hours: time.hours,minutes: time.minutes - 1,seconds: 59});elsesetTime({hours: time.hours,minutes: time.minutes,seconds: time.seconds - 1});};// 重置const reset = () => {setTime({hours: parseInt(hours),minutes: parseInt(minutes),seconds: parseInt(seconds)});setPaused(false);setOver(false);};React.useEffect(() => {// 执行定时let timerID = setInterval(() => tick(), 1000);// 卸载组件时进行清理return () => clearInterval(timerID);});return (<div><p>{`${time.hours.toString().padStart(2, "0")}:${time.minutes.toString().padStart(2, "0")}:${time.seconds.toString().padStart(2, "0")}`}</p><div>{over ? "Time's up!" : ""}</div><button onClick={() => setPaused(!paused)}>{paused ? "Resume" : "Pause"}</button><button onClick={() => reset()}>Restart</button></div>);}
例子
export default function() {return <CountDown hours="1" minutes="45" />;}
FileDrop文件拖放组件
文件拖放组件。
- 为此组件创建一个名为
dropRef的引用。 - 使用
React.useState()钩子来创建drag和filename变量,分别初始化为false和空字符串。变量dragCounter和drag用于确定是否正在拖动文件,而filename用于存储被删除文件的名称。 - 创建
handleDrag,handleDragIn,handleDragOut和handleDrop方法来处理拖放功能,将它们绑定到组件的上下文。 - 每个方法都将处理一个特定的事件,在
React.useEffect()钩子及其附加的cleanup()方法中创建和删除它的监听器。 handleDrag阻止浏览器打开拖动的文件,handleDragIn和handleDragOut处理进入和退出组件的拖动文件,而handleDrop处理被删除的文件并将其传递给props.handleDrop。- 返回一个适当样式的
<div>并使用drag和filename来确定其内容和样式。 - 最后,将创建的
<div>的ref绑定到dropRef。
.filedrop {min-height: 120px;border: 3px solid #d3d3d3;text-align: center;font-size: 24px;padding: 32px;border-radius: 4px;}.filedrop.drag {border: 3px dashed #1e90ff;}.filedrop.ready {border: 3px solid #32cd32;}
import React from "react";import styles from "./FileDrop.css";function FileDrop(props) {const [drag, setDrag] = React.useState(false);const [filename, setFilename] = React.useState("");// 创建组件引用let dropRef = React.createRef();let dragCounter = 0;const handleDrag = e => {e.preventDefault();e.stopPropagation();};const handleDragIn = e => {e.preventDefault();e.stopPropagation();dragCounter++;if (e.dataTransfer.items && e.dataTransfer.items.length > 0) setDrag(true);};const handleDragOut = e => {e.preventDefault();e.stopPropagation();dragCounter--;if (dragCounter === 0) setDrag(false);};const handleDrop = e => {e.preventDefault();e.stopPropagation();setDrag(false);if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {props.handleDrop(e.dataTransfer.files[0]);setFilename(e.dataTransfer.files[0].name);e.dataTransfer.clearData();dragCounter = 0;}};React.useEffect(() => {// 监听拖放事件let div = dropRef.current;div.addEventListener("dragenter", handleDragIn);div.addEventListener("dragleave", handleDragOut);div.addEventListener("dragover", handleDrag);div.addEventListener("drop", handleDrop);// 销毁时移除事件return function cleanup() {div.removeEventListener("dragenter", handleDragIn);div.removeEventListener("dragleave", handleDragOut);div.removeEventListener("dragover", handleDrag);div.removeEventListener("drop", handleDrop);};});return (<div// ref 引用ref={dropRef}className={drag? `${styles.filedrop} ${styles.drag}`: filename? `${styles.filedrop} ${styles.ready}`: styles.filedrop}>{filename && !drag ? <div>{filename}</div> : <div>Drop files here!</div>}</div>);}
例子
export default function() {return <FileDrop handleDrop={console.log} />;}
Mailto发送电子邮件
格式化为发送电子邮件的链接。
- 构造组件的props,使用
email,subject和body创建一个具有href属性的<a>元素。 - 使用
props.children呈现链接的内容。
function Mailto({ email, subject, body, ...props }) {return (<a href={`mailto:${email}?subject=${subject || ''}&body=${body || ''}`}>{props.children}</a>);}
例子
export default function() {return (<Mailto email="foo@bar.baz" subject="Hello" body="Hello world!">Mail me!</Mailto>);}
Modal模态框组件
可通过事件控制的模态组件。
要使用该组件,只导入一次Modal,然后通过将一个布尔值传递给isVisible属性来显示它。
- 使用对象解构来设置模态组件的某些属性的默认值。
- 定义
keydownHandler方法,用于处理所有键盘事件,可以根据你的需要使用它来调度动作(例如,当按下Esc时关闭模态)。 使用React.useEffect()hook来添加或删除keydown事件监听器,它调用keydownHandler。 使用isVisible道具来确定是否应该显示模态。 *使用CSS来设置和定位模态组件。
样式:
.modal {position: fixed;top: 0;bottom: 0;left: 0;right:0;width: 100%;z-index: 9999;display: flex;align-items: center;justify-content: center;background-color: rgba(0, 0, 0, 0.25);animation-name: appear;animation-duration: 300ms;}.modal-dialog{width: 100%;max-width: 550px;background: white;position: relative;margin: 0 20px;max-height: calc(100vh - 40px);text-align: left;display: flex;flex-direction: column;overflow:hidden;box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);-webkit-animation-name: animatetop;-webkit-animation-duration: 0.4s;animation-name: slide-in;animation-duration: 0.5s;}.modal-header,.modal-footer{display: flex;align-items: center;padding: 1rem;}.modal-header{border-bottom: 1px solid #dbdbdb;justify-content: space-between;}.modal-footer{border-top: 1px solid #dbdbdb;justify-content: flex-end;}.modal-close{cursor: pointer;padding: 1rem;margin: -1rem -1rem -1rem auto;}.modal-body{overflow: auto;}.modal-content{padding: 1rem;}@keyframes appear {from {opacity: 0;}to {opacity: 1;}}@keyframes slide-in {from {transform: translateY(-150px);}to { transform: translateY(0);}}
组件:
import React from "react";import styles from "./Modal.css";function Modal({ isVisible = false, title, content, footer, onClose }) {React.useEffect(() => {// 监听事件document.addEventListener("keydown", keydownHandler);// 取消监听return () => document.removeEventListener("keydown", keydownHandler);});function keydownHandler({ key }) {// esc 键,关闭模态框switch (key) {case "Escape":onClose();break;default:}}// 控制模态框显示return !isVisible ? null : (<div className={styles["modal"]} onClick={onClose}><divclassName={styles["modal-dialog"]}onClick={e => e.stopPropagation()}><div className={styles["modal-header"]}><h3 className={styles["modal-title"]}>{title}</h3><span className={styles["modal-close"]} onClick={onClose}>×</span></div><div className={styles["modal-body"]}><div className={styles["modal-content"]}>{content}</div></div>{footer && <div className={styles["modal-footer"]}>{footer}</div>}</div></div>);}
例子
// 将组件添加到 render 函数function App() {const [isModal, setModal] = React.useState(false);return (<React.Fragment>{/* 按钮显示模态框 */}<button onClick={() => setModal(true)}>显示模态框</button><ModalisVisible={isModal}title="标题"content={<p>正文</p>}footer={<button onClick={() => setModal(false)}>关闭模态框</button>}onClose={() => setModal(false)}/></React.Fragment>);}export default function() {return <App />;}
StarRating星级评分
星级评分组件。
- 定义一个名为“Star”的组件,它将根据父组件的状态为每个星形呈现适当的外观。
- 在
StarRating组件中,使用React.useState()钩子来定义rating和selection状态变量,初始值为props.rating(如果无效或未传入,则为 0 )和 0 。 - 创建一个方法
hoverOver,根据传入的event更新selected和rating。 - 创建一个
<div>来包装<Star>组件,这些组件是使用Array.prototype.map在5个元素的数组上创建的,使用Array.from创建,并处理onMouseLeave将selection设置为0的事件,onClick事件设置rating和onMouseOver事件,分别将selection设置为event.target的star-id属性。 - 最后,将适当的值传递给每个
<Star>组件(starId和marked)。
星星组件:
function Star({ marked, starId }) {return (<span star-id={starId} style={{ color: "#ff9933" }} role="button">{/* 空星,实星 */}{marked ? "\u2605" : "\u2606"}</span>);}
星级评分:
function StarRating(props) {// 分数显示const [rating, setRating] = React.useState(typeof props.rating == "number" ? props.rating : 0);// 鼠标移入效果const [selection, setSelection] = React.useState(0);const hoverOver = event => {let val = 0;if (event && event.target && event.target.getAttribute("star-id"))val = event.target.getAttribute("star-id");setSelection(val);};return (<div// 鼠标移入效果onMouseOut={() => hoverOver(null)}// 点击选中分数onClick={event =>setRating(event.target.getAttribute("star-id") || rating)}onMouseOver={hoverOver}>{/* 创建5个组件 */}{Array.from({ length: 5 }, (v, i) => (<StarstarId={i + 1}key={`star_${i + 1} `}marked={selection ? selection >= i + 1 : rating >= i + 1}/>))}</div>);}
例子
export default function() {return <div><StarRating /><StarRating rating={2} /></div>;}
Tabs选项卡组件
选项卡组件。
- 定义一个
TabItem组件,将它传递给Tab并通过在props.children中识别函数的名称来删除除了TabItem外的不必要的节点。 - 使用
React.useState()hook 将bindIndex状态变量的值初始化为props.defaultIndex。 - 使用
Array.prototype.map来渲染tab-menu和tab-view。 - 定义
changeTab,用于tab-menu单击<button>时执行。 - 这导致根据他们的
index反过来重新渲染tab-view项的style和className以及tab-menu。 changeTab执行传递的回调函数onTabClick,并更新bindIndex,这会导致重新渲染,根据它们的index改变tab-view项目和tab-menu按钮的style和className。
.tab-menu > button {cursor: pointer;padding: 8px 16px;border: 0;border-bottom: 2px solid transparent;background: none;}.tab-menu > button.focus {border-bottom: 2px solid #007bef;}.tab-menu > button:hover {border-bottom: 2px solid #007bef;}
import styles from "./Tabs.css";function TabItem(props) {return <div {...props} />;}function Tabs(props) {const [bindIndex, setBindIndex] = React.useState(props.defaultIndex);const changeTab = newIndex => {if (typeof props.onTabClick === "function") props.onTabClick(newIndex);setBindIndex(newIndex);};const items = props.children.filter(item => item.type.name === TabItem.name);return (<div className={styles["wrapper"]}><div className={styles["tab-menu"]}>{items.map(({ props: { index, label } }) => (<buttononClick={() => changeTab(index)}key={index}className={bindIndex === index ? styles["focus"] : ""}>{label}</button>))}</div><div className={styles["tab-view"]}>{items.map(({ props }) => (<div{...props}className={styles["tab-view_item"]}key={props.index}style={{ display: bindIndex === props.index ? "block" : "none" }}/>))}</div></div>);}
例子
export default function() {return (<Tabs defaultIndex="1" onTabClick={console.log}><TabItem label="A" index="1">A 选修卡的内容</TabItem><TabItem label="B" index="2">B 选修卡的内容</TabItem></Tabs>);}
Ticker时间控制组件
时间控制组件
- 使用
React.useState()hook 将ticker状态变量初始化为0。 - 定义两个方法,
tick和reset,它们将根据interval周期性地递增timer并分别重置interval。 - 返回带有两个
<button>元素的<div>,分别调用tick和reset。
function Ticker(props) {// 当前 ticker ,默认 0const [ticker, setTicker] = React.useState(0);let interval = null;// 开始计时const tick = () => {reset();interval = setInterval(() => {if (ticker < props.times) setTicker(ticker + 1);else clearInterval(interval);}, props.interval);};// 重置为0,并清除计时器const reset = () => {setTicker(0);clearInterval(interval);};return (<div><span style={{ fontSize: 100 }}>{ticker}</span><button onClick={tick}>Tick!</button><button onClick={reset}>Reset</button></div>);}
注:useState 与 setInterval 运行在 umi 的 demo 中存在异常,这里使用“类组件”的形式完成Ticker组件。
import React from "react";class Ticker extends React.Component {constructor(props) {super(props);this.state = { ticker: 0 };this.interval = null;}tick = () => {this.reset();this.interval = setInterval(() => {if (this.state.ticker < this.props.times) {this.setState(({ ticker }) => ({ ticker: ticker + 1 }));} else {clearInterval(this.interval);}}, this.props.interval);};reset = () => {this.setState({ ticker: 0 });clearInterval(this.interval);};render() {return (<div><span style={{ fontSize: 100 }}>{this.state.ticker}</span><button onClick={this.tick}>Tick!</button><button onClick={this.reset}>Reset</button></div>);}}
例子
export default function() {return <Ticker times={5} interval={1000} />;}
Toggle开关组件
开关组件
- 使用
React.useState()将isToggleOn状态变量初始化为false。 - 使用一个对象
style来保存单个组件及其状态的样式。 - 返回一个
<button>,当它的onClick事件被触发时改变组件的isToggledOn,并根据isToggleOn确定内容的外观,从style对象应用适当的CSS规则。
function Toggle(props) {const [isToggleOn, setIsToggleOn] = React.useState(false);style = {on: {backgroundColor: 'green'},off: {backgroundColor: 'grey'}};return (<button onClick={() => setIsToggleOn(!isToggleOn)} style={isToggleOn ? style.on : style.off}>{isToggleOn ? 'ON' : 'OFF'}</button>);}
例子
export default function() {return <Toggle />;}
Tooltip提示
提示组件。
- 使用
React.useState()钩子创建show变量并将其初始化为false。 - 返回一个
<div>元素,其中包含将作为工具提示的<div>和传递给组件的children。 - 通过改变
show变量的值来处理onMouseEnter和onMouseLeave方法。
.tooltip {position: relative;background: rgba(0, 0, 0, 0.7);color: white;visibility: hidden;padding: 5px;border-radius: 5px;}.tooltip-arrow {position: absolute;top: 100%;left: 50%;border-width: 5px;border-style: solid;border-color: rgba(0, 0, 0, 0.7) transparent transparent;}
import styles from "./Tooltip.css";function Tooltip({ children, text, ...rest }) {const [show, setShow] = React.useState(false);return (<div><divclassName={styles["tooltip"]}style={show ? { visibility: "visible" } : {}}>{text}<span className={styles["tooltip-arrow"]} /></div><div{...rest}onMouseEnter={() => setShow(true)}onMouseLeave={() => setShow(false)}>{children}</div></div>);}
例子
export default function() {return (<Tooltip text="提示文本~"><button>鼠标移入!</button></Tooltip>);}
注:本仓库使用“谷歌机器翻译+本人校对优化”的方式进行中文化。
30-seconds-of-react正在进行中。 如果您想贡献,请查看未解决的问题,看看您可以在何处以及如何提供帮助!
原版README是使用 markdown-builder 构建的。
