React中 `锁定`this的N种‘facade pattern’模式

栏目: IOS · Android · 发布时间: 5年前

内容简介:由于本文是主要介绍React中看下在es6之前我们是如何解决这样看起来好奇怪呀,但是没办法,我们早已经习惯,自从

由于本文是主要介绍React中 锁定this 的N种方法,不会过多的介绍 this 多面性的原因,相信大家应该都知道 词法作用域动态作用域 。并且也知道在 es6 之前我们依然有很多种方式,去 锁定this 的指向(call, apply, bind)。接下来我们也会结合这些方式,在 React 中来 锁定this ;不过在这之前,我们先看下之前我们采取的方式。对于本文标题 facade pattern (外观模式)指的是,这些 锁定this 的方式,只是看起来不一样,有些本质上是一样的,有些是 es5通过一些技巧实现的 ,有些是 es6原生支持的(箭头函数) ,这些看起来很不一样的方式,有些在 babel 编译以后本质是一样的(使用闭包锁住上下文, 通过高阶函数返回新的函数)。

千好万好 ES6 好(箭头函数 () => {}好)

看下在es6之前我们是如何解决 this

// 回调里面使用this
var Demo = {
    init() {
        this.initEvents();  
    }
    
    validateData() {
        return true;
    }
    
    initEvents() {
        var self = this;
        $('a.submit').click(function () {
            self.validateData();
        });
    }    
}
Demo.init();

// 借用其他对象的方法
var name = '影帝';
var Person = {
    name: '渣渣辉',
    sayName() {
        console.log(this.name);
    }
};
var Other = {
    name: '张家辉'
};
var sayName = Person.sayName;

sayName(); // '影帝'
sayName.call(Other); // 张家辉'
Other.sayName = sayName;
var otherSayName = Other.sayName;
otherSayName(); // '影帝'
Other.sayName.call(Person); '渣渣辉',
复制代码

这样看起来好奇怪呀,但是没办法,我们早已经习惯,自从 ES2015 称为标准以来,我们已经很少看到这种代码了。好了言归正题,我们开始看看 Reactthis 的问题。

React中this的问题

既然 js 中有这些问题,当然 react 也不能列外,使用 react 的我们都知道,为了保持组件的高复用,组件可以分为 容器组件UI组件容器组件 组件负责业务处理, UI组件 负责页面渲染,一般情况下 UI组件 都尽量要是 纯的 ,没有自己的状态,也不处理业务,但是有时候需要触发一些事件,这样就需要执行从父组件等传过来的函数,同时这些函数里面一般还会出现 this ,我们希望 this 的指向是上层组件的引用,而这个时候函数的执行者却不是上层组件,于是 this 开始变脸,变得我们不认识。但是我们要避免这种情况,就需要 锁定this锁定this 的方式有很多,我们一一分析,这其中各有优劣,也有 react 推荐的 最佳实践 。至于如何选择,看业务场景,以及团队编码风格,建议最好还是遵守 最佳实践 ;

React中‘锁定’this的N种方法

1. 函数包装模式

/**
 * 函数包装模式 function wrapper pattern
 */
class Component extends React.Component {
    doSomething() {
    
    childDoSomething() {}
    
    render() {
        return (
            <div onClick={() => this.doSomething();}>
                <ChildComponent doSomething={() => this.childDoSomething();} />
            </div>
        );
    }
}
复制代码

建议: 不推荐 也不禁止。

优缺点:

