React学习之类组件的this指向问题

免责声明

我们幼儿园有适合自己看的注释拉满版文档,目标是我奶来都能看懂(不是)。

1. 前置知识

1.1 es6类的简单回顾

      class Person {
        // 构造器
        constructor(name, age) {
          console.log(this); // this 指向类的实例对象(new的作用)
          this.name = name;
          this.age = age;
        }
        // 一般方法,放在了 Person 的原型对象上,因此实例.say() 沿着原型链可以调用该方法
        say() {
          // 【通过实例】调用 say 时,say 中的 this 指向实例
          console.log(`我是${this.name},今年${this.age}`);
        }
      }

      const p1 = new Person('ouo', 18);
      p1.say(); // 【通过实例】调用 say

      // ======================================

      // 继承
      class Student extends Person {
        constructor(name, age, color) {
          // 构造器内使用 this 前
          super(name, age); // 调用父类的 constructor()
          this.color = color;
        }
        call() {
          console.log(this); // 实例调用 ,this 指向实例
        }
        // 子类可以重写从父类继承的方法
        say() {
          console.log(`我是${this.name},今年${this.age},喜欢${this.color}色`);
        }
      }

      const c = new Student('kk', 19, 'red');
      c.say();
      c.toString();

这里是自己 new 实例,实例调用方法;React是帮我们创建类实例对象,用实例对象调用render

2. 类组件的this指向问题

2.1 问题引入

// <script type="text/babel">
	// babel 会给整个 script 开启严格模式
	// ==== 1. 创建类组件 ====
	  class Weather extends React.Component {
	    // 构造函数的执行上下文的 this 绑定 类的实例对象,是 new 的作用
		// props 是 new Xxx() 传递的,而这个 new 的过程是 React 帮我们做的
		// React.Component 的构造器会有一个 值为 null 的 state
		constructor(props) {
		  // super 必须在使用 this 关键字之前调用,调用了父类的构造器
		  super(props);
		  console.log(this, '构造函数内的 this 指向类的实例对象');
		  // 初始化状态
		  this.state = {
			isHot: false,
			wind: '微风',
		  };
		}
		// render 一般会放在 Weather 的 原型对象上
		// render 会调用 1 + n 次,1是初始化的那次 n是状态更新的次数
		// render内的 this 指向类的实例对象,因为  ReactDOM.render(<Weather />, document.getElementById('test')); React会帮我们 new Weather,并且通过 实例.render调用
		render() {
		  console.log(this, 'render内的 this 指向类的实例对象');
		  const { isHot, wind } = this.state;
		  return (
            // 这里是 demo 函数声明提升 + 点击事件回调吧
			<h2 onClick={demo}>
			  今天天气很{isHot ? '炎热' : '凉爽'}
			</h2>
		  );
		}
	  }
	  // 写在 class 外的函数
	  function demo() {
	  // 在此处修改 isHot的值 this.state.isHot
	  // 首先,这个 demo 不是 DOM 事件绑定的回调!btn.addEventListener('xxx', function (e) {});才是 DOM 事件绑定,回调函数内的 this 才指向绑定的元素
	  // click 触发后,才执行 demo 函数,本质是 【window】 调用的,指向 window;又由于 babel 开启了严格模式,因此最终指向 undefined
		console.log(this,'开启严格模式,this 指向 undefined') // this.state.isHot error
	  }

	 // ==== 2. 渲染组件到页面 ====
	  ReactDOM.render(<Weather />, document.getElementById('test'));
// </script>

总结:

  1. 类的构造器的执行上下文的 this 绑定 类的实例对象,是 new 的作用
  2. render内的 this 指向类的实例对象,是因为 react 帮我们实例类组件对象后,通过 实例.render 调用
  3. btn.addEventListener('xxx', function (e) {});才是 DOM 元素事件绑定,回调函数内的 this 才指向绑定的事件源元素;
  4. onClick={demo}html 事件绑定,回调函数的 this 指向 window,但 babel 开启了严格模式,因此最终指向 undefined

2.2 解决this指向问题

在构造器内的 this 指向类的实例对象,可以配合 Function.prototype.bind(),返回一个调用原始函数并将其 this 关键字设置为给定的值的新函数。

