Maison >interface Web >js tutoriel >Explication détaillée de l'application des composants d'ordre supérieur React et des décorateurs ES6 (avec code)

Explication détaillée de l'application des composants d'ordre supérieur React et des décorateurs ES6 (avec code)

不言
不言avant
2018-11-21 11:55:353485parcourir

Cet article vous apporte une explication détaillée de l'application des composants de haut niveau React et des décorateurs ES6 (avec code). Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. .

1 Modèle de décorateur

Donner la priorité à la composition des objets par rapport à l'héritage de classe. -- "Modèle de conception"

1. Qu'est-ce que le modèle de décorateur

Définition : Ajoutez dynamiquement des propriétés ou des comportements supplémentaires à un objet. Par rapport à l'utilisation de l'héritage, le modèle de décorateur est plus flexible.

2. Participant au modèle Décorateur

Composant : La classe parent commune du décorateur et du décoré est une interface ou classe abstraite utilisée pour définir des comportements de base
ConcreteComponent : définit l'objet spécifique, c'est-à-dire le décorateur
Decorator : Décorateur abstrait, hérite de Component et étend ConcreteComponent de la classe externe. Pour ConcreteComponent, il n'est pas nécessaire de connaître l'existence de Decorator. Decorator est une interface ou une classe abstraite
ConcreteDecorator : Décorateur en béton, utilisé pour étendre ConcreteComponent
Remarque : Decorator et les objets décorés ont le même super type, car le décorateur et le décoré doivent être du même type. L'héritage est utilisé ici pour obtenir une correspondance de type, plutôt que d'utiliser l'héritage pour obtenir un comportement.

L'utilisation de l'héritage pour concevoir des sous-classes ne peut être déterminée que de manière statique au moment de la compilation, et toutes les sous-classes hériteront du même comportement ; l'utilisation d'une combinaison pour étendre les objets peut être développée dynamiquement au moment de l'exécution. Le modèle du décorateur suit le principe ouvert-fermé : une classe doit être ouverte pour extension et fermée pour modification. En utilisant des décorateurs, nous pouvons implémenter de nouveaux décorateurs pour ajouter de nouveaux comportements sans modifier le code existant. Cependant, si nous nous appuyons uniquement sur l'héritage, nous devrons modifier le code existant chaque fois que de nouveaux comportements sont nécessaires.

  1. javascript Comment utiliser le modèle décorateur

javascript Les caractéristiques du langage dynamique rendent très simple l'utilisation du modèle décorateur Le contenu principal. de cet article présentera deux exemples pratiques d'utilisation du motif décorateur.

Deux composants d'ordre supérieur réagissent

Nous savons tous ce que sont les composants d'ordre supérieur qui sont en fait utilisés de la même manière, mais les paramètres sont transmis. in devient un composant de réaction et renvoie un nouveau composant

A higher-order component is a function that takes a component and returns a new component.

a la forme suivante :

const EnhancedComponent = higherOrderComponent(WrappedComponent);

Les composants d'ordre élevé sont une partie très importante des applications de réaction. réutilisation de la logique des composants . Il ne s'agit pas d'une fonction définie par l'API React, mais d'un design pattern dérivé des fonctionnalités de composition de React.
Si vous avez utilisé redux, alors vous devez avoir été exposé à des composants d'ordre élevé, car connect inreact-redux est un composant d'ordre élevé.

Commençons par le composant d'ordre élevé le plus simple

import React, { Component } from 'react';
import simpleHoc from './simple-hoc';

class Usual extends Component {
  render() {
    console.log(this.props, 'props');
    return (
      dc6dce4a544fdca2df29d5ac0ea9906b
        Usual
      16b28748ea4df4d9c2150843fecfba68
    )
  }
}
export default simpleHoc(Usual);
import React, { Component } from 'react';

const simpleHoc = WrappedComponent => {
  console.log('simpleHoc');
  return class extends Component {
    render() {
      return 52be7657ec74da90f3cdb693591b3ded
    }
  }
}

