Heim  >  Artikel  >  Web-Frontend  >  Detaillierte Erläuterung der Anwendung von Reaktionskomponenten höherer Ordnung und ES6-Dekoratoren (mit Code)

Detaillierte Erläuterung der Anwendung von Reaktionskomponenten höherer Ordnung und ES6-Dekoratoren (mit Code)

不言
不言nach vorne
2018-11-21 11:55:353432Durchsuche

Dieser Artikel bietet Ihnen eine detaillierte Erklärung der Anwendung von React-High-Order-Komponenten und ES6-Dekoratoren (mit Code). Ich hoffe, dass er für Sie hilfreich ist. .

1 Dekoratormuster

Priorität der Objektkomposition gegenüber der Klassenvererbung. -- „Entwurfsmuster“

1. Was ist das Dekoratormuster

Definition: Fügen Sie einem Objekt dynamisch einige zusätzliche Eigenschaften oder Verhaltensweisen hinzu. Im Vergleich zur Verwendung der Vererbung ist das Dekoratormuster flexibler.

2. Teilnehmer des Dekoratormusters

Komponente: Die gemeinsame übergeordnete Klasse des Dekorators und des Dekorierten ist eine Schnittstelle oder abstrakte Klasse, die zum Definieren grundlegender Verhaltensweisen verwendet wird
ConcreteComponent: Definiert das spezifische Objekt, also den Dekorator
Decorator: Abstrakter Dekorator, erbt von Component und erweitert ConcreteComponent von der äußeren Klasse. Für ConcreteComponent ist es nicht erforderlich, die Existenz von Decorator zu kennen. Decorator ist eine Schnittstelle oder abstrakte Klasse Derselbe Supertyp, da der Dekorateur und der Dekorateur vom gleichen Typ sein müssen, wird hier die Vererbung verwendet, um eine Typübereinstimmung zu erreichen, und nicht die Vererbung, um das Verhalten zu erhalten.
Die Verwendung von Vererbung zum Entwerfen von Unterklassen kann nur zur Kompilierungszeit statisch bestimmt werden, und alle Unterklassen erben das gleiche Verhalten. Mithilfe von Kombinationen können Objekte zur Laufzeit dynamisch erweitert werden. Das Dekorationsmuster folgt dem Open-Closed-Prinzip: Eine Klasse sollte für Erweiterungen offen und für Änderungen geschlossen sein. Mithilfe von Dekoratoren können wir neue Dekoratoren implementieren, um neue Verhaltensweisen hinzuzufügen, ohne den vorhandenen Code zu ändern. Wenn wir uns jedoch ausschließlich auf die Vererbung verlassen, müssen wir den vorhandenen Code immer dann ändern, wenn neue Verhaltensweisen erforderlich sind.

Javascript So verwenden Sie das Dekoratormuster

  1. Javascript Die Eigenschaften der dynamischen Sprache machen die Verwendung des Dekoratormusters sehr einfach In diesem Artikel werden zwei praktische Beispiele für die Verwendung des Dekoratormusters vorgestellt.

Zwei reagierende Komponenten höherer Ordnung

Wir alle wissen, was Funktionen höherer Ordnung sind, die tatsächlich auf die gleiche Weise verwendet werden, aber die übergebenen Parameter wird zur Reaktionskomponente und gibt eine neue Komponente zurück.

A higher-order component is a function that takes a component and returns a new component.
sieht aus wie:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

Komponenten höherer Ordnung sind ein wichtiger Bestandteil von Reaktionsanwendungen und ihr größtes Merkmal ist die Wiederverwendung der Komponentenlogik. Es handelt sich nicht um eine von der React-API definierte Funktion, sondern um ein Entwurfsmuster, das aus den Kompositionsfunktionen von React abgeleitet ist.

Wenn Sie Redux verwendet haben, müssen Sie Komponenten höherer Ordnung ausgesetzt gewesen sein, da connect in React-Redux eine Komponente höherer Ordnung ist.

Beginnen wir mit der einfachsten Komponente höherer Ordnung

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;

Die Komponente Usual wird von simpleHoc gepackt und verfügt über ein Protokoll ... Dann ist simpleHoc eine Komponente höherer Ordnung, indem sie eine Komponentenklasse empfängt Üblich und gibt eine Komponentenklasse zurück. Tatsächlich können wir sehen, dass wir in dieser Funktion viele Operationen ausführen können. Und die Rückgabekomponente hat auch einen eigenen Lebenszyklus und eine eigene Funktion. Darüber hinaus sehen wir, dass Requisiten auch an WrappedComponent (verpackte Komponente) übergeben werden können.

Es gibt zwei Möglichkeiten, Komponenten höherer Ordnung

Props-Proxy zu implementieren. Komponenten höherer Ordnung manipulieren Requisiten über die umschlossene React-Komponente.

Umgekehrte Vererbung (Vererbungsumkehr). Komponenten höherer Ordnung erben von der umschlossenen React-Komponente.


Property Proxy

Die einfachste Form, die wir in der Einleitung geschrieben haben, ist die Form des Props Proxy. WrappedComponent wird durch hoc umschlossen, was im Beispiel Usual ist. Die ursprünglich an Usual übergebenen Requisiten werden in hoc empfangen, was ein Props-Proxy ist. Daraus können wir einige Vorgänge ausführen

1. Die intuitivste Art, Requisiten zu bedienen ist das Empfangen von Requisiten. Wir können alle benutzerdefinierten Vorgänge wie Lesen, Bearbeiten und Löschen ausführen. Einschließlich der in hoc definierten benutzerdefinierten Ereignisse können diese über Requisiten weitergegeben werden.

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;

Wenn dann unsere Usual-Komponente gerendert wird, erhält console.log(this.props) handleClick.


2.refs ruft die Komponenteninstanz ab

Wenn wir Usual umschließen, wenn Sie möchten Um ihre Instanz abzurufen, können Sie die Referenz (ref) verwenden. Wenn die übliche Komponente gemountet ist, wird die Rückruffunktion von ref ausgeführt, um die Instanz der Komponente in hoc abzurufen.

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. Status extrahieren

Hier wird der Status nicht über ref abgerufen, sondern über { props, callback function } an die WrappedComponent-Komponente übergeben, und der Status wird über die Callback-Funktion abgerufen. Was hier häufiger verwendet wird, ist die Verarbeitung von Formularen durch Reagieren. Normalerweise werden bei React-Prozessen im Allgemeinen kontrollierte Komponenten (Dokumente) verwendet, d. Natürlich kann dieser Vorgang auch über die Containerkomponente durchgeführt werden. Die spezifischen Unterschiede werden später verglichen. Schauen Sie sich einfach den Code an und Sie werden wissen, was los ist:

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; 
    }
}
In diesem Beispiel extrahieren wir die onChange-Methode der Namensstütze in der Eingabekomponente in eine Komponente höherer Ordnung und abstrahieren so effektiv die gleiche staatliche Operation.


Umgekehrte Vererbung

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)) 来实现。

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der Anwendung von Reaktionskomponenten höherer Ordnung und ES6-Dekoratoren (mit Code). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen