中文版:reactpatterns.cn/ 原版:reactpatterns.comhtml
函数组件 是最简单的一种声明可复用组件的方法react
他们就是一些简单的函数。web
function Greeting() {
return <div>Hi there!</div>;
}
复制代码
从第一个形参中获取属性集 (props)ajax
function Greeting(props) {
return <div>Hi {props.name}!</div>;
}
复制代码
按本身的须要能够在函数组件中定义任意变量json
最后必定要返回你的 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"
};
复制代码
解构赋值 是一种 JavaScript 特性。性能优化
出自 ES2015 版的 JavaScript 新规范。ide
因此看起来可能并不常见。函数
比如字面量赋值的反转形式。
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 中的属性
参考上面的 属性解构,
咱们能够 扩散 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>;
}
复制代码
组件就是一种抽象。
好的抽象是能够扩展的。
好比说下面这个组件使用 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} />; } 复制代码
不能够在一个组件声明中使用 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>
);
}
复制代码
不少类型均可以作为 React 的子元素。
多数状况下会是 数组
或者 字符串
。
String
<div>Hello World!</div>
复制代码
Array
<div>{["Hello ", <span>World</span>, "!"]}</div>
复制代码
将数组作为子元素是很常见的。
列表是如何在 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>
复制代码
React 组件不支持函数类型的子元素。
然而 渲染属性 是一种能够建立组件并以函数做为子元素的模式。
这里有个组件,使用了一个渲染回调函数 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);
}
}
复制代码
许多开发人员都喜欢 高阶组件 来实现这种功能。但这只是我的喜爱问题。
你可能会建立一个组件,这个组件会使用 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);
复制代码
(我并不肯定这个名字的准确叫法 译:代理、中介、装饰?
)
按钮在 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>
复制代码
这也是一种 代理组件,用来处理样式。
假如咱们有一个按钮,它使用了「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" />
复制代码
这对于样式维护来讲是很是好的。它将样式的全部关注点分离到单个组件上。
当咱们在写事件处理函数的时候,一般会使用 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" })}
复制代码
在遇到性能问题以前,不要担忧性能优化。真的不要
布局组件表现为一些静态 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>
}
}
复制代码
「容器用来获取数据而后渲染到子组件上,仅仅如此。」—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} /> } } 复制代码
对于不一样的应用上下文,咱们能够写不一样的容器组件。
高阶函数 是至少知足下列一个条件的函数:
因此高阶组件又是什么呢?
若是你已经用过 容器组件, 这仅仅是一些泛化的组件, 包裹在一个函数中。
让咱们以 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);
复制代码
这是一个强大的模式,它能够用来获取数据和给定数据到任意 函数组件 中。
函数组件 没有状态 (就像名字暗示的同样)。
事件是状态的变化。
它们的数据须要传递给状态化的父 容器组件
这就是所谓的「状态提高」。
它是经过将回调从容器组件传递给子组件来完成的
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 })} />; } } 复制代码
这个状态 被提高 到了容器中,经过添加回调函数,回调中能够更新本地状态。这就设置了一个很清晰边界,而且使功能组件的可重用性最大化。
这个模式并不限于函数组件。由于函数组件没有生命周期事件,你也能够在类组件中使用这种模式。
受控输入 是一种与状态提高同时使用时很重要的模式
(最好是在一个状态化的组件上处理事件对象)
讨论受控输入的抽象并不容易。让咱们以一个不受控的(一般)输入开始。
<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 界面的时候很是有用。