export default simpleHoc;

Le composant Usual est empaqueté par simpleHoc et a un journal... Ensuite, le simpleHoc est un composant d'ordre élevé via les réceptions. une classe de composants Usual et renvoie une classe de composants. En fait, on voit que dans cette fonction, on peut faire de nombreuses opérations. Et le composant return a également son propre cycle de vie et sa propre fonction. De plus, nous voyons que les accessoires peuvent également être transmis à WrappedComponent (composant enveloppé).

Il existe deux façons d'implémenter des composants d'ordre élevé
props proxy. Les composants d'ordre supérieur manipulent les accessoires via le composant React encapsulé.
Héritage inversé (inversion de l'héritage). Les composants d'ordre supérieur héritent du composant React encapsulé.

Property Proxy
Le formulaire le plus simple que nous avons écrit dans l'introduction est le formulaire Props Proxy. WrappedComponent est encapsulé via hoc, qui est Usual dans l'exemple. Les accessoires initialement transmis à Usual sont reçus de manière hoc, qui est un proxy d'accessoires. À partir de là, nous pouvons effectuer certaines opérations

1. La façon la plus intuitive d'utiliser les accessoires
est de recevoir des accessoires. Nous pouvons effectuer de nombreuses opérations personnalisées telles que la lecture, l'édition et la suppression. Y compris les événements personnalisés définis ponctuellement, ils peuvent être transmis via des accessoires.

import React, { Component } from 'react';
const propsProxyHoc = WrappedComponent => class extends Component {
handleClick() {
console.log('click');
}
render() {
return (<WrappedComponent
  {...this.props}
  handleClick={this.handleClick}
/>);
}
};
export default propsProxyHoc;

Ensuite, lorsque notre composant habituel sera rendu, console.log(this.props) obtiendra handleClick.

2.refs obtient l'instance du composant
Lorsque nous enveloppons Habituel Lorsque vous souhaitez obtenir son instance, vous pouvez utiliser reference (ref). Lorsque le composant Usual est monté, la fonction de rappel de ref sera exécutée pour obtenir l'instance du composant en conséquence.

import React, { Component } from &#39;react&#39;;
const refHoc = WrappedComponent => class extends Component {
componentDidMount() {
console.log(this.instanceComponent, &#39;instanceComponent&#39;);
}
render() {
return (<WrappedComponent
  {...this.props}
  ref={instanceComponent => this.instanceComponent = instanceComponent}
/>);
}
};
export default refHoc;

3. Extraire l'état
Ici, l'état n'est pas obtenu via ref, mais transmis au composant enveloppéComponent via { props, callback function }, et l'état est obtenu via la fonction de rappel. Ce qui est davantage utilisé ici, c'est lorsque React traite des formulaires. Habituellement, lorsque des processus de réaction se forment, ils utilisent généralement des composants contrôlés (documents), c'est-à-dire que les entrées sont contrôlées, et lorsque la valeur est modifiée, l'événement onChange est utilisé pour la synchroniser avec l'état. Bien entendu, ce type d'opération peut également être effectué via le composant Container. Les différences spécifiques seront comparées ultérieurement. Jetez un œil au code et vous saurez ce qui se passe :

import React, { Component } from 'React';
const MyContainer = (WrappedComponent) => class extends Component {
    constructor(props) { super(props); 
        this.state = {
              name: '', 4 
        };
        this.onNameChange = this.onNameChange.bind(this); 
    }
    onNameChange(event) { 
        this.setState({
            name: event.target.value, 
        })
    }
    render() {
        const newProps = {
            name: {
                value: this.state.name, 
                onChange: this.onNameChange,
            },
        } 
        return b380ec041c5bd892a7454b55ec3a227e; 
    }
}

Dans cet exemple, nous extrayons la méthode onChange de la prop name dans le composant d'entrée dans un composant d'ordre supérieur, donc efficacement abstraction de la même opération d’état.

Héritage inversé

const MyContainer = (WrappedComponent) => class extends WrappedComponent {
    render() {
        return super.render();
    } 
}

正如所见,高阶组件返回的组件继承于 WrappedComponent。因为被动地继承了 WrappedCom- ponent,所有的调用都会反向,这也是这种方法的由来。
这种方法与属性代理不太一样。它通过继承 WrappedComponent 来实现,方法可以通过 super 来顺序调用。因为依赖于继承的机制,HOC 的调用顺序和队列是一样的:

didmount→HOC didmount→(HOCs didmount)→will unmount→HOC will unmount→(HOCs will unmount)

在反向继承方法中,高阶组件可以使用 WrappedComponent 引用,这意味着它可以使用WrappedComponent 的 state、props 、生命周期和 render 方法。但它不能保证完整的子组件树被解析。

1.渲染劫持
渲染劫持指的就是高阶组件可以控制 WrappedComponent 的渲染过程,并渲染各种各样的结 果。我们可以在这个过程中在任何 React 元素输出的结果中读取、增加、修改、删除 props,或 读取或修改 React 元素树,或条件显示元素树,又或是用样式控制包裹元素树。
正如之前说到的,反向继承不能保证完整的子组件树被解析,这意味着将限制渲染劫持功能。 渲染劫持的经验法则是我们可以操控 WrappedComponent 的元素树,并输出正确的结果。但如果 元素树中包括了函数类型的 React 组件,就不能操作组件的子组件。
我们先来看条件渲染的示例:

const MyContainer = (WrappedComponent) => class extends WrappedComponent {
render() {
    if (this.props.loggedIn) {
        return super.render(); 
    } else {
        return null;
     }
   }
 }

第二个示例是我们可以对 render 的输出结果进行修改:

const MyContainer = (WrappedComponent) => class extends WrappedComponent {
  render() {
    const elementsTree = super.render();
    let newProps = {};
    if (elementsTree && elementsTree.type === 'input') { 
        newProps = {value: 'may the force be with you'};
    }
    const props = Object.assign({}, elementsTree.props, newProps);
    const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children); 
    return newElementsTree;
  } 
}

在这个例子中,WrappedComponent 的渲染结果中,顶层的 input 组件的 value 被改写为 may the force be with you。因此,我们可以做各种各样的事,甚至可以反转元素树,或是改变元素 树中的 props。这也是 Radium 库构造的方法。

2.控制state
高阶组件可以读取、修改或删除 WrappedComponent 实例中的 state,如果需要的话,也可以 增加 state。但这样做,可能会让 WrappedComponent 组件内部状态变得一团糟。大部分的高阶组 件都应该限制读取或增加 state,尤其是后者,可以通过重新命名 state,以防止混淆。
我们来看一个例子:

const MyContainer = (WrappedComponent) => class extends WrappedComponent {
 render() { 
    return (
        e388a4556c0f65e1904146cc1a846bee
        c1a436a314ed609750bd7c7d319db4daHOC Debugger Component2e9b454fa8428549ca2e64dfac4625cd
        e388a4556c0f65e1904146cc1a846beeProps94b3e26ee717c64999d7867364b1b4a3 
        e03b848252eb9375d56be284e690e873{JSON.stringify(this.props, null, 2)}bc5574f69a0cba105bc93bd3dc13c4ec 
        e388a4556c0f65e1904146cc1a846beeState94b3e26ee717c64999d7867364b1b4a3
        e03b848252eb9375d56be284e690e873{JSON.stringify(this.state, null, 2)}bc5574f69a0cba105bc93bd3dc13c4ec                 
        {super.render()}
        94b3e26ee717c64999d7867364b1b4a3 );
     } 
 }

在这个例子中,显示了 WrappedComponent 的 props 和 state,以方便我们在程序中去调试它们。

三 ES6 装饰器

高阶组件可以看做是装饰器模式(Decorator Pattern)在React的实现。即允许向一个现有的对象添加新的功能,同时又不改变其结构,属于包装模式(Wrapper Pattern)的一种
ES7中添加了一个decorator的属性,使用@符表示,可以更精简的书写。那上面的例子就可以改成:

import React, { Component } from 'react';
import simpleHoc from './simple-hoc';

@simpleHoc
export default class Usual extends Component {
  render() {
    return (
      e388a4556c0f65e1904146cc1a846bee
        Usual
      94b3e26ee717c64999d7867364b1b4a3
    )
  }
}

//simple-hoc
const simpleHoc = WrappedComponent => {
  console.log('simpleHoc');
  return class extends Component {
    render() {
      return 52be7657ec74da90f3cdb693591b3ded
    }
  }
}

和高阶组件是同样的效果。

类的装饰

@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

上面代码中,@testable 就是一个装饰器。它修改了 MyTestableClass这 个类的行为,为它加上了静态属性isTestable。testable 函数的参数 target 是 MyTestableClass 类本身。

如果觉得一个参数不够用,可以在装饰器外面再封装一层函数。

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

上面代码中,装饰器 testable 可以接受参数,这就等于可以修改装饰器的行为。

方法的装饰
装饰器不仅可以装饰类,还可以装饰类的属性。

class Person {
  @readonly
  name() { return `${this.first} ${this.last}` }
}

上面代码中,装饰器 readonly 用来装饰“类”的name方法。
装饰器函数 readonly 一共可以接受三个参数。

function readonly(target, name, descriptor){
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}

readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);

