React 模式(中文版)

中文版:reactpatterns.cn/ 原版:reactpatterns.comhtml

函数组件 (Function component)

函数组件 是最简单的一种声明可复用组件的方法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"
};
复制代码

属性解构 (Destructuring props)

解构赋值 是一种 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 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 界面的时候很是有用。

若是你使用 函数组件 作为表单元素,那就得阅读 状态提高 一节,把状态转移到上层的组件树上。

相关文章
相关标签/搜索