React开发——组件相关


函数组件

ReactJS 函数组件是在 React 中工作时会遇到的一些更常见的组件。这些只是 JavaScript 函数。我们可以通过编写 JavaScript 函数在 React 中创建一个函数组件。这些函数可能会也可能不会接收数据作为参数。在函数组件中,返回值是要呈现到 DOM 树的 JSX 代码。

React 组件必须大写,否则它们将无法按预期运行——当 JSX 被解析时,React 使用大写来区分 HTML 标签和 React 组件的实例。 使用小写将被解释为一个没有特殊含义的普通 HTML 元素。

简单的函数组件

Greeting.jsx

function Greeting(){
    return <h1>Hello World!</h1>
}

export default Greeting;

// 如果使用命名导出
// export function Greeting(){
//     return <h1>Hello World!</h1>
// }

// 如果使用命名导入
// import { Greeting } from './Greeting.jsx';

main.jsx

import React from "react";
import ReactDOM from "react-dom";
import Demo from "./App";
 
const root = ReactDOM.createRoot(
    document.getElementById("root")
);
root.render(
    <React.StrictMode>
        <Demo />
    </React.StrictMode>
);

与类组件相比,函数组件缺少大量功能,并且它们无法访问专用状态变量。

上述问题可以在函数组件中使用hooks(钩子)来帮助解决:

带有钩子的函数组件

Hooks 是让你能够使用 React 特性的函数。所有的 Hooks 都可以通过 use 前缀来识别。例如,useState 就是一个 Hook。在我们深入课程的过程中,我们会使用更多的这些 Hooks。现在,记住 Hooks 有我们需要遵守的规则:

  • Hooks 只能在函数组件的顶层调用。
  • Hooks 不能在循环或条件语句中调用。
// 错误!Hook 在 if 语句中
function MyComponent() {
  const [count, setCount] = useState(0);

  if (count > 5) {
    useEffect(() => {
      document.title = 'Count is greater than 5!';
    });
  }

  // ...
}

// 错误!Hook 在循环中
function MyComponent({ items }) {
  items.forEach(item => {
    const [count, setCount] = useState(0); // 错误!
    // ...
  });

  // ...
}
function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    if (count > 5) {
      document.title = 'Count is greater than 5!';
    }
  });

  // ...
}

使用useState()钩子的函数组件

useState 让我们可以在函数组件中使用状态(state),这是一种可以在组件的多次渲染之间保持并更改的数据。

注意:这个状态并不是全局的,它是特定于该组件的。每个组件都有自己的状态,一个组件的状态改变不会影响到其他组件的状态。

  • useState() 接受一个状态初始值作为参数

  • useState() 返回一个数组:数组包含两个元素——第一个元素是当前的状态值,第二个元素是一个函数(用来更新状态值

    这个函数被设计为接收一个参数,并将这个参数作为新的状态值。

Greeting_useState.jsx

import { useState } from "react";

function Greeting_useState(){
    const [change,setChange] = useState(true)

    return (
        <div>
            <button onClick={()=>setChange(!change)}>Click Here~!</button>
            {
            change ? (
                <h1>Welcome to useState</h1>
            ):(
                <h1>Rock and Roll!</h1>
            )
        }
        
        </div>
       
    )
}

export default Greeting_useState

main.jsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import Greeting from "./Greeting.jsx";
import Greeting_useState from "./Greeting_useState.jsx";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <Greeting></Greeting>
    <Greeting_useState></Greeting_useState>
    {/* <App /> */}
  </React.StrictMode>
);

在 React 中,当一个组件的状态或 props 发生变化时,该组件会被完全销毁并从头开始重新创建。没错,你没听错:销毁。这包括变量、函数和 React 节点。整个组件会被重新创建,但这次最新的状态值将从 useState 返回,这个过程被称为重新渲染。

重新渲染是 React 的一个关键特性,它使得 React 能够有效地更新用户界面以响应底层数据的变化。

React协调算法:

重新渲染的过程会生成一个新的虚拟 DOM(文档对象模型)树。虚拟 DOM 是实际 DOM 的轻量级表示,React 使用它来跟踪 UI 的当前状态。

然后,React 将新的虚拟 DOM 树与之前的进行比较,并计算出更新实际 DOM 所需的最小改变集。这就是所谓的协调算法。

本质上,组件会被重新创建,这意味着函数以及我们的 ApponButtonClickdivbutton 也会被重新创建。

你可能会想,状态不应该也被重新创建吗?其实,React 负责跟踪最新的状态并提供给组件。

初始状态值只用于组件的首次渲染,在后续的渲染中会被忽略。

使用useEffect()钩子的函数组件

函数组件不能像基于类的组件那样访问生命周期函数,因为生命周期函数需要在类的边界内定义。需要使用一个特殊的 React Hook,叫做 useEffect()

React 中的某些组件需要与自身外部的事物进行交互。这些事情可以是任何东西,从从服务器查询数据到查找/更改组件在网页上的位置,甚至在必要时将一些数据发送到服务器。这种与外界的互动称为副作用。

注意:useEffect() 并不是生命周期函数的精确复制,它的工作方式和行为有些不同。

useEffect() 接受两个参数:一个是执行副作用的函数,一个是依赖数组

  • ⚠️只提供了第一个参数,那么这个副作用会在每次组件渲染后执行。

——对应生命周期componentDidMountcomponentDidUpdate 的结合。

  • ⚠️如果你提供了一个空的依赖数组 [] 作为第二个参数,那么副作用只会在组件挂载和卸载时执行,而不会在更新时执行。

—— 对应生命周期componentDidMount()

  • ⚠️如果你在依赖数组中添加了一些值,例如 [value1, value2],那么每当 value1value2 改变时,副作用函数就会执行。因此,如果你有一个状态变量,并且希望在状态发生变化时发生一些副作用,你可以使用这个钩子,并在依赖数组中提及状态变量。

——对应生命周期componentDidMountcomponentDidUpdate 的结合,但只在依赖项改变!

  • 钩子中的返回函数等同于 componentWillUnmount

Greeting_useEffect.jsx

import { useEffect } from "react";

function Greeting_useEffect(){
    useEffect(() => {
        console.log("Mounting...");
    });
    return <h1>Geeks....!</h1>;
}

export default Greeting_useEffect

main.jsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import Greeting from "./Greeting.jsx";
import Greeting_useState from "./Greeting_useState.jsx";
import Greeting_useEffect from "./Greeting_useEffect.jsx";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <Greeting></Greeting>
    <Greeting_useState></Greeting_useState>
    <Greeting_useEffect></Greeting_useEffect>
    {/* <App /> */}
  </React.StrictMode>
);

你可以从钩子中的回调返回一个函数,该函数每次在运行下一个效果之前执行,最后一次在卸载组件时执行

import React, { useEffect, useState } from "react";

export default function Clock() {
  const [counter, setCounter] = useState(0);

  useEffect(() => {
    const key = setInterval(() => {
      setCounter(count => count + 1)
    }, 1000);

    return () => {
      clearInterval(key);
    };
  }, [])

  return (
    <p>{counter} seconds have passed.</p>
  );
}

受控组件

有些原生的 HTML 元素会维护它们自己的内部状态。<input> 元素就是一个很好的例子。你在 <input> 中输入文字,它会在每次按键时更新自己的值。在许多使用场景中,你可能希望控制 <input> 元素的值,即自己设置它的值。这就是所谓的受控组件。

你可以使用 setState 来更新这个值,然后将这个值传递给组件的 value 属性。当用户与组件交互(例如,输入文字)时,你可以通过事件处理函数来更新状态,从而更新组件的值。

function ControlledInput() {
  const [value, setValue] = useState('');

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  return <input value={value} onChange={handleChange} />;
}

类组件

继承父类

类组件不能只是另一个类,它需要具有某些属性,使其有资格成为 React 组件。React 在一个名为Component的类上为我们提供了所有这些属性,我们可以通过扩展给定的类来编写我们的组件,如下所示:

import React, { Component } from "react";

class ClassInput extends Component {
  // Some code goes here
}

/*
  This can also be written as:

  import React from 'react';
  class ClassInput extends React.Component {}
  export default ClassInput;

  instead of destructuring the `Component` during import
*/

export default ClassInput;

构造函数

一个类通常在没有构造函数的情况下是不完整的,所以让我们添加一个。

传递给这个组件的 props,会被传递给类的 constructor。这个,连同 super 方法,允许你在 this 的上下文中使用 props,其中 this 在这种情况下指的是组件。

如果你的组件没有任何 props,那么在 constructorsuper 中不带任何参数是可以的。

import React, { Component } from "react";

class ClassInput extends Component {
  constructor(props) {
    super(props);
  }
  // Some more code goes here
}

export default ClassInput;

渲染JSX

你可以通过从一个方法中返回你的 JSX 来渲染JSX:

import React, { Component } from "react";

class ClassInput extends Component {
  constructor(props) {
    super(props);
  }
  // Some more code goes here

