React-CheatSheet
React CheatSheet | React 设计理念,语法纵览与实践清单
这是一篇非常冗长的文章,是笔者 现代
Principles | 设计理念
小而美的视图层
任何一个编程生态都会经历三个阶段,第一个是原始时期,由于需要在语言与基础的
不过很多人对
声明式组件
笔者在现代
而随着从传统的前后端未分离的巨石型
声明式编程的核心理念在于描述做什么,通过声明式的方式我们能够以链式方法调用的形式对于输入的数据流进行一系列的变换处理。
<select value={this.state.value} onChange={this.handleChange}>
{somearray.map((element) => (
<option value={element.value}>{element.text}</option>
))}
</select>
Virtual DOM
在组件中,我们不需要关心data-reactid
属性,这有助于React.createElement
函数的抽象,而该函数主要的作用是将朴素的
在现代浏览器中,对于
基于组件的架构
软件工程导论中介绍过,模块更多是为了
When Facebook released React.js in 2013 it redefined the way in which Front End Developers could build user interfaces. React.js, a JavaScript library, introduced a concept called Component-Based-Architecture, a method for encapsulating individual pieces of a larger user interface (aka components) into self-sustaining, independent micro-systems.
Essentially, if you’re using a client-side MVC framework like Ember.js, and to a lesser extent, Angular, you have templates that present the UI, routes that determine which templates to render, and services that define helper functions. Even if a template has routes and associated methods, all of these exist at different levels of an application’s architecture.
In the case of CBA, responsibility is split on a component-by-component basis. This means that the design, logic, and helper methods exist all within the same level of the architecture (generally the view). As aforementioned, everything that pertains to a particular component is defined within that component’s class.
Component-Based Architecture
Component | 组件系统
类组件
典型的
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
如果我们不省略构造函数,那么需要注意调用
constructor(props) {
super();
console.log(this.props); // undefined
console.log(props); // defined
}
constructor(props) {
super(props);
console.log(this.props); // props will get logged.
}
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const MyComponent = React.memo(function MyComponent(props) {
/* only rerenders if props change */
});
不过函数式组件也并非处处适用,使用函数式组件时,我们无法使用
This is another popular way of classifying components. And the criteria for the classification is simple: the components that have state and the components that don’t.
Stateful components are always class components.
JSX
目前组件支持返回数组元素,我们也可以使用
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
函数式组件
生命周期

