在解释什么是高阶组件之前,我们先要了解一个js中的概念—高阶函数。高阶函数就是:
const debounce = (fn, delay) => {
let timeId = null
return () => {
timeId && clearTimeout(timeId)
timeId = setTimeout(fn, delay)
}
}
高阶函数的应用有很多,函数去抖,函数节流,bind函数,函数柯里化,map,Promise的then函数等。
接下来我们说下高阶组件。高阶组件其实和高阶函数很像,他们都是一个函数,但是不同于高阶函数的地方在于高阶组件传入的是组件,返回的也是组件。总之,高阶组件就是包裹传入的React组件,经过一系列处理,返回一个包装后的组件。
高阶组件有比较常用的两种实现方式
下面来说来说一下两种方式的实现。
属性代理是实现高阶组件的最常见的方式。说白了就是在高阶组件函数内获取传入的组件的属性,对属性进行一系列的操作,再将新的属性传入旧的组件内,从而产生一个新的组件,达到组件增强的目的。下面来看代码:
const addHeader = WrappedComponent => {
return class Hoc extends React.Component {
render () {
const newProps = {
id: Math.random().toString(36).substring(2).toUpperCase()
}
return (
<div class="header-component">
<h1>this is Header</h1>
<WrappedComponent {...this.props} {...newProps}/>
</div>
)
}
}
}
可以看到我们在Hoc组件内拿到了WrappedComponent组件的props。并向WrappedComponent组件注入了新的newProps属性。然后返回了一个在旧的组件基础上添加了h1的组件。
反向继承也是一种实现高阶组件的方法。反向继承就是新组件通过extends关键词继承旧组件,然后在新组件内对旧组件的属性、方法进行重写,然后返回一个基于旧组件的扩展组件。
const header = WrappedComponent => {
return class Hoc extends WrappedComponent {
render () {
return (
<div class="header-component">
<h1>this is Header</h1>
{ super.render() }
</div>
)
}
componentWillMount () {
console.log('this is Hoc componentWillMount')
}
}
}
在继承了旧的组件之后,我们可以在Hoc组件内获取到WrappedComponent组件的属性、方法、生命周期等(静态方法获取不到)。值得注意的是,我们如果要在Hoc组件中render WrappedComponent的内容,需要调用super.render方法,渲染父类的dom元素。
在说完了高阶函数和高阶组件之后,让我们来了解一下es7中的装饰器(decorator),为什么要将装饰器和高阶函数、高阶组件放在一起呢?因为我觉得这三个东西其实都是有相似之处的,都是通过一个函数对传入的参数进行一系列的包装,最后返回一个新的东西,来达到扩展原有函数、组件的目的。
简单来说,装饰器就是用一个代码包装另一个代码的简单方式,就是简单的将一个函数包装成为另一个函数。
function doSomething(name) {
console.log('Hello, ' + name);
}
function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
}
const wrapped = loggingDecorator(doSomething);
上个例子产生新函数wrapped,此函数与doSomething做同样事情,但是他们不同在于在包装函数之前和之后输出一些语句。
JavaScript中装饰器使用特殊的语法,使用@作为标识符,且放置在被装饰代码之前。
@log()
@immutable()
class Example {
@time('demo')
doSomething() {
}
}
上例中定义了一个类,采用了三个装饰器:两个用于类本身,一个用于类的属性:
现在,装饰器只支持类和类属性,这包含属性、方法、get函数和set函数。
装饰器只会在程序第一次运行时执行一次,装饰的代码会被返回的值代替。
属性装饰器适用于类的单独成员-无论是属性、方法、get函数或set函数。
装饰器函数调用三个参数:
function readonly (target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class example extend {
a () {}
@readonly
b () {}
}
通过使用readonly装饰器让b方法变成了只读方法。下面是一个log日志装饰器的代码。
function log(name) {
return function decorator(t, n, descriptor) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function(...args) {
console.log(`Arguments for ${name}: ${args}`);
try {
const result = original.apply(this, args);
console.log(`Result from ${name}: ${result}`);
return result;
} catch (e) {
console.log(`Error from ${name}: ${e}`);
throw e;
}
}
}
return descriptor;
};
}
类装饰器用于整个类,装饰器函数的参数为被装饰的构造器函数。
注意只用于构造器函数,而不适用于类的每个实例。这就意味着如果想控制实例,就必须返回一个包装版本的构造器函数。
通常,类装饰器没什么用处,因为你所需要做的,同样可以用一个简单函数来处理。你所做的只需要在结束时返回一个新的构造函数来代替类的构造函数。
回到我们记录那个例子,编写一个记录构造函数参数:
function log(Class) {
return (...args) => {
console.log(args);
return new Class(...args);
};
}
这里接收一个类作为参数,返回新函数作为构造器。此函数打印出参数,返回这些参数构造的实例。