React 面向组件编程 之 类式组件、组件实例的三大核心属性
类式组件
import React, { Component } from "react";
export default class App extends Component {
render() {
return <h2>我是类式组件</h2>
}
}
创建类式组件,必须继承React.Component,render必须写,render必须要有返回值。
组件实例的三大核心属性 1:state
state是组件对象最重要的属性,值必须是对象。
组件被称为状态机,通过更新组件的state来更新对应页面的显式,重新渲染组件。
强烈注意:
- 组件中的render方法中的this,为组件的实例对象
- 组件中自定义的方法,this为undefined,如何解决?
- 第一种方法:通过函数bind强制绑定this
- 第二种方法:使用赋值语句+箭头函数(主流推荐)
- state不可以直接更新,必须通过React提供的API来进行更新,setState。
import React, { Component } from "react";
export default class App extends Component {
// 自定义方法
test() {
console.log(this); // undefined
}
render() {
return (
<button onClick={this.test}>test</button>
)
}
}
为什么自定义方法test()的this会为undefined?
答:首先,test方法是放在App的原型对象上的,供实例对象使用。
了解this指向的同学应该知道,在对象上的方法,其this的指向是,谁调用这个方法,就指向谁。
但由于,test是作为onClick的回调,所以并不是通过实例对象去调用的,而是直接调用,因此this的指向本应该是window。
然而类中的方法默认开启了局部的严格模式,所以test中的this为undefined。
解决方法:
第一种,通过函数bind强制绑定this
import React, { Component } from "react";
export default class App extends Component {
// 如果写了构造器,一定要接受props,和super(props)去传props,不然会出现undefined的bug
constructor(props) {
super(props);
this.test = this.test.bind(this);
}
// 自定义方法
test() {
console.log(this); // App {props: {…}, context: {…}, refs: {…}, updater: {…}, test: ƒ, …}
}
render() {
return (
<button onClick={this.test}>test</button>
)
}
}
第二种,使用赋值语句+箭头函数(主流推荐)
import React, { Component } from "react";
export default class App extends Component {
test = () => {
console.log(this); // App {props: {…}, context: {…}, refs: {…}, updater: {…}, test: ƒ, …}
}
render() {
return (
<button onClick={this.test}>test</button>
)
}
}
为什么将普通函数写成箭头函数就可以使得this指向实例对象呢?
答:箭头函数的this指向,是定义时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象,因此指向实例对象App
类式组件中state的使用:
import React, { Component } from "react";
export default class App extends Component {
// state初始化状态
state = {count: 1}
add = () => {
let { count } = this.state;
// 更新state的API
this.setState({ count: count + 1 });
}
render() {
return (
<>
<h2>{ this.state.count }</h2>
<button onClick={ this.add }>+1</button>
</>
)
}
}
组件实例的三大核心属性 2:props
props是只读的,无法修改。
import React, { Component } from "react";
class App extends Component {
render() {
const { name, sex, age } = this.props
return (
<ul>
<li>姓名:{ name }</li>
<li>性别:{ sex }</li>
<li>年龄:{ age }</li>
</ul>
)
}
}
// 将App组件标签渲染到index页面的div上
ReactDOM.createRoot(document.getElementById('root')).render(<App name="cxk" sex="女" age="22" />);
批量传递props
let obj = {
name: 'cxk',
sex: '女',
age: '22'
};
ReactDOM.createRoot(document.getElementById('root')).render(<App {...obj} />);
为什么这里的...obj不会报错?
答:众所周知,...展开运算符是无法展开对象的。
但在原生JS中{...obj}的写法,不会报错,因为它是复制对象的一种写法。
然而在React中,{...obj},最外层的{},只是单纯的表示里面要写js表达式了,因此这里真的只是...obj。
但既然对象无法展开,会报错,那为什么这里...obj不会报错呢?
因为React中babel会将...obj翻译,如果在原生JS中...obj必然会报错。
组件实例的三大核心属性 3:refs
refs是React中用来取得某个JSX组件或者某个DOM中的一些状态值的时候,用来获取节点的方法。
在React官方的解释中,它的适用范围如下:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
React文档中再三强调,请不要过度使用refs,所以当我们可以用dom原生对象解决时,尽量不要使用refs。
1、字符串形式的ref
这里的ref可以理解为id。
字符串形式的ref已经不被官方推荐使用了,之后很有可能会被删除。
因为string类型的ref存在效率问题,写多了程序会很慢。但是在之前版本的编码中还是有很多人使用,因为它太方便了。
import React, { Component } from "react";
export default class App extends Component {
showData = () => {
// 这是获取到一个真实DOM
console.log(this.refs.input1);
}
showData2 = () => {
alert(this.refs.input2.value)
}
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button ref="button1" onClick={this.showData}>点我提示左侧的数据</button>
<input ref="input2" type="text" placeholder="失去焦点提示数据" onBlur={this.showData2} />
</div>
)
}
}
2、 回调函数形式的ref
import React, { Component } from "react";
export default class App extends Component {
showData = () => {
// 这是获取到一个真实DOM
console.log(this.input1.value);
}
showData2 = () => {
alert(this.input2.value)
}
render() {
return (
<div>
<input ref={c => { this.input1 = c }} type="text" placeholder="点击按钮提示数据" />
<button ref={c => { this.button1 = c }} onClick={this.showData}>点我提示左侧的数据</button>
<input ref={c => {this.input2 = c }} type="text" placeholder="失去焦点提示数据" onBlur={this.showData2} />
</div>
)
}
}
回调ref中回调执行次数的问题
如果ref回调函数是以内联函数的方式定义的,那么在更新状态state的过程中,它会被执行两次。
第一次传入参数null,然后第二次会传入参数DOM元素,这是因为在每次渲染时会创建一个新的函数实例,所以react为了清空旧的ref并且设置新的。
通过将ref的回调函数定义成class的绑定函数的方式可以避免上述问题,但大多时候这个问题是无关紧要的。
开发中,都是写成内联的。
3、createRef的使用
React官方最推荐的创建ref的方法:
import React, { Component } from "react";
export default class App extends Component {
myRef = React.createRef();
myRef2 = React.createRef();
showData = () => {
// 这是获取到一个真实DOM
console.log(this.myRef.current.value);
}
showData2 = () => {
alert(this.myRef2.current.value)
}
render() {
return (
<div>
<input ref={ this.myRef } type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
<input ref={ this.myRef2 } type="text" placeholder="失去焦点提示数据" onBlur={this.showData2} />
</div>
)
}
}
4、React中的事件处理
请勿过度使用ref。触发事件的元素,和获取数据的元素,是同一个DOM元素的时候,可以不用写ref:
import React, { Component } from "react";
export default class App extends Component {
showData = (event) => {
alert(event.target.value);
}
render() {
return (
<div>
<input ype="text" placeholder="失去焦点提示数据" onBlur={this.showData} />
</div>
)
}
}