乐趣区

关于react.js:React组件设计模式纯组件函数组件高阶组件

一、组件

(1) 函数组件

如果你想写的组件只蕴含一个 render 办法,并且不蕴含 state,那么应用函数组件就会更简略。咱们不须要定义一个继承于 React.Component 的类,咱们能够定义一个函数,这个函数接管 props 作为参数,而后返回须要渲染的元素。

function Square(props) {
  return (<button className="square" onClick={props.onClick}>
    {props.value}    </button>
  );
}

(2) React.Component

shouldComponentUpdate 仅查看了 props.color 或 state.count 是否扭转。如果这些值没有扭转,那么这个组件不会更新

class CounterButton extends React.Component {shouldComponentUpdate(nextProps, nextState) {if (this.props.color !== nextProps.color) {return true;}
    if (this.state.count !== nextState.count) {return true;}
    return false;
  }
}

(3) PureComponent

如果你的组件更简单一些,你能够应用相似“浅比拟”的模式来查看 props 和 state 中所有的字段,以此来决定是否组件须要更新。React 曾经提供了一位好帮手来帮你实现这种常见的模式 – 你只有继承 React.PureComponent 就行了。

class CounterButton extends React.PureComponent {}

大部分状况下,你能够应用 React.PureComponent 来代替手写 shouldComponentUpdate。但它只进行浅比拟 (例如:1 == 1 或者 ture==true,数组和对象 援用是否雷同),所以当 props 或者 state 某种程度是可变的话,浅比拟会有脱漏,那你就不能应用它了。

不要在 props 和 state 中扭转 对象和数组,如果你在你的父组件中扭转对象,你的PureComponent 将不会更新。尽管值曾经被扭转,然而子组件比拟的是之前 props 的援用是否雷同,所以不会检测到不同。

因而,你能够通过应用 es6 的 assign 办法或者 数组的扩大运算符 或者应用第三方库,强制返回一个新的对象。

当数据结构很简单时,状况会变得麻烦,存在性能问题
(比拟原始值和对象援用是低耗时操作。如果你有一列子对象并且其中一个子对象更新,对它们的 props 和 state 进行查看要比从新渲染每一个子节点要快的多。)

(4) 何时应用 Component 或 PureComponent ?

<1> 当组件是独立的,组件在页面中的个数为 1 或 2 的,组件有很多 props、state,并且当中还有些是 数组和对象 的,组件须要 每次都渲染 的,应用 Component

<2> 当组件常常作为 子组件,作为列表 ,组件在页面中数量泛滥,组件 props, state 属性少,并且属性中 根本没有数组和对象,组件不须要每次都渲染,只有变动了才渲染,应用 PureComponent

凭主观,我感觉
以下组件适宜 Component

Button
Input

以下组件适宜 PureComponent

Radio
Checkbox
Option

二、高阶函数

HOC (高阶组件 higherOrderComponent) 本身不是 React API 的一部分,它是一种基于 React 的组合个性而造成的 设计模式

组件是将 props 转换为 UI,而 高阶组件是将组件转换为另一个组件。(组件是 React 中代码复用的根本单元。)

高阶组件例如 Redux 的 connect 和 Relay 的 createFragmentContainer。

(1)HOC 不会批改传入的组件,也不会应用继承来复制其行为。

相同,HOC 通过 将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。

(2)HOC 应该透传与本身无关的 props

HOC 为组件增加个性。本身不应该大幅扭转约定。
HOC 应该透传与本身无关的 props,HOC 返回的组件与原组件应放弃相似的接口。

(3)约定:包装显示名称以便轻松调试 HOC

创立的容器组件会与任何其余组件一样,会显示在 React Developer Tools 中。为了不便调试,请抉择一个显示名称,以表明它是 HOC 的产物

最常见的形式是用 HOC 包住被包装组件的显示名称。比方高阶组件名为 withSubscription,并且被包装组件的显示名称为 CommentList,显示名称应该为 WithSubscription(CommentList):

