react高阶组件


title: react-高阶组件
date: 2018-07-11 09:42:35
tags: web


组件间抽象

在React组件的构建过程中,常常有这样的场景,有一类功能需要被不同的组件公用,此时,就涉及抽象的话题,在不同设计理念下,有许多的抽象方法,而针对React,我们重点讨论两种:mixin和高阶组件。


png

mixin

mixin的特性一直广泛存在于各种面向对象语言中。比如在Ruby中,include关键词即是mixin。是将一个模块混入到一个另一个模块中,或是一个类中。

为什么编程语言要引入这样一种特性呢?

事实上,包括C++等一些年龄较大的OOP语言,它们都有一个强大但危险的多重继承特性。在现代语言中,为了权衡利弊,大都舍弃了多重继承,只采用单继承,但单继承在实现抽象时有很多不方便的地方,为了弥补缺失,java引入了接口interface。其他一些语言则引入了像mixin的技巧。

封装mixin方法

const mixin = function(obj,mixins){
    const newObj = obj;

    newObj.prototype = Object.create(obj.prototype);

    for(let prop in mixins){
        if(mixins.hasOwnProperty(prop)){
            newObj.prototype[prop] = mixins[prop];
        }
    }

    return newObj;
}

const BigMixin = {
    fly:()=>{
        console.log('I can fly');
    }
};

const Big = function(){
    console.log('new big');
}

const FlyBig = mixin(Big,BigMixin);

const flyBig = new FlyBig();  //=>'new big'
 
flyBig.fly(); //=> 'I can fly'

从上面的代码,我们不难看出,对于广义的mixin方法,就是用赋值的方式将mixin对象里的方法都挂载到原对象上,来实现对对象的混入。

从上述的实现,我们可以联想到 underscore库中的extend 或 lodash库中的 assign方法,或者说ES6中的Object.assign()方法。

在react中使用mixin

在官方封装的'reat-addons-pure-render-mixin';在git上没找到相关的有价值的库,应该是react认为mixin是一种反模式形式。

但是发现了react-immutable-render-mixin这样的库。只是很久没维护了,不建议使用
参考链接

我们可以看到,使用createClass实现的mixin为组件做了两件事。

工具方法:这是mixin的基本功能,如果你想共享一些工具类方法,就可以定义它们,直接在各个组件中使用。
生命周期继承:prips与state合并。这是mixin特别重要的功能,

ES6 Classes 与 decorator

然而,使用我们推荐的ES6 classes形势构建组件时,它并不支持mixin。React文档中也未能给出解决方法。

要在class的基础上封装mixin,就要说到class的本质。ES6并没有改变js面向对象方法基于原型的本质,不过再次智商提供了一些语法糖。class就是其中之一。

对于是按mixin方法来说,这就没什么不一样了。接下来我们来聊聊另一个语法糖decorator。正巧可以用来实现class的mixin。

decorator 是ES7定义的新特性,与java 的 pre-defined annotation(预定义注解)相似。但与java 的annotation 不同的是,decorator是运用在运行时的方法,在Redux或其他一些应用层框架中,越来越多的使用decorator以实现对组件的修饰。

这样我们就可以使用@mixin。

import {getOwnPropertyDescriptors} from './private/utils';

const { defineProperty } = Object;

function handleClass(target,mixins){
    if(!mixins.length){
        throw new SyntaxError('@mixin() class .....')
    }

    for(let i=0;i<mixins.length;i++){

        const descs = getOwnPropertyDescriptors(mixins[i]);

        for(const key in descs){
            if(!(key in target.prototype)){
                defineProperty(target.prototype,key,descs[key])
            }
        }
    }
}

export default function mixin(...mixins){
    if(typeof mixins[0] == 'function'){
        return handelClass(mixins[0],[]);
    }
    else{
        return target=>{
            return handleClass(target,mixins);
        }
    }
}

可以看到,源代码十分简单,它将每一个mixin对象的方法都跌价到target 对象的原型上以达到mixin的目的,这样,就可以用@mixin来做多个重用模块的叠加了。

对于react,我们自然可以用上述方法来实现mixin。但不幸的是,社区从0.14版本开始渐渐开始剥离mixin。那么,到底是什么原因导致mixin成为反模式了呢?

mixin问题

  • 破坏了原有组件的封装
  • 命名冲突
  • 增加复杂性

针对这些困扰,React社区提出来新的方式来取代mixin,那就是高阶组件。

高阶组件(Higher-Order Components)

高阶组件(HOC)是 React 中用于重用组件逻辑的高级技术。 HOC 本身不是 React API 的一部分。 它们是从 React 构思本质中浮现出来的一种模式。

具体来说,高阶组件是一个函数,能够接受一个组件并返回一个新的组件。

在我们项目中使用react-redux框架的时候,有一个connect的概念,这里的connect其实就是一个高阶组件。也包括类似react-router-dom中的withRouter的概念

connect

构建一个简单的hoc

function hello (){
    console.log("hello i  love react ")
}


function hoc(fn){
    return ()=>{
          console.log("first");
            fn();
          console.log("end");
    }
}