  • 缺点:没有明显的缺点,只是需要多包裹一层
  • 优点:简单,易于理解,对新手比较友好

实现原理:

这里是通过 es6 箭头函数来实现 this 的锁定;

当然对应的有 es5 版本,其实就是我们之前熟悉的那种方式,且看代码

/**
    * function wrapper pattern es5
    */
   class Component extends React.Component {
       doSomething() {
       
       childDoSomething() {}
       
       render() {
           const self = this;
           return (
               <div onClick={function () { self.doSomething();}>
                   <ChildComponent doSomething={function () { self.childDoSomething();} />
               </div>
           );
       }
   }
复制代码

建议: 不推荐, 最好不要这样写。

优缺点:

  • 缺点:需要对this的指向进行保存,导致代码没有箭头函数来的简洁, 优雅(其实也就是箭头函数的优点)
  • 优点:对熟悉es5老式写法的比较友好

实现原理:

使用变量先保持对 this 的引用,使用的时候是这个变量,也就是此函数外部的 this ;

2. 渲染绑定模式

/**
 * 渲染绑定模式 render bind pattern
 * 或者叫
 * 懒绑定模式 lazy bind pattern
 */
class Component extends React.Component {
    doSomething() {}
    
    childDoSomething() {}
    
    render() {
        return (
            <div onClick={this.doSomething.bind(this)}>
                <ChildComponent doSomething={this.childDoSomething.bind(this);} />
            </div>
        );
    }
}
复制代码

建议: 禁止采取这种模式

优缺点:

render
constructor

实现原理:

就是使用 bing锁定 ,关于 bind 的使用以及原理可以参考 mdn 或者网上其他文章或者教程,当然你也可以实现自己的 bind ;

3. 覆写绑定模式

/**
 * 覆写绑定模式rewrite bind pattern
 * 或者叫 
 * 预绑定模式 prepare bind pattern
 */
class Component extends React.Component {
    
    constructor() {
        this.doSomething = this.doSomething.bind(this);
        this.childDoSomething = this.childDoSomething.bind(this);
    }
    
    doSomething() {}
    
    childDoSomething() {}
    
    render() {
        return (
            <div onClick={this.doSomething}>
                <ChildComponent doSomething={this.childDoSomething} />
            </div>
        );
    }
}
复制代码

建议: 建议采用这种方式,也是 react最佳实践 推荐的写法。

优缺点:

this
react最佳实践

实现原理:

和渲染时绑定模式实现原理一样,只是在这种方式下是提前绑定好。

对比:

这种模式和上一种在 render 时绑定实现原理是一样的,这种方式只会绑定一次,性能是好于在 render 里面的绑定;对比下来在写法上面也有些区别,一个是在 constructor 提前绑定,一个是在准备要用的时候懒绑定。

4. 属性赋值模式

/**
 * 属性赋值模式 attribute assignment pattern
 */
class Component extends React.Component {
    
    doSomething = () => {}
    
    childDoSomething = () => {}
    
    render() {
        return (
            <div onClick={this.doSomething}>
                <ChildComponent doSomething={this.childDoSomething} />
            </div>
        );
    }
}
复制代码

建议: 可以采用, react最佳实践 也有推荐的这种写法。

优缺点:

bind

实现原理:

借用箭头函数在定义的时候就绑定好了 this

5. 高阶函数渲染绑定模式

/**
 * 高阶函数渲染绑定模式 higher-order function render bind pattern
 * 或者叫 高阶函数懒绑定模式 higher-order function lazy bind pattern
 */
class Component extends React.Component {
    
    doSomething(data) {
        return () => {
            // 使用this, data
        }
    }
    
    childDoSomething(data) {
        return () => {
            // 使用this, data
        }
    }
    
    render() {
        return (
            <div onClick={this.doSomething()}>
                <ChildComponent doSomething={this.childDoSomething()} />
            </div>
        );
    }
}
复制代码

建议: 可以采用,尝试函数式写法。

优缺点:

  • 缺点:不熟悉高阶函数(或者函数式),接受起来有难度,需要调用一次, 每次都产生新的函数。
  • 优点:优雅,高阶函数, 可以提前保存一些变量。

实现原理:

利用高阶函数返回箭头函数, 实现 this 的锁定。

当然这种方法有对应的 es5 版本

/**
 * higher-order function es5 pattern
 */
class Component extends React.Component {
    
    doSomething(data) {
        cosnt self = this;
        return function() {
            // self, data
        }
    }
    
    childDoSomething(data) {
        cosnt self = this;
        return function() {
            // self, data
        }
    }
    
    render() {
        return (
            <div onClick={this.doSomething()}>
                <ChildComponent doSomething={this.childDoSomething()} />
            </div>
        );
    }
}
复制代码

注意:

这种模式被我称为懒模式,和在 render 里面使用 bind 的方式很像,在准备要使用的时候才绑定,每次都产生一个新的函数,可能会带来性能问题。当然又这种懒模式,我们也有提前绑定模式。

6. 高阶函数覆写绑定模式

/**
 * 高阶函数覆写绑定模式 higher-order function rewrite bind pattern 
 * 或者叫 
 * 高阶函数预绑定模式 higher-order function prepare bind pattern
 */
class Component extends React.Component {
    
    constructor() {
        this.doSomething = this.doSomething();
        this.childDoSomething = this.childDoSomething();
    }
    
    doSomething(data) {
        return () => {
            // 使用this, data
        }
    }
    
    childDoSomething(data) {
        return () => {
            // 使用this, data
        }
    }
    
    render() {
        return (
            <div onClick={this.doSomething}>
                <ChildComponent doSomething={this.childDoSomething} />
            </div>
        );
    }
}
复制代码

建议: 可以采用,尝试函数式写法。

优缺点:

  • 缺点:不熟悉高阶函数(或者函数式),接受起来有难度,需要调用一次。
  • 优点:没有显式绑定,在某些场景下可以提前保存一些变量, 对比上一种模式性能较好。

实现原理:

和上一个 高阶函数渲染绑定模式 一样利用高阶函数返回箭头函数, 实现 this 的锁定。不同的是这个模式是在构造函数里面提前调用。绑定后函数只会产生一次。

当然这种方法有对应的 es5 版本, 和上个模式的 es5 版本很像,也是通过变量缓存 this , 不同就是在 constructor 里面调用一次函数,而不是在 render 里面。

我是分割线,到了最后一种方式了

7. 属性getter渲染绑定模式

/**
 * 属性getter渲染绑定模式 attribute getter render bind pattern
 * 或者叫 
 * 属性getter懒绑定模式 attribute getter lazy bind pattern
 */
class Component extends React.Component {
    
    get doSomething() {
        // 这里也可以使用this, 做一些属性的计算, 比如 this.xxx + this.yyyy
        return () => {
            // 使用this
        }
    }
    
    get childDoSomething() {
        // 这里也可以使用this, 做一些属性的计算, 比如 this.xxx + this.yyyy
        return () => {
            // 使用this
        }
    }
    
    render() {
        return (
            <div onClick={this.doSomething}>
                <ChildComponent doSomething={this.childDoSomething} />
            </div>
        );
    }
}
复制代码

建议: 可以采用,尝试新的写法。

优缺点:

  • 缺点:接受起来需要成本, 每次产生新的函数。
  • 优点:被标准所支持,没有显式绑定,没有显式调用,比较简洁优雅,可以提前做一些属性的聚合或者计算。

实现原理:

借用属性的 getter , 返回一个箭头函数绑定 this ;

说明:

这种模式和高阶函数很像,都是返回一个新的函数,这种模式在特定情况下很强大,简洁的同时,可以对当前对象的一些属性做一些计算(是不是很像 Vue 的计算属性), 这种模式下每次 getter 后返回的都是一个新的函数,可能会有性能问题,但是如果对其他属性进行了聚合计算,或者说是依赖其他属性的 最新值 ,就需要在 render 里面 getter ,以保证依赖的属性都是 干净的值 (最新的值);

当然大家知道里面返回的是箭头函数,肯定也有 es5 版本,其实和其他模式的 es5 版本都很像,在这里就不写了。既然这种模式下有可能产生性能问题,对比其他模式,我们可定也有 预绑定模式 。请往下看

8. 属性getter赋值绑定模式

/**
 * 属性getter赋值绑定模式attribute getter assignment bind pattern
 * 或者叫 
 * 属性getter预绑定模式 attribute getter prepare bind pattern
 */
class Component extends React.Component {
    constructor() {
        this.doSomethingBind = this.doSomething;
        this.childDoSomethingBind = this.childDoSomething
    }
    
    get doSomething() {
        // 这里也可以使用this, 做一些属性的计算, 比如 this.xxx + this.yyyy
        return () => {
            // 使用this
        }
    }
    
    get childDoSomething() {
        // 这里也可以使用this, 做一些属性的计算, 比如 this.xxx + this.yyyy
        return () => {
            // 使用this
        }
    }
    
    render() {
        return (
            <div onClick={this.doSomethingBind}>
                <ChildComponent doSomething={this.childDoSomethingBind} />
            </div>
        );
    }
}
复制代码

建议: 可以采用,尝试新的写法。

优缺点:

  • 缺点:接受起来需要成本,赋值的函数需要另外一个名字 。
  • 优点:被标准所支持,没有显式绑定,只产生一次函数,比较简洁优雅,可以提前做一些属性的聚合或者计算。

实现原理:

借用属性的 getter , 返回一个箭头函数绑定 this ;赋值给对象的另外一个属性,调用的是另外一个方法。

说明:

这种模式和上一种模式区别在于,提前绑定,只会产生一次函数。但是要注意不是重写函数,而是赋值给另外一个不同的方法名,可能大家觉得这种换名字不够好,但是换个角度考虑一下,系对象多了一个方法,同时又持有之前的 getter ,这样可以更加的灵活,可以选择性的使用这两种函数。

同其他一些返回箭头的模式一样,这种模式依然有 es5 版本,写法同上,不在赘述。

总结

上面列举的这些模式,不一定是全部写法,不过足以应对工作中的大多数场景,同时有些模式还可以让我们去接触另外的实现方式。列举了这么多种,每一种都有优劣,工作中可以选择性的去使用,看场景和团队风格。

展望

既然 JavaScriptthis 的问题一直困扰着我们,那么有没有一种方式可以不使用 this ,就可以实现我们想要的所有功能,答案是肯定的。 React 16.7.0-alpha 版本加入了特别神奇的 hooks (好像Vue 3.0里面也已经加入了相似的特性),可以让我们彻底摆脱 this 的困扰(当然 this 依然是 js 里面一个神奇的存在),同时让我们的代码更加 函数式 ,更大程度的复用 处理逻辑 ,当然这个特性还在等待成为事实标准,我们希望这一天很快到来,不过我们仍然可以现在就是使用它。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

设计模式之禅

设计模式之禅

秦小波 / 机械工业出版社 / 2010年3月 / 69.00元

如果说“四人帮”的《设计模式》是设计模式领域的“圣经”,那么之后出版的各种关于设计模式的书都可称之为“圣经”的“注释版”或“圣经的故事”。本书是得道者对“圣经”的“禅悟”,它既不像“圣经”那样因为惜字如金、字字珠玑而深奥、晦涩和难懂,又比“圣经”的“注释版”更深刻和全面、更通俗和生动、更接近开发者遇到的实践场景,更具指导性。本书兼收并蓄、博采众长,也许是设计模式领域里的下一个里程碑之作。 全......一起来看看 《设计模式之禅》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具