介紹
React 是一個用于建構使用者界面的 JavaScript 庫。
- React 官方文檔 (reactjs.org)
- Styled Components 備忘清單 (jaywcjlove.github.io)
import {createRoot} from 'react-dom/client'
import App from './App'
const elm = document.getElementById('app')
const root = createRoot(elm);
root.render(<App />);
快速建立React項目 (CRA)
npx create-react-app my-app
導入多個導出
import React, {Component} from 'react'
import ReactDOM from 'react-dom'
export class Hello extends Component {
...
}
export default function World() {
/* ... */
}
使用 export 導出 Hello,export default 導出 World 元件
import World, { Hello } from './hello.js';
使用 import 導入 Hello 元件,在示例中使用。
React 元件中的 CSS
import React from "react";
import "./Student.css";
export const Student = (
<div className="Student"></div>
);
注意:類屬性 className
const divStyle = {
backgroundImage: 'url(' + imgUrl + ')',
};
export const Student = (
<div style={divStyle}></div>
);
屬性
<Student name="Julie" age={23}
pro={true} />
函數元件 Student 中通路屬性
function Student(props) {
return <h1>Hello, {props.name}</h1>;
}
Class 元件 Student 中通路屬性
class Student extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
class 元件使用 this.props 通路傳遞給元件的屬性。
Children
function Example() {
return (
<AlertBox>
<h1>您有待處理的通知</h1>
</AlertBox>
)
}
函數 AlertBox 元件
function AlertBox(props) {
return (
<div className="alert-box">
{props.children}
</div>
);
}
{props.children}
Class AlertBox 元件,與函數元件 AlertBox 元件相同
class AlertBox extends React.Component {
render () {
return (
<div className="alert-box">
{this.props.children}
</div>
);
}
}
{this.props.children}
children 作為子元件的的屬性傳遞。
State
函數中的 State,Hook 是 React 16.8 的新增特性
import { useState } from 'react';
function Student() {
const [count, setCount] = useState(0);
const click = () => setCount(count + 1);
return (
<div>
<p>您點選了 {count} 次</p>
<button onClick={click}>
點選我
</button>
</div>
);
}
使用 setState 更新狀态,下面是函數元件讀取狀态
<p>您點選了 {count} 次</p>
Class 中的 State
import React from 'react';
class Student extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
// 確定函數可以通路元件屬性(ES2015)
this.click = this.click.bind(this);
}
click() {
const count = this.state.count;
this.setState({ count: count + 1})
}
render() {
return (
<div>
<button onClick={this.click}>
點選我
</button>
<p>您點選了{this.state.count}次</p>
</div>
);
}
}
使用 setState 更新狀态,class 元件中不能使用 hooks。下面是 class 元件讀取狀态
<p>您點選了{this.state.count}次</p>
循環
const elm = ['one', 'two', 'three'];
function Student() {
return (
<ul>
{elm.map((value, index) => (
<li key={index}>{value}</li>
))}
</ul>
);
}
key 值在兄弟節點之間必須唯一
事件監聽
export default function Hello() {
function handleClick(event) {
event.preventDefault();
alert("Hello World");
}
return (
<a href="/" onClick={handleClick}>
Say Hi
</a>
);
}
函數注入
function addNumbers(x1, x2) {
return x1 + x2;
}
const element = (
<div>
{addNumbers(2, 5)}
</div>
);
嵌套
import { useState } from 'react'
import Avatar from './Avatar';
import Profile from './Profile';
function Student() {
const [count, setCount] = useState(0);
return (
<div>
<Avatar src={count} />
<Profile username={count} />
</div>
);
}
Portals
React 并_沒有_建立一個新的 div。它隻是把子元素渲染到 domNode 中。domNode 是一個可以在任何位置的有效 DOM 節點。
render() {
return ReactDOM.createPortal(
this.props.children,
domNode
);
}
提供了一種将子節點渲染到存在于父元件以外的 DOM 節點的優秀的方案
Fragment
import { Fragment } from 'react'
import Avatar from './Avatar';
import Profile from './Profile';
const Student = () => (
<Fragment>
<Avatar src="./demo.jpg" />
<Profile username="name" />
</Fragment>
);
從 v16.2.0 開始 Fragment 可用于傳回多個子節點,而無需向 DOM 添加額外的包裝節點。或者使用 <></> 效果是一樣的。
const Student = () => (
<>
<Avatar src="./demo.jpg" />
<Profile username="name" />
</>
);
檢視: Fragments & strings
傳回字元串
render() {
return 'Look ma, no spans!';
}
您可以隻傳回一個字元串。檢視: Fragments & strings
傳回數組
const Student = () => [
<li key="A">First item</li>,
<li key="B">Second item</li>
];
不要忘記 key!檢視: Fragments & strings
Refs 轉發
const FancyButton = React.forwardRef(
(props, ref) => (
<button ref={ref} className="btn">
{props.children}
</button>
)
);
使用
// 你可以直接擷取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>
點選我
</FancyButton>;
Class 元件内部使用 ref 屬性
import {Component,createRef} from 'react'
class MyComponent extends Component {
constructor(props) {
super(props);
this.myRef = createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
提示:Refs 适用于類元件,但不适用于函數元件(除非您使用 useRef hook,請參閱hooks)
函數元件内部使用 ref 屬性
function CustomTextInput(props) {
// 這裡必須聲明 $input,這樣 ref 才可以引用它
const $input = useRef(null);
function handleClick() {
$input.current.focus();
}
return (
<div>
<input type="text" ref={$input} />
<input
type="button" value="聚焦文本輸入"
onClick={handleClick}
/>
</div>
);
}
嚴格模式 StrictMode
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
- 識别不安全的生命周期
- 關于使用過時字元串 ref API 的警告
- 關于使用廢棄的 findDOMNode 方法的警告
- 檢測意外的副作用
- 檢測過時的 context API
- 確定可複用的狀态
突出顯示應用程式中潛在問題的工具。請參閱:嚴格模式
Profiler
測量一個 React 應用多久渲染一次以及渲染一次的 代價
<Profiler id="Navigation" onRender={callback}>
<Navigation {...props} />
</Profiler>
為了分析 Navigation 元件和它的子代。應該在需要時才去使用它。
id(string) | 發生送出的 Profiler 樹的 id |
onRender(function) | 元件樹任何元件 “送出” 一個更新的時候調用這個函數 |
onRender 回調函數
phase: "mount" | "update" | 判斷是由 props/state/hooks 改變 或 “第一次裝載” 引起的重渲染 |
actualDuration: number | 本次更新在渲染 Profiler 和它的子代上花費的時間 |
baseDuration: number | 在 Profiler 樹中最近一次每一個元件 render 的持續時間 |
startTime: number | 本次更新中 React 開始渲染的時間戳 |
commitTime: number | 本次更新中 React commit 階段結束的時間戳 |
interactions: Set | 當更新被制定時,“interactions” 的集合會被追蹤 |
預設值
Class 元件預設 props
class CustomButton extends React.Component {
// ...
}
CustomButton.defaultProps = {
color: 'blue'
};
使用
<CustomButton /> ;
不傳值 props.color 将自動設定為 blue
Class 元件預設 state
class Hello extends Component {
constructor (props) {
super(props)
this.state = { visible: true }
}
}
在構造 constructor()中設定預設狀态。
class Hello extends Component {
state = { visible: true }
}
函數元件預設 props
function CustomButton(props) {
const { color = 'blue' } = props;
return <div>{color}</div>
}
函數元件預設 state
function CustomButton() {
const [color, setColor]=useState('blue')
return <div>{color}</div>
}
JSX
介紹
JSX 僅僅隻是 React.createElement(component, props, ...children) 函數的文法糖
<MyButton color="blue" shadowSize={2}>
點選我
</MyButton>
會編譯為
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'點選我'
);
沒有子節點
<div className="sidebar" />
會編譯為
React.createElement(
'div',
{className: 'sidebar'}
)
JSX 點文法
const Menu = ({ children }) => (
<div className="menu">{children}<div>
);
Menu.Item = ({ children }) => (
<div>{children}<div>
);
<Menu>
<Menu.Item>菜單一</Menu.Item>
<Menu.Item>菜單二</Menu.Item>
<Menu>
JSX Element
let element = <h1>Hello, world!</h1>;
let emptyHeading = <h1 />;
const root = ReactDOM.createRoot(
document.getElementById('root')
);
const element = <h1>Hello, world</h1>;
root.render(element);
參考:渲染元素
JSX 屬性
const avatarUrl = "img/picture.jpg"
const element = <img src={avatarUrl} />;
const element = (
<button className="btn">
點選我
</button>
);
注意:類屬性 className
JSX 表達式
let name = '張三';
let element = <h1>Hello, {name}</h1>;
function fullName(firstName, lastName) {
return firstName + ' ' + lastName;
}
let element = (
<h1>
Hello, {fullName('三', '張')}
</h1>
);
JSX style
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};
function MyComponent() {
return <div style={divStyle}>元件</div>;
}
JSX dangerouslySetInnerHTML
const markup = {__html: '我 · 你' };
const MyComponent = () => (
<div dangerouslySetInnerHTML={markup} />
);
dangerouslySetInnerHTML 是 React 為浏覽器 DOM 提供 innerHTML 的替換方案。
JSX htmlFor
const MyComponent = () => (
<div>
<input type="radio" id="ab" name="v">
<label for="ab">HTML</label>
</div>
);
for 在 JS 中是保留字,JSX 元素使用了 htmlFor 代替
JSX defaultValue
非受控元件的屬性,設定元件第一次挂載時的 value
<textarea defaultValue="Hello" />
<input>、<select> 和 <textarea> 支援 value 屬性
JSX defaultChecked
非受控元件的屬性,設定元件是否被選中
<input type="radio" defaultChecked />
類型為 checkbox 或 radio 時,元件支援 checked 屬性
JSX className
屬性用于指定 CSS 的 class
<div className="warp">...</div>
React 中使用 Web Components 使用 class 屬性代替
JSX 條件渲染
import React from "react";
function formatName(user) {
return user.firstName
+ ' '
+ user.lastName;
}
export function Greeting(user) {
if (user) {
return (
<h1>你好, {formatName(user)}!</h1>
);
}
return (
<h1>你好, 先生。</h1>
);
}
注意:元件必須總是傳回一些東西。
使用
<Greeting firstName="三" lastName="張" />
JSX 三目運算符 / 與運算符 &&
export default function Weather(props) {
const isLoggedIn = props.isLoggedIn;
return (
<div>
<b>{isLoggedIn ? '已' : '未'}</b>登入。
</div>
);
}
{isShow && <div>内容</div>}
JSX 元件
<Dropdown>
下拉清單
<Menu>
<Menu.Item>菜單一</Menu.Item>
<Menu.Item>菜單二</Menu.Item>
<Menu.Item>菜單三</Menu.Item>
</Menu>
</Dropdown>
元件名稱以大駝峰式命名。
JSX 元素變量
function Greeting(props) {
let button;
if (props.isLoggedIn) {
button = <UserGreeting />;
} else {
button = <GuestGreeting />;
}
return <div>{button}</div>;
}
JSX 注釋
function Student() {
const [count, setCount] = useState(0);
return (
<Fragment>
{/* 這裡寫注釋 */}
</Fragment>
);
}
元件
函數元件
import React from 'react';
const UserName = () => <h1>Kenny</h1>;
export default function UserProfile() {
return (
<div className="UserProfile">
<div>Hello</div>
<UserName />
</div>
);
}
注意:每個元件都需要一個根元素,更多說明。
Class 元件
class Welcome extends React.Component {
render() {
return <h1>{this.props.name}</h1>;
}
}
Class 元件 API
額外的 API
this.forceUpdate() | 強制重新渲染 |
this.setState({ ... }) | 更新狀态 |
this.setState(state =>{ ... }) | 更新狀态 |
屬性
defaultProps | 預設 props |
displayName | 顯示元件名稱(用于調試) |
執行個體屬性
this.props | 元件接受參數 |
this.state | 元件内狀态 |
Pure 元件
import React, {PureComponent} from 'react'
class MessageBox extends PureComponent {
···
}
高階元件
import React, { Component } from 'react';
// 高階元件 with
const with = data => WrappedComponent => {
return class extends Component {
constructor(props) {
super(props);
}
render() {
return (
<WrappedComponent data={data} />
)
}
}
}
使用高階元件
const LowComponent = (props) => (
<div>{props.data}</div>
);
const MyComp = with('Hello')(LowComponent)
包含關系
function FancyBorder(props) {
return (
<div className={'Fancy'+props.color}>
{props.children}
</div>
);
}
元件可以通過 JSX 嵌套
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="title">歡迎</h1>
<p className="message">
感謝您通路我們的宇宙飛船
</p>
</FancyBorder>
);
}
作為參數傳遞
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="left">
{props.left}
</div>
<div className="right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={<Contacts />}
right={<Chat />}
/>
);
}
給元件 SplitPane 傳遞 left 和 right 兩個元件參數
嵌入内部元件
import React from 'react';
import UserAvatar from "./UserAvatar";
export default function UserProfile() {
return (
<div className="UserProfile">
<UserAvatar />
<UserAvatar />
</div>
);
}
注意:假設 UserAvatar 在 UserAvatar.js 中聲明
嵌入外部元件
import React from 'react';
import {Button} from 'uiw';
export default function UserProfile() {
return (
<div className="UserProfile">
<Button type="primary">
主要按鈕
</Button>
</div>
);
}
注意:uiw 元件在 npmjs.com 上找到,需要先安裝導入
點元件文法技巧
const Menu = ({ children }) => (
<div className="menu">{children}<div>
);
Menu.Item = ({ children }) => (
<div>{children}<div>
);
<Menu>
<Menu.Item>菜單一</Menu.Item>
<Menu.Item>菜單二</Menu.Item>
<Menu>
Hooks
Hooks API 參考
基礎 Hook
useState | 傳回一個 state,更新 state 的函數 # |
useEffect | 可能有副作用代碼的函數 # |
useContext | 接收并傳回該 context 的目前值 # |
額外的 Hook
useReducer | useState 的替代方案 # |
useCallback | 傳回一個回調函數 # |
useMemo | 傳回一個 memoized 值# |
useRef | 傳回一個可變的 ref 對象 # |
useImperativeHandle | 暴露給父元件的執行個體值 # |
useLayoutEffect | DOM 變更後同步調用函數 # |
useDebugValue | 開發者工具中顯示标簽 # |
useDeferredValue | 接受并傳回該值的新副本 # |
useTransition | 過渡任務的等待狀态 # |
useId | 用于生成唯一 ID # |
Library Hooks
useSyncExternalStore | 讀取和訂閱外部資料源 # |
useInsertionEffect | DOM 突變之前 同步觸發 # |
函數式更新
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count} <button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
useRef
function TextInputWithFocusButton() {
const $input = useRef(null);
const onButtonClick = () => {
$input.current.focus();
};
return (
<>
<input ref={$input} type="text" />
<button onClick={onButtonClick}>
聚焦輸入
</button>
</>
);
}
current 指向已挂載到 DOM 上的文本輸入元素
useImperativeHandle
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
父元件使用
<FancyInput ref={inputRef} />
inputRef.current.focus()
useEffect
useEffect(() => {
const subs = props.source.subscribe();
return () => {
subs.unsubscribe();
};
}, [props.source]);
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
const memoizedValue = useMemo(
() => {
return computeExpensiveValue(a, b)
},
[a, b]
);
useId
function Checkbox() {
const id = useId();
return (
<>
<label htmlFor={id}>
你喜歡React嗎?
</label>
<input id={id} type="checkbox" />
</>
);
};
用于生成跨服務端和用戶端穩定的唯一 ID 的同時避免 hydration 不比對
useDebugValue
function useFriendStatus(friendID) {
const [
isOnline, setIsOnline
] = useState(null);
// ...
// 在開發者工具中的這個 Hook 旁邊顯示标簽
// e.g. "FriendStatus: Online"
useDebugValue(
isOnline ? 'Online' : 'Offline'
);
return isOnline;
}
不推薦你向每個自定義 Hook 添加 debug 值
componentDidMount & componentWillUnmount
useEffect(
() => {
// componentDidMount
// 元件挂載時,可以在這裡完成你的任務
return () => {
// componentWillUnmount
// 解除安裝時執行,清除 effect
};
},
[ ]
);
這是一個類似 class 元件中 componentDidMount & componentWillUnmount 兩個生命周期函數的寫法。
生命周期
挂載
constructor (props) | 渲染前 # |
static getDerivedStateFromProps() | 調用 render 方法之前調用 # |
render() | class 元件中唯一必須實作的方法 # |
componentDidMount() | 在元件挂載後(插入 DOM 樹中)立即調用 # |
UNSAFE_componentWillMount() | 在挂載之前被調用,建議使用 constructor() # |
在 constructor() 上設定初始狀态。在 componentDidMount() 上添加 DOM 事件處理程式、計時器(等),然後在 componentWillUnmount() 上删除它們。
解除安裝
componentWillUnmount() | 在元件解除安裝及銷毀之前直接調用 # |
過時 API
componentWillMount() | UNSAFE_componentWillMount() # |
componentWillReceiveProps() | UNSAFE_componentWillReceiveProps() # |
componentWillUpdate() | UNSAFE_componentWillUpdate() # |
17+ 之後不再支援,在 17 版本之後,隻有新的 UNSAFE_ 生命周期名稱可以使用。
更新
static getDerivedStateFromProps(props, state) | 調用 render 之前調用,在初始挂載及後續更新時都會被調用 # |
shouldComponentUpdate(nextProps, nextState) | 如果傳回 false,則跳過 render() # |
render() | 在不修改元件 state 的情況下,每次調用時都傳回相同的結果 # |
getSnapshotBeforeUpdate() | 在發生更改之前從 DOM 中捕獲一些資訊(例如,滾動位置) # |
componentDidUpdate() | 這裡使用 setState(),但記得比較 props。首次渲染不會執行此方法 # |
錯誤處理
static getDerivedStateFromError(error) | 後代元件抛出錯誤後被調用,它将抛出的錯誤作為參數,并傳回一個值以更新 state # |
componentDidCatch(error, info) | 在後代元件抛出錯誤後被調用,會在“送出”階段被調用,是以允許執行副作用 # |
render()
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
constructor()
constructor(props) {
super(props);
// 不要在這裡調用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
static getDerivedStateFromError()
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以顯降級 UI
return { hasError: true };
}
render() {
if (this.state.hasError) {
// 你可以渲染任何自定義的降級 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
componentDidUpdate()
componentDidUpdate(prevProps) {
// 典型用法(不要忘記比較 props):
if (this.props.uid !== prevProps.uid) {
this.fetchData(this.props.uid);
}
}
getSnapshotBeforeUpdate()
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我們是否在 list 中添加新的 items ?
// 捕獲滾動位置以便我們稍後調整滾動位置。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
PropTypes 屬性類型檢查
PropTypes
import PropTypes from 'prop-types'
any | 任意類型 |
(props, propName, 元件名稱)=>{} | 自定義驗證器 |
基礎
string | 字元串 |
number | 數組 |
func | 函數 |
bool | 布爾值 |
symbol | - |
枚舉 Enum
oneOf(any) | 枚舉類型 |
oneOfType([type]) | 幾種類型中的任意一個類型 |
數組 Array
array | 數組 |
arrayOf | 數組由某一類型的元素組成 |
對象 Object
object | 對象 |
objectOf | 對象由某一類型的值組成 |
instanceOf(...) | 類的執行個體 |
shape | 對象由特定的類型值組成 |
exact | 有額外屬性警告 |
元素 Elements
element | React 元素 |
elementType | React 元素類型(即 MyComponent) |
node | DOM 節點 |
必需的
(···).isRequired | 必需的 |
請參閱:使用 PropTypes 進行類型檢查
基本類型
MyComponent.propTypes = {
email: PropTypes.string,
seats: PropTypes.number,
callback: PropTypes.func,
isClosed: PropTypes.bool,
any: PropTypes.any
symbol: PropTypes.symbol,
}
你可以将屬性聲明為 JS 原生類型,預設都是可選的。
必需的
MyComponent.propTypes = {
// 確定這個 prop 沒有被提供時,會列印警告資訊
requiredFunc: PropTypes.func.isRequired,
// 任意類型的必需資料
requiredAny: PropTypes.any.isRequired,
}
你可以在任何 PropTypes 屬性後面加上 isRequired。
枚舉
MyComponent.propTypes = {
// 隻能是特定的值,枚舉類型。
optionalEnum: PropTypes.oneOf([
'News', 'Photos'
]),
// 一個對象可以是幾種類型中的任意一個類型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
}
元素 Elements
MyComponent.propTypes = {
// 任何可被渲染的元素
// (包括數字、字元串、元素或數組)
// (或 Fragment) 也包含這些類型。
node: PropTypes.node,
// 一個 React 元素。
element: PropTypes.element,
// 一個 React 元素類型(即,MyComponent)
elementType: PropTypes.elementType,
}
對象 Object
MyComponent.propTypes = {
// 可以指定一個對象由某一類型的值組成
objectOf: PropTypes.objectOf(
PropTypes.number
),
// 可以指定一個對象由特定的類型值組成
objectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// 帶有額外屬性警告的對象
objectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
}
自定義驗證器
MyComponent.propTypes = {
custom: (props, propName, compName) => {
if (!/matchm/.test(props[propName])) {
// 它在驗證失敗時應傳回一個 Error 對象
return new Error(
'無效的prop `'
` \`${propName}\` 提供給` +
` \`${compName}\`。驗證失敗。`
);
}
},
}
請不要使用 console.warn 或抛出異常,因為這在 oneOfType 中不會起作用。
自定義的arrayOf或objectOf驗證器
MyComponent.propTypes = {
arrayProp: PropTypes.arrayOf((propValue, key, componentName, location, propFullName) => {
if (!/matchme/.test(propValue[key])) {
// 它應該在驗證失敗時傳回一個 Error 對象。
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
}
propValue 是數組或對象本身,key 是他們目前的鍵。
數組
MyComponent.propTypes = {
arr: PropTypes.arrayOf(PropTypes.number),
};
可以指定一個數組由某一類型的元素組成
驗證類的執行個體
MyComponent.propTypes = {
message: PropTypes.instanceOf(Message),
};
聲明 message 為類的執行個體
React 官方中文文檔 (zh-hans.reactjs.org)