All Projects → heibaimeng → 30-seconds-of-react-zh_CN-umi

heibaimeng / 30-seconds-of-react-zh_CN-umi

Licence: CC0-1.0 license
React 30 秒速学: 精选有用的 React 片段。30-seconds-of-react 的中文版本,已全部完成翻译、上线。

Programming Languages

javascript
184084 projects - #8 most used programming language
CSS
56736 projects

Projects that are alternatives of or similar to 30-seconds-of-react-zh CN-umi

crypto-watchdog
Crypto Watchdog is an open-source developer friendly project, periodically queries crypto market and notifies potential pumps & recently added tokens/coins via web-hooks.
Stars: ✭ 22 (-56%)
Mutual labels:  hook
react-cool-virtual
😎 ♻️ A tiny React hook for rendering large datasets like a breeze.
Stars: ✭ 1,031 (+1962%)
Mutual labels:  hook
use-async-memo
React hook for generating async memoized data.
Stars: ✭ 130 (+160%)
Mutual labels:  hook
JVMTI Demo
android p jvmti/android studio apply change
Stars: ✭ 46 (-8%)
Mutual labels:  hook
logrus-redis-hook
Hook for Logrus which enables logging to RELK stack (Redis, Elasticsearch, Logstash and Kibana)
Stars: ✭ 33 (-34%)
Mutual labels:  hook
umi-plugin-electron-builder
umi的electron插件
Stars: ✭ 115 (+130%)
Mutual labels:  umi
objc-format-check
🛠A code-format tool for C or OC
Stars: ✭ 75 (+50%)
Mutual labels:  hook
react-health-check
Lightweight React hook for checking health of API services.
Stars: ✭ 28 (-44%)
Mutual labels:  hook
react-use-hotkeys
React hook for creating simple keyboard shortcuts
Stars: ✭ 74 (+48%)
Mutual labels:  hook
semantic-commit-hook
Git hook that enforces semantic commit messages.
Stars: ✭ 34 (-32%)
Mutual labels:  hook
SSDTHOOK
An SSDT hook for Windows
Stars: ✭ 44 (-12%)
Mutual labels:  hook
SandVXposed
Xposed environment without root (OS 5.0 - 10.0)
Stars: ✭ 832 (+1564%)
Mutual labels:  hook
route-utils
umi route utils
Stars: ✭ 32 (-36%)
Mutual labels:  umi
use-smooth-scroll
React hook which gives a smooth scrolling function.
Stars: ✭ 41 (-18%)
Mutual labels:  hook
hookey
Enables all the DLCs. Like Creamapi but just for linux and a subset of Paradox games.
Stars: ✭ 87 (+74%)
Mutual labels:  hook
AndroidGotHook
GOT Hook implemented in Android
Stars: ✭ 63 (+26%)
Mutual labels:  hook
use-double-tap
React hook for handling double tap on mobile devices
Stars: ✭ 18 (-64%)
Mutual labels:  hook
umi-dva-typescript-mock
基于umi + dva + typescript + mock + antd的react框架,内置PWA
Stars: ✭ 17 (-66%)
Mutual labels:  umi
letsencrypt-manual-hook
Allows you to use dehydrated (a Let's Encrypt/Acme Client) and DNS challenge response with a DNS provider that requires manual intervention
Stars: ✭ 61 (+22%)
Mutual labels:  hook
umi-plugin-md
🍚 Markdown(*.md) component plugin for umi.
Stars: ✭ 16 (-68%)
Mutual labels:  umi

30-seconds-of-react 项目的中文翻译版本,使用 umi.js 对所有案例进行分析、注释、上线。

共 25 个组件,目前已全部翻译完成 。

Logo

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 install
npm run dev

相关产品

目录

Array渲染数组

Input输入

Object对象渲染

String字符串处理

Visual视觉效果渲染


Array渲染数组

DataList渲染为列表

通过数组渲染元素列表。

  • 使用 isOrdered prop 的值有条件地渲染<ol><ul>列表。
  • 使用Array.prototype.mapdata中的每个项目渲染为<li>元素,给它一个由其索引和值的串联产生的key
  • 默认情况下,省略 isOrdered prop 以渲染<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渲染为表格

通过数组渲染表格,动态创建每一行。

*渲染一个带有两列(IDValue)的<table>元素。 *使用Array.prototype.mapdata中的每个项目渲染为<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.mappropertyNames数组中的每个值渲染为<th>元素。
  • 使用Array.prototype.mapfilteredData数组中的每个对象渲染为<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 (
    <input
      type={type}
      disabled={disabled}
      readOnly={readOnly}
      placeholder={placeholder}
      // event.target.value
      onChange={({ 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>
      <textarea
        rows={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 创建contentwordCount状态变量,并将它们的值分别设置为value0
  • 创建一个方法setFormattedContent,它使用String.prototype.split(' ')将输入转换为单词数组,并使用 Array.prototype.filter(Boolean)检查 length是否比 limit长。
  • 如果上述length超过limit,则修剪输入,否则返回原始输入,在两种情况下都相应地更新contentwordCount
  • 使用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>
      <textarea
        rows={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 (
    <MultiselectCheckbox
      options={options}
      onChange={data => {
        console.log(data);
      }}
    />
  );
}

ps:

PasswordRevealer密码可见

使用“显示”按钮呈现密码输入字段。

  • 使用React.useState()钩子创建shown状态变量并将其值设置为false
  • 使用div>包装<input><button>元素,用于切换textpassword之间输入字段的类型。
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来传递valuetext元素的数组以及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 (
    <select
      value={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 (
    <Select
      values={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 (
    <input
      type="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 (
    <textarea
      cols={cols}
      rows={rows}
      disabled={disabled}
      readOnly={readOnly}
      placeholder={placeholder}
      onChange={({ target: { value } }) => callback(value)}
    />
  );
}

例子

export default function() {
  return (
    <TextArea
      placeholder="Insert some text here..."
      callback={val => console.log(val)}
    />
  );
}

ps:

Object对象渲染

TreeView可折叠无限层级树组件

可折叠、无限层级、支持数组和对象的树组件。

  • 使用对象解构来设置某些传入属性的默认值。
  • 使用传入的 toggled 属性来确定内容的初始状态(折叠/展开)。
  • 使用React.setState() hook 来创建isToggled状态变量,并在最初为它赋予传入的 toggled的值。
  • 返回一个<div>来包装组件的内容和用于改变组件的isToggled状态的<span>元素。
  • 根据data上的isParentToggledisTogglednameArray.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 (
    <div
      style={{ marginLeft: isChildElement ? 16 : 4 + "px" }}
      // 如果父折叠就隐藏
      className={isParentToggled ? styles["tree-element"] : styles.collapsed}
    >
      {/* 折叠按钮,点击设置反状态 */}
      <span
        className={
          isToggled ? styles.toggler : `${styles.toggler} ${styles.closed}`
        }
        onClick={() => setIsToggled(!isToggled)}
      />
      {name ? <strong>&nbsp;&nbsp;{name}: </strong> : <span>&nbsp;&nbsp;</span>}
      {/* 开始符 */}
      {Array.isArray(data) ? "[" : "{"}
      {/* 子元素被折叠 */}
      {!isToggled && "..."}
      {/* 渲染对象的子元素 */}
      {Object.keys(data).map((v, i, a) =>
        // 是对象,递归调用自身
        typeof data[v] == "object" ? (
          <TreeView
            data={data[v]}
            key={i}
            isLast={i === a.length - 1}
            // 子元素的属性名,对象需要显示属性名,数组不显示
            name={Array.isArray(data) ? null : v}
            isChildElement
            isParentToggled={isParentToggled && isToggled}
          />
        ) : ( // 不是对象,显示内容即可
          <p
            key={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
        //  bar
        console.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>
      {/* 控制显示、隐藏状态 */}
      <div
        className="collapse-content"
        style={props.isCollapsed ? style.collapsed : style.expanded}
        aria-expanded={props.isCollapsed}
      >
        {/* 内容 */}
        {props.children}
      </div>
    </div>
  );
}

Accordion 组件:

function Accordion(props) {
  // 目前显示的 index
  const [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 }) => (
        <AccordionItem
          isCollapsed={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 使用setTimeoutactive的值更新为下一个项的索引。
  • 构造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 改变会影响视图而重新渲染,所以也会再次触发 useEffect
      setActive((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 (
    <Carousel
      carouselItems={[
        <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>
      <button
        style={style.buttonStyle}
        onClick={() => setIsCollapsed(!isCollapsed)}
      >
        {isCollapsed ? "显示" : "隐藏"} 内容
      </button>
      <div
        className="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倒计时

渲染倒数计时器,在达到零时打印消息。

  • 使用对象解构来设置hoursminutessecondsprop 的默认值。
  • 使用React.useState()钩子来创建timepausedover状态变量,并将它们的值分别设置为传递的props,falsefalse的值。
  • 创建一个方法tick,它根据当前值更新time的值(即将时间减少一秒)。
  • 如果pausedovertruetick将立即返回。
  • 创建一个方法reset,将所有状态变量重置为其初始状态。
  • 使用React.useEffect()钩子通过使用setInterval()每秒调用tick方法,并在卸载组件时使用clearInterval()进行清理。
  • 使用<div>time状态变量的文本表示形式包装<p>元素,以及分别暂停/取消暂停和重启计时器的两个<button>元素。
  • 如果overtrue,计时器将显示一条消息,而不是 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 默认值是一个 object
  const [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
      });
    else
      setTime({
        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()钩子来创建dragfilename变量,分别初始化为false和空字符串。变量dragCounterdrag用于确定是否正在拖动文件,而filename用于存储被删除文件的名称。
  • 创建handleDraghandleDragInhandleDragOuthandleDrop方法来处理拖放功能,将它们绑定到组件的上下文。
  • 每个方法都将处理一个特定的事件,在React.useEffect()钩子及其附加的cleanup()方法中创建和删除它的监听器。
  • handleDrag阻止浏览器打开拖动的文件,handleDragInhandleDragOut处理进入和退出组件的拖动文件,而handleDrop处理被删除的文件并将其传递给props.handleDrop
  • 返回一个适当样式的<div>并使用dragfilename来确定其内容和样式。
  • 最后,将创建的<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,使用emailsubjectbody创建一个具有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="[email protected]" 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}>
      <div
        className={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}>
            &times;
          </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>
      <Modal
        isVisible={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()钩子来定义ratingselection状态变量,初始值为props.rating(如果无效或未传入,则为 0 )和 0 。
  • 创建一个方法hoverOver,根据传入的event更新selectedrating
  • 创建一个<div>来包装<Star>组件,这些组件是使用Array.prototype.map在5个元素的数组上创建的,使用Array.from创建,并处理onMouseLeaveselection设置为0的事件,onClick事件设置ratingonMouseOver事件,分别将selection设置为event.targetstar-id属性。
  • 最后,将适当的值传递给每个<Star>组件(starIdmarked)。

星星组件:

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) => (
        <Star
          starId={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-menutab-view
  • 定义 changeTab ,用于 tab-menu 单击 <button> 时执行。
  • 这导致根据他们的index 反过来重新渲染 tab-view项的styleclassName以及tab-menu
  • changeTab 执行传递的回调函数 onTabClick ,并更新 bindIndex ,这会导致重新渲染,根据它们的 index 改变 tab-view 项目和 tab-menu 按钮的 styleclassName
.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 } }) => (
          <button
            onClick={() => 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
  • 定义两个方法,tickreset,它们将根据interval周期性地递增timer并分别重置interval
  • 返回带有两个<button>元素的<div>,分别调用tickreset
function Ticker(props) {
  // 当前 ticker ,默认 0
  const [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变量的值来处理onMouseEnteronMouseLeave方法。
.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>
      <div
        className={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 构建的。

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].