componentDidUpdate(prevProps, prevState, snapshot);
在
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.currentRow === prevState.lastRow){
return null;
}
return {
lastRow: nextProps.currentRow,
isScrollingDown: nextProps.currentRow > prevState.lastRow
}
}
值得一提的是,
组件与DOM
Ref
class VideoPlayer extends React.Component {
constructor() {
super();
this.state = {
isPlaying: false,
};
this.handleVideoClick = this.handleVideoClick.bind(this);
}
handleVideoClick() {
if (this.state.isPlaying) {
this.video.pause();
} else {
this.video.play();
}
this.setState({ isPlaying: !this.state.isPlaying });
}
render() {
return (
<div>
<video
ref={(video) => (this.video = video)}
onClick={this.handleVideoClick}
>
<source src="some.url" type="video/ogg" />
</video>
</div>
);
}
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
componentDidMount() {
this.inputRef.current.focus();
}
}
render() {
// React 并不会创建新的 div,而是将其渲染到指定的 DOM 节点中
return ReactDOM.createPortal(
this.props.children,
domNode
);
}
事件监听与响应
为了避免过多地事件监听,stopPropagation()
与 preventDefault()
,并且保证了跨浏览器的一致性。出于性能的考虑,
function onClick(event) {
console.log(event); // => nullified object.
console.log(event.type); // => "click"
const eventType = event.type; // => "click"
this.setState({ eventType: event.type });
}
对于
<div onClickCapture={this.handleClickViaCapturing}>
<button onClick={this.handleClick}>
Click me, and my parent's `onClickCapture` will fire *first*!
</button>
</div>
组件样式
样式类
import cx from "classnames";
import styles from "./capsule.css";
// 使用 classnames
let className = cx(styles.base, {
[styles.clickable]: this.props.clickable,
[styles.withIcon]: !!this.props.icon,
});
return <div className={className} />;
// 使用朴素的数组操作
return (
<div
classNames={[styles.base, styles.clickable, styles.withIcon].join(" ")}
/>
);
CSS-in-JS
组件动画与变换
import Transition from "react-transition-group/Transition";
const duration = 300;
const defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
};
const transitionStyles = {
entering: { opacity: 0 },
entered: { opacity: 1 },
};
const Fade = ({ in: inProp }) => (
<Transition in={inProp} timeout={duration}>
{(state) => (
<div
style={{
...defaultStyle,
...transitionStyles[state],
}}
>
I'm a fade Transition!
</div>
)}
</Transition>
);
📎 完整代码参阅 CodeSandbox
<CSSTransition
in={showValidationMessage}
timeout={300}
classNames="message"
unmountOnExit
onExited={() => {
this.setState({
showValidationButton: true,
});
}}
>
{(state) => (
<HelpBlock>
Your name rocks!
<CSSTransition
in={state === "entered"}
timeout={300}
classNames="star"
unmountOnExit
>
<div className="star">⭐</div>
</CSSTransition>
</HelpBlock>
)}
</CSSTransition>
其中
classNames={{
appear: 'my-appear',
appearActive: 'my-active-appear',
enter: 'my-enter',
enterActive: 'my-active-enter',
enterDone: 'my-done-enter,
exit: 'my-exit',
exitActive: 'my-active-exit',
exitDone: 'my-done-exit,
}}
最后的<Transition>
或<TransitionGroup>
,作为状态机来管理组件挂载或者卸载时候的状态。
Component Dataflow | 组件数据流
Props
import PropTypes from "prop-types";
MyComponent.propTypes = {
// 指定类实例
optionalMessage: PropTypes.instanceOf(Message), // 枚举类型
optionalEnum: PropTypes.oneOf(["News", "Photos"]), // 可能为多种类型
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message),
]), // 包含指定类型的数组
optionalArrayOf: PropTypes.arrayOf(PropTypes.number), // 包含指定值类型的对象
optionalObjectOf: PropTypes.objectOf(PropTypes.number), // 某个具体形状的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number,
}),
// ...
};
Children
props.children
来引用当前组件的子组件;
// 复制某个元素
React.cloneElement(element, [props], [...children]);
// 从某个组件类或者类型中创建元素
React.createElement(type, [props], [...children]);
// 转换子元素
React.Children.map(children, function[(thisArg)])
// 遍历子元素
React.Children.forEach(children, function[(thisArg)])
// 如果仅有单个子元素,则返回
React.Children.only(children)
// 忽略首个元素
{
React.Children.map(children, (child, i) => {
// Ignore the first child
if (i < 1) return;
return child;
});
}
// 即使传入的是函数,也能够正常执行
<IgnoreFirstChild>
{() => <h1>First</h1>} // <- Ignored 💪
</IgnoreFirstChild>
React.Children.count
则是能够对子元素进行正确的统计:
// Renders "3"
<ChildrenCounter>
{() => <h1>First!</h1>}
Second!
<p>Third!</p>
</ChildrenCounter>
能将React.Children.toArray
方法。如果你需要对它们进行排序,这个方法是非常有用的。
class Sort extends React.Component {
render() {
const children = React.Children.toArray(this.props.children);
// Sort and render the children
return <p>{children.sort().join(" ")}</p>;
}
}
<Sort>
// We use expression containers to make sure our strings // are passed as
three children, not as one string
{"bananas"}
{"oranges"}
{"apples"}
</Sort>;
在已知仅有一个子元素的情况下,我们也可以使用 only
函数来获取该元素实例:
class Executioner extends React.Component {
render() {
return React.Children.only(this.props.children)();
}
}
在需要对子元素进行修改的场景下,我们可以使用 cloneElement
,将想要克隆的元素当作第一个参数,然后将想要设置的属性以对象的方式作为第二个参数。
renderChildren() {
return React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
name: this.props.name
})
})
}
State
// 先获取元素下标,然后执行删除
const array = [...this.state.people]; // make a separate copy of the array
const index = array.indexOf(e.target.value);
array.splice(index, 1);
// 使用 filter 进行过滤删除
不可变对象
removePeople(e) {
var array = [...this.state.people]; // make a separate copy of the array
var index = array.indexOf(e.target.value)
array.splice(index, 1);
this.setState({people: array});
},
removePeople(e) {
this.setState({people: this.state.people.filter(function(person) {
return person !== e.target.value
})};
}
异步数据处理
async componentDidMount() {
try {
const response = await fetch(`https://api.coinmarketcap.com/v1/ticker/?limit=10`);
if (!response.ok) {
throw Error(response.statusText);
}
const json = await response.json();
this.setState({ data: json });
} catch (error) {
console.log(error);
}
}
受控组件
input
这样的标签,状态直接存放在
render() {
return (
<input />
)
}
我们可以通过接管标签的改变事件与值,来将非受控组件转化为受控组件:
handleChange: function (propertyName, event) {
const contact = this.state.contact;
contact[propertyName] = event.target.value;
this.setState({ contact: contact });
},
render: function () {
return (
<div>
<input type="text" onChange={this.handleChange.bind(this, 'firstName')} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChange.bind(this, 'lastName')} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChange.bind(this, 'phone')} value={this.state.contact.lastName}/>
</div>
);
}
Context
const ThemeContext = React.createContext("light");
class ThemeProvider extends React.Component {
state = { theme: "light" };
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
{this.props.children}
</ThemeContext.Provider>
);
}
}
class ThemedButton extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{(theme) => <Button theme={theme} />}
</ThemeContext.Consumer>
);
}
}
我们也可以更为灵活地通过声明类的
class MyClass extends React.Component {
// 或者在类外声明
static contextType = MyContext;
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;
Suspense
Suspense allows you to defer rendering part of your application tree until some condition is met (for example, data from an endpoint or a resource is loaded).
理论上,
render(渲染)阶段,将决定是否需要更新
commit(提交)阶段,当
React Router
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
<PrivateRoute path='/protected' component={Protected} />
Design Pattern | 架构模式
HoC | 高阶组件
Presentational Components & Container Components
This is another pattern that is very useful while writing components. The benefit of this approach is that the behavior logic is separated from the presentational logic.
- Presentational Components
Presentational components are coupled with the view or how things look. These components accept props from their container counterpart and render them. Everything that has to do with describing the UI should go here.
Presentational components are reusable and should stay decoupled from the behavioral layer. A presentational component receives the data and callbacks exclusively via props and when an event occurs, like a button being pressed, it performs a callback to the container component via props to invoke an event handling method.
Functional components should be your first choice for writing presentational components unless a state is required. If a presentational component requires a state, it should be concerned with the UI state and not actual data. The presentational component doesn’t interact with the Redux store or make API calls.
- Container Components
Container components will deal with the behavioral part. A container component tells the presentational component what should be rendered using props. It shouldn’t contain limited DOM markups and styles. If you’re using Redux, a container component contains the code that dispatches an action to a store. Alternatively, this is the place where you should place your API calls and store the result into the component’s state.
The usual structure is that there is a container component at the top that passes down the data to its child presentational components as props. This works for smaller projects; however, when the project gets bigger and you have a lot of intermediate components that just accept props and pass them on to child components, this will get nasty and hard to maintain. When this happens, it’s better to create a container component unique to the leaf component, and this will ease the burden on the intermediate components.
renderProps
Hooks
工程实践
<StrictMode>
<App />
</StrictMode>
异常处理
性能优化
组件分割
SystemJS 或者

一般来说,我们可以根据路由或者组件来执行懒加载,不过在
class MyComponent extends React.Component {
state = {
Bar: null,
};
componentWillMount() {
import("./components/Bar").then((Bar) => {
this.setState({ Bar });
});
}
render() {
let { Bar } = this.state;
if (!Bar) {
return <div>Loading...</div>;
} else {
return <Bar />;
}
}
}
react-loadable 是非常不错的异步组件加载库,同时能够支持服务端渲染等多种场景:
import Loadable from "react-loadable";
const LoadableBar = Loadable({
loader: () => import("./components/Bar"),
loading() {
return <div>Loading...</div>;
},
});
class MyComponent extends React.Component {
render() {
return <LoadableBar />;
}
}
Async Rendering | 异步渲染


Ecosystem | React 生态圈
在跨平台开发领域,
Same syntax as React Native Works with existing React libraries such as Redux Cross platform Native components. No more Electron Compatible with all normal Node.js packages
Proton Native does the same to desktop that React Native did to mobile. Build cross-platform apps for the desktop, all while never leaving the React eco-system. Popular React packages such as Redux still work.
TypeScript
import * as React from "react";
import formatPrice from "../utils/formatPrice";
export interface IPriceProps {
num: number;
symbol: "$" | "€" | "£";
}
const Price: React.SFC<IPriceProps> = ({ num, symbol }: IPriceProps) => (
<div>
<h3>{formatPrice(num, symbol)}</h3>
</div>
);
export function positionStyle<T>(
Component: React.ComponentType
): React.StatelessComponent<T & { left: number; top: number }> {
return (props: any) => {
const { top, left, ...rest } = props;
return (
<div style={{ position: "absolute", top, left }}>
<Component {...rest} />
</div>
);
};
}
高阶组件
譬如在 types/react-redux 中,
export interface Connect {
...
<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, State = {}>(
mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>
): InferableComponentEnhancerWithProps<TStateProps & TDispatchProps, TOwnProps>;
...
}
export declare const connect: Connect;