【react总结(一)】:一次性完全弄懂组件(函数式组件、PureComponent、React.memo、高阶组件)

1. 函数式组件(无状态组件)

1. 什么是函数式组件

首先须要去理解纯函数的概念:相同的输入,拥有有相同输出,没有任何反作用。同理: 咱们的函数式组件也不会去根据组件的状态(state)的不一样输入,不会拥有不一样的表现形式。(与state无关,取决于props与context)。javascript

2. 函数式组件有什么优缺点
1. 优势:
  1. 不须要声明Class,也就不须要constructor、extends代码(书写简洁,内存占用小:无需class 的 props context _context 等诸多属性,组件不须要被实例化,无生命周期,提高性能)。
  2. 能够写成无反作用的纯函数。
  3. 视图和数据的解耦分离。
2. 缺点:
  1. 没有实例化,没法使用ref;
  2. 没有生命周期方法;
  3. shouldComponentUpdate方法没有,重复渲染都无法避免。
3. 函数式组件的应用场景
  1. 无需state。
  2. 无需生命周期函数。
4. 我在开发中的应用场景
// 引入package等省略,这是一个全局公共的Loading。
const Loading = ({ size = '', loading = false, inner = false }) => {
  if (loading) {
    return inner? (
      // 没有size属性,默认用自定义的大小
      <div className={classNames(
        styles['inner-loading-container'],
        {[styles['custom-size']]: !size}
      )}>
        <Spin size={size || 'small'} className={styles.loading} /> </div>
    ): (
      createPortal(
        <div className={styles['loading-container']}> <Spin className={styles.loading} /> </div>, document.querySelector('body'), ) ); } return null; }; 复制代码

2. 基于Class声明的组件

  1. ES5 React.createClass (会自动绑定函数,这样会致使没必要要的性能开销。拥有mixin。弃用)
  2. ES6 React.Component(按需绑定,HOC)

3. PureComponent (v15.3新增)

1. 为何会有PureComponent

正常状况下,父组件每次有state或者props改变,子组件都会从新渲染。可是若是咱们在shouldComponentUpdate阶段判断新旧属性和状态是否相等,是否须要从新渲染子组件。减小render()方法的触发,节省了在虚拟DOM生成与对比的过程,性能获得提高。前端

2. PureComponent的局限性

这个比较的过程是一个浅比较,没法判断复杂数据类型(引用类型)的变化。java

3. 如何优化
  1. 不要将复杂的状态写入一个组件,将部分仅须要简单状态类展现型组件抽离。
  2. 复杂的数据类型带来的反作用能够经过immutable来解决。
4. 咱们是否能够所有都写成PureComponent形式
  1. 即便是浅比较,一样是须要消耗性能。react

  2. 若是不是immutale数据的话你可能会出现问题(同一引用类型改变致使不更新渲染),一旦深层次的结构出现问题,你定位问题可能须要好久。git

  3. 这不足以成为一个正常应用的性能瓶颈,当你发现已经到不优化不可的地步,那确定不是PureComponent致使的,优化的优点也就不那么明显了。github

    // 浅比较shallowEqual的源码
    const hasOwn = Object.prototype.hasOwnProperty
    // 这个函数其实是Object.is()的polyfill
    function is(x, y) {
      if (x === y) {
        return x !== 0 || y !== 0 || 1 / x === 1 / y
      } else {
        return x !== x && y !== y
      }
    }
    
    export default function shallowEqual(objA, objB) {
        // 首先对基本数据类型的比较
      if (is(objA, objB)) return true
     // 因为Obejct.is()能够对基本数据类型作一个精确的比较, 因此若是不等
      // 只有一种状况是误判的,那就是object,因此在判断两个对象都不是object以后,就能够返回falseif (typeof objA !== 'object' || objA === null ||
          typeof objB !== 'object' || objB === null) {
        return false
      }
    // 过滤掉基本数据类型以后,就是对对象的比较了
      // 首先拿出key值,对key的长度进行对比
      const keysA = Object.keys(objA)
      const keysB = Object.keys(objB)
    // 长度不等直接返回false
      if (keysA.length !== keysB.length) return false
      for (let i = 0; i < keysA.length; i++) {
      // key值相等的时候
      // 借用原型链上真正的 hasOwnProperty 方法,判断ObjB里面是否有A的key的key值
      // 属性的顺序不影响结果也就是{name:'daisy', age:'24'} 跟{age:'24',name:'daisy' }是同样的
      // 最后,对对象的value进行一个基本数据类型的比较,返回结果
        if (!hasOwn.call(objB, keysA[i]) ||
            !is(objA[keysA[i]], objB[keysA[i]])) {
          return false
        }
      }
    
      return true
    }
    复制代码

