Junwen's home
  • ES6

    • ES6 Decorator
    • ES6核心特性
    • Promise&Generator
  • js原理

    • 简单实现bind、apply和call
    • 如何遍历一个dom tree
    • 实现函数currying
    • 实现一个event
    • 详解js的继承
    • 详解requestAnimationFrame
    • Canvas api详解
    • DOM事件
    • EventLoop详解
    • JavaScript的内存管理
    • JavaScript的运行机制
    • Math对象
    • new操作符都做了什么
    • create基本实现原理
    • Set、Map、WeakSet和WeakMap
    • web worker原理
    • WebGL教程(MDN)
  • jsInfoSeries

    • 简介
    • JavaScript基础知识
    • 基础知识2
    • 基础知识3
    • 基础知识4
  • 技巧

    • 5个js解构有趣用途
    • 如何使用set提高代码性能
    • cordova构建项目时的问题
    • js中轻松遍历对象属性的几种方式
  • 怎么写出更好的css
  • BFC详解
  • box-shadow详解
  • CSS小技巧
  • Grid布局详解
HTML
  • IP十问
  • http笔试
  • http协议
  • 浏览器原理
  • 浏览器缓存其实就这么一回事儿
  • 浏览器兼容性问题
  • 移动端开发兼容性适配
  • 前端性能优化
  • 前端如何进行seo优化
  • webpack

    • webpack HMR
    • webpack优化基本方法
  • leetcode题解

    • 两数之和
    • 判断整数是否为回文串
    • 无重复字符的最长子串
  • Js链表
  • JavaScript排序
  • React

    • 虚拟DOM原理理解
    • React Hook
    • 组件复用指南
  • Vue

    • Vue举一反三
面试题
读书笔记
GitHub (opens new window)

Syun0216

多读书多种树
  • ES6

    • ES6 Decorator
    • ES6核心特性
    • Promise&Generator
  • js原理

    • 简单实现bind、apply和call
    • 如何遍历一个dom tree
    • 实现函数currying
    • 实现一个event
    • 详解js的继承
    • 详解requestAnimationFrame
    • Canvas api详解
    • DOM事件
    • EventLoop详解
    • JavaScript的内存管理
    • JavaScript的运行机制
    • Math对象
    • new操作符都做了什么
    • create基本实现原理
    • Set、Map、WeakSet和WeakMap
    • web worker原理
    • WebGL教程(MDN)
  • jsInfoSeries

    • 简介
    • JavaScript基础知识
    • 基础知识2
    • 基础知识3
    • 基础知识4
  • 技巧

    • 5个js解构有趣用途
    • 如何使用set提高代码性能
    • cordova构建项目时的问题
    • js中轻松遍历对象属性的几种方式
  • 怎么写出更好的css
  • BFC详解
  • box-shadow详解
  • CSS小技巧
  • Grid布局详解
HTML
  • IP十问
  • http笔试
  • http协议
  • 浏览器原理
  • 浏览器缓存其实就这么一回事儿
  • 浏览器兼容性问题
  • 移动端开发兼容性适配
  • 前端性能优化
  • 前端如何进行seo优化
  • webpack

    • webpack HMR
    • webpack优化基本方法
  • leetcode题解

    • 两数之和
    • 判断整数是否为回文串
    • 无重复字符的最长子串
  • Js链表
  • JavaScript排序
  • React

    • 虚拟DOM原理理解
    • React Hook
    • 组件复用指南
  • Vue

    • Vue举一反三
面试题
读书笔记
GitHub (opens new window)
  • React

    • 虚拟DOM原理理解
    • React Hook
    • React组件复用指南
    • Vue

      • Vue原理举一反三
    • framework
    junwen
    2021-03-20

    组件复用指南

    React 组件复用指南

    # 高阶组件

    # 什么是高阶组件?

    高阶组件就是一个 React 组件包裹着另外一个 React 组件
    这种模式通常使用函数来实现,基本上是一个类工厂

    hocFactory:: w: React.Component => E: React.Component
    
    1

    其中 W (WrappedComponent) 指被包裹的 React.Component,E (EnhancedComponent) 指返回类型为 React.Component 的新的 HOC。

    1. 属性代理(Props Proxy): HOC 对传给 WrappedComponent 的 props 进行操作
    2. 反向继承(Inheritance Inversion): HOC 继承 WrappedComponent

    # HOC 工厂实现方法

    # 属性代理 PP

    function ppHOC(WrappedComponent) {
      return class PP extends React.Component {
        render() {
          return <WrappedComponent {...this.props} />;
        }
      };
    }
    
    1
    2
    3
    4
    5
    6
    7

    # PP 可以做什么

    • 操作 props
    • 通过 Refs 访问到组件实例
    • 提取 state
    • 用其他元素包裹 WrappedComponent

    # 通过 refs 访问到组件实例

    你可以通过 ref 访问到 this,但为了得到引用,WrappedComponent 还需要一个初始渲染,意味着你需要在 HOC 的 render 方法中返回 WrappedComponent 元素,让 React 开始它的一致化处理,你就可以得到 WrappedComponent的实例的引用。

    eg:

    function refsHOC(WrappedComponent) {
      return class RefsHOC extends React.Component {
        proc(wrappedComponentInstance) {
          wrappedComponentInstance.method();
        }
        render() {
          const props = Object.assign({}, this.props, {
            ref: this.proc.bind(this)
          });
          return <WrappedComponent {...props} />;
        }
      };
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    详细实现:

    • a 方法:类似 React Redux 的 connect,添加一个参数 withRef,并通过 getWrappedInstance 方法获取
    function refsHOC(wrappedComponent) {
      return class HOC extends React.Component {
        wrappedInstance = null;
        getWrappedInstance = () => {
          if (this.props.withRef) {
            return this.wrappedInstance;
          }
        };
        setWrappedInstance = ref => {
          this.wrappedInstance = ref;
        };
        render() {
          let props = { ...this.props };
          if (this.props.withRef) {
            props.ref = this.setWrappedInstance;
          }
          return <wrappedComponent {...props} />;
        }
      };
    }
    
    class ParentCompoent extends React.Component {
      doSomethingWithMyComponent() {
        let instance = this.refs.child.getWrappedInstance();
        // ....
      }
    
      render() {
        return <MyComponent ref="child" withRef />;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    • b 方法:父级传一个方法来获取 ref:
    class parent extends React.Component {
      wrappedInstance = null;
      getInstance = ref => {
        this.wrappedInstance = ref;
      };
      render() {
        return <MyComponent getInstance={this.getInstance} />;
      }
    }
    
    function HOCFactory(wrappedComponent) {
      return class HOC extends React.Component {
        render() {
          let props = {
            ...this.props
          };
    
          if (typeof this.props.getInstance === "function") {
            props.ref = this.props.getInstance;
          }
    
          return <wrappedComponent {...props} />;
        }
      };
    }
    
    export default HOCFactory(MyComponent);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    # hoist-non-react-statics

    通过 hoist-non-react-statics 组件,将自动绑定所有在对象上的非 React 方法到新对象上,解决 static 方法找不到的问题

    # 提取 state

    你可以通过传入 props 和回调函数把 state 提取出来,eg:

    function ppHOC(WrappedComponent) {
      return class PP extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            name: ""
          };
          this.onNameChange = this.onNameChange.bind(this);
        }
        onNameChange(event) {
          this.setState({
            name: event.target.value
          });
        }
        render() {
          const newProps = {
            name: {
              value: this.state.name,
              onChange: this.onNameChange
            }
          };
          return <WrappedComponent {...this.props} {...newProps} />;
        }
      };
    }
    
    @ppHOC
    class Example extends React.Component {
      render() {
        return <input name="name" {...this.props.name} />;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    # 反向继承(Inheritance Inversion)

    function iiHOC(WrappedComponent) {
      return class Enhancer extends WrappedComponent {
        render() {
          return super.render();
        }
      };
    }
    
    1
    2
    3
    4
    5
    6
    7

    ii 允许 HOC 通过 this 访问到 WrappedComponent,意味着它可以访问到 state、props、组件生命周期方法和 render 方法。

    # 一致化处理

    React 元素有两种类型:字符串和函数。字符串类型的 React 元素代表 DOM 节点,函数类型的 React 元素代表继承 React.Component 的组件。更多关于元素(Element)和组件(Component)请看这篇文章 (opens new window)。函数类型的 React 元素会在一致化 (opens new window)处理中被解析成一个完全由字符串类型 React 组件组成的树(而最后的结果永远是 DOM 元素)。

    # ii 的高阶组件不一定会解析完整子树

    • 渲染劫持

      • 在由 render 输出的任何 React 元素中读取、添加、删除 props
      function iiHOC(WrappedComponent) {
        return class Enhancer extends WrappedComponent {
          render() {
            if (this.props.loggedIn) {
              return super.render();
            } else {
              return null;
            }
          }
        };
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      • 读取和修改由 render 输出的 React 元素树
      function iiHOC(WrappedComponent) {
        return class Enhancer extends WrappedComponent {
          render() {
            const elementsTree = super.render();
            let newProps = {};
            if (elementsTree && elementsTree.type === "input") {
              newProps = { value: "may the force be with you" };
            }
            const props = Object.assign({}, elementsTree.props, newProps);
            const newElementsTree = React.cloneElement(
              elementsTree,
              props,
              elementsTree.props.children
            );
            return newElementsTree;
          }
        };
      }
      //你可以遍历整个元素树,然后修改元素树中任何元素的 props
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      • 有条件地渲染元素树
      • 把样式包裹进元素树(就像在 Props Proxy 中的那样)

      # 操作 state

      export function IIHOCDEBUGGER(WrappedComponent) {
        return class II extends WrappedComponent {
          render() {
            return (
              <div>
                <h2>HOC Debugger Component</h2>
                <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
                <p>State</p>
                <pre>{JSON.stringify(this.state, null, 2)}</pre>
                {super.render()}
              </div>
            );
          }
        };
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15

      # HOC 和参数

      function HOCFactoryFactory(...params) {
        return function HOCFactory(WrappedComponent) {
          return classHOC extends React.Component {
            render() {
              return <WrappedComponent {...this.props}/>
            }
          }
        }
      }
      //用法
      HOCFactoryFactory(params)(WrappedComponent)
      //或
      @HOCFatoryFactory(params)
      class WrappedComponent extends React.Component{}
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14

      # 渲染属性(Render Props)

      # Mixins 存在的问题

      从 createClass 的 mixins 技术说起。我们创建一个 MouseMixin.

      import React from "react";
      import ReactDOM from "react-dom";
      // mixin 中含有了你需要在任何应用中追踪鼠标位置的样板代码。
      // 我们可以将样板代码放入到一个 mixin 中,这样其他组件就能共享这些代码
      const MouseMixin = {
        getInitialState() {
          return { x: 0, y: 0 };
        },
        handleMouseMove(event) {
          this.setState({
            x: event.clientX,
            y: event.clientY
          });
        }
      };
      const App = React.createClass({
        // 使用 mixin!
        mixins: [MouseMixin],
      
        render() {
          const { x, y } = this.state;
          return (
            <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}>
              <h1>
                The mouse position is ({x}, {y})
              </h1>
            </div>
          );
        }
      });
      ReactDOM.render(<App />, document.getElementById("app"));
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31

      # HOC是新的Mixin

      es6 class不支持mixins,且存在着命名冲突的问题

      # Render Props

      一个普通组件只需要一个函数prop就能够进行一些state共享

      import React from 'react'
      import ReactDOM from 'react-dom'
      import PropTypes from 'prop-types'
      
      //render props 组件共享代码
      class Mouse extends React.Component {
        static propTypes = {
          render: PropTypes.func.isRequired
        }
        state = {x: 0, y: 0}
        handleMouseMove = e => {
          this.setState({
            x: e.clientX,
            y: e.clientY
          })
        }
        render() {
          return (
            <div onMouseMove={this.handleMouseMove}>
              {this.props.render(this.state)}
            </div>
          )
        }
      }
      const App = React.createClass({
        render() {
          return (
            <div>
              <Mouse render={({x, y}) => (
                <h3>The mouse position is ({x}, {y})</h3>
              )}/>
            </div> 
          )
        }
      })
      
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36

      # Render Props > HOCs

      一个更将强有力的,能够证明 render prop 比 HOC 要强大的证据是,任何 HOC 都能使用 render prop 替代,反之则不然。下面的代码展示了使用一个一般的、具有 render prop 的 组件来实现的 withMouse HOC:

      const withMouse = (Component) => {
        return class extends React.Component {
          render() {
            return <Mouse render={mouse => (
              <Component {...this.props} mouse={mouse}/>
            )}/>
          }
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
    在Github上编辑此页 (opens new window)
    #前端框架
    上次更新: 3/22/2021, 3:14:54 AM
    React Hook
    Vue原理举一反三

    ← React Hook Vue原理举一反三→

    最近更新
    01
    如何打造全链路项目生命周期的统一交付平台
    04-10
    02
    如何建立前端标准化研发流程
    04-10
    03
    如何从0到1一步步成体系地搭建CI
    04-10
    更多文章>
    Theme by Vdoing | Copyright © 2019-2021 Syun
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式