React:类式组件、函数式组件及state和props

20433
2022/11/16 01:51:44

本章将讨论 React 的类式组件、函数式组件以及 React 两个最重要的属性:stateprops

根据编写组件的风格不同,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 的属性,既然我们的组件是继承自它,自然也有了 statestate 默认值是 {},我们可以根据需要在自己的组件里给它初始化所需要的值:

访问 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

通过上面对类式组件的学习,你会发现:stateprops 都是定义在 React.Component 这个基类上的一个成员属性。而函数并没有成员属性的概念。那么函数式组件该如何处理 stateprops 呢?

我们先来看 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。

  • 引入 useStateimport { useState } from 'react'
  • 使用 useStateconst [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