R-6.React高阶组件详解
看题目感觉好高级的样子,千万不要被名字吓到,它一点都不高深。
按照惯例先上图,这一章的概览:
1.从高阶函数说起
维基百科对高阶函数的定义:
在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
- 接受一个或多个函数作为输入
- 输出一个函数
是不是很简单?满足任一条件一句话说就是接受或者返回一个函数的函数就是高阶函数。
举个例子
funA(){
return funB(){};
}
funA 就是一个高阶函数。
顺便提一下图上的高阶函数对应的三个实现,也是面试经常问道的,这里简单说一下。
1).函数回调
我们开发最长用的封装网络请求是一个高阶函数。
function funRequest(params){
return Http.post(params.URL,params.data);// 这里实际返回的是一个promise
}
2).柯里化
是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术
其实就是 fun(a,b,c) -------柯里化-----> fun(a)(b)(c);
举个例子:
function add(a,b,c) {
return (a + b + c);// 返回三个参数的和
}
// 把上述函数柯里化(这是一个简单实现,有兴趣的想想怎么实现任意个数的求和)
function addH(a) {
return function (b) {
return function (c) {
return (a + b + c);
}
}
}
console.log(add(1,2,3));
console.log(addH(1)(2)(3));
3).函数节流
这个也不高深,就是平时我们页面加载过程中会有上拉频繁,加载数据会出现之前的请求还没有返回就又加载新的内容,不停的发送请求。这时我们就会进行节流。
伪代码:
function request() {
let start = true;
return function (params) {
if(start) {
start = false;
Http.post(params.URL, params.data).then(res => {
start = true;
})
}
}
}
let startRequest = request();
if('触发请求'){
startRequest({URL:'sslslssllsl',data:{}});
}
2.高阶组件(order-hight-component)
高阶组件跟高阶函数极度相似,把函数的传入值和返回值从函数变成组件,那么这个函数就是高阶组件。
WrapperComponent参数是一个组件,那么下面函数HocComp1和HocComp2都是高阶组件
const HocComp1 = WrapperComponent =>
class extends Component{// 继承Component
render(){
return (
<div>
HOC
<WrapperComponent />
</div>
)
}
}
const HocComp2 = WrapperComponent =>
class extends WrapperComponent{// 继承的是传入的组件
render(){
const oldElements = super.render();
return (
<div>
HOC
{oldElements}
</div>
)
}
}
上面就是最简单的创建的两个高阶组件,功能是一样的在元组件的顶上加上“HOC”。
- 继承Component的是属于属性代理方式创建的高阶组件;
- 继承传入组件的属于反向继承方式创建的高阶组件。
1).属性代理
属性代理顾名思义,就是替代的意思。高阶组件替传入组件管理控制props里面一切属性,管理控制包括增,删,改,查。同时他自身还有自身的状态,即state,来强化传入组件。打个比方,传入组件是画一个圆,其中只有一个props属性半径radius。那我们在高阶组件中就可以随意操作这个属性值,可大可小,还可以为props增加新的属性,比如增加一个color属性,表示圆的颜色;在组件外层加一个背景,美化传入组件。这个比方由大家去实现。
这里举一个稍微比这个难一点点的例子,受控的输入框。输入框组件的要求很简单,就是输入框中一直要有“输入:”这两个字和冒号。
分析:根据什么是受控组件,第一我们要通过state控制input的value属性。
第二我们要监控input输入值得变化,每当变化是我们拿到最新输入值,然后在前面拼接上“输入:”,设置一个state就可以了。所以需要onChange监听。
组件代码如下:
class ControlInput extends Component{
// 一个受控组件,通过属性代理的方式,把控制逻辑放进高阶组件中。
render(){
const { value , eventOnChange} = this.props;
return (
<input value={value} {...eventOnChange}/>
)
}
}
注意这里没有任何逻辑,属性代理嘛,逻辑当然都在高阶组件中啦。
高阶组件如下:
import React,{ Component } from 'react';
import '../../style/higherOrderComponent/higherOrderComponent.scss';
const AttributeAgentHigherOrderComponent2 = (BaseComponent) =>
class extends Component{
constructor(props){
super(props);
this.state = {
value:this.props.initValue || '',
}
}
onValueChange = (event) => {
let value = event.target.value.toString();
// 这句最直观的体现什么是受控(要什么值显示什么值)
value = `输入:${value === '输入' ? '' : value.replace('输入:','')}`;
this.setState({value:value});
}
render(){
const { value } = this.state;
const newProps = {
value: value,// input 的value属性
eventOnChange:{
onChange: this.onValueChange,// input的onChange监听,方法在高阶组件内
},
}
const props = Object.assign({},this.props,newProps);// 合成最新的props传给传入组件
return (
<BaseComponent {...props}/>
)
}
}
export default AttributeAgentHigherOrderComponent2;
怎么用的呢,在导出ControlInput组件的地方调用即可
export default AttributeAgentHigherOrderComponent2(ControlInput);
也可以使用ES6注解方式:
Es6注解方式
2).反向继承
属性代理方式在高阶组件中返回的组件继承的是Component,而反向继承则是继承的传入组件,根据继承的特性,继承可获取父类的所有静态资源,非私有属性和方法,且根据情况可对原方法进行重写。所以反向继承的方式也可以操作传入组件的props以及state。还有一个更重的就是反向继承可以进行渲染劫持。
我们来句一个简单的例子,还是一个输入框,要求
-
输入框中有值时就出现提交按钮,没有值时则消失。
-
提交按钮可用
按照组件开发,不用高阶组件完全可以写,还很简单。但是现在我们就用高阶组件写,看有什么区别。先写传入组件:class ReverseInput extends Component{ constructor(props){ super(props); this.state = { value:'' } } // 处理提交动作。定义了方法没有方法实体 toSubmit = () => {} // 处理输入值变化动作。定义了方法没有方法实体 valueChange = (eve) => {} render(){ const { value } = this.state; return ( <div> <input onChange={this.valueChange} value={value}/> <button onClick={this.toSubmit}>提交</button> </div> ) } }
上面就是将要被继承的组件,里面有方法,但是却没有方法实体。这就是反向继承可以在高阶组件中进行方法的重写。注意组件中是有提交按钮的,我们要在高阶组件中进行控制显示和隐藏,使用的就是渲染劫持。
const ReverseInherit1 = BaseComponent =>
class extends BaseComponent{ // 继承传入组件
// 在这里定义监听value值变化的函数
valueChange = (eve) => {
console.log(eve.target.value);
this.setState({value:eve.target.value})
}
// 在这里重写提交的函数
toSubmit = () => {
alert(`您要提交的值是:${this.state.value}`);
}
render(){
const { value } = this.state;
const superEle = super.render();// 拿到父组件的要渲染的结构对象,做渲染劫持的关键
const newElement = React.cloneElement(superEle,this.props,superEle.props.children);
if(value){// 如果value有值就不做任何处理返回父组件的render
return (
super.render()
)
}else{// value 有值则对原来的结构进行调整
newElement.props.children.splice(1,1);
return (newElement)
}
console.log(superEle);
}
}
用法跟上一个属性代理的一致。
是不是感觉高阶组件也不过如此?实际上难就难在去抽象组件逻辑,针对不同的需求。我门项目上就有一个问题比如权限相关的,所有组件中都有于某一权限相关的按钮或者其他内容,这些按钮和内容是和权限相关的,权限不足的人不能看到,如果每个人写组件的时候都在自己组件里判断,然后进行是否显示,这就造成一个问题,如果以后权限变动了,就要改很多处,这是比较麻烦的。但是高阶组件很好的解决了这个问题。只要在你写的组件中进行权限控制显示的内容上加一个标记,比如ref=‘xxxPower’等。我就可以让你组件通过我的高阶组件时对你的加了权限标记的内容进行显示和隐藏,所有组件都可以。
这个例子写在下面的工程源码里面了。运行后就是菜单 “HOC-反向继承2.1”.源码位置在powerButton文件夹。
工程源码地址,点击这里