装饰器第一个参数是 类的原型对象,上例是 Person.prototype,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身);
第二个参数是 所要装饰的属性名
第三个参数是 该属性的描述对象
另外,上面代码说明,装饰器(readonly)会修改属性的 描述对象(descriptor),然后被修改的描述对象再用来定义属性。

四 更加抽象的装饰

ES5 中,mixin 为 object 提供功能“混合”能力,由于 JavaScript 的原型继承机制,通过 mixin 一个或多个对象到构造器的 prototype上,能够间接提供为“类”的实例混合功能的能力。

下面是例子:

function mixin(...objs){
    return objs.reduce((dest, src) => {
        for (var key in src) {
            dest[key] = src[key]
        }
        return dest;    
    });
}

function createWithPrototype(Cls){
    var P = function(){};
    P.prototype = Cls.prototype;
    return new P();
}

function Person(name, age, gender){
    this.name = name;
    this.age = age;
    this.gender = gender;
}

function Employee(name, age, gender, level, salary){
    Person.call(this, name, age, gender);
    this.level = level;
    this.salary = salary;
}

Employee.prototype = createWithPrototype(Person);

mixin(Employee.prototype, {
    getSalary: function(){
        return this.salary;
    }
});

function Serializable(Cls, serializer){
    mixin(Cls, serializer);
    this.toString = function(){
        return Cls.stringify(this);
    } 
}

