React Hooks:管理状态和处理副作用的综合指南2023
#javascript #react #前端 #reactjsdevelopment

目录


什么是反应钩

React钩子是函数,可让您使用功能组件中的不同反应特征。这意味着您可以管理状态,进行API调用以及基于类的组件可以从功能组件中做的其他事情。

反应中有不同的钩子,可以让您做不同的事情。

  • useState挂钩让您创建和管理状态。
  • useEffect让您在React应用程序中处理副作用
  • useContext钩子让您可以在没有道具钻探的情况下共享状态,好吗?
  • useMemo钩子:让您记住您的反应组件
  • useCallback用于记忆功能,通过防止不必要的重新渲染依赖这些功能的组件来优化性能。
  • useReducer:此钩子类似于Usestate钩,但更有效地处理复杂状态。
  • useRef钩子提供了一种创建对呈现在渲染中的元素或价值的可变引用的方法。

这不是一个详尽的列表,而是您将在99%的时间使用的钩子。

使用Usestate Hook 管理状态

USESTATE挂钩使您可以在React组件中添加和管理状态。它等同于班级组件中的this.state = {}

要使用usestate钩子,请在组件的顶部称呼它:

const [state, setState] = useState(initialState);

它返回一个完全有两个值的数组:当前状态和更新该状态的函数。

让我们仔细观察它们。

state:这是当前状态。当您的组件首次渲染时,状态等于initialState

setState:这是更新状态并触发重新渲染的功能。

initialState:您希望状态最初的价值。它可以是任何有效的JavaScript值。

注意setState功能仅更新下一个渲染的状态变量。如果您在调用setState函数后读取状态变量,则在调用之前仍将获得屏幕上的旧值。

此外,如果您提供的值与当前状态相同,则REECT会跳过组件的重新渲染。

用法

要使用usestate钩子,您将其称为组件的顶级。

import {useState} from react
   function (){
const [state, setState] = useState(‘’);
}

usestate 的基本应用

更新文本字段

在这个简单的示例中,firstNamelastName状态变量容纳字符串。当您输入输入字段时,handleFirstNamehandleLastName updater函数在DOM中读取您的输入并相应地更新UI。

import { useState } from 'react';

export default function MyInput() {
  const [firstName, setFirstName] = useState(‘’);
  const [lastName, setLastName] = useState(‘’);

  function handleFirstName(e) {
    setFirstName(e.target.value);
  }
function handleLastName(e) {
    setLastName(e.target.value);
  }

  return (
    <>
      <input value={firstName} onChange={handleFirstName} />
     <input value={lastName} onChange={handleLastName} />
      <p>{firstName} {lastName} is typing.</p>
    </>
  );
}

更新状态的对象和数组

更新状态的数组或对象时,您不应直接突变状态。

这是我的意思ð

state.age = 42; // you should not directly mutate state.

相反,您应该用新状态替换旧状态。

SetState({
...old state, 
age: 42
})

这也适用于数组。

state.push(Banana) // not recommended 
     setState([...prevState, Banana])

状态中的对象的示例

在此示例中,员工状态变量包含一个对象。每个输入字段都有一个更改处理程序,该处理程序调用setEmployee函数以更新状态。传播语法(...)以确保替换状态并不会突变。

import { useState } from 'react';

export default function Employee() {
  const [employee, setEmployee] = useState({
    firstName: “”,
    lastName: “”,
    job: '',
  });

  return (
    <>
      <label>
        First name:
        <input
          value={employee.firstName}
          onChange={e => {
            setEmployee({
              ...employee,
              firstName: e.target.value
            });
          }}
        />
      </label>
      <label>
        Last name:
        <input
          value={employee.lastName}
          onChange={e => {
            setEmployee({
              ...employee,
              lastName: e.target.value
            });
          }}
        />
      </label>
      <label>
        Job:
        <input
          value={employee.job}
          onChange={e => {
            setEmployee({
              ...employee,
              job: e.target.value
            });
          }}
        />
      </label>
      <p>
        {employee.firstName}{' '}
        {employee.lastName}{' '}
        ({employee.job})
      </p>
    </>
  );
}

状态数组的示例

在此示例中,水果状态变量保存一个数组。每个调用setFruits功能以更新状态的按钮处理程序。传播语法(...)以确保替换状态并不会突变。

import { useState } from 'react';
import AddTodo from './AddTodo.js';
import TaskList from './TaskList.js';

let nextId = 3;
const initialTodos = [
  { id: 0, title: "'Buy milk', done: true },"
  { id: 1, title: "'Eat tacos', done: false },"
  { id: 2, title: "'Brew tea', done: false },"
];

export default function TaskApp() {
  const [todos, setTodos] = useState(initialTodos);

  function handleAddTodo(title) {
    setTodos([
      ...todos,
      {
        id: nextId++,
        title: "title,"
        done: false
      }
    ]);
  }

  function handleChangeTodo(nextTodo) {
    setTodos(todos.map(t => {
      if (t.id === nextTodo.id) {
        return nextTodo;
      } else {
        return t;
      }
    }));
  }

  function handleDeleteTodo(todoId) {
    setTodos(
      todos.filter(t => t.id !== todoId)
    );
  }

  return (
    <>
      <AddTodo
        onAddTodo={handleAddTodo}
      />
      <TaskList
        todos={todos}
        onChangeTodo={handleChangeTodo}
        onDeleteTodo={handleDeleteTodo}
      />
    </>
  );
}

使用useEffect处理副作用

useEffect钩用于处理React中的副作用。副作用平均操作不受反应控制。常见的副作用包括:

  • 打电话给第三方API
  • 与浏览器的本地存储交互
  • 与DOM沟通
  • 实施某些库,例如:动画库。

要使用useEffect钩,在组件的顶部声明

import React, { useEffect } from 'react';

function ExampleComponent() {
  useEffect(() => {
    // Side effect code goes here

    // Clean up the effect (optional)
    return () => {
      // Cleanup code goes here
    };
  }, []);

  return (
    // Component JSX goes here
  );
}


export default ExampleComponent;

useEffect钩子进行了两个参数:

第一个参数是一个回调函数,其中包含副作用的代码。在这里,您可以执行API调用,操纵DOM,与浏览器存储进行交互或与外部库集成。

第二个参数是依赖性数组,该数组确定效果应何时运行。通过传递一个空数组[],效果只能在初始渲染后运行一次。
如果您希望在某些依赖关系发生变化时再次运行效果,则可以将它们包含在依赖项数组中。

既然您对useEffect钩及其处理React副作用的目的有很好的了解,让我们看一些常见用例,看看如何在实践中使用。

打电话给第三方Apis

在此示例中,我们将研究如何在React中使用useEffect钩以对第三方API进行呼叫。拨打API呼叫是您作为前端开发人员更经常进行的,而useEffect挂钩提供了一种方便的方法来处理异步操作。

我们将使用IMDB API检索电影的细节。
让我们研究代码以查看如何完成。

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

function FilmDetails() {
  const [film, setFilm] = useState(null);

  useEffect(() => {
    // Function to fetch film details from the IMDB API
    const fetchFilmDetails = async () => {
      try {
        const response = await fetch('https://api.example.com/films/123');
        const data = await response.json();
        setFilm(data);
      } catch (error) {
        console.error('Error fetching film details:', error);
      }
    };

    fetchFilmDetails();

    // Cleanup function
    return () => {
      // Cleanup code goes here
      // This will be executed when the component is unmounted or when the dependency changes
    };
  }, []);

  if (!film) {
    return <p>Loading film details...</p>;
  }

  return (
    <div>
      <h2>{film.title}</h2>
      <p>{film.description}</p>
      {/* Render other film details */}
    </div>
  );
}

export default FilmDetails;

在此示例中,我们有一个功能组件FilmDetails。我们还使用useState挂钩来管理最初设置为nullfilm状态。
useEffect钩子内部,我们定义了一个异步函数以从IMDB API获取数据。

如果API调用成功,我们使用setFilm检索到的数据更新film状态。

如果在API调用期间发生错误,我们将错误消息记录到catch块中的控制台。

我们将一个空的依赖性数组作为第二个参数。这意味着效果只能对初始渲染进行一次。

为了获得更好的用户体验,我们添加了一个加载状态,以防胶片数据可用。一旦可用,我们将渲染组件JSX。

最后,useEffect中的return语句返回清理功能,当组件被卸载或依赖关系更改时,该功能将执行。您可以在清理功能中添加任何必要的清理代码,例如取消正在进行的请求或删除事件听众。

如果API呼叫成功,您应该看到浏览器中显示的胶片细节。

与浏览器的本地存储互动

在此示例中,我们将阅读并写入浏览器的本地存储。
浏览器的本地存储用于持久数据。

让我们潜入代码。

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

function LocalStorageExample() {
  const [name, setName] = useState('');

  useEffect(() => {
    // Read from local storage
    const storedName = localStorage.getItem('name');
    if (storedName) {
      setName(storedName);
    }
  }, []);

  const handleNameChange = (e) => {
    const newName = e.target.value;
    setName(newName);

    // Write to local storage
    localStorage.setItem('name', newName);
  };

  return (
    <div>
      <h2>Hello, {name || 'stranger'}!</h2>
      <input type="text" value={name} onChange={handleNameChange} />
    </div>
  );
}

export default LocalStorageExample;

在此示例中,我们定义了功能组件LocalStorageExample。我们使用useState钩管理name状态。

我们使用useEffect钩与本地存储进行交互。首先,我们使用getItem()方法从本地存储中读取。如果存在name密钥,我们将使用setName函数将name状态设置为值。
接下来,我们定义一个handleNameChange函数,该功能在input的值更改时会触发。它更新了具有新值的名称状态,并使用localStorage.setItem()将新值写入本地存储,将其与密钥“名称”相关联。

该组件呈现一个标题,该标题显示了当前名称(如果设置了未设置名称的stranger),以及用户可以输入新名称的输入字段。

注释:由于localstorage仅存在浏览器中,因此此代码在服务器端不起作用。

与钩子的功能组件重构类成分

挂钩仅在React 16中引入。这意味着,如果您在此之前一直使用React,那么您很可能会使用类组件来跟踪和管理数据,以及获取和发送数据。

在本节中,我将向您展示如何使用钩子将类组件重构为功能组件。

重构状态管理:从类组件到功能组件

要使用状态的重构类组件到具有状态的功能组件,我们使用this.statethis.setState将类组件的状态管理转换为使用USESTATE挂钩的功能组件。

类组件

import React, { Component } from 'react';

class ClassComponentWithState extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  incrementCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.incrementCount}>Increment</button>
      </div>
    );
  }
}

export default ClassComponentWithState;

相应的功能组件

import React, { useState } from 'react';

function FunctionalComponentWithState() {
  const [count, setCount] = useState(0);

  const incrementCount = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>Increment</button>
    </div>
  );
}

export default FunctionalComponentWithState;

我们通过用useState钩替换this.statethis.setState将类组件转换为功能组件。如上所述,useState钩返回一个完全有两个值的数组:当前状态和更新的函数。

重构数据获取:从类组件到功能组件

要从类组件到功能组件进行重构数据获取,我们转换了类组件的方法,该方法通常使用componentDidMountcomponentDidUpdate之类的生命周期方法,以利用useEffect挂钩来处理数据获取和更新组件状态的功能组件。

与数据获取的类组件

import React, { Component } from 'react';

class ClassComponentWithDataFetching extends Component {
  constructor(props) {
    super(props);
    this.state = {
      data: [],
      updateCount: 0,
    };
  }

  componentDidMount() {
    // Data fetching
    this.fetchData();
  }

  componentDidUpdate(prevProps, prevState) {
    // Check if updateCount has changed
    if (prevState.updateCount !== this.state.updateCount) {
      this.fetchData();
    }
  }

  fetchData = () => {
    // Data fetching
    fetch('https://api.example.com/data')
      .then((response) => response.json())
      .then((jsonData) => this.setState({ data: jsonData }))
      .catch((error) => console.error('Error fetching data:', error));
  };

