|
导读网页的本质就是超级文本标记语言,通过结合使用其他的Web技术(如:脚本语言、公共网关接口、组件等),可以创造出功能强大的网页。因而,超级文本标记语言是万维网(Web)编程的基础,也就是说万维网是建立... 网页的本质就是超级文本标记语言,通过结合使用其他的Web技术(如:脚本语言、公共网关接口、组件等),可以创造出功能强大的网页。因而,超级文本标记语言是万维网(Web)编程的基础,也就是说万维网是建立在超文本基础之上的。超级文本标记语言之所以称为超文本标记语言,是因为文本中包含了所谓“超级链接”点。 本篇文章给大家带来的内容是关于react高阶组件和ES6装饰器的应用详解(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。一 装饰者模式 优先使用对象组合而不是类继承。 --《设计模式》 1.什么是装饰者模式 定义:动态的给对象添加一些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。 2.装饰者模式参与者 Component:装饰者和被装饰者共同的父类,是一个接口或者抽象类,用来定义基本行为 利用继承设计子类,只能在编译时静态决定,并且所有子类都会继承相同的行为;利用组合的做法扩展对象,就可以在运行时动态的进行扩展。装饰者模式遵循开放-关闭原则:类应该对扩展开放,对修改关闭。利用装饰者,我们可以实现新的装饰者增加新的行为而不用修改现有代码,而如果单纯依赖继承,每当需要新行为时,还得修改现有的代码。
javascript 动态语言的特性使得使用装饰器模式十分的简单,文章主要内容会介绍两种使用装饰者模式的实际例子。 二 react高阶组件 我们都知道高阶函数是什么, 高阶组件其实是差不多的用法,只不过传入的参数变成了react组件,并返回一个新的组件. A higher-order component is a function that takes a component and returns a new component. 形如: const EnhancedComponent = higherOrderComponent(WrappedComponent); 高阶组件是react应用中很重要的一部分,最大的特点就是重用组件逻辑。它并不是由React API定义出来的功能,而是由React的组合特性衍生出来的一种设计模式。 先来一个最简单的高阶组件 import React, { Component } from 'react';
import simpleHoc from './simple-hoc';
class Usual extends Component {
render() {
console.log(this.props, 'props');
return (
<div>
Usual
</div>
)
}
}
export default simpleHoc(Usual);
import React, { Component } from 'react';
const simpleHoc = WrappedComponent => {
console.log('simpleHoc');
return class extends Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
export default simpleHoc;组件Usual通过simpleHoc的包装,打了一个log... 那么形如simpleHoc就是一个高阶组件了,通过接收一个组件class Usual,并返回一个组件class。 其实我们可以看到,在这个函数里,我们可以做很多操作。 而且return的组件同样有自己的生命周期,function,另外,我们看到也可以把props传给WrappedComponent(被包装的组件)。 实现高阶组件的方法有两种 属性代理 1.操作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组件render的时候, console.log(this.props) 会得到handleClick. 2.refs获取组件实例 import React, { Component } from 'react';
const refHoc = WrappedComponent => class extends Component {
componentDidMount() {
console.log(this.instanceComponent, 'instanceComponent');
}
render() {
return (<WrappedComponent
{...this.props}
ref={instanceComponent => this.instanceComponent = instanceComponent}
/>);
}
};
export default refHoc;3.抽离state 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 <WrappedComponent {...this.props} {...newProps} />;
}
}在这个例子中,我们把 input 组件中对 name prop 的 onChange 方法提取到高阶组件中,这样就有效地抽象了同样的 state 操作。 反向继承 const MyContainer = (WrappedComponent) => class extends WrappedComponent {
render() {
return super.render();
}
}正如所见,高阶组件返回的组件继承于 WrappedComponent。因为被动地继承了 WrappedCom- ponent,所有的调用都会反向,这也是这种方法的由来。 didmount→HOC didmount→(HOCs didmount)→will unmount→HOC will unmount→(HOCs will unmount) 在反向继承方法中,高阶组件可以使用 WrappedComponent 引用,这意味着它可以使用WrappedComponent 的 state、props 、生命周期和 render 方法。但它不能保证完整的子组件树被解析。 1.渲染劫持 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 const MyContainer = (WrappedComponent) => class extends WrappedComponent {
render() {
return (
<p>
<h2>HOC Debugger Component</h2>
<p>Props</p>
<pre>{JSON.stringify(this.props, null, 2)}</pre>
<p>State</p>
<pre>{JSON.stringify(this.state, null, 2)}</pre>
{super.render()}
</p> );
}
}在这个例子中,显示了 WrappedComponent 的 props 和 state,以方便我们在程序中去调试它们。 三 ES6 装饰器高阶组件可以看做是装饰器模式(Decorator Pattern)在React的实现。即允许向一个现有的对象添加新的功能,同时又不改变其结构,属于包装模式(Wrapper Pattern)的一种 import React, { Component } from 'react';
import simpleHoc from './simple-hoc';
@simpleHoc
export default class Usual extends Component {
render() {
return (
<p>
Usual
</p>
)
}
}
//simple-hoc
const simpleHoc = WrappedComponent => {
console.log('simpleHoc');
return class extends Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}和高阶组件是同样的效果。 类的装饰 @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方法。 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参数指的是类本身); 四 更加抽象的装饰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); //falseES6 中的 mixin 式继承 用继承实现 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中文网其它相关文章! 网站建设是一个广义的术语,涵盖了许多不同的技能和学科中所使用的生产和维护的网站。 |
温馨提示:喜欢本站的话,请收藏一下本站!