mixin(Employee.prototype, new Serializable(Employee, {
        parse: function(str){
            var data = JSON.parse(str);
            return new Employee(
                data.name,
                data.age,
                data.gender,
                data.level,
                data.salary
            );
        },
        stringify: function(employee){
            return JSON.stringify({
                name: employee.name,
                age: employee.age,
                gender: employee.gender,
                level: employee.level,
                salary: employee.salary
            });
        }
    })
);

从一定程度上,mixin 弥补了 JavaScript 单一原型链的缺陷,可以实现类似于多重继承的效果。在上面的例子里,我们让 Employee “继承” Person,同时也“继承” Serializable。有趣的是我们通过 mixin Serializable 让 Employee 拥有了 stringify 和 parse 两个方法,同时我们改写了 Employee 实例的 toString 方法。

我们可以如下使用上面定义的类:

var employee = new Employee("jane",25,"f",1,1000);
var employee2 = Employee.parse(employee+""); //通过序列化反序列化复制对象

console.log(employee2, 
    employee2 instanceof Employee,    //true 
    employee2 instanceof Person,    //true
    employee == employee2);        //false

ES6 中的 mixin 式继承
在 ES6 中,我们可以采用全新的基于类继承的 “mixin” 模式设计更优雅的“语义化”接口,这是因为 ES6 中的 extends 可以继承动态构造的类,这一点和其他的静态声明类的编程语言不同,在说明它的好处之前,我们先看一下 ES6 中如何更好地实现上面 ES5 代码里的 Serializable:

用继承实现 Serializable

class Serializable{
  constructor(){
    if(typeof this.constructor.stringify !== "function"){
      throw new ReferenceError("Please define stringify method to the Class!");
    }
    if(typeof this.constructor.parse !== "function"){
      throw new ReferenceError("Please define parse method to the Class!");
    }
  }
  toString(){
    return this.constructor.stringify(this);
  }
}

class Person extends Serializable{
  constructor(name, age, gender){
    super();
    Object.assign(this, {name, age, gender});
  }
}

class Employee extends Person{
  constructor(name, age, gender, level, salary){
    super(name, age, gender);
    this.level = level;
    this.salary = salary;
  }
  static stringify(employee){
    let {name, age, gender, level, salary} = employee;
    return JSON.stringify({name, age, gender, level, salary});
  }
  static parse(str){
    let {name, age, gender, level, salary} = JSON.parse(str);
    return new Employee(name, age, gender, level, salary);
  }
}

let employee = new Employee("jane",25,"f",1,1000);
let employee2 = Employee.parse(employee+""); //通过序列化反序列化复制对象

console.log(employee2, 
  employee2 instanceof Employee,  //true 
  employee2 instanceof Person,  //true
  employee == employee2);   //false
上面的代码,我们用 ES6 的类继承实现了 Serializable,与 ES5 的实现相比,它非常简单,首先我们设计了一个 Serializable 类:

class Serializable{
  constructor(){
    if(typeof this.constructor.stringify !== "function"){
      throw new ReferenceError("Please define stringify method to the Class!");
    }
    if(typeof this.constructor.parse !== "function"){
      throw new ReferenceError("Please define parse method to the Class!");
    }
  }
  toString(){
    return this.constructor.stringify(this);
  }
}

它检查当前实例的类上是否有定义 stringify 和 parse 静态方法,如果有,使用静态方法重写 toString 方法,如果没有,则在实例化对象的时候抛出一个异常。

这么设计挺好的,但它也有不足之处,首先注意到我们将 stringify 和 parse 定义到 Employee 上,这没有什么问题,但是如果我们实例化 Person,它将报错:

let person = new Person("john", 22, "m");
//Uncaught ReferenceError: Please define stringify method to the Class!

这是因为我们没有在 Person 上定义 parse 和 stringify 方法。因为 Serializable 是一个基类,在只支持单继承的 ES6 中,如果我们不需要 Person 可序列化,只需要 Person 的子类 Employee 可序列化,靠这种继承链是做不到的。

另外,如何用 Serializable 让 JS 原生类的子类(比如 Set、Map)可序列化?

所以,我们需要考虑改变一下我们的设计模式:

用 mixin 实现 Serilizable

const Serializable = Sup => class extends Sup {
  constructor(...args){
    super(...args);
    if(typeof this.constructor.stringify !== "function"){
      throw new ReferenceError("Please define stringify method to the Class!");
    }
    if(typeof this.constructor.parse !== "function"){
      throw new ReferenceError("Please define parse method to the Class!");
    }
  }
  toString(){
    return this.constructor.stringify(this);
  }
}

class Person {
  constructor(name, age, gender){
    Object.assign(this, {name, age, gender});
  }
}

