【一起进大厂】7天掌握react基础系列(2)-组件基础

213 阅读10分钟

学习目标

  • 学会使用函数组件 ★ ★ ★ ★ ★
  • 学会使用 class 语法 extends继承以及 class 创建组件-类组件 ★ ★ ★ ★ ★
  • 学会 绑定事件 ★ ★ ★ ★ ★
  • 学会处理表单事件 ★ ★ ★ ★ ★

学习目标.png

组件暂时先学这么多,一步步来

组件

简介: 组件是 react 最基本内容。组件可以扩展 HTML 元素,封装可重用的代码。组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:例如随便打开一个百度网页,都是由不同组件组合在一起。

组件简介.png

函数组件

简介: 由普通 JavaScript 函数或者箭头函数创建的组件。

JavaScript 函数组件

import React from 'react';
import ReactDOM from 'react-dom/client';

function Hi() {
	return <div>普通函数组件</div>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<Hi />);

箭头函数

import React from 'react';
import ReactDOM from 'react-dom/client';

const Hi = () => <div>箭头函数组件</div>;
ReactDOM.createRoot(document.getElementById('root')).render(<Hi />);

注意:1、函数名称以大写开头区分。 2、 必需有返回值,空值传null也行。

class类组件

简介: 使用 ES6 class 语法创建的组件。

这里默认大家都懂 ES6 class语法以及相关原理。忘记可以百度一下

import React from 'react';
import ReactDOM from 'react-dom/client';

class Hi extends React.Component {
  render() {
    return <div>Hi ES6 class 创建类组件</div>
  }
}
ReactDOM.createRoot(document.getElementById('root')).render(<Hi />);

// 或 
import React, { Component } from 'react'; //  { Component } 这里不同了
import ReactDOM from 'react-dom/client';
class Hi extends Component {  //  { Component } 这里不同了
  render() {
    return <div>Hi ES6 class 创建类组件</div>
  }
}
ReactDOM.createRoot(document.getElementById('root')).render(<Hi />);

通过上面例子可以看到,我们创建了一个叫 Hi 名字的组件,继承父类组件 React.Component ,那么它继承的父类中所有的成员(属性和方法)。自身也是可以添加方法和属性。

注意:1、类函数组件名称以大写开头区分。 2、 类函数组件继承 React.Component 父类。 3、记得写 render 方法,以及 return 返回值。 该值是一个 JSX 组件结构。

类组件 state

简介:state中文翻译过来就是状态意思。我们把整个组件看作一个状态机,顾名思义就是一台机器,通过用户对 UI 的操作, state 状态会随操作而变化,然后根据不同的 state 变化,来进行渲染组件 UI 的变化。

  • state 是类组件的内部状态,只能在当前组件使用
  • state 是一个对象,代表组件里面有很多不同的数据
  • state 用法
import React, { Component } from 'react';
import ReactDOM from 'react-dom/client';
// 普通写法
class Hi extends Component {
	constructor() {
		super();
		// 提供组件私有状态、数据
		this.state = {
                    name: '程序员米粉',
                    age: 18
		};
	}
	render() {
		return (
                    <div>
                        <div>姓名: {this.state.name}</div>
                        <div>年龄 {this.state.age}</div>
                    </div>
		);
	}
}
ReactDOM.createRoot(document.getElementById('root')).render(<Hi />);

这里需要注意2个地方:

1、在组件的构造方法 constructor 内,首先要调用 super(),这一步实际上是调用了 React.Component 这个class的constructor方法,用来完成React组件的初始化工作。

2、在constructor中,通过this.state定义了组件的状态。

学了 state 之后我们来做一个小功能。做一个计算器。给点一下就 +1。

import React, { Component } from 'react';
import ReactDOM from 'react-dom/client';
class PostItem extends Component {
	constructor() {
		super();
		// 提供组件状态、数据
		this.state = {
			number: 0
		};
	}
	// 点赞
	handleClick() {
    // 要想改变 state 私有数据必须用这个方法,setState,用法跟微信小程序开发 `setData`类似
		this.setState({
                    number: this.state.number + 1
		});
	}
	render() {
		return (
			<div>
                            <button onClick={this.handleClick.bind(this)}>+</button> {this.state.number}
			</div>
		);
	}
}
ReactDOM.createRoot(document.getElementById('root')).render(<PostItem />);

加法按钮.png

setSate 方法

简介: setState是唯一能改变或修改组件私有数据 state方法,修改或添加 state 数据更新 UI。

用法: this.setState({ 改变或者添加的数据 })

用 setState 的时候要注意3点:

1、不要直接修改 State
2State 的更新可能是异步的
3State 的更新会被合并

不要直接修改 State

// 错误写法, 视图不会更新
this.state.number = 1;
// 直接赋值给 state这种写法在react里面是不可行的。切记!
// 正确, 视图更新
this.setState({
  number: 1
});

State 的更新可能是异步的

class PostItem extends Component {
	constructor() {
		super();
		// 提供组件状态、数据
		this.state = {
                    number: 0
		};
	}
	// 加法
	handleClick = (e) => {
		this.setState({
                    number: this.state.number + 1
		});
		// 初始化number为0,按照一般思路,这里是 0 + 1 = 1结果,这里打印出来却是0,
		// 打印出来是0,说明了this.setState方法是异步的,并没有+1,log出来为0,因为log是同步的。还没等 setState 数据计算好,log就已经执行了。
		// 如何避免呢?
		console.log(this.state.number) 
	}
	render() {
		return (
			<div>
                            <button onClick={this.handleClick}>+</button> {this.state.number}
			</div>
		);
	}
}

// 如何避免 setState方法异步,导致取值有问题
// 把 handleClick 方法改成这样
handleClick = (e) => {
	this.setState({
		number: this.state.number + 1
	}, () => {
		// 这里就可以得到我们预想到的值,0 + 1 = 1;的结果了
		console.log(this.state.number)
	});
}

由于 setState 方法是异步的,改变值后不会立马更新,如果想立马更新的话。那就 请使用 componentDidUpdate 或者 setState 的回调函数(setState(updater, callback)),这两种方式都可以保证在应用更新后触发

componentDidUpdate 这个是组件生命周期的更新函数,后面会讲。 为什么不直接更新 this.state?这个也后面讲,嘻嘻。毕竟这个是先入门嘛。

State 的更新会被合并

有状态组件和无状态组件

什么叫有状态组件?

  • 由 ES6 class 创建的组件,通常称之为有状态组件,自身私有状态 state

什么叫无状态组件?

  • 由函数组件创建的组件,通常称之为无状态组件。

是不是每个组件内部都需要定义state呢?当然不是。state用来反映组件内部状态的变化,如果一个组件的内部状态是不变的,当然就用不到state,这样的组件称之为无状态组件。

所以 ES6 class 创建的组件假如单纯是渲染页面的,没有操作 state 也可称之为无状态组件。在一个复杂的项目中,通常都有无状态组件和有状态组件构成的。看实际需求,而使用什么样的组件。

事件处理

注册事件

在刚才计算器功能小例子用到了事件处理。那么 React 中处理事件和其它框架、或者原生 JavaScript 中的事件是是否有异同。

注意:在 React 中编写事件。

  • React 注册事件中使用驼峰命名法,例如原生 JavaScript onclick 事件 ,在 React 会被写成 onClickonchange 要写成 onChang ,这里要注意一下。

  • 处理事件的响应函数要以对象的形式赋值给事件属性,而不是DOM中的字符串形式。例如,在DOM中绑定一个点击事件这样写:

// 原生 js
<button onclick="handleClick()">按钮</button>
// React
<button onClick={handleClick}>按钮</button>

这里注意两点:1、事件名称用驼峰命名法。2、绑定事件写在花括号 {}里面

事件对象

React中的事件是合成事件,并不是原生的DOM事件。React根据W3C规范定义了一套兼容各个浏览器的事件对象。

合成事件又是啥?由于 React 是一个框架,它不单单兼容各个浏览器,还会跨平台,例如: IOS、安卓。为了兼容跨平台应用,所以他的事件是自己重写过的。一套代码,兼容各个端可以运行。至于 React 是怎样兼容的我们不用管,我们照 W3C 规范写就行了。DOM事件和React事件在使用上并无差别。如果在某些场景下必须使用 DOM 提供的原生事件,可以通过 React事件对象的 nativeEvent 属性获取。

示例:

<a id="btn" href="">按钮</a>
<script>
    const btn = document.getElementById("btn");
    btn.onclick = (e) => {
        e.preventDefault();
        console.log("原生js 事件对象", e)
    }
</script>

原生事件对象.png

class Eventdemo extends Component {
	handleClick(e) {
		e.preventDefault();
		console.log('事件对象', e);
	}
	render() {
		return (
			<div>
                            <a href="" onClick={this.handleClick.bind(this)}>
                                    按钮
                            </a>
			</div>
		);
	}
}
ReactDOM.createRoot(document.getElementById('root')).render(<Eventdemo />);

React原生事件对象.png

其实两个图就可以看出来, Reatc事件对象中 nativeEvent 熟悉,就是原生 js 的事件对象。 在事件中这些都不难,难的是时间中 this 指向问题,下面我们来说一下,事件处理中的 this。

this指向问题

看一下示例:

class PostItem extends Component {
	constructor() {
		super();
		// 提供组件状态、数据
		this.state = {
			number: 0
		};
	}
	handleClick(e) {
    console.log(this); // 打印不出指
	}
	render() {
            return (
                    <div>
                            <button onClick={this.handleClick}>按钮</button>
                    </div>
            );
	}
}

上面例子打印出来的 this 为 undefined。为啥呢? 原来因为 ES 6 class 并不会为方法自动绑定this到当前对象。所以我们要主动绑定 this。也就是说,这个方法一开始的时候没有绑定 this 指向哪个对象,在运行的时候 this 就不知道属于哪个对象,所以 undefined 了。

this指向解决方案

