学习目标
- 学会使用函数组件 ★ ★ ★ ★ ★
- 学会使用 class 语法 extends继承以及 class 创建组件-类组件 ★ ★ ★ ★ ★
- 学会 绑定事件 ★ ★ ★ ★ ★
- 学会处理表单事件 ★ ★ ★ ★ ★
组件暂时先学这么多,一步步来
组件
简介: 组件是 react 最基本内容。组件可以扩展 HTML 元素,封装可重用的代码。组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:例如随便打开一个百度网页,都是由不同组件组合在一起。
函数组件
简介: 由普通 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 />);
setSate 方法
简介: setState是唯一能改变或修改组件私有数据 state方法,修改或添加 state 数据更新 UI。
用法: this.setState({ 改变或者添加的数据 })
用 setState 的时候要注意3点:
1、不要直接修改 State
2、State 的更新可能是异步的
3、State 的更新会被合并
不要直接修改 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 会被写成onClick,onchange要写成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>
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 />);
其实两个图就可以看出来, 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>
);
}
}
后面的 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对组件状态管理的一致性,往往容易出现不容易排查的问题,因此非特殊情况下,不建议大家使用。
受控与不受控组件应用场景
结语
希望看完这篇文章对你有帮助
文中如有错误,欢迎在评论区指正,如果这篇文章帮助到了你,欢迎点赞和关注,后续会输出更好的分享。
欢迎关注公众号:【程序员米粉】 公众号分享开发编程、职场晋升、大厂面试经验