class Employee extends Serializable(Person){
  constructor(name, age, gender, level, salary){
    super(name, age, gender);
    this.level = level;
    this.salary = salary;
  }
  static stringify(employee){
    let {name, age, gender, level, salary} = employee;
    return JSON.stringify({name, age, gender, level, salary});
  }
  static parse(str){
    let {name, age, gender, level, salary} = JSON.parse(str);
    return new Employee(name, age, gender, level, salary);
  }
}

let employee = new Employee("jane",25,"f",1,1000);
let employee2 = Employee.parse(employee+""); //通过序列化反序列化复制对象

console.log(employee2, 
  employee2 instanceof Employee,  //true 
  employee2 instanceof Person,  //true
  employee == employee2);   //false

在上面的代码里,我们改变了 Serializable,让它成为一个动态返回类型的函数,然后我们通过 class Employ extends Serializable(Person) 来实现可序列化,在这里我们没有可序列化 Person 本身,而将 Serializable 在语义上变成一种修饰,即 Employee 是一种可序列化的 Person。于是,我们要 new Person 就不会报错了:

let person = new Person("john", 22, "m"); 
//Person {name: "john", age: 22, gender: "m"}

这么做了之后,我们还可以实现对原生类的继承,例如:

继承原生的 Set 类

const Serializable = Sup => class extends Sup {
  constructor(...args){
    super(...args);
    if(typeof this.constructor.stringify !== "function"){
      throw new ReferenceError("Please define stringify method to the Class!");
    }
    if(typeof this.constructor.parse !== "function"){
      throw new ReferenceError("Please define parse method to the Class!");
    }
  }
  toString(){
    return this.constructor.stringify(this);
  }
}

class MySet extends Serializable(Set){
  static stringify(s){
    return JSON.stringify([...s]);
  }
  static parse(data){
    return new MySet(JSON.parse(data));
  }
}

let s1 = new MySet([1,2,3,4]);
let s2 = MySet.parse(s1 + "");
console.log(s2,         //Set{1,2,3,4}
            s1 == s2);  //false

通过 MySet 继承 Serializable(Set),我们得到了一个可序列化的 Set 类!同样我们还可以实现可序列化的 Map:

class MyMap extends Serializable(Map){
    ...
    static stringify(map){
        ...
    }
    static parse(str){
        ...
    }
}

如果不用 mixin 模式而使用继承,我们就得分别定义不同的类来对应 Set 和 Map 的继承,而用了 mixin 模式,我们构造出了通用的 Serializable,它可以用来“修饰”任何对象。

我们还可以定义其他的“修饰符”,然后将它们组合使用,比如:

const Serializable = Sup => class extends Sup {
  constructor(...args){
    super(...args);
    if(typeof this.constructor.stringify !== "function"){
      throw new ReferenceError("Please define stringify method to the Class!");
    }
    if(typeof this.constructor.parse !== "function"){
      throw new ReferenceError("Please define parse method to the Class!");
    }
  }
  toString(){
    return this.constructor.stringify(this);
  }
}

const Immutable = Sup => class extends Sup {
  constructor(...args){
    super(...args);
    Object.freeze(this);
  }
}

class MyArray extends Immutable(Serializable(Array)){
  static stringify(arr){
    return JSON.stringify({Immutable:arr});
  }
  static parse(data){
    return new MyArray(...JSON.parse(data).Immutable);
  }
}

let arr1 = new MyArray(1,2,3,4);
let arr2 = MyArray.parse(arr1 + "");
console.log(arr1, arr2, 
    arr1+"",     //{"Immutable":[1,2,3,4]}
    arr1 == arr2);

arr1.push(5); //throw Error!

上面的例子里,我们通过 Immutable 修饰符定义了一个不可变数组,同时通过 Serializable 修饰符修改了它的序列化存储方式,而这一切,通过定义 class MyArray extends Immutable(Serializable(Array)) 来实现。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer