中文版:https://reactpatterns.cn/
原版:https://reactpatterns.com
函数组件 (Function component)
函数组件 是最简朴的一种声明可复用组件的要领
他们就是一些简朴的函数。
function Greeting() {
return <div>Hi there!</div>;
}
从第一个形参中猎取属性集 (props)
function Greeting(props) {
return <div>Hi {props.name}!</div>;
}
按本身的须要能够在函数组件中定义恣意变量
末了肯定要返回你的 React 组件。
function Greeting(props) {
let style = {
fontWeight: "bold",
color: context.color
};
return <div style={style}>Hi {props.name}!</div>;
}
运用 defaultProps
为恣意必有属性设置默认值
function Greeting(props) {
return <div>Hi {props.name}!</div>;
}
Greeting.defaultProps = {
name: "Guest"
};
属性解构 (Destructuring props)
解构赋值 是一种 JavaScript 特征。
出自 ES2015 版的 JavaScript 新范例。
所以看起来能够并不罕见。
比如字面量赋值的反转情势。
let person = { name: "chantastic" };
let { name } = person;
一样适用于数组。
let things = ["one", "two"];
let [first, second] = things;
解构赋值被用在很多 函数组件 中。
下面声明的这些组件是雷同的。
function Greeting(props) {
return <div>Hi {props.name}!</div>;
}
function Greeting({ name }) {
return <div>Hi {name}!</div>;
}
有一种语法能够在对象中网络盈余属性。
叫做 盈余参数,看起来就像如许。
function Greeting({ name, ...restProps }) {
return <div>Hi {name}!</div>;
}
那三个点 (...
) 会把一切的盈余属性分配给 restProps
对象
然则,你能运用 restProps
做些什么呢?
继承往下看…
JSX 中的属性睁开 (JSX spread attributes)
属性睁开是 JSX 中的一个的特征。
它是一种语法,特地用来把对象上的属性转换成 JSX 中的属性
参考上面的 属性解构),
我们能够 散布 restProps
对象的一切属性到 div 元素上
function Greeting({ name, ...restProps }) {
return <div {...restProps}>Hi {name}!</div>;
}
这让 Gretting
组件变得异常天真。
我们能够经由过程传给 Gretting 组件 DOM 属性并肯定这些属性肯定会被传到 div
上
<Greeting name="Fancy pants" className="fancy-greeting" id="user-greeting" />
防止通报非 DOM 属性到组件上。
解构赋值是云云的受欢迎,是由于它能够星散 组件特定的属性
和 DOM/平台特定属性
function Greeting({ name, ...platformProps }) {
return <div {...platformProps}>Hi {name}!</div>;
}
兼并解构属性和别的值 (Merge destructured props with other values)
组件就是一种笼统。
好的笼统是能够扩大的。
比如说下面这个组件运用 class
属性来给按钮增加款式。
function MyButton(props) {
return <button className="btn" {...props} />;
}
平常状况下如许做就够了,除非我们须要扩大别的的款式类
<MyButton className="delete-btn">Delete...</MyButton>
在这个例子中把 btn
替换成 delete-btn
JSX 中的属性睁开) 对先后递次是敏感的
散布属性中的 className
会掩盖组件上的 className
。
我们能够转变它两的递次,然则如今来讲 className
只要 btn
。
function MyButton(props) {
return <button {...props} className="btn" />;
}
我们须要运用解构赋值来兼并入参 props 中的 className
和基本的(组件中的) className
。
能够经由过程把一切的值放在一个数组内里,然后运用一个空格衔接它们。
function MyButton({ className, ...props }) {
let classNames = ["btn", className].join(" ");
return <button className={classNames} {...props} />;
}
为了保证 undefined
不被显如今 className 上,能够运用 默认值。
function MyButton({ className = "", ...props }) {
let classNames = ["btn", className].join(" ");
return <button className={classNames} {...props} />;
}
前提衬着 (Conditional rendering)
不能够在一个组件声明中运用 if/else 语句
You can’t use if/else statements inside a component declarations.
所以能够运用 前提(三元)运算符 和 短路盘算。
如果
{
condition && <span>Rendered when `truthy`</span>;
}
除非
{
condition || <span>Rendered when `falsy`</span>;
}
如果-不然
{
condition ? (
<span>Rendered when `truthy`</span>
) : (
<span>Rendered when `falsy`</span>
);
}
子元素范例 (Children types)
很多范例都能够做为 React 的子元素。
多半状况下会是 数组
或许 字符串
。
字符串 String
<div>Hello World!</div>
数组 Array
<div>{["Hello ", <span>World</span>, "!"]}</div>
数组做为子元素 (Array as children)
将数组做为子元素是很罕见的。
列表是如安在 React 中被绘制的。
我们运用 map()
要领建立一个新的 React 元素数组
<ul>
{["first", "second"].map(item => (
<li>{item}</li>
))}
</ul>
这和运用字面量数组是一样的。
<ul>{[<li>first</li>, <li>second</li>]}</ul>
这个形式能够团结解构、JSX 属性散布以及别的组件一同运用,看起来简约非常
<ul>
{arrayOfMessageObjects.map(({ id, ...message }) => (
<Message key={id} {...message} />
))}
</ul>
函数做为子元素 (Function as children)
React 组件不支持函数范例的子元素。
然则 衬着属性 是一种能够建立组件并以函数作为子元素的形式。
衬着属性 (Render prop)
这里有个组件,运用了一个衬着回调函数 children。
如许写并没有什么用,然则能够做为入门的简朴例子。
const Width = ({ children }) => children(500);
组件把 children 做为函数挪用,同时还能够传一些参数。上面这个 500
就是实参。
为了运用这个组件,我们能够在挪用组件的时刻传入一个子元素,这个子元素就是一个函数。
<Width>{width => <div>window is {width}</div>}</Width>
我们能够获得下面的输出。
<div>window is 500</div>
有了这个组件,我们就能够用它来做衬着战略。
<Width>
{width => (width > 600 ? <div>min-width requirement met!</div> : null)}
</Width>
如果有更庞杂的前提推断,我们能够运用这个组件来封装别的一个新组件来运用本来的逻辑。
const MinWidth = ({ width: minWidth, children }) => (
<Width>{width => (width > minWidth ? children : null)}</Width>
);
明显,一个静态的 Width
组件并没有什么用途,然则给它绑定一些浏览器事宜就不一样了。下面有个完成的例子。
class WindowWidth extends React.Component {
constructor() {
super();
this.state = { width: 0 };
}
componentDidMount() {
this.setState(
{ width: window.innerWidth },
window.addEventListener("resize", ({ target }) =>
this.setState({ width: target.innerWidth })
)
);
}
render() {
return this.props.children(this.state.width);
}
}
很多开发人员都喜欢 高阶组件 来完成这类功用。但这只是个人喜欢题目。
子组件的通报 (Children pass-through)
你能够会建立一个组件,这个组件会运用 context
而且衬着它的子元素。
class SomeContextProvider extends React.Component {
getChildContext() {
return { some: "context" };
}
render() {
// 如果能直接返回 `children` 就完美了
}
}
你将面对一个挑选。把 children
包在一个 div 中并返回,或许直接返回 children
。第一种状况须要要你增加分外的标记(这能够会影响到你的款式)。第二种将发生一个没什么用途的毛病。
// option 1: extra div
return <div>{children}</div>;
// option 2: unhelpful errors
return children;
最好把 children
做为一种不透明的数据范例看待。React 供应了 React.Children
要领来处置惩罚 children
。
return React.Children.only(this.props.children);
代办组件 (Proxy component)
(我并不肯定这个名字的正确叫法 译:代办、中介、装潢?
)
按钮在 web 运用中随处可见。而且一切的按钮都须要一个 type="button"
的属性。
<button type="button">
反复的写这些属性很轻易失足。我们能够写一个高层组件来代办 props
到底层组件。
const Button = props =>
<button type="button" {...props}>
我们能够运用 Button
组件替代 button
元素,并确保 type
属性始终是 button。
<Button />
// <button type="button"><button>
<Button className="CTA">Send Money</Button>
// <button type="button" class="CTA">Send Money</button>
款式组件 (Style component)
这也是一种 代办组件,用来处置惩罚款式。
如果我们有一个按钮,它运用了「primary」做为款式类。
<button type="button" className="btn btn-primary">
我们运用一些单一功用组件来天生上面的构造。
import classnames from "classnames";
const PrimaryBtn = props => <Btn {...props} primary />;
const Btn = ({ className, primary, ...props }) => (
<button
type="button"
className={classnames("btn", primary && "btn-primary", className)}
{...props}
/>
);
能够可视化的展现成下面的模样。
PrimaryBtn()
↳ Btn({primary: true})
↳ Button({className: "btn btn-primary"}, type: "button"})
↳ '<button type="button" class="btn btn-primary"></button>'
运用这些组件,下面的这几种体式格局会获得一致的效果。
<PrimaryBtn />
<Btn primary />
<button type="button" className="btn btn-primary" />
这关于款式保护来讲是异常好的。它将款式的一切关注点星散到单个组件上。
构造事宜 (Event switch)
当我们在写事宜处置惩罚函数的时刻,一般会运用 handle{事宜名字}
的定名体式格局。
handleClick(e) { /* do something */ }
当须要增加很多事宜处置惩罚函数的时刻,这些函数名字会显得很反复。这些函数的名字并没有什么代价,由于它们只代办了一些行动或许函数。
handleClick() { require("./actions/doStuff")(/* action stuff */) }
handleMouseEnter() { this.setState({ hovered: true }) }
handleMouseLeave() { this.setState({ hovered: false }) }
能够斟酌写一个事宜处置惩罚函数来依据差别的 event.type
来构造事宜。
handleEvent({type}) {
switch(type) {
case "click":
return require("./actions/doStuff")(/* action dates */)
case "mouseenter":
return this.setState({ hovered: true })
case "mouseleave":
return this.setState({ hovered: false })
default:
return console.warn(`No case for event type "${type}"`)
}
}
别的,关于简朴的组件,你能够在组件中运用箭头函数直接挪用导入的行动或许函数
<div onClick={() => someImportedAction({ action: "DO_STUFF" })}
在碰到机能题目之前,不要忧郁机能优化。真的不要
规划组件 (Layout component)
规划组件表现为一些静态 DOM 元素的情势。它们平常并不须要常常更新。
就像下面的这个组件一样,双方各自衬着了一个 children。
<HorizontalSplit
leftSide={<SomeSmartComponent />}
rightSide={<AnotherSmartComponent />}
/>
我们能够优化这个组件。
HorizontalSplit 组件是两个子组件的父元素,我们能够通知组件永久都不要更新
class HorizontalSplit extends React.Component {
shouldComponentUpdate() {
return false;
}
render() {
<FlexContainer>
<div>{this.props.leftSide}</div>
<div>{this.props.rightSide}</div>
</FlexContainer>
}
}
容器组件 (Container component)
「容器用来猎取数据然后衬着到子组件上,仅仅云云。」—Jason Bonta
这有一个 CommentList
组件。
const CommentList = ({ comments }) => (
<ul>
{comments.map(comment => (
<li>
{comment.body}-{comment.author}
</li>
))}
</ul>
);
我们能够建立一个新组件来担任猎取数据衬着到上面的 CommentList
函数组件中。
class CommentListContainer extends React.Component {
constructor() {
super()
this.state = { comments: [] }
}
componentDidMount() {
$.ajax({
url: "/my-comments.json",
dataType: 'json',
success: comments =>
this.setState({comments: comments});
})
}
render() {
return <CommentList comments={this.state.comments} />
}
}
关于差别的运用上下文,我们能够写差别的容器组件。
高阶组件 (Higher-order component)
高阶函数 是最少满足以下一个前提的函数:
- 吸收一个或多个函数作为输入
- 输出一个函数
所以高阶组件又是什么呢?
如果你已用过 容器组件, 这仅仅是一些泛化的组件, 包裹在一个函数中。
让我们以 Greeting
组件最先
const Greeting = ({ name }) => {
if (!name) {
return <div>衔接中...</div>;
}
return <div>Hi {name}!</div>;
};
如果 props.name
存在,组件会衬着这个值。不然将展现「衔接中…」。如今来增加点高阶的觉得
const Connect = ComposedComponent =>
class extends React.Component {
constructor() {
super();
this.state = { name: "" };
}
componentDidMount() {
// this would fetch or connect to a store
this.setState({ name: "Michael" });
}
render() {
return <ComposedComponent {...this.props} name={this.state.name} />;
}
};
这是一个返回了入参为组件的一般函数
接着,我们须要把 Greeting
包裹到 Connect
中
const ConnectedMyComponent = Connect(Greeting);
这是一个壮大的形式,它能够用来猎取数据和给定数据到恣意 函数组件 中。
状况提拔 (State hoisting)
函数组件 没有状况 (就像名字暗示的一样)。
事宜是状况的变化。
它们的数据须要通报给状况化的父 容器组件
这就是所谓的「状况提拔」。
它是经由过程将回调从容器组件通报给子组件来完成的
class NameContainer extends React.Component {
render() {
return <Name onChange={newName => alert(newName)} />;
}
}
const Name = ({ onChange }) => (
<input onChange={e => onChange(e.target.value)} />
);
Name
组件从 NameContainer
组件中吸收 onChange
回调,并在 input 值变化的时刻挪用。
上面的 alert
挪用只是一个简朴的演示,但它并没有转变状况
让我们来转变 NameContainer
组件的内部状况。
class NameContainer extends React.Component {
constructor() {
super();
this.state = { name: "" };
}
render() {
return <Name onChange={newName => this.setState({ name: newName })} />;
}
}
这个状况 被提拔 到了容器中,经由过程增加回调函数,回调中能够更新当地状况。这就设置了一个很清楚边境,而且使功用组件的可重用性最大化。
这个形式并不限于函数组件。由于函数组件没有生命周期事宜,你也能够在类组件中运用这类形式。
受控输入 是一种与状况提拔同时运用时很主要的形式
(最好是在一个状况化的组件上处置惩罚事宜对象)
受控输入 (Controlled input)
议论受控输入的笼统并不轻易。让我们以一个不受控的(一般)输入最先。
<input type="text" />
当你在浏览器中调解此输入时,你会看到你的变动。 这个是一般的
受控的输入不允许 DOM 变动,这使得这个形式成为能够。经由过程在组件局限中设置值而不是直接在 DOM 局限中修正
<input type="text" value="This won't change. Try it." />
显现静态的输入框值关于用户来讲并没有什么用途。所以,我们从状况中通报一个值到 input 上。
class ControlledNameInput extends React.Component {
constructor() {
super();
this.state = { name: "" };
}
render() {
return <input type="text" value={this.state.name} />;
}
}
然后当你转变组件的状况的时刻 input 的值就自动转变了。
return (
<input
value={this.state.name}
onChange={e => this.setState({ name: e.target.value })}
/>
);
这是一个受控的输入框。它只会在我们的组件状况发生变化的时刻更新 DOM。这在建立一致 UI 界面的时刻异常有效。