// <script type="text/babel">
	  class Weather extends React.Component {
		constructor(props) {
		  super(props);
		  console.log(this, '构造函数的 this 指向实例对象');
		  this.state = {
			isHot: false,
			wind: '微风',
		  };
		  // =========== 解决方式 ===========
		  // this.demo 是沿着 类的实例对象 的原型链调用 demo 方法
		  // this.demo.bind(this) 返回一个【改变了 this 从 undefined 指向为 类的实例对象】后的 this.demo 函数的拷贝;疑问 bind是深拷贝吗还是创建的新函数,疑似需要去学一下bind,this.demo的 this 并没有改?
          // this在执行时才确定,所以 this.demo内的 this 并没有改
		  // this.changeWeather 是在【类的实例上】添加一个方法,赋值
		  // 至此,再用类的实例调用 changeWeather 方法时,就会就近调用新方法
		  this.changeWeather = this.demo.bind(this);
		}
		render() {
		  const { isHot, wind } = this.state;
		  return (
		    // 当用户点击按钮时 React 会调用你传递的事件处理函数,这里不必纠结 this.changeWeather 是一个函数而不是一个表达式。
			<h2 onClick={this.changeWeather}>
			  今天天气很{isHot ? '炎热' : '凉爽'}
			</h2>
		  );
		}
		// =========== 问题所在 ===========
		// 写在类中,会在类的原型对象上,如果通过 Weather 实例调用,则 this 指向实例对象
		// onClick={this.demo} 没有调用demo,而是 click 触发后,才执行实例对象原型链上的这个 demo 函数,本质是 【window】 调用的,指向 window;又由于类中的一般方法默认开启局部严格模式,因此最终指向 undefined
		// 所以 this.state 会报错,必须有 this.changeWeather = this.demo.bind(this) 先构造改变指向的新方法 + onClick={this.changeWeather} 来解决问题
		 demo() {
			console.log(this,'类中的一般方法默认开启严格模式,因此指向 undefined',
			'但onClick={this.changeWeather}调用的是实例对象的 changeWeather 方法');
			// this.changeWeather = this.demo.bind(this) 后,方法体内当成 this.changeWeather 写~
			const { isHot } = this.state;
			this.setState({ isHot: !isHot});
		  }
	  }
	
	  ReactDOM.render(<Weather />, document.getElementById('test'));
// </script>

总结:

  1. this.changeWeather = this.demo.bind(this);是在类的实例上添加一个改变了 this 从 undefined 指向为 类的实例对象后的 this.demo 函数的拷贝
  2. onClick={this.changeWeather}在点击后,才触发执行实例对象原型链上的这个 changeWeather 函数
  3. 区分 <h2 onClick={this.changeWeather}><h2 onClick={this.demo}> 打印的 this 不同,即可
  4. React 会调用你传递的事件处理函数,这里不必纠结 this.changeWeather 是一个函数而不是一个表达式

3. state的简写

为什么需要?当有大量事件回调时,需要在构造器内写大量重复代码解决this指向问题。

// <script type="text/babel">
	  class Weather extends React.Component {
		// React.Component 的构造器会有一个 值为 null 的 state
		// 不需要在 new 的时候传参的属性可以省略构造函数直接写赋值语句,这些属性依然会在实例对象身上
		state = {
		  isHot: false,
		  wind: '微风',
		};
		render() {
		  const { isHot, wind } = this.state;
		  return (
			<h1 onClick={this.changeWeather}>
			  今天天气很{isHot ? '炎热' : '凉爽'}
			</h1>
		  );
		}
		// 函数表达式 + 箭头函数
		// 表达式 是 类似 state = xxx 的写法,这样实例身上就有了 changeWeather 方法
		// 箭头函数的 this 是它声明所在的作用域的 this,即实例对象,因此就不需要在构造器中使用 bind 再改变 this 指向
		// why? 指向实例对象
		changeWeather = () => {
		  const isHot = this.state.isHot;
		  this.setState({ isHot: !isHot });
		};
	  }
	  ReactDOM.render(<Weather />, document.getElementById('test'));
// </script>

总结:

省略构造器 + 表达式 + 箭头函数

疑问: 箭头函数内的 this 指向它所在的作用域的 this binding,箭头函数再往上找是全局执行上下文,即使开了严格模式应该指向 undefined 呀,为什么会指向实例?
因为在 class 中写的函数表达式 + 箭头函数会作为实例的属性,因此箭头函数的上级是构造函数执行上下文, new 的作用指向了实例,因此箭头函数也指向实例。

posted @ 2023-09-21 10:48  雨宮莲  阅读(13)  评论(0编辑  收藏  举报