function withSubscription(WrappedComponent) {class WithSubscription extends React.Component {/* ... */}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
  return WithSubscription;
}

function getDisplayName(WrappedComponent) {return WrappedComponent.displayName || WrappedComponent.name || 'Component';}

(4)注意事项:

<1> 不要在 render 办法中应用 HOC

render() {
  // 每次调用 render 函数都会创立一个新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2

  const EnhancedComponent = enhance(MyComponent);

  // 这将导致子树每次渲染都会进行卸载,和从新挂载的操作!return <EnhancedComponent />;
}

<2> 务必复制静态方法

有时在 React 组件上定义静态方法很有用。例如,Relay 容器裸露了一个静态方法 getFragment 以不便组合 GraphQL 片段。

然而,当你将 HOC 利用于组件时,原始组件将应用容器组件进行包装。这意味着新组件没有原始组件的任何静态方法。

// 定义动态函数
WrappedComponent.staticMethod = function() {/*...*/}
// 当初应用 HOC
const EnhancedComponent = enhance(WrappedComponent);

// 加强组件没有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
为了解决这个问题,你能够在返回之前把这些办法拷贝到容器组件上:

你能够应用 hoist-non-react-statics 主动拷贝所有非 React 静态方法:

import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}

除了导出组件,另一个可行的计划是再额定导出这个静态方法。

// 应用这种形式代替...
MyComponent.someFunction = someFunction;
export default MyComponent;

// ... 独自导出该办法...
export {someFunction};

// ... 并在要应用的组件中,import 它们
import MyComponent, {someFunction} from './MyComponent.js';

<3> Refs 不会被传递

尽管高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不实用。那是因为 ref 实际上并不是一个 prop – 就像 key 一样,它是由 React 专门解决的。如果将 ref 增加到 HOC 的返回组件中,则 ref 援用指向容器组件,而不是被包装组件。

这个问题的解决方案是通过应用 React.forwardRef API(React 16.3 中引入)

参考 React 实战视频解说:进入学习

三、React Redux 的 connect

React Redux 的 connect 函数是一个 返回高阶组件的高阶函数

最常见的 HOC 签名如下:

// React Redux 的 `connect` 函数
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

刚刚产生了什么?!如果你把它离开,就会更容易看出产生了什么。

// connect 是一个函数,它的返回值为另外一个函数。const enhance = connect(commentListSelector, commentListActions);

// 返回值为 HOC,它会返回曾经连贯 Redux store 的组件
const ConnectedComment = enhance(CommentList);

这种模式可能看起来令人困惑或不必要,但它有一个有用的属性。最大化可组合性
像 connect 函数返回的单参数 HOC 具备签名 Component => Component。输入类型与输出类型雷同的函数很容易组合在一起。

// 而不是这样...
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))

// ... 你能够编写组合工具函数
// compose(f, g, h) 等同于 (...args) => f(g(h(...args)))

const enhance = compose(
  // 这些都是单参数的 HOC
  withRouter,
  connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)

//(同样的属性也容许 connect 和其余 HOC 承当装璜器的角色)

四、其余

(1)key

每当一个列表从新渲染时,React 会依据每一项列表元素的 key 来检索上一次渲染时与每个 key 所匹配的列表项。如果 React 发现以后的列表有一个之前不存在的 key,那么就会创立出一个新的组件。如果 React 发现和之前比照少了一个 key,那么就会销毁之前对应的组件。如果一个组件的 key 产生了变动,这个组件会被销毁,而后应用新的 state 从新创立一份。

咱们强烈推荐,每次只有你构建动静列表的时候,都要指定一个适合的 key。

如果你没有指定任何 key,React 会收回正告,并且会把数组的索引当作默认的 key。然而 如果想要对列表进行从新排序、新增、删除操作时,把数组索引作为 key 是有问题的

显式地应用 key={i} 来指定 key 的确会打消正告,然而依然和数组索引存在同样的问题,所以大多数状况下最好不要这么做。

组件的 key 值并不需要在全局都保障惟一,只须要在以后的同一级元素之前保障惟一即可。

退出移动版