  render() {
    return (
      <section>
        <h3>{this.props.name}</h3>
        {/* The input field to enter To-Do's */}
        <form>
          <label htmlFor="task-entry">Enter a task: </label>
          <input type="text" name="task-entry" />
          <button type="submit">Submit</button>
        </form>
        <h4>All the tasks!</h4>
        {/* The list of all the To-Do's, displayed */}
        <ul></ul>
      </section>
    );
  }
}

export default ClassInput;

如何使用状态和管理上下文

在基于类的组件中,状态作为构造函数的一部分被初始化:

import React, { Component } from "react";

class ClassInput extends Component {
  constructor(props) {
    super(props);

    this.state = {
      todos: [],
      inputVal: "",
    };
  }
  // Some more code goes here

  render() {
    return (
      <section>
        <h3>{this.props.name}</h3>
        <form>
          <label htmlFor="task-entry">Enter a task: </label>
          <input type="text" name="task-entry" />
          <button type="submit">Submit</button>
        </form>
        <h4>All the tasks!</h4>
        <ul></ul>
      </section>
    );
  }
}

export default ClassInput;

预定义的 setState 方法可以用来再次设置它!记住,状态不能被改变,所以每次都必须设置一个新的状态。

现在,是时候通过添加所有的功能来完成它了!几乎是相同的,除了一个小的区别。每当声明一个方法时,你必须将该方法的 this 绑定到类的 this,以便能够使用它,因为默认情况下,类中的方法并没有绑定到它。

通常,你在构造函数中做这个,而不是在运行时(在 render 方法中)。

在类组件中,你必须使用 this.setState 方法来更新状态。这是因为 React 需要知道状态何时发生变化,以便重新渲染组件。

然而,你可以在类的方法中封装 this.setState 调用,以便根据你的应用程序逻辑自定义状态更新。

import React, { Component } from "react";

class ClassInput extends Component {
  constructor(props) {
    super(props);

    this.state = {
      todos: [],
      inputVal: "",
    };

    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleInputChange(e) {
    this.setState((state) => ({
      ...state,
      inputVal: e.target.value,
    }));
  }

  handleSubmit(e) {
    e.preventDefault();
    this.setState((state) => ({
      todos: state.todos.concat(state.inputVal),
      inputVal: "",
    }));
  }

  render() {
    return (
      <section>
        <h3>{this.props.name}</h3>
        <form onSubmit={this.handleSubmit}>
          <label htmlFor="task-entry">Enter a task: </label>
          <input
            type="text"
            name="task-entry"
            value={this.state.inputVal}
            onChange={this.handleInputChange}
          />
          <button type="submit">Submit</button>
        </form>
        <h4>All the tasks!</h4>
        <ul>
          {this.state.todos.map((todo) => (
            <li key={todo}>{todo}</li>
          ))}
        </ul>
      </section>
    );
  }
}

export default ClassInput;

组件生命周期

  • render 函数是最常用的生命周期方法,它是类组件中唯一必需的生命周期方法。它在组件的挂载和更新时运行。render 应该是纯净的,意味着它不修改组件状态,每次被调用时返回相同的结果(给定相同的输入),并且不直接与浏览器交互。

  • constructor(): 构造函数,在创建组件的实例时调用。

  • static getDerivedStateFromProps(): 在组件实例化后以及重新渲染前调用。

  • componentWillMount() : 在组件挂载前调用。

  • componentDidMount(): 在组件挂载后调用。此方法在组件挂载(插入到 DOM 树中)后运行。

    你应该在这里进行任何需要为组件获取数据的调用。它也是一个很好的地方,可以做任何依赖于组件的事情,比如在你刚刚渲染的画布元素上绘图。

  • componentWillReceiveProps() : 在组件接收新的 props 时调用。

  • shouldComponentUpdate(): 在状态或 props 改变时调用,返回一个布尔值决定是否进行重新渲染。

  • componentWillUpdate() : 在组件更新前调用。

  • getSnapshotBeforeUpdate(): 在最新的渲染输出被提交给 DOM 前调用。

  • componentDidUpdate(): 在组件更新后调用。因此,你必须小心在此方法中更新什么,因为如果你无差别地更新状态,会导致重新渲染,并且你将陷入无尽的循环中。你可以通过在更新状态时使用关于前一个和当前 props 的等式的条件语句来避免这个问题。

  • componentWillUnmount(): 在组件卸载及销毁之前调用。在这个方法中,你应该执行清理操作,比如取消网络请求,清除计时器等。

  • componentDidCatch(): 在组件渲染过程、生命周期方法或构造函数中发生错误时调用。

image-20240518181234477

参考网站:


文章作者: QT-7274
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 QT-7274 !
评论
  目录