>  기사  >  웹 프론트엔드  >  React 상위 컴포넌트 및 ES6 데코레이터 적용에 대한 자세한 설명(코드 포함)

React 상위 컴포넌트 및 ES6 데코레이터 적용에 대한 자세한 설명(코드 포함)

不言
不言앞으로
2018-11-21 11:55:353432검색

이 글은 React 상위 컴포넌트와 ES6 데코레이터(코드 포함)에 대한 자세한 설명을 제공합니다. 필요한 친구들이 참고할 수 있기를 바랍니다. . 돕다.

1 데코레이터 패턴

클래스 상속보다 객체 구성을 우선시합니다. -- 《디자인 패턴》

1. 데코레이터 패턴이란 무엇입니까

Definition: 객체에 몇 가지 추가 속성이나 동작을 동적으로 추가합니다. 상속을 사용하는 것에 비해 데코레이터 패턴은 더 유연합니다.

2. 데코레이터 패턴 참여자

Component: 데코레이터와 데코레이팅된 클래스의 공통 상위 클래스는 인터페이스 또는 추상 클래스입니다. 기본 동작 정의
ConcreteComponent: 특정 개체, 즉 데코레이터를 정의합니다.
Decorator: 구성 요소에서 상속된 추상 데코레이터, Extend ConcreteComponent 외부 클래스에서. ConcreteComponent의 경우 Decorator의 존재를 알 필요가 없습니다. Decorator는 인터페이스 또는 추상 클래스입니다
ConcreteDecorator: Concrete Component를 확장하는 데 사용됩니다
참고: Decorator 및 데코레이팅된 객체는 동일한 슈퍼 유형을 갖습니다. 왜냐하면 데코레이터와 데코레이팅된 객체는 동일한 유형이어야 하기 때문입니다. 여기서는 상속을 사용하여 동작을 얻는 대신 상속을 사용하여 유형 일치를 달성합니다.

상속을 사용하여 하위 클래스를 설계하는 것은 컴파일 시 정적으로만 결정될 수 있으며 모든 하위 클래스는 동일한 동작을 상속하여 객체를 확장할 수 있으며 런타임 시 동적으로 확장할 수 있습니다. 데코레이터 패턴은 개방형-폐쇄형 원칙을 따릅니다. 즉, 클래스는 확장을 위해 열려 있어야 하고 수정을 위해 닫혀 있어야 합니다. 데코레이터를 사용하면 기존 코드를 수정하지 않고도 새 데코레이터를 구현하여 새로운 동작을 추가할 수 있습니다. 그러나 상속에만 의존한다면 새로운 동작이 필요할 때마다 기존 코드를 수정해야 합니다.

  1. javascript 데코레이터 패턴 사용법

javascript 동적 언어의 특성은 매우 데코레이터 패턴을 사용하는 것이 편리함 간단하고 기사의 주요 내용에서는 데코레이터 패턴을 사용하는 두 가지 실제 사례를 소개합니다.

two 반응 고차 컴포넌트

우리 모두는 고차 함수가 실제로 동일하게 사용된다는 것을 알고 있습니다. 입력 매개변수는 반응 구성요소가 되어 새로운 구성요소를 반환합니다.

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

Form:

const EnhancedComponent = higherOrderComponent(WrappedComponent);

고차 구성요소는 반응에서 매우 중요한 부분입니다. 애플리케이션 중 가장 큰 특징은 컴포넌트 로직을 재사용하는 것입니다. React API에서 정의한 함수가 아니라 React의 구성 기능에서 파생된 디자인 패턴입니다.
redux를 사용했다면 고차 구성요소에 노출되었을 것입니다. 왜냐하면 React-redux의 연결은 고차 구성요소이기 때문입니다.

가장 간단한 고차 컴포넌트부터 시작하겠습니다

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;

Usual 컴포넌트는 simpleHoc으로 패키징되고 로그가 있습니다... 그렇다면 simpleHoc은 고차 컴포넌트입니다. 평소 컴포넌트 클래스를 수신하고 컴포넌트 클래스를 반환합니다. 실제로 이 함수에서는 많은 작업을 수행할 수 있음을 알 수 있습니다. 그리고 반환 구성 요소에도 자체 수명 주기와 기능이 있습니다. 또한 props를 WrappedComponent(래핑된 구성 요소)에 전달할 수도 있음을 알 수 있습니다.

고차 컴포넌트를 구현하는 방법에는 두 가지가 있습니다.
속성 프록시(props 프록시). 고차 컴포넌트는 래핑된 React 컴포넌트를 통해 props를 조작합니다.
역상속(상속 반전). 고차 구성 요소는 래핑된 React 구성 요소에서 상속됩니다.

Property Proxy
소개에서 작성한 가장 간단한 형태는 Props Proxy 형태입니다. WrappedComponent는 예제에서 Usual인 hoc를 통해 래핑됩니다. 원래 Usual에 전달된 props는 props 프록시인 hoc로 수신됩니다. 여기에서 몇 가지 작업을 수행할 수 있습니다

🎜🎜#1. props 작동

가장 직관적인 것은 props를 수신하여 읽기, 편집, 삭제 등 다양한 사용자 정의 작업을 수행할 수 있다는 것입니다. hoc에 정의된 맞춤 이벤트를 포함하여 props를 통해 전달할 수 있습니다.

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;

그런 다음 Usual 구성 요소가 렌더링되면 console.log(this.props)가 handlerClick을 가져옵니다.

2.refs가 구성 요소 인스턴스를 가져옵니다

When we When Packaging Usual, 인스턴스를 얻으려면 어떻게 해야 하나요? .

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. Detach state

여기서 상태는 ref를 통해 얻어지는 것이 아니라, { props, callback function }을 통해 WrappedComponent 컴포넌트에 전달되고, 콜백 함수를 통해 상태를 얻습니다. . 여기서 더 많이 사용되는 것은 반응이 양식을 처리하는 경우입니다. 일반적으로 React 프로세스가 형성될 때 일반적으로 제어되는 구성 요소(문서)를 사용합니다. 즉, 입력이 제어되고 값이 변경되면 onChange 이벤트를 사용하여 상태에 동기화합니다. 물론 이러한 작업은 Container 컴포넌트를 통해서도 가능합니다. 구체적인 차이점은 나중에 비교하겠습니다. 코드를 살펴보면 무슨 일이 일어나고 있는지 알 수 있습니다.

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; 
    }
}
이 예에서는 입력 구성 요소에 있는 name prop의 onChange 메서드를 고차 구성 요소로 추출합니다. 동일한 상태 작업을 효과적으로 추상화합니다.

역상속

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

위 내용은 React 상위 컴포넌트 및 ES6 데코레이터 적용에 대한 자세한 설명(코드 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제