React 高阶组件浅析
高阶组件的这种写法的诞生来自于社区的实践,目的是解决一些交叉问题(Cross-Cutting Concerns)。而最早时候 React
官方给出的解决方案是使用 mixin
。而 React 也在官网中写道:
We previously recommended mixins as a way to handle cross-cutting concerns. We've since realized that mixins create more trouble than they are worth.
官方明显也意识到了使用mixins
技术来解决此类问题所带来的困扰远高于其本身的价值。更多资料可以查阅官方的说明。
高阶函数的定义
说到高阶组件,就不得不先简单的介绍一下高阶函数。下面展示一个最简单的高阶函数
const add = (x,y,f) => f(x)+f(y)
当我们调用add(-5, 6, Math.abs)
时,参数 x,y 和f 分别接收 -5,6 和 Math.abs
,根据函数定义,我们可以推导计算过程为:
x ==> -5
y ==> 6
f ==> abs
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11
用代码验证一下:
add(-5, 6, Math.abs); //11
高阶在维基百科的定义如下
高阶函数是至少满足下列一个条件的函数:
接受一个或多个函数作为输入
输出一个函数
高阶组件的定义
那么,什么是高阶组件呢?类比高阶函数的定义,高阶组件就是接受一个组件作为参数并返回一个新组件的函数。这里需要注意高阶组件是一个函数,并不是组件,这一点一定要注意。
同时这里强调一点高阶组件本身并不是 React
API。它只是一种模式,这种模式是由 React
自身的组合性质必然产生的。
更加通俗的讲,高阶组件通过包裹(wrapped)被传入的React组件,经过一系列处理,最终返回一个相对增强(enhanced)的 React 组件,供其他组件调用。
<!-- more -->
一个简单的高阶组件
下面我们来实现一个简单的高阶组件
export default WrappedComponent => class HOC extends Component {
render() {
return (
<fieldset>
<legend>默认标题</legend>
<WrappedComponent {...this.props} />
</fieldset>
);
}
};
在其他组件中,我们引用这个高阶组件来强化它
export default class Demo extends Component {
render() {
return (
<div>
我是一个普通组件
</div>
);
}
}
const WithHeaderDemo = withHeader(Demo);
下面我们来看一下React DOM Tree
,调用了高阶组件之后,发生了什么:
可以看到,Demo
被 HOC
包裹(wrapped)了之后添加了一个标题默认标题。但是同样会发现,如果调用了多个 HOC
之后,我们会看到很多的HOC
,所以应
该做一些优化,也就是在高阶组件包裹(wrapped)以后,应该保留原有的名称。
我们改写一下上述的高阶组件代码,增加一个 getDisplayName
函数,之后为Demo
添加一个静态属性 displayName
。
const getDisplayName = component => component.displayName || component.name || 'Component';
export default WrappedComponent => class HOC extends Component {
static displayName = `HOC(${getDisplayName(WrappedComponent)})`;
render() {
return (
<fieldset>
<legend>默认标题</legend>
<WrappedComponent {...this.props} />
</fieldset>
);
}
};
再次观察React DOM Tree
可以看到,该组件原本的名称已经显示在React DOM Tree
上了。
这个HOC 的功能是为原有的组件添加一个标题,也就是说所有需要添加标题的组件都可以通过调用此 HOC 进行包裹(wrapped) 后实现此功能。
为高阶组件传参
现在,我们的 HOC
已经可以为其他任意组件提供标题了,但是我们还希望可以修改标题中的字段。由于我们的高阶组件是一个函数,所以可以为其添加一个参数title
。下面我们对HOC
进行改写:
export default (WrappedComponent, title = '默认标题') => class HOC extends Component {
static displayName = `HOC(${getDisplayName(WrappedComponent)})`;
render() {
return (
<fieldset>
<legend>{title}</legend>
<WrappedComponent {...this.props} />
</fieldset>
);
}
};
之后我们进行调用:
const WithHeaderDemo = withHeader(Demo,'高阶组件添加标题');
此时观察React DOM Tree
。
可以看到,标题已经正确的进行了设置。
当然我们也可以对其进行柯里化:
export default (title = '默认标题') => WrappedComponent => class HOC extends Component {
static displayName = `HOC(${getDisplayName(WrappedComponent)})`;
render() {
return (
<fieldset>
<legend>{title}</legend>
<WrappedComponent {...this.props} />
</fieldset>
);
}
};
const WithHeaderDemo = withHeader('高阶组件添加标题')(Demo);
常见的HOC 实现方式
基于属性代理(Props Proxy)的方式
属性代理是最常见的高阶组件的使用方式,上面所说的高阶组件就是这种方式。
它通过做一些操作,将被包裹组件的props
和新生成的props
一起传递给此组件,这称之为属性代理。
export default function GenerateId(WrappedComponent) {
return class HOC extends Component {
static displayName = `PropsBorkerHOC(${getDisplayName(WrappedComponent)})`;
render() {
const newProps = {
id: Math.random().toString(36).substring(2).toUpperCase()
};
return createElement(WrappedComponent, {
...this.props,
...newProps
});
}
};
}
调用GenerateId
:
const PropsBorkerDemo = GenerateId(Demo);
之后我们观察React Dom Tree
:
可以看到我们通过 GenerateId
顺利的为 Demo
添加了 id
。
基于反向继承(Inheritance Inversion)的方式
首先来看一个简单的反向继承的例子:
export default function (WrappedComponent) {
return class Enhancer extends WrappedComponent {
static displayName = `InheritanceHOC(${getDisplayName(WrappedComponent)})`;
componentWillMount() {
// 可以方便地得到state,做一些更深入的修改。
this.setState({
innerText: '我被Inheritance修改了值'
});
}
render() {
return super.render();
}
};
}
如你所见返回的高阶组件类(Enhancer
)继承了 WrappedComponent
。而之所以被称为反向继承是因为 WrappedComponent
被动地被 Enhancer
继承,而不是 WrappedComponent
去继承 Enhancer
。通过这种方式他们之间的关系倒转了。
反向继承允许高阶组件通过 this
关键词获取 WrappedComponent
,意味着它可以获取到 state
,props
,组件生命周期(Component Lifecycle)钩子,以及渲染方法(render)。深入了解可以阅读__@Wenliang__文章中Inheritance Inversion(II)
这一节的内容。
使用高阶组件遇到的问题
静态方法丢失
当使用高阶组件包装组件,原始组件被容器组件包裹,也就意味着新组件会丢失原始组件的所有静态方法。
下面为 Demo 添加一个静态方法:
Demo.getDisplayName = () => 'Demo';
之后调用 HOC
:
// 使用高阶组件
const WithHeaderDemo = HOC(Demo);
// 调用后的组件是没有 `getDisplayName` 方法的
typeof WithHeaderDemo.getDisplayName === 'undefined' // true
解决这个问题最简单(Yǘ Chǚn)的方法就是,将原始组件的所有静态方法全部拷贝给新组件:
export default (title = '默认标题') => (WrappedComponent) => {
class HOC extends Component {
static displayName = `HOC(${getDisplayName(WrappedComponent)})`;
render() {
return (
<fieldset>
<legend>{title}</legend>
<WrappedComponent {...this.props} />
</fieldset>
);
}
}
HOC.getDisplayName = WrappedComponent.getDisplayName;
return HOC;
};
这样做,就需要你清楚的知道都有哪些静态方法需要拷贝的。或者你也可是使用hoist-non-react-statics来帮你自动处理,它会自动拷贝所有非React的静态方法:
import hoistNonReactStatic from 'hoist-non-react-statics';
export default (title = '默认标题') => (WrappedComponent) => {
class HOC extends Component {
static displayName = `HOC(${getDisplayName(WrappedComponent)})`;
render() {
return (
<fieldset>
<legend>{title}</legend>
<WrappedComponent {...this.props} />
</fieldset>
);
}
}
// 拷贝静态方法
hoistNonReactStatic(HOC, WrappedComponent);
return HOC;
};
Refs属性不能传递
一般来说,高阶组件可以传递所有的props属性给包裹的组件,但是不能传递 refs
引用。因为并不是像 key
一样,refs
是一个伪属性,React
对它进行了特殊处理。
如果你向一个由高级组件创建的组件的元素添加 ref
应用,那么 ref
指向的是最外层容器组件实例的,而不是包裹组件。
但有的时候,我们不可避免要使用 refs
,官方给出的解决方案是:
传递一个ref回调函数属性,也就是给ref应用一个不同的名字
同时还强调道:React在任何时候都不建议使用 ref应用
改写 Demo
class Demo extends Component {
static propTypes = {
getRef: PropTypes.func
}
static getDisplayName() {
return 'Demo';
}
constructor(props) {
super(props);
this.state = {
innerText: '我是一个普通组件'
};
}
render() {
const { getRef, ...props } = this.props;
return (
<div ref={getRef} {...props}>
{this.state.innerText}
</div>
);
}
}
之后我们进行调用:
<WithHeaderDemo
getRef={(ref) => {
// 该回调函数被作为常规的props属性传递
this.headerDemo = ref;
}}
/>
虽然这并不是最完美的解决方案,但是React
官方说他们正在探索解决这个问题的方法,能够让我们安心的使用高阶组件而不必关注这个问题。
React 高阶组件浅析的更多相关文章
- 聊聊React高阶组件(Higher-Order Components)
使用 react已经有不短的时间了,最近看到关于 react高阶组件的一篇文章,看了之后顿时眼前一亮,对于我这种还在新手村晃荡.一切朝着打怪升级看齐的小喽啰来说,像这种难度不是太高同时门槛也不是那么低 ...
- 当初要是看了这篇,React高阶组件早会了
当初要是看了这篇,React高阶组件早会了. 概况: 什么是高阶组件? 高阶部件是一种用于复用组件逻辑的高级技术,它并不是 React API的一部分,而是从React 演化而来的一种模式. 具体地说 ...
- react高阶组件的理解
[高阶组件和函数式编程] function hello() { console.log('hello jason'); } function WrapperHello(fn) { return fun ...
- 函数式编程与React高阶组件
相信不少看过一些框架或者是类库的人都有印象,一个函数叫什么creator或者是什么什么createToFuntion,总是接收一个函数,来返回另一个函数.这是一个高阶函数,它可以接收函数可以当参数,也 ...
- React高阶组件学习笔记
高阶函数的基本概念: 函数可以作为参数被传递,函数可以作为函数值输出. 高阶组件基本概念: 高阶组件就说接受一个组件作为参数,并返回一个新组件的函数. 为什么需要高阶组件 多个组件都需要某个相同的功能 ...
- 利用 React 高阶组件实现一个面包屑导航
什么是 React 高阶组件 React 高阶组件就是以高阶函数的方式包裹需要修饰的 React 组件,并返回处理完成后的 React 组件.React 高阶组件在 React 生态中使用的非常频繁, ...
- react高阶组件的一些运用
今天学习了react高阶组件,刚接触react学习起来还是比较困难,和大家分享一下今天学习的知识吧,另外缺少的地方欢迎补充哈哈 高阶组件(Higher Order Components,简称:HOC) ...
- React——高阶组件
1.在React中higher-order component (HOC)是一种重用组件逻辑的高级技术.HOC不是React API中的一部分.HOC是一个函数,该函数接收一个组件并且返回一个新组件. ...
- react 高阶组件的 理解和应用
高阶组件是什么东西 简单的理解是:一个包装了另一个基础组件的组件.(相对高阶组件来说,我习惯把被包装的组件称为基础组件) 注意:这里说的是包装,可以理解成包裹和组装: 具体的是高阶组件的两种形式吧: ...
随机推荐
- Oracle数据的导入与导出
本文针对window操作系统与oracle12C的版本. 1.sqlplus执行单个sql文件 1.执行sqlplus登陆命令:sqlplus username/password@host:port/ ...
- TIME_WAIT状态全是3306解决办法
刚吃完晚饭,手机短信一直响个不停,打开一看全是告警信息,立即打开电脑查看,发现很多网页很不稳定 一会能打开,一会打不开 登录服务器查看负载情况,cpu.内存 .磁盘io 负载都不高,查看日志发现ng ...
- linux个人常用命令【持续更新】
netstat -tnl 查看网络相关的端口情况 ps -A 查看所有进程的情况 cat /proc/cpuinfo| grep "physical id"| sort| uniq ...
- SecureCRT进行端口转发
总共3台机器:my电脑.跳转机器(外网).内网服务器. 首先配置至跳板机(150.236.223.72:22)的连接: 配置完成后选择Connect连接至跳板机,输入密码后可选择“Save p ...
- 从OkHttp的源码来看 HTTP
先来了解一下OkHttp的历史,最早是square公司觉得Android给的HttpClient这块的库不太好用,于是乎做了一层包装,再后来他们包装的这个库被Android官方给收回去了,而Andro ...
- Three.js入门详解
什么是WebGL WebGL(Web 图形库)是一种 JavaScript API,用于在任何兼容的 Web 浏览器中呈现交互式 3D 和 2D 图形,而无需使用插件.WebGL 通过引入一个与 ...
- React 之 render props 的理解
1.基本概念 在调用组件时,引入一个函数类型的 prop,这个 prop定义了组件的渲染方式. 2.回调渲染 回顾组件通信的几种方式 父-> 子 props 子-> 父 回调.消息通道 任 ...
- BZOJ 1181: [CROATIAN2009] IZBROI选举(二分+dp)
题面 在一个地区的选举中,共有V个人参加了投票,每一票只可能投给N个政党中的一个.当地的议会共有M个席位.不妨将N个政党编号为1到N,并且设编号为i的政党最终的得票为Vi,则议会中的席位按如下规则分配 ...
- SQL动态标签
MyBatis的动态SQL详解MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦.拼接的时候要确保不 ...
- REST与RPC区别
OSI网络七层模型 第一层:应用层.定义了用于在网络中进行通信和传输数据的接口: 第二层:表示层.定义不同的系统中数据的传输格式,编码和解码规范等: 第三层:会话层.管理用户的会话,控制用户间逻辑连接 ...