hello = hoc(hello);

hello();

实现高阶组件的方法

实现高阶组件的方法有如下两种:

  • 属性代理。高阶组件通过呗包裹的React组件来操作props
  • 反向继承。高阶组件继承于被包裹的React组件

接下来我们分别来阐述这两种方法。

属性代理

属性代理是我们react中常见高阶组件的实现方法,我们通过一个例子来说明:


import React,{Component} from 'react';

const MyContainer = (WraooedComponent) => 
    
    class extends Component {
        render(){
            return <WrappedComponent {...this.props} />
        }
    }

从这里看到最重要部分是render 方法中返回了传入 WrappedComponent的React组件。这样,我们就可以通过高阶组件来传递props。这种方法即为属性代理。

自然,我们想要使用MyContainer这个高阶组件就变得非常容易:

import React,{Component} from 'react';

class MyComponent extends Component{
    //...

}

export default MyContainer(MyComponent);

这样组件就可以一层层地作为参数被调用,原始组件就具备了高阶组件对它的修饰。就这么简单,保持单个组件封装性的同时还保留了易用性。当然,我们也可以用decorator来转换。

当使用属性代理构建高阶组件时,调用顺序不同于mixin。上述执行生命周期的过程类似于堆栈调用:

didmount ->HOC didmount ->(HOCs didmount)->(HOCs will unmount)->HOC will unmount -> unmount

反向继承

另一种构建高阶组件的方法称为反向继承,从字面意思上看,它一定与继承性相关。我们同样来看一个简单的实现。

const MyContainer = (WrappedComponent)=>{
    class extends WrappedComponent {
        render(){
            return super.render();
        }
    }
}

如上代码。高阶组件返回的组件继承于 WrappedComponent 。因为被动地继承了 WrappedComponent,所有的调用都会反向,这也是种方法的由来。

这种方法与属性代理不太一样。它通过继承WrappedComponent来实现,方法可以通过super来顺序调用。因为依赖于继承机制。HOC的调用顺序和队列是一样的。

didmount -> HOC didmount ->(HOCs didmount) -> will unmount ->HOC will unmount ->(HOCs will unmount)

在反向继承方法中,高阶组件可以使用 WrappedComponent 引用,这意味着它可以使用 WrappedComponent 的state 、props。生命周期和render方法。但它不能保证完整的子组件树被解析。它有两个比较大的特点,下面我们展开来讲一讲。

渲染劫持

渲染劫持就是指的是高阶组件可以控制 WrappedComponent的渲染过程,并渲染各种各样的结果。我们可以在这个过程中在任何React元素输出的结果中读取、增加、修改、删除props,或读取或修改React元素树,或条件显示。又或者用样式包裹元素树

控制state

高阶组件可以读取、修改或删除WrappedComponent实例中的state,如果需要的话,也可以增加state。

组件命名

当包裹一个高阶组件时,我们失去了原始 WrappedComponent的displayName,而组件名字是方便我们开发与调试的重要属性。

组件参数

有时,我们调用高阶组件需要传入一些参数,这可以用非常简单的方式来实现。


import React from 'react'

function HOCFactoryFactory(...params){
    return function HOCFactory(WrappedComponent){
        return class HOC extends Component{
            render(){
                return <WrappedComponent {...this.props} />
            }
        }
    }
}

当你使用的时候,可以这么写:

HOCFactoryFactory(params)(WrappedComponent)

//or

@HOCFactoryFactory(params)
class WrappedComponent extends React.Component{}

这也是利用了函数式编程的特征。可见,在React抽象的过程中,处处可见它的影子。

参考链接

react中文文档
深入React技术栈 作者:陈屹 出版社:人民邮电出版社
深入浅出React高阶组件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 148,827评论 1 317
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 63,511评论 1 266
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 99,318评论 0 218
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,108评论 0 189
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,112评论 1 266
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,387评论 1 185
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,905评论 2 283
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,657评论 0 177
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,144评论 0 223
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,744评论 2 225
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,100评论 1 236
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,565评论 2 222
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,041评论 3 216
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,769评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,301评论 0 178
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,173评论 2 239
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,313评论 2 242

推荐阅读更多精彩内容

  • 在目前的前端社区,『推崇组合,不推荐继承(prefer composition than inheritance)...
    Wenliang阅读 77,312评论 16 126
  • 前言 学习react已经有一段时间了,期间在阅读官方文档的基础上也看了不少文章,但感觉对很多东西的理解还是不够深刻...
    Srtian阅读 1,589评论 0 7
  • React进阶之高阶组件 前言 本文代码浅显易懂,思想深入实用。此属于react进阶用法,如果你还不了解react...
    流动码文阅读 1,167评论 0 1
  • React高阶组件探究 在使用React构建项目的过程中,经常会碰到在不同的组件中需要用到相同功能的情况。不过我们...
    绯色流火阅读 2,536评论 4 19
  • 打开电脑,点开音乐播放列表,随意的干点什么,耳旁飘来一句“最美的不是下雨天,是曾与你躲过雨的屋檐”,思绪飘飞,我想...
    荒止阅读 326评论 0 0