4. 高阶组件

1. 什么是高阶组件

高阶组件的概念其实是来源于咱们的高阶函数:接收函数做为输入,或者输出另外一个函数的一类函数,被称做高阶函数。高阶组件: 接受React组件做为输入,输出一个新的React组件的组件。高阶组件和装饰器是一个模式,高阶组件能够看成装饰器使用。redux

2.常见用法: 1.属性代理(Props Proxy) 2. 反向继承(Inheritance Inversion)
  1. 属性代理:1. 更改props 2. 抽象state 3. 经过refs访问组件实例 4. 封装样式、布局等。
  2. 反向继承:劫持被继承class的render内容,进行修改,过滤后,返回新的显示内容。(super.render()、super.Fn())
3. 使用案例:
  1. react-redux中的connect(mapStateToProps, mapDispatchToProps, mergeProps,options): 此方法会将react组件链接到redux的store。connect经过函数参数mapStateToProps,从全局store中取出当前组件须要的state,并把state转化成当前组件的props;同时经过函数参数mapDispatchToProps,把当前组件用到的Redux的action creator,以props的方式传递给当前组件。connect并不会修改传递进去的组件的定义,而是它会返回一个新的组件。bash

  2. react-router中的withRouter: 经过withRouter包装的组件,咱们能够在props中访问到location, router等对象,这正是withRouter经过高阶组件的方式传递过来的。react-router

  3. UI框架中的受控组件:react-hoc-example框架

4. 注意事项
  1. 不要在组件的render方法中使用高阶组件,尽可能也不要在组件的其余生命周期方法中使用高阶组件。由于高阶组件每次都会返回一个新的组件,在render中使用会致使每次渲染出来的组件都不相等(===),因而每次render,组件都会卸载(unmount),而后从新挂载(mount),既影响了效率,又丢失了组件及其子组件的状态。高阶组件最适合使用的地方是在组件定义的外部,这样就不会受到组件生命周期的影响了。

  2. 若是须要使用被包装组件的静态方法,那么必须手动拷贝这些静态方法。由于高阶组件返回的新组件,是不包含被包装组件的静态方法。hoist-non-react-statics能够帮助咱们方便的拷贝组件全部的自定义静态方法。

  3. Refs不会被传递给被包装组件。尽管在定义高阶组件时,咱们会把全部的属性都传递给被包装组件,可是ref并不会传递给被包装组件,由于ref根本不属于React组件的属性。若是你在高阶组件的返回组件中定义了ref,那么它指向的是这个返回的新组件,而不是内部被包装的组件。若是你但愿获取被包装组件的引用,你能够把ref的回调函数定义成一个普通属性(给它一个ref之外的名字)。

    function FocusInput({ inputRef, ...rest }) {
      return <input ref={inputRef} {...rest} />;
    }
    
    //enhance 是一个高阶组件
    const EnhanceInput = enhance(FocusInput);
    
    // 在一个组件的render方法中...
    return (<EnhanceInput 
      inputRef={(input) => {
        this.input = input
      }
    }>)
    
    // 让FocusInput自动获取焦点
    this.input.focus();
    复制代码

5. React.memo(v16.6新增)

类组件使用PureComponent或者shouldComponentUpdate可以优化props值不变时候的渲染性能(默认是shallowEqual)。如今, 你能够经过使用React.memo对function组件进行一样的优化。固然你也能够在方法的第二个参数自定义compare方法。实际意义是:函数式组件也有“shouldComponentUpdate”生命周期了

const MyComponent = React.memo(function MyComponent(props) {
  /* 只在props更改的时候才会从新渲染 */
});

function areEqual(prevProps, nextProps) {
  /*
  return true if passing nextProps to render would return
  the same result as passing prevProps to render,
  otherwise return false
  */
}
function MyComponent(props) {
     /* render using props */
}
export default React.memo(MyComponent, areEqual);
复制代码

6. 关于为何在react组件中this会丢失

1. 为何

类声明和类表达式的主体以 严格模式 执行,主要包括构造函数、静态方法和原型方法。Getter 和 setter 函数也在严格模式下执行。不是React的缘由,这是JavaScript中原本就有的。若是你传递一个函数名给一个变量,而后经过在变量后加括号()来调用这个方法,此时方法内部的this的指向就会丢失.

2. 为何使用箭头函数没有this丢失问题

箭头函数没有 this,因此须要经过查找做用域链来肯定 this 的值。这就意味着若是箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this。this 是有词法约束力的。这意味它可使用封闭的函数上下文或者全局上下文做为 this 的值。

3. 关于this请看另外一篇:前端基本功(三):javascript中让人脑袋疼的this关键字
相关文章
相关标签/搜索