解决这个 this 的方法一共有3种方法:

方法1:箭头函数
方法2:bind修改this指向
方法3:类实例方法 

推荐使用方法3,详情请看下文

方法1:箭头函数

class PostItem extends Component {
	constructor() {
		super();
		// 提供组件状态、数据
		this.state = {
			number: 0
		};
	}
	handleClick(e) {
    console.log(this);  // 可以打印
	}
	render() {
		return (
			<div>
				<button onClick={() => this.handleClick()}>按钮</button>
			</div>
		);
	}
}

缺点:直接在render方法中为元素事件定义事件处理函数,每次render调用时,都会重新创建一个新的事件处理函数,带来额外的性能开销,组件所处层级越低,这种开销就越大,因为任何一个上层组件的变化都可能会触发这个组件的render方法。当然,在大多数情况下,这点性能损失是可以不必在意的。

方法2:bind修改this指向

class PostItem extends Component {
	constructor() {
		super();
		// 提供组件状态、数据
		this.state = {
			number: 0
		};
	}
	handleClick(e) {
    console.log(this); // 可以打印
	}
	render() {
		return (
			<div>
				<button onClick={this.handleClick.bind(this)}>按钮</button> 
			</div>
		);
	}
}

// 或者
class PostItem extends Component {
	constructor() {
		super();
		// 提供组件状态、数据
		this.state = {
			number: 0
		};
    // 这里绑定
    this.handleClick = this.handleClick.bind(this);
	}
	handleClick(e) {
    console.log(this); // 可以打印
	}
	render() {
		return (
			<div>
				<button onClick={this.handleClick}>按钮</button> 
			</div>
		);
	}
}

缺点: 并不会像 class 创建那样,每次 render 都创造出一个函数,造成性能消耗,但是项目大了起来,一直像这样绑定函数 this.handleClick = this.handleClick.bind(this);,挺繁琐的。

方法3:类实例方法

// class实例方法
class PostItem extends Component {
	constructor() {
		super();
		// 提供组件状态、数据
		this.state = {
			number: 0
		};
	}
	handleClick = (e) => {
    console.log(this); 
	}
	render() {
		return (
			<div>
				<button onClick={this.handleClick}>按钮</button> 
			</div>
		);
	}
}

优点: 这个语法还在试验阶段,但是脚手架有babel的转义,这样写没有任何问题。既不会性能损耗,也不会很繁琐。

表单

在 React 里面,表单分为两种:

1、受控组件
2、非受控组件 (原生JavaScript DOM 操作)

受控组件

受控组件?啥事受控组件,听着一脸懵逼。看个例子:


class MyComponent extends Component {
	constructor() {
		super();
		// 1、初始化 state 数据
		this.state = {
			value: 'Hi! 程序员米粉'
		};
	}
	onChange = (e) => {
		// 3、改变state数据
		this.setState({
			value: e.target.value
		});
	}
	render() {
		return (
			<div>
				// 2、绑定事件onChange, 以及value值
				<input type="text" value={this.state.value} onChange={this.onChange}/>
			</div>
		);
	}
}

受控组件.png 后面的 666 是我手动输入的。

从代码可以看到<input type="text" value={this.state.value} onChange={this.onChange}/>this.onChange 事件绑定到 onChange 方法上面,然后input触发 onChange 方法通过 this.setState来改变 value 值, 而 this.state.value 绑定到 input 框上 value

记得,onChange 里面要写上 this.setState({ value: e.target.value });,不然输入框只会显示初始值,输入没有变化。

受控组件特点:

1、value值受到了react控制的表单元素

2、React中将state中的数据与表单元素的value值绑定到了一起,由state的值来控制表单元素的值

3、React 中,可变状态 state改变,一定要使用 setState 方法进行修改

非受控组件

非受控组件特点:通过 React 里面的特殊属性ref,使用原生DOM的方式来获取表单元素的值

使用步骤

  • 1、调用React.createRef()方法
  • 2、将创建好的ref对象添加到文本框中
  • 3、通过ref对象获取文本框的值
// 非受控组件
class MyComponent extends Component {
	constructor() {
		super();
		// 创建一个ref 
		this.textRef = React.createRef();
	}
	onChange = (e) => {
		// 通过ref对象获取文本框的值
		console.log(this.textRef.current.value);
	}
	render() {
		return (
			<div>
				// 将创建好的ref对象添加到文本框中
				<input type="text" defaultValue={'Hello'} ref={this.textRef} onChange={this.onChange}/>
				// defaultValue 为默认值
			</div>
		);
	}
}

非受控组件看似简化了操作表单元素的过程,但这种方式破坏了React对组件状态管理的一致性,往往容易出现不容易排查的问题,因此非特殊情况下,不建议大家使用。

受控与不受控组件应用场景

受控与不受控组件对比.png

结语

希望看完这篇文章对你有帮助

文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你,欢迎点赞和关注,后续会输出更好的分享。

欢迎关注公众号:【程序员米粉】 公众号分享开发编程、职场晋升、大厂面试经验