传送门:
React教程(一):React基础
一.组件概念
react官方解释:
React 允许你将标记、CSS 和 JavaScript 组合成自定义“组件”,即应用程序中可复用的 UI 元素。
个人理解:
组件是对一些UI和数据的封装,便于复用和维护。
可以把页面中的所有组成部分都当成是组件,即可以把页面中一切元素都当成是组件(比如一个简单的button按钮)。组件可大可小,大的组件代码较多能够完成丰富的功能实现和展示,小的组件也许就是一个简单的标签而已。不同的组件有不同的功能,组件可以嵌套可以组合等等
理解了这些概念之后,就会发现其实所有的页面都可以由多个组件构成,比如导航组件、商品展示组件、购物车组件等等。
放一张图更好的理解组件:
二.函数组件
React组件分为函数组件
和类组件
,我们先来了解一下函数组件。
1.概念
使用JS的函数(或者箭头函数)创建的组件,叫做
函数组件
2.定义与渲染
// 引入react核心包
import React from "react";
其他的时间
// 普通函数形式定义函数组件
function MyComponent() {
return <div>我是一个函数组件</div>;
}
// 箭头函数形式定义函数组件
const MyComponent2 = () => {
return <div>我是一个函数组件</div>;
};
function App() {
return (
<div>
{
/* 使用时可以双标签闭合 */}
<MyComponent></MyComponent>
{
/* 也可以单标签闭合 */}
<MyComponent />
<MyComponent2 />
</div>
);
}
// 导出组件
export default App;
3. 约定说明
- 组件的名称必须首字母大写,react内部会根据这个判断是组件还是HTML标签。
- 函数组件必须要有返回值,表示该组件的UI结构,如果不需要渲染任何内容则返回null。
- 组件就像HTMl标签一样可以被渲染到HTMl页面中,组件表示的是一段结构内容,对于函数组件来说,渲染的内容是函数的返回值对应的内容。
三. 类组件
1. 概念
使用ES6的class创建的组件,叫做类组件。
2. 定义与渲染
// 引入react核心包
import React from "react";
// 定义类组件
// 表示继承React组件类(父类)
class Hello extends React.Component {
// render函数用于渲染ui
render() {
return <div>我是类组件</div>; /* 返回值 */
}
}
function App() {
return (
<div>
<Hello />
</div>
);
}
// 导出组件
export default App;
3.约定说明
- 类名也必须以大写字母开头
- 类组件应该是继承React.Component父类,从而使用父类中提供的方法或属性。
- 类组件必须提供render方法,render方法必须有返回值,表述组件的UI结构。
四. 组件事件绑定
1. 如何绑定事件
- 语法
on + 事件名 = (事件处理函数),例如绑定一个点击事件:
<div onClick={
()=>{
}}>点我</div>
- 注意点
react 事件采用驼峰命名法,比如onClick、onMouseEnter、onFocus - 样例
// 引入react核心包
import React from "react";
// 函数组件
const HelloWorld = () => {
// 定义事件回调函数
const dealClick = () => {
alert("函数组件,你好");
};
return <div onClick={
dealClick}>点我弹出你好</div>;
};
// 类组件
class Hello extends React.Component {
// 定义事件回调函数(标准写法,避免this组件问题),回调函数中的this会指向当前的实例对象
dealClick = () => {
alert("类组件,你好");
};
render() {
// this会指向当前的实例对象(表示调用实例对象中的方法)
return <div onClick={
this.dealClick}>点我弹出你好</div>; /* 返回值 */
}
}
function App() {
return (
<div>
<HelloWorld />
<Hello />
</div>
);
}
export default App;
- 注意点:
在类组件中,请注意定义事件函数的标准写法,避免this指向问题:
dealClick = () => {
alert("类组件,你好");
};
2. 获取事件对象
- 通过事件处理程序的参数获取事件对象
// 引入react核心包
import React from "react";
// 函数组件
const HelloWorld = () => {
// 定义事件回调函数
const dealClick = (e) => {
// 阻止默认事件(此时a标签被点击则不会触发跳转事件)
e.preventDefault();
console.log("e事件对象", e);
};
return (
<a onClick={
dealClick} href="www.baidu.com">
点我打印事件对象
</a>
);
};
function App() {
return (
<div>
<HelloWorld />
</div>
);
}
export default App;
可见事件对象e被打印到了控制台。
- 事件绑定传递自定义参数
利用箭头函数的形式去传递额外参数
const HelloWorld = () => {
// 定义事件回调函数
const dealClick = (msg) => {
console.log("msg", msg);
};
return (
<div
onClick={
() => {
dealClick("icy is godlike");
}}
>
点我打印msg
</div>
);
};
function App() {
return (
<div>
<HelloWorld />
</div>
);
}
export default App;
执行的结果:
- 同时传递额外参数和获取事件对象e
需要将事件对象e作为形参传递。
const HelloWorld = () => {
// 定义事件回调函数
const dealClick = (e, msg) => {
// 此处接收时间对象e和额外参数msg
console.log("e事件对象", e);
console.log("msg", msg);
};
return (
<div
onClick={
(e) => {
// 需要在此处将事件对象e作为形参传入
dealClick(e, "icy is godlike");
}}
>
点我打印msg和事件对象e
</div>
);
};
打印的结果:
写法小结:
- 不需要传递参数 onClick={函数名} ,在该函数中可以直接获取事件对象e
- 需要传递参数 onClick = { ( )=>{ 函数名(参数) } } // 利用箭头函数
- 需要事件对象e又需要传递参数 ,把e作为参数传入
onClick = { ( ) => { 函数名(e, 其他参数) } }
五. 组件状态
背景:
在react hook 出现之前,函数组件没有自己的状态,所以我们先通过类组件来讲解组件状态。
流程: 初始化状态 -> 读取状态-> 修改状态 -> 影响视图
1.初始化状态
- 通过类的实例属性state来初始化状态
- state的值是一个对象结构,表示一个组件可以有多个数据状态
class HelloWorld extends React.Component {
state = {
count: 81,
};
render() {
return <div></div>;
}
}
2.读取状态
通过this.state 来读取状态
class HelloWorld extends React.Component {
state = {
count: 81,
name: "icy",
};
render() {
return (
<div>
{
this.state.name}的体重:{
this.state.count}
<div>
<button>点我减少体重</button>
</div>
</div>
);
}
}
渲染出来的效果:
3.修改状态
通过this.setState() 方法来修改
注意: 不可以直接赋值修改
class HelloWorld extends React.Component {
// 初始化状态
state = {
weight: 81,
name: "icy",
};
// 定义回调函数修改状态
loseWeight = () => {
this.setState({
// 将state中的值解构出来
...this.state,
// 修改count的值为原来的值减1
weight: this.state.weight - 1,
});
// 打印一下结果
console.log("icy的体重:", this.state.weight);
};
render() {
return (
<div>
{
this.state.name}的体重:{
this.state.weight}
<div>
{
/* 这里别忘记了绑定事件 */}
<button onClick={
this.loseWeight}>点我减少体重</button>
</div>
</div>
);
}
}
执行的结果:
注意事项:
- 修改基本数据类型
this.setState({ name:'新的值'})
- 修改复杂数据类型(对象,数组),可以通过扩展运算符解构(浅拷贝)
this.setState(list:[...this.state.lsit,'新的一项']
3.注意如果state中状态过多,也可以通过解构赋值来修改
this.setState({ // 将state中的值解构出来 ...this.state, // 修改count的值为原来的值减1 weight: this.state.weight - 1, });
六. this指向问题说明
必须谨慎对待JSX回调函数中的this,在javascript中,class方法默认不会绑定this,如果忘记绑定 this.handleClick,并把它传入onClick,则当你调用这个函数的时候,this的值将会为undefined。
代码说明:
1.错误的使用方法
class HelloWorld extends React.Component {
// 初始化状态
state = {
weight: 81,
name: "icy",
};
// 定义回调函数修改状态
loseWeight() {
this.setState({
// 将state中的值解构出来
...this.state,
// 修改count的值为原来的值减1
weight: this.state.weight - 1,
});
// 打印一下结果
console.log("icy的体重:", this.state.weight);
}
render() {
return (
<div>
{
this.state.name}的体重:{
this.state.weight}
<div>
<button onClick={
this.loseWeight}>点我减少体重</button>
</div>
</div>
);
}
}
此时会报错,因为this为undefined。
2. 正确的写法(1)
在类的构造函数constructor中手动改变回调函数的this指向
class HelloWorld extends React.Component {
// 初始化状态
state = {
weight: 81,
name: "icy",
};
constructor() {
// super()固定写法用来继承父类的方法或属性
super();
// 使用bind函数改变loseWeight的this指向为当前类的实例对象
// 即在组件初始化时就修正了该方法的this指向
this.loseWeight = this.loseWeight.bind(this);
}
// 定义回调函数修改状态
loseWeight() {
this.setState({
// 将state中的值解构出来
...this.state,
// 修改count的值为原来的值减1
weight: this.state.weight - 1,
});
// 打印一下结果
console.log("icy的体重:", this.state.weight);
}
render() {
return (
<div>
{
this.state.name}的体重:{
this.state.weight}
<div>
<button onClick={
this.loseWeight}>点我减少体重</button>
</div>
</div>
);
}
}
export default App;
3.正确的写法(2)
给onClick绑定事件时,使用箭头函数的写法
class HelloWorld extends React.Component {
// 初始化状态
state = {
weight: 81,
name: "icy",
};
// 定义回调函数修改状态
loseWeight() {
this.setState({
// 将state中的值解构出来
...this.state,
// 修改count的值为原来的值减1
weight: this.state.weight - 1,
});
// 打印一下结果
console.log("icy的体重:", this.state.weight);
}
render() {
return (
<div>
{
this.state.name}的体重:{
this.state.weight}
<div>
<button
/* 此处使用箭头函数写法,箭头函数没有自己的this,会向作用域链上级查找 */
onClick={
() => {
this.loseWeight();
}}
>
点我减少体重
</button>
</div>
</div>
);
}
}
4.正确的写法(3)最推荐的写法
在定义回调函数时,直接使用箭头函数的写法。
class HelloWorld extends React.Component {
// 初始化状态
state = {
weight: 81,
name: "icy",
};
// 此处直接写成箭头函数的形式,从而引用作用域链上级的this
loseWeight = () => {
this.setState({
// 将state中的值解构出来
...this.state,
// 修改count的值为原来的值减1
weight: this.state.weight - 1,
});
// 打印一下结果
console.log("icy的体重:", this.state.weight);
};
render() {
return (
<div>
{
this.state.name}的体重:{
this.state.weight}
<div>
{
/* 此处保持原样 */}
<button onClick={
this.loseWeight}>点我减少体重</button>
</div>
</div>
);
}
}
七.React的状态不可变说明
**概念:**不要直接修改状态的值,而是基于当前状态创建新的状态值。
1.错误的做法:直接修改state的值
// 初始化状态
state = {
weight: 81,
list: [1, 2, 3],
person: {
name: "icy",
age: 23,
},
};
updateState = () => {
// 直接修改简单类型Number(错误,千万别这么干,达咩)
this.state.weight++;
++this.state.weight;
this.state.weight += 1;
this.state.weight = 70;
// 直接修改数组 千万别这么干,达咩!!
this.state.list.push(2222);
this.state.list.slice(1, 2);
// 直接修改对象 千万别这么干,达咩达咩哟!!
this.state.person.name = "icy大魔王";
};
vscode也会给出warning,让你别这么干。
2.正确的做法:基于当前状态创建新的值
this.setState({
count: this.state.count + 1,
list: [...this.state.list, 4],
person: {
...this.state.person,
// 解构然后覆盖原来的name,实际工作中中常用写法
name: "icy大魔王",
},
});
八.处理表单
使用React处理表单元素,一般有两种方式:
1.受控组件(推荐使用)
2.非受控组件
1.受控表单组件
什么是受控组件?
受控组件是可以被react的状态控制的组件
React组件的状态在state中,input表单元素也有自己的状态在value中,React将state与表单元素的值(value)绑定到一起,由state的值来控制表单元素的值,从而保证单一数据源特性。
实现步骤:
以获取文本框的值为例,受控组件使用步骤如下:
- 在组件的state中声明一个组件的状态数据
- 将状态数据设置为input标签元素的value属性的值
- 为input添加onChange事件
- 在事件处理函数中,通过事件对象e获取到当前文本框的值(即用户输入的值)
- 调用setState方法,将文本框的值作为state状态的最新值。
代码演示:
class HelloWorld extends React.Component {
// 初始化状态
state = {
// 用来控制input输入框的value状态,初始为空
text: "",
};
// 事件处理函数
changeText = (e) => {
// 使用e事件对象获取用户输入的值,并打印出来看看对不对
// 此处也可以自行打印e对象,看看value是如何拿到的
console.log("用户输入的值:", e.target.value);
// 使用setStae改变state的状态为用户输入的值
this.setState({
text: e.target.value,
});
};
render() {
return (
<div>
{
/* 此处value绑定state中的text状态 同时绑定表单的onChange事件*/}
<input value={
this.state.text} onChange={
this.changeText} />
</div>
);
}
}
控制台效果:
如果大家还学过vue,vue中有个v-model指令可以双向绑定表单数据,以上也是双向绑定的原理。
2.非受控组件
什么是非受控组件?
非受控组件是通过手动操作dom的方式获取文本框的值,文本框的状态不受react组件中的state控制,直接通过原生dom获取输入框的值。
实现步骤:
- 导入
createRef
函数 - 调用createRef函数,创建一个ref对象,存储到名为xxxx(自定义名字如如:msgRef)的实例属性中
- 为input添加属性,值为msgRef(上面自定义的名字)
- 在按钮的事件处理函数中,通过
msgRef.current
(固定写法)即可拿到input对应的dom元素,而其中msgRef.current.value
(固定写法)拿到的则是文本框的值。
代码演示:
class HelloWorld extends React.Component {
// 使用createRef创建一个可以存放dom对象的容器
msgRef = createRef();
changeMsg = () => {
// 获取输入框用户输入的值 this.msgRef.current.value
console.log("value:", this.msgRef.current.value);
//
};
render() {
return (
<div>
{
/* 给表单绑定一个ref属性 */}
<input ref={
this.msgRef} onChange={
this.changeMsg} />
</div>
);
}
}
控制台效果: