React 中 this 详解

JavaScript 中的作用域 scope 和上下文 context 是这门语言的独到之处,每个函数有不同的变量上下文和作用域。这些概念是 JavaScript 中一些强大的设计模式的后盾。在 ES5规范里,可以遵循一个原则,即每个 function内的上下文this 指向该 function的调用方,比如 :
var Module = {
    name: 'Jafeney',
    first: function() {
        console.log(this); // this对象指向调用该方法的 Module对象
        var second = (function() {
            console.log(this); // 由于变量提升,this对象指向 Window对象
        })()
    },
    init: function() {
        this.first()
    }
};

Module.init()


在 ES6 中 箭头操作符 => 替代原先 ES5 里 function 作用来快速声明函数

箭头函数使用注意点
1> 函数体内的 this对象,就是定义时所在的对象,而不是使用时所在的对象
2> 不可以当作构造函数,也就是说,不可以使用 new命令,否则会抛出一个错误
3> 不可以使用 arguments对象,该对象在函数体内不存在。如果要用,可以用 Rest参数代替
4> 不可以使用 yield命令,因此箭头函数不能用作Generator函数

this 指向固定化 : ES5规范中,this对象的指向是可变的,但是在ES6的箭头函数中,它却是固定的
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 }); // 输出 id: 42
代码中,setTimeout 参数是一个箭头函数,这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到100毫秒后。如果是普通函数,执行时 this应该指向全局对象window,这时应该输出 21。但是,箭头函数导致 this总是指向函数定义生效时所在的对象 (本例是{id: 42}),所以输出的是42

箭头函数的原理 : this指向的固定化,并不是因为箭头函数内部有绑定 this的机制,实际原因是箭头函数根本没有自己的 this,导致内部的 this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数,所以,箭头函数转成ES5的代码如下
// ES6
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id); //  箭头函数里面根本没有自己的this,而是引用外层的this
  }, 100);
}

两道经典的面试题
1> 下面代码其实只有一个this,就是函数 foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。另外,由于箭头函数没有自己的this,所以也不能用call()、apply()、bind()这些方法去改变this的指向
function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // 输出 id: 1
var t2 = f().call({id: 3})(); // 输出 id: 1
var t3 = f()().call({id: 4}); // 输出 id: 1

2> 代码中,箭头函数没有自己的this,所以bind方法无效,内部的this指向外部的this,所以上面的代码最终输出 ['outer']
(function() {
  return [
    (() => this.x).bind({ x: 'inner' })()
  ];
}).call({ x: 'outer' }); // ['outer']


函数绑定 ::
箭头函数可以绑定this对象,大大减少了显式绑定this对象的写法 (call、apply、bind)。但是,箭头函数并不适用于所有场合,所以 ES7提出 “函数绑定” (function bind) 运算符,用来取代 call、apply、bind 调用。虽然该语法还是 ES7 的一个提案,但是 Babel转码器已经支持

函数绑定运算符是并排的两个双冒号 (::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境 (即this对象),绑定到右边的函数上面
foo::bar;
// 等同于
bar.bind(foo);

foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return obj::hasOwnProperty(key);
}

如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;

let log = ::console.log;
// 等同于
var log = console.log.bind(console);

由于双冒号运算符返回的还是原对象,因此可以采用链式写法
// 例一
import { map, takeWhile, forEach } from "iterlib";

getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));

// 例二
let { find, html } = jake;

document.querySelectorAll("div.myClass")
::find("p")
::html("hahaha");


React 中的各种 this
Component 方法内部的 this : 如下是一个 Table组件
import React, { Component } from 'react'
import Checkbox from '../../FormControls/Checkbox/'
import './style.less'

class Table extends Component {
    constructor(props) {
        super(props)
        this.state = {
            dataSource: props.dataSource || [],
            columns: props.columns || [],
            wrapClass: props.wrapClass || null,
            wrapStyle: props.wrapStyle || null,
            style: props.style || null,
            className: props.className || null,
        }
        this.renderRow = props.renderRow || null
    }

    onSelectAll() {
        for (let ref in this.refs) {
            if (ref!=='selectAll') {
                this.refs[ref].setState({checked:true})
            }
        }
    }

    offSelectAll() {
        for (let ref in this.refs) {
            if (ref!=='selectAll') {
                this.refs[ref].setState({checked:false})
            }
        }
    }

    _renderHead() {
        return this.state.columns.map((item,i) => {
            return [<th>{i===0?<Checkbox ref="selectAll" onConfirm={()=>this.onSelectAll()} onCancel={()=>this.offSelectAll()} />:''}{item.title}</th>]
        })
    }

    _renderBody() {
        let _renderRow = this.renderRow;
        return this.state.dataSource.map((item) => {
            return _renderRow && _renderRow(item)
        })
    }

    render() {
        let state = this.state;
        return (
            <div className={state.wrapClass} style={state.wrapStyle} >
                <table
                    border="0"
                    style={state.style}
                    className={"ry-table " + (state.className && state.className : "")}>
                    <thead>
                        <tr>{this._renderHead()}</tr>
                    </thead>
                    <tbody>
                        {this._renderBody()}
                    </tbody>
                </table>
            </div>
        )
    }
}

