本章将讨论 React 的类式组件、函数式组件以及 React 两个最重要的属性:state
和 props
。
根据编写组件的风格不同,React 的组件可以分成:类式组件和函数式组件。
除了在讨论一些原理性的知识(比如本章以后面章节的“生命周期”)时,本教程会使用类式组件,其它场景下均使用函数式组件。
无论你是通过 create-react-app
工具还是使用 vite 创建的脚手架,使用的都是函数式组件,让我们再来回顾一下:
import React from "react";
function App() {
return <h1>你好,AXUM中文网</h1>;
}
export default App;
你会看到,函数式组件非常简单:
- 定义一个函数,函数名通常就是组件名
- 函数的返回值是 JSX。它会在内存中作为 React 的虚拟DOM进行处理,并最终渲染到页面的真实DOM中
类式组件
下面,我们尝试把 App
组件改成使用类的形式来定义的类式组件:
import React from 'react';
class App extends React.Component {
render() {
return <h1>你好,axum.rs!</h1>;
}
}
export default App;
- 第3行:定义一个
App
类,它继承自React.Component
- 第4~6行:定义一个
render()
方法,它返回 JSX - 第9行:默认导出这个类
你可以查看和编辑源码。
通过上面的小例子,我们可以大胆地小结一下,对于类式组件:
- 类的名称就是组件名称
- 该类必须继承
React.Component
- 该类必须提供
render()
方法
render()是一个最重要的方法,它负责组件的最终渲染,在类式组件中,需要显式的定义 render()
方法,而在函数式组件中呢?——函数式组件本身就可以视为 render()
方法。函数式组件中,函数的返回值就可以视为render()
方法的返回值。
组件的状态:state
React 是状态驱动的页面的,也就是说可以根据不同状态来呈现不同UI。现在有个需求,要求把上例中的问候语改成动态内容。我们试着实现一下:
// 错误示范
import React from 'react';
class App extends React.Component {
name = 'axum.rs';
render() {
return <h1>你好,{this.name}!</h1>;
}
}
export default App;
这里我们在类中定义了一个 name
属性,并在 JSX 中访问它。运行一下发现貌似可以,其实不然:再跟我读一遍:React 是状态驱动的页面的。这个 name
是类的成员属性,并不是组件的状态。
在 React 有一个特殊的成员:state
,由它来维护组件的状态。
初始化 state
React.Component
定义了一个名为 state
的属性,既然我们的组件是继承自它,自然也有了 state
。state
默认值是 {}
,我们可以根据需要在自己的组件里给它初始化所需要的值:
访问 state
现在,我们可以访问 state
中的元素了(当然包括在 JSX 中访问):
class App extends React.Component {
// 初始化 state
state = {
name: 'axum.rs',
};
render() {
return <h1>你好,{this.state.name}!</h1>; // 访问state
}
}
改变 state
请大声读三遍以下内容:
通过 setState
来改变状态,而不是直接修改!
// 错误:不要直接修改状态
this.state.name = 'AXUM中文网';
// 正确:通过 setState 来修改状态
this.setState({name:'AXUM中文网'});
setState
多种形式
setState
的参数有多种形式,你可以先通过官方文档来体验一下。这里我们先做简单的说明,在后续章节和实战部分会有真实案例。
setState(obj)
:这种方式的参数是一个 Javascript 的简单对象类型,比如上例的{name:'AXUM中文网'}
。这是最简单的方式,在这种方式下,如果name
不存在,则会新增到state
中,如果存在则更新它的值setState(currentState => returnNewStateFunction)
:这种方式的参数是一个函数,它的参数是当前的state
,它的返回值是新的state
。比如上例可以修改为:this.setState(currentState => ({name:'axum中文网'}));
。这种方式主要用于,新的状态依赖于当前状态。由于上例不存在这种需求,所以这种方式对于上例来说有点多余。
本节源码:axum-rs-react-class-state
组件的属性:props
这里说的属性可不是 OOP 中说的属性,给个 HTML/JSX 的例子给你看,你或许就知道了:
<h1 id="title" width="50%" height="10rem">你好,axum.rs!</h1>
上面代码中的 id="title" width="50%" height="10rem"
就是组件的属性,在 React 使用特殊的 props
来维护。和 state
一样, 它们可以使用 Javascript 的简单对象来表示:
在类式组件中,通过构造函数来接收传递过来的 props
:
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h1>你好,{this.props.name}!</h1>;
}
}
export default App;
- 第4~6行:构造函数,通过
props
参数接收外部传递过来的属性 - 第8行:通过
this.props.name
读取传递过来的属性。由于继承React.Component
并通过super(props);
初始化了,所以作为子类的App
可以直接读取到this.props
在使用 App
组件时,就可以给它传递属性了:
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App name="axum.rs" />
</React.StrictMode>
);
- 第3行:使用
App
组件,并传递属性name
,它的值是axum.rs
本节代码在axum-rs-react-class-props
函数式组件的 props
通过上面对类式组件的学习,你会发现:state
和 props
都是定义在 React.Component
这个基类上的一个成员属性。而函数并没有成员属性的概念。那么函数式组件该如何处理 state
和 props
呢?
我们先来看 props
。回顾一下类式组件是如何获取 props
的:通过构建函数的参数。让我们简化一下:通过函数的参数!是的,由于 props
是通过函数的参数传递的,所以能适用于类组件的构建函数里,自然也适用于函数式组件。
import React from 'react';
function App(props) {
return <h1>你好,{props.name}</h1>;
}
export default App;
现在我们把 App
组件改回为函数式组件,并新加一个 props
形参,这样它就可以接收外部传递过来的属性了,并且可以在包括 JSX 在内的代码中访问它。
同样,在使用 App
组件时,我们向它传递属性:
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App name="axum.rs" />
</React.StrictMode>
);
本节代码在:axum-rs-react-fn-props
hooks:让函数式组件拥有 state
为了让函数式组件能使用 state
,React 提供了名为useState
的 hook。
- 引入
useState
:import { useState } from 'react'
- 使用
useState
:const [name, setName] = useState('axum.rs')
useState
是一个函数,它:
- 返回一个数组,数组的成员分别是:绑定到该状态的变量名(比如上面的
name
变量)以及更新该状态的函数名(比如上面的setName
) - 接收一个参数,该参数用于设置状态的初始化(比如上面的
axum.rs
)
import React, { useState } from 'react';
function App() {
// 初始化 state
const [name, setName] = useState('axum.rs');
// 更新state
// setName('AXUM中文网');
return <h1>你好,{name}</h1>; // 读取state
}
export default App;
本节代码在:axum-rs-react-fn-state