React学习 高阶组件和decorator

总有一些东西,是你再努力都触碰不到的。

Decorator

es7语法糖,就是一个修饰类以及类的属性或者行为的函数。

配置

1
npm install customize-cra react-app-rewired @babel/plugin-proposal-decorators --save

项目根目录新建config-overrides.js文件加入以下代码:

1
2
3
4
const { override, addDecoratorsLegacy } = require('customize-cra');
module.exports = override(
addDecoratorsLegacy()
);

修改package.json文件如下:

1
2
3
4
5
6
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},

使用

1
2
3
4
5
6
7
8
9
function testAble(target){
target.isTest = true;//静态属性
target.prototypr.isTest = true;//实例属性
}

@testAble
class A {}

console.log(A.isTest);// true

在一个修饰器中,参数target就是你要修饰的类,这个类也叫做Decorator的修饰目标对象。

多参数的修饰器的实现

1
2
3
4
5
6
7
8
9
10
function testAble(value) {
return function(target){
target.prototype.isTrue = value;
}
}

@testAble(true)
class A {};

new A().isTrue;// true

修饰对象的属性

除了修饰class,还用来修饰对象属性。

修饰器修饰一个属性的时候,有3个参数

第一个 target 修饰器对应的class

第二个 name 属性名

第三个 descriptor 描述符

descriptor的说明

1
2
3
4
5
6
7
let obj = {};
Object.defineProperty(obj, 'name', {
configurable: true,
writable: true,
enumberable: true,
value: '...',
});

就是其中的描述符对象。

@log的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function log(target, name, descriptor){
var oldValue = descriptor.value;
descriptor.value = function(){
console.log(`this prop ${name} 的参数为 ${arguments}`);
return oldValue.appay(null, arguments);
}
return descriptor;
}

class Math {
@log
add(a, b){
return a + b;
}
}

可见本质是对descriptor的实现。另外要注意修饰器不能用于函数。(原因:函数存在函数提升)

core-decorator第三方模块,提供常用的几个修饰器

(1) @override

检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。

(2)@autobind

使得方法中的this对象,绑定原始对象。

(3) @readonly

使得class中的属性或者方法不可写。

高阶组件

目的是解决一些交叉问题(Cross-Cutting Concerns)。而最早时候 React 官方给出的解决方案是使用 mixin

高阶组件通过包裹(wrapped)被传入的React组件,经过一系列处理,最终返回一个相对增强(enhanced)的 React 组件,供其他组件调用。高阶组件是接受一个组件作为参数并返回一个新组件的函数

先复习一下class表达式语法的规则

1
2
3
4
5
6
7
8
9
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
let inst = new MyClass();
inst.getClassName();//Me
MyClass.name//Me
Me.className//ReferenceError: Me is not defined

Me只能在class里面用,并且MyClass的引用是class Me {},所以在外面MyClass.nameMe

如果类的内部没用到的话,可以省略Me

1
2
3
4
5
6
7
8
9
10
11
12
const MyClass = class { /* ... */ };

/*立即执行*/
let person = new class {
constructor(name) {
this.name = name;
}

sayName() {
console.log(this.name);
}
}('张三');

一个简单的高阶组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
export default function withHeader(WrappedComponent) {
return class HOC extends Component {
render() {
return <div>
<div className="demo-header">
我是标题
</div>
<WrappedComponent {...this.props}/>
</div>
}
}
}

/*在其他组件里,我们引用这个高阶组件,用来强化它。*/
@withHeader
export default class Demo extends Component {
render() {
return (
<div>
我是一个普通组件
</div>
);
}
}

@withHeaderdecorator,也可以写成

1
export default withHeader(Demo);

这样写的问题

高阶组件调用多次,会造成组件数出现多个HOC,调试困难。

优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function getDisplayName(component) {
return component.displayName || component.name || 'Component';
}
export default function (WrappedComponent) {
return class HOC extends Component {
static displayName = `HOC(${getDisplayName(WrappedComponent)})`
render() {
return <div>
<div className="demo-header">
我是标题
</div>
<WrappedComponent {...this.props}/>
</div>
}
}
}

由此可以看出,高阶组件的主要功能是封装并抽离组件的通用逻辑,让此部分逻辑在组件间更好地被复用。

displayName 定义调试时的组件name

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
function withHOC(WrapComponent) {
// 此处未定义名称或者希望动态定义名称
return class extends React.Component {
// 定义displayName
static displayName = `withHOC(${WrapComponent.displayName || WrapComponent.name})`;
render(){
console.log("inside HOC")
return <WrapComponent {...this.props } />;
}
}
}

App = withHOC(App);

如果未定义displayName,那么进行调试的时候,就会显示如下:

1
2
3
4
// react自动定义名称
|---_class2
|---App
...

定义displayName后,显示如下:

1
2
3
|---withHOC(App)
|---App
...

组件参数

1
2
3
4
5
6
7
8
@withHeader('Demo') 
export default class Demo extends Component {
render() {
return (
//...
);
}
}

改写一下HOC

1
2
3
4
5
6
7
8
9
10
11
12
export default (title) => (WrappedComponent) => class HOC extends Component {
render() {
return <div>
<div className="demo-header">
{title
? title
: '我是标题'}
</div>
<WrappedComponent {...this.props}/>
</div>
}
}

实现方式

(1)属性代理

通过做一些操作,将被包裹组件的props和新生成的props一起传递给此组件,这称之为属性代理

1
2
3
4
5
6
7
8
9
10
11
12
13
export default function withHeader(WrappedComponent) {
return class HOC extends Component {
render() {
const newProps = {
test:'hoc'
}
// 透传props,并且传递新的newProps
return <div>
<WrappedComponent {...this.props} {...newProps}/>
</div>
}
}
}

(2)基于反向继承

1
2
3
4
5
6
7
8
9
10
11
export default function (WrappedComponent) {
return class Inheritance extends WrappedComponent {
componentDidMount() {
// 可以方便地得到state,做一些更深入的修改。
console.log(this.state);
}
render() {
return super.render();
}
}
}

组合多个高阶组件

1
2
3
@withHeader
@withLoading
class Demo extends Component{}

简化语法

1
2
3
const enhance = compose(withHeader,withLoading);
@enhance
class Demo extends Component{}
-------------要说再见啦感谢大佬的光临~-------------