export default Table
Component是 React内的一个基类,用于继承和创建 React自定义组件。ES6规范下的面向对象实现起来非常精简,class关键字 可以快速创建一个类,而Component类 内的所有属性和方法均可以通过 this访问。换而言之,在 Component内的任意方法内,可以通过 this.xxx的方式调用该 Component的其它属性和方法。通过外部传递dataSource (数据源)、columns (表格的表头项)、renderRow (当行渲染的模板函数)来完成一个Table的构建,支持全选和取消全选的功能、允许外部传递className 和 style对象来修改样式
注 : 只要不采用 function定义函数,Component 所有方法内部的 this对象始终指向该类自身

container 调用 component 时传递的 this : 继续上面的例子,下面在一个做为Demo的container里调用之前 的Table
import Table from '../../components/Views/Table/'

接着编写 renderRow函数并传递给 Table组件
_renderRow(row) {
    // ------------ 注意:这里对callback函数的写法 -----------
    let onEdit = (x)=> {
        console.log(x+x)
    }, onDelete = (x)=> {
        console.log(x*x)
    }
    // ---------------------------------------------------
    return (
        <tr>
            <td><Checkbox ref={"item_" + row.key} />{row.key}</td>
            <td>{row.name}</td>
            <td>{row.age}</td>
            <td>{row.birthday}</td>
            <td>{row.job}</td>
            <td>{row.address}</td>
            <td>
                <Button type="primary" callback={()=>onEdit(row.key)} text="编辑" />
                <Button type="secondary" callback={()=>onDelete(row.key)} text="删除" />
            </td>
        </tr>
    )
}

//... 省略一大堆代码

render() {
    let dataSource = [{
        key: '1',
        name: '胡彦斌',
        age: 32,
        birthday: '2016-12-29',
        job: '前端工程师',
        address: '西湖区湖底公园1号'
        }, {
        key: '2',
        name: '胡彦祖',
        age: 42,
        birthday: '2016-12-29',
        job: '前端工程师',
        address: '西湖区湖底公园1号'
    }],columns = [{
        title: '编号',
        dataIndex: 'key',
        key: 'key',
        },{
        title: '姓名',
        dataIndex: 'name',
        key: 'name',
        }, {
        title: '年龄',
        dataIndex: 'age',
        key: 'age',
        }, {
        title: '生日',
        dataIndex: 'birthday',
        key: 'birthday',
        }, {
        title: '职务',
        dataIndex: 'job',
        key: 'job',
        },{
        title: '住址',
        dataIndex: 'address',
        key: 'address',
        }, {
        title: '操作',
        dataIndex: 'operate',
        key: 'operate',
    }];
    return (
        <div>
            <Table dataSource={dataSource} columns={columns} renderRow={this._renderRow}/>
        </div>
    );
}

有几处容易出错的地方 :
1> _renderRow 作为 component方法 来定义,然后在对应的 render函数 内通过 this来调用。很重要的一点,这里 this._renderRow作为的是函数名方式传递
2> _renderRow 内部 Button组件的 callback是按钮点击后触发的回调,也是一个函数,但是这个函数没有像上面一样放在 component的方法里定义,而是作为一个变量定义并通过匿名函数的方式传递给子组件 :
let onEdit = (x)=> {
    console.log(x+x);
}

// .....
callback={ () => onEdit(row.key) }

这样就避开了使用this时上下文变化的问题,如果沿用上面的写法很容易这样写
onEdit(x) {
   console.log(x+x)
}

// ...
callback={ () => this.onEdit(row.key); }
这样写this传递到子组件后会变成undefined,从而报错

父组件如要调用子组件的方法,有两种方式 :
第一种 : 通过匿名函数的方式
callback = { ()=>this.modalShow() }
第二种 使用 bind :
callback = { this.modalShow.bind(this) }
如果要绑定的函数需要传参数,可以这么写 : xxx.bind(this,arg1,arg2...)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`unstated-next` 是 `unstated` 的升级版,是一个基于 React Hooks 的状态管理库。其 `createContainer` 是其 API 之一,用于创建一个状态容器。 `createContainer` 的基本语法如下: ``` import { createContainer } from 'unstated-next'; const useContainer = createContainer(ContainerClass); ``` 其,`ContainerClass` 是一个类,用于定义状态容器的数据和方法。`useContainer` 是一个 React Hook,可以在组件使用该 Hook 获取状态容器的实例。状态容器实例包含了类定义的数据和方法。 下面是一个简单的示例: ``` import { createContainer } from 'unstated-next'; import { useState } from 'react'; class CounterContainer { state = { count: 0 } increment = () => { this.setState({ count: this.state.count + 1 }); } decrement = () => { this.setState({ count: this.state.count - 1 }); } } const useCounter = createContainer(CounterContainer); function Counter() { const { state, increment, decrement } = useCounter.useContainer(); return ( <div> <button onClick={decrement}>-</button> <span>{state.count}</span> <button onClick={increment}>+</button> </div> ); } ``` 以上代码,`CounterContainer` 类定义了一个包含 `count` 属性和 `increment`、`decrement` 方法的状态容器。`useCounter` Hook 获取到该状态容器的实例,通过 `state` 属性和 `increment`、`decrement` 方法实现了一个简单的计数器组件。 需要注意的是,`unstated-next` 的 `createContainer` 与 `Docker` API 的 `createContainer` 是两个完全不同的东西,它们没有任何关系,也不要混淆。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值