  handleUpdate = () => {
    this.setState((prevState) => ({
      updateCount: prevState.updateCount + 1,
    }));
  };

  render() {
    return (
      <div>
        {this.state.data.map((item) => (
          <p key={item.id}>{item.name}</p>
        ))}
        <button onClick={this.handleUpdate}>Update</button>
      </div>
    );
  }
}

export default ClassComponentWithDataFetching

具有数据获取的功能组件

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

function FunctionalComponentWithDataFetching() {
  const [data, setData] = useState([]);
  const [updateCount, setUpdateCount] = useState(0);

  useEffect(() => {
    // Data fetching
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        const jsonData = await response.json();
        setData(jsonData);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, [updateCount]); // Update when updateCount changes

  // Simulating component update triggering the useEffect
  const handleUpdate = () => {
    setUpdateCount((prevCount) => prevCount + 1);
  };

  return (
    <div>
      {data.map((item) => (
        <p key={item.id}>{item.name}</p>
      ))}
      <button onClick={handleUpdate}>Update</button>
    </div>
  );
}

export default FunctionalComponentWithDataFetching;

我们通过替换componentDidMountcomponentDidUpdateuseEffect挂钩来转换类组件。依赖性阵列可以帮助我们模仿两种生命周期方法。

使用React Hooks 的好处

使用React Hooks在开发React应用程序中提供了一些好处:

  1. 简单
    挂钩使功能组件能够具有自己的状态和生命周期方法。这使得在不处理类组件的复杂性的情况下更容易阅读,写入和理解代码。

  2. 可重用性
    挂钩促进代码重复使用。自定义挂钩使您可以将逻辑和状态行为封装到应用程序中多个组件中使用的可重复使用的功能。这促进了模块化和可维护的代码。

  3. 增强功能组件
    钩子使功能组件能够具有状态和副作用,以前仅在类组件中可用。您可以使用useState挂钩管理组件状态,并使用useEffect钩处理副作用,从而使功能组件更强大,更通用。

  4. 提高性能
    钩子通过减少不必要的重新租赁来优化性能。使用useMemouseCallback钩子,您可以分别记忆值和功能,以防止不必要的计算或重新渲染组件。

  5. 更轻松的测试
    挂钩促进功能编程概念,这使得测试变得更加容易,因为逻辑分为小型,可测试的功能。这使您可以隔离测试组件。

  6. 从类组件迁移
    钩子简化了从类组件到功能组件的迁移过程。现有的类组件可以使用钩子重构为功能组件,保留功能,同时提高代码可读性和可维护性。

  7. 社区支持
    钩子已获得了React社区的广泛采用和支持。这意味着您可以找到充足的资源,文档和社区驱动的图书馆,以帮助有效地学习和使用钩子。


总结思想

React Hooks是一个强大的功能,它允许开发人员在功能组件中利用不同的反应功能。它们使国家管理,处理副作用以及将类组件重构为功能组件。

使用USESTATE挂钩用于管理状态,而使用效果挂钩用于处理副作用,例如进行API调用。钩子提供了诸如代码简单性,可重复使用和消除对类组件的需求之类的好处。

总体而言,React Hooks提供了一种有效的方法来增强反应应用中的功能和开发经验。

TwitterLinkedIn上关注我以获取更多内容和更新。