函数式编程-高阶组件在前端的应用

前言

之前开发项目写过一些装饰器觉得很不错,比如全局loading的应用,还有一个页面上多种弹窗造成页面的state过于繁重,维护很困难,因此抽离出各种公共组件便于维护,代码也会缩短很多,因此写篇文章深入的记录一下

一、什么是高阶组件 目的是什么

官方解释是:一个传入一个组件,返回另一个组件的函数,其概念与高阶函数的将函数作为参数传入类似。

使用目的
  • 将高度相似的部分抽离出来,比如一个常用组件,比如一个弹窗,可能有不同颜色的弹窗,或者只是某些小的地方不同,这样抽离抽离出来便于前端代码的维护
  • 生命周期 state 的捕获 渲染劫持

二、使用方法

1.简单的装饰器
  • 取到或操作原组件的props✅
  • 能否取到或操作state❌
  • 能否通过ref访问到原组件中的dom元素❌
  • 是否影响原组件生命周期等方法✅
  • 是否取到原组件static方法✅
  • 劫持原组件生命周期❌
  • 渲染劫持❌
// common.js 公共抽离部分
import React from 'react';

const common = WrapperComponet => class extends WrapperComponet {
      constructor (props) {
        super(props)
        this.state = {
          ...this.state,
          list: [],
          num: 1
        }
      }
      onClick = () => {
        this.setState({ num: this.state.num + 3 });
      }
  
      render () {
        const newProps = {a: 1};
        
        return <WrapperComponet onClick={this.onClick} { ...newProps}/>
          )
      }
  }
//页面
import React from 'react';

import './App.css';
import common from './common';

@common
class App extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
     
    }
  }
   render () {
     console.log(this.props); // {onClick: ƒ, a: 1} 装饰器里面可以对传入的类进行一些更改
    return (
      <div className="App">
        
       点击 {this.state.num}
      </div>
    );
   }
 
}
export default App;

2.利用高阶组件的反向继承,操作state 方法等

不太推荐利用操作反向继承操作state 这样会和原组件造成冲突,最好的应用场景是调试组件,在里面写一些调试代码

  • 取到或操作原组件的props✅
  • 能否取到或操作state✅
  • 能否通过ref访问到原组件中的dom元素✅
  • 是否影响原组件生命周期等方法✅
  • 是否取到原组件static方法✅
  • 劫持原组件生命周期✅
  • 渲染劫持✅

反向继承最核心的两个作用,一个是渲染劫持,另一个是覆盖原有的state

(1)覆盖原有state
const common = WrapperComponet => class extends WrapperComponet {
      constructor (props) {
        super(props)
        this.state = {
          ...this.state,
          list: [],
          num: 1
        }
      }
      onClick = () => { //被覆盖
        this.setState({ num: 100 });
      }
  
      render () {
        
        // return <WrapperComponet onClick={this.onClick} { ...newProps}/>
          const elementsTree = super.render();
          const { children, ...otherProps } = elementsTree.props;

          return React.cloneElement(
            elementsTree,
            otherProps,
            ...children,
          )
      }
  }


@common
class App extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
     
    }
  }

   onClick = () => {
    this.setState({ num: 99 });
   }
   render () {
    return (
      <div className="App" onClick={this.onClick}>
        
       点击 {this.state.num}
      </div>
    );
   }
 
}
export default App;
反向继承控制了传入页面的state以及方法
(2)渲染劫持

render函数实际是利用react.createElement产生元素 我们可以拿到它 但是我们不能对其进行更改 ,render 函数应该是一个纯函数,完全根据 this.state 和this.props 来决定返回的结果,而且不要产生任何副作用。在 render 函数中去调用 this.setState 毫无疑问是错误的,因为一个纯函数不应该引起状态的改变。 我们只能通过react.cloneElement对其进行增强。

const HOC  = (WrappedComponent) =>  {
    return class extends WrappedComponent {
      render() {
        const tree = super.render();
        let newProps = {};
        if (tree && tree.type === 'input') {
          newProps = { value: 'value被劫持' };
        }
        const props = { ...tree.props, ...newProps };
        const newTree = React.cloneElement(tree, props, tree.props.children);
        return newTree;
      }
    }
  }

  export default HOC;

三、修饰器几种类型

1、装饰一个类的属性
function log(target, key, descriptor) {
    console.log(target);
    console.log(target.hasOwnProperty('constructor'));
    console.log(target.constructor);
    console.log(key);
    console.log(descriptor);
}

class Bar {
    @log;
    bar() {}
}

// {}
// true
// function Bar() { ...
// bar
// {"enumerable":false,"configurable":true,"writable":true}
readonly(target, name, descriptor) {
  descriptor.writable = false;
   return descriptor;
}

class cat {
@readonly
say() {
  console.log('cat');
}
 
}

var kitty = new Cat();
kitty.say = function() {
    console.log("dog");
}
kitty.say()    // cat

同时 我们也可以在target上进行装饰

class Bar {
    @log;
    bar() {}
}
log(target, name, descriptor){
  target.speed = 20;
 let run = descriptor.value;
  descriptor.value = function(){
     run();
     console.log(`speed${this.speed}`);
  }
 return descriptor;
}
var a = new Bar();
a.speed // 20
a.bar(); // speed20

以上修饰原理使用的

let descriptor = {
    value: function() {
        console.log("meow ~");
    },
    enumerable: false,
    configurable: true,
    writable: true
};
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;
Object.defineProperty(Cat.prototype, "say", descriptor);
2、装饰一个类
@fruit
class Lala() {}

fruit(item) {
  item.apple = 1;
  return item;
}
Lala.apple // 1

装饰类对本身操作 装饰类的属性对描述符descriptor进行操作

4、高阶组件的应用

1.页面复用(工厂模式)

比如一个公共页面 只是某些字段发生改变,可以将这个公共页面设计成工厂(高阶组件),外部传入一个json配置给这个装饰器的参数,下面举例一个简单的🌰

import shopList from './shopList';

const defaultProps = {
   fetchData: () => {console.log('fetch some data');},
   labelName: 'title',
   value: '123'
};

const App = shopList(defaultProps);
export default App;
import React from 'react';
function CommonPage(config) {
  const {
    fetchData,
    labelName,
    value
  } = config;
  return class extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            data: [],
        };
    }
    
  render() {
    return <div onClick={fetchData}>
        {`${labelName}: ${value}`}
    </div>
    }
  };
}
export default CommonPage;

可以根据传入的json去生成对应的页面
2.页面的选择渲染

比如 一个页面如果根据权限去渲染一些不同的页面 而且这个判断设计很多页面 那么我们不能将这些判断都写在代码中 重复的逻辑应该抽离出出来

import AuthPage from './two';


class App extends React.Component {
  componentWillMount() {
      // 获取业务数据
  }
  render() {
    return <div>业务页面</div>;
      
  }
}

export default AuthPage(App);

import React from 'react';

const AuthPage = WrappedComponent => class extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
              permission: -1,
            };
        }
        componentWillMount() {
            // 权限获取接口,在此模拟promise
            new Promise(resolve => resolve(0)).then((res) => {
                // success
                this.setState({
                    permission: res,
                });
            });
        }
        render() {
           
            if (this.state.permission) {// 非0显示特殊页面
                return <div>特殊页面</div>;
            }
            return <WrappedComponent {...this.props} />;
        }
    }


export default AuthPage;
3.页面的性能指标监控

对某些页面进行时间监控 利用高阶组件防止重复代码

import React from 'react';

function Performance(WrappedComponent) {
   return class extends WrappedComponent {
       constructor(props) {
           super(props);
           this.start = 0;
           this.end = 0;
       }
       componentWillMount() {
           super.componentWillMount && super.componentWillMount();
           this.start = Date.now();
       }
       componentDidMount() {
           super.componentDidMount && super.componentDidMount();
           this.end = Date.now();
           console.log(`组件渲染时间为 ${this.end - this.start} ms`);
       }
       render() {
           return super.render();
       }
   };
}

export default Performance;
import Performance from './three';


class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
    }
}
  componentWillMount() {
      // 获取业务数据
  }
  render() {
    return <div>业务页面</div>;
      
  }
}

export default Performance(App);
打印出组件渲染时间
4.对组件进行二次封装

点击按钮希望请求未回来的时候显示loading状态防止二次请求

import Button from './Button';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: false
    }
  }
  componentWillMount() {
      // 获取业务数据
  }

  onClick = () => {
    // 模拟一个接口
    return new Promise(resolve => { 

      setTimeout(() => {
        resolve(4);
      }, 4000);
    }).then((res) => {
       console.log(res, '业务代码');
    });
    
  }

  render() {
    return <Button type="primary"  onClick={this.onClick}>请求</Button>;
      
  }
}

export default App;
// ButtonWrapper.js
import React from 'react';

const Button = WrappedComponent => class extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
          loading: false
          
        };
    }
    componentWillMount() {
    }

    handleClick = () => {
      // 只有return一个promise才会加载loading状态,否则正常执行,因为判断是否有then必须还要执行一遍onclick,所以暂时只能加try catch
      try {
        this.setState({ loading: true });
        this.props.onClick().then((res) => {
          this.setState({ loading: false });
        });
      }
       catch(e) {
        this.setState({ loading: false });
      }
    }

    render() {  
      return <WrappedComponent {...this.props} loading={this.state.loading} onClick={this.handleClick}/>;
    }
}


export default Button;
import { Button } from 'antd';
import ButtonWrapper from './ButtonWrapper';

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

推荐阅读更多精彩内容