React useState() 使用指南

1.使用 useState() 进行状态管理

useState()是改变状态的开关,将状态添加到函数组件需要4个步骤:启用状态、初始化、读取和更新。

1.1 启用状态

要将 转换为有状态组件,需要告诉 React:从'react'包中导入useState钩子,然后在组件函数的顶部调用useState()。

import React, { useState } from 'react';

function Bulbs() {
  ... = useState(...);
  return 
; }

在Bulbs函数的第一行调用useState(),在组件内部调用会使该函数成为有状态的函数组件。
启用状态后,下一步是初始化。

1.2初始化状态

import React, { useState } from 'react';

function Bulbs() {
  ... = useState(false);
  return 
; }

useState(false)用false初始化状态。

1.3 读取状态

import React, { useState } from 'react';

function Bulbs() {
  const [on] = useState(false);
  return 
; }

on状态变量保存状态值。

状态已经启用并初始化,现在可以读取它了。但是如何更新呢?再来看看useState(initialState)返回什么。

1.4 更新状态

用值更新状态

useState(initialState)返回一个数组,其中第一项是状态值,第二项是一个更新状态的函数。

import React, { useState } from 'react';

function Bulbs() {
  const [on, setOn] = useState(false);

  const lightOn = () => setOn(true);
  const lightOff = () => setOn(false);

  return (
    <>
      
); }

状态一旦改变,React 就会重新渲染组件,on变量获取新的状态值。
状态更新作为对提供一些新信息的事件的响应。这些事件包括按钮单击、HTTP 请求完成等,确保在事件回调或其他回调中调用状态更新函数。

使用回调更新状态

import React, { useState } from 'react';

function Bulbs() {
  const [on, setOn] = useState(false);

  const lightSwitch = () => setOn(on => !on);

  return (
    <>
      
); }

setOn(on => !on)使用函数更新状态。

2. 多种状态

通过多次调用useState(),一个函数组件可以拥有多个状态。

import React, { useState } from 'react';

function Bulbs() {
  const [on, setOn] = useState(false);
  const [count, setCount] = useState(1);

  const lightSwitch = () => setOn(on => !on);
  const addBulbs = () => setCount(count => count + 1);

  const bulb = 
; const bulbs = Array(count).fill(bulb); return ( <>
{bulbs}
); }

[on, setOn] = useState(false) 管理开/关状态
[count, setCount] = useState(1)管理数量。
多个状态可以在一个组件中正确工作

3.状态的延迟初始化

每当 React 重新渲染组件时,都会执行useState(initialState)。 如果初始状态是原始值(数字,布尔值等),则不会有性能问题。
当初始状态需要昂贵的性能方面的操作时,可以通过为useState(computeInitialState)提供一个函数来使用状态的延迟初始化,如下所示:

import React, { useState } from 'react';

function MyComponent({ bigJsonData }) {
  const [value, setValue] = useState(function getInitialState() {
    const object = JSON.parse(bigJsonData); 
    return object.initialValue;
  });
}

getInitialState()仅在初始渲染时执行一次,以获得初始状态。在以后的组件渲染中,不会再调用getInitialState(),从而跳过昂贵的操作。

4. useState() 中的坑

4.1 在哪里调用 useState()

在使用useState() 时,必须遵循的规则
1、仅顶层调用:不能在循环,条件,嵌套函数等中调用useState().在多个useState()调用中,渲染之间的调用顺序必须相同。
2、仅从React 函数调用 :必须仅在函数组件或自定义钩子内部调用useState()。
下面看下useState()的正确用法和错误用法的例子。

有效调用useState()

useState()在函数组件的顶层被正确调用

import React, { useState } from 'react';

function Bulbs() {
  const [on, setOn] = useState(false);
}

以相同的顺序正确地调用多个useState()调用:

import React, { useState } from 'react';

function Bulbs() {
  const [on, setOn] = useState(false);
  const [count, setCount] = useState(1);
}

useState()在自定义钩子的顶层被正确调用

import React, { useState } from 'react';

function useToggleHook(initial) {
  const [on, setOn] = useState(initial);
  return [on, () => setOn(!on)];
}

function Bulbs() {
  const [on, toggle] = useToggleHook(false);
}

useState() 的无效调用

在条件中调用useState()是不正确的

import React, { useState } from 'react';

function Switch({ isSwitchEnabled }) {
  if (isSwitchEnabled) {
    const [on, setOn] = useState(false);
  }
}

在嵌套函数中调用useState()也是不对的

import React, { useState } from 'react';

function Switch() {
  let on = false;
  let setOn = () => {};

  function enableSwitch() {
    // Bad
    [on, setOn] = useState(false);
  }

  return (
    
  );
}

4.2 过时状态

闭包是一个从外部作用域捕获变量的函数。
闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。 由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。
来看看一个过时的状态是如何表现出来的。组件延迟3秒计数按钮点击的次数

import React, { useState } from 'react';

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

  const handleClickAsync = () => {
    setTimeout(function delay() {
      setCount(count + 1);
    }, 3000);
  }

  return (
    
{count}
); }

快速多次点击按钮。count 变量不能正确记录实际点击次数,有些点击被吃掉。
delay() 是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。
为了解决这个问题,使用函数方法来更新count状态:

import React, { useState } from 'react';

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

  const handleClickAsync = () => {
    setTimeout(function delay() {
      setCount(count => count + 1);
    }, 3000);
  }

  return (
    
{count}
); }

现在setCount(count => count + 1)在delay()中正确更新计数状态。React 确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。
快速单击按钮。 延迟过去后,count 能正确表示点击次数。

4.3 复杂状态管理

useState()用于管理简单状态。对于复杂的状态管理,可以使用useReducer() 。它为需要多个状态操作的状态提供了更好的支持。
假设需要编写一个最喜欢的电影列表。用户可以添加电影,也可以删除已有的电影,实现方式大致如下:

import React, { useState } from 'react';

function FavoriteMovies() {
  const [movies, setMovies] = useState([{ name: "Heat" }]);
  const [newMovie, setNewMovie] = useState("");

  const add = movie => setMovies([...movies, movie]);

  const remove = index => {
    setMovies([...movies.slice(0, index), ...movies.slice(index + 1)]);
  };

  const handleAddClick = () => {
    if (newMovie === "") {
      return;
    }
    add({ name: newMovie });
    setNewMovie("");
  };

  return (
    <>
      
{movies.map((movie, index) => { return remove(index)} />; })}
setNewMovie(event.target.value)} />
); } function Movie({ movie, onRemove }) { return (
{movie.name}
); } function App() { return (

My favorite movies

); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

状态列表需要几个操作:添加和删除电影,状态管理细节使组件混乱。
更好的解决方案是将复杂的状态管理提取到reducer中:
reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,而 dispatch 是一个可以发布事件来更新 state 的
在 useReducer 传入 reducer 函数根据 action 来更新 state,如果 action 为 add 正增加 state 也就是增加 count。
在 button 中调用 dispatch 发布 add 事件,发布 add 事件后就会在 reducer 根据其类型对 state 进行对应操作,更新 state。

import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case "add":
      return [...state, action.item];
    case "remove":
      return [
        ...state.slice(0, action.index),
        ...state.slice(action.index + 1)
      ];
    default:
      throw new Error();
  }
}

function FavoriteMovies() {
  const [movies, dispatch] = useReducer(reducer, [{ name: "Heat" }]);
  const [newMovie, setNewMovie] = useState("");

  const handleAddClick = () => {
    if (newMovie === "") {
      return;
    }
    dispatch({ type: "add", item: { name: newMovie } });
    setNewMovie("");
  };

  return (
    <>
      
{movies.map((movie, index) => { return ( dispatch({ type: "remove", index })} /> ); })}
setNewMovie(event.target.value)} />
); } function Movie({ movie, onRemove }) { return (
{movie.name}
); } function App() { return (

My favorite movies

); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

reducer管理电影的状态,有两种操作类型:

  • "add"将新电影插入列表

  • "remove"从列表中按索引删除电影

注意组件功能没有改变。但是这个版本的更容易理解,因为状态管理已经被提取到reducer中。

还有一个好处:可以将reducer 提取到一个单独的模块中,并在其他组件中重用它。另外,即使没有组件,也可以对reducer 进行单元测试。
这就是关注点分离的威力:组件渲染UI并响应事件,而reducer 执行状态操作。
查看效果:https://codesandbox.io/s/react-usestate-complex-state-usereducer-gpw87

4.4 状态 vs 引用

考虑这样一个场景:咱们想要计算组件渲染的次数。
一种简单的实现方法是初始化countRender状态,并在每次渲染时更新它(使用useEffect())
函数组件中没有生命周期,那么可以使用 useEffect 来替代。如果你熟悉 React class 的生命周期函数,你可以把 useEffect 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

import React, { useEffect } from 'react';

function CountMyRenders() {
  const [countRender, setCountRender] = useState(0);
  
  useEffect(function afterRender() {
    setCountRender(countRender => countRender + 1);
  });

  return (
    
I've rendered {countRender} times
); }

useEffect()在每次渲染后调用afterRender()回调。但是一旦countRender状态更新,组件就会重新渲染。这将触发另一个状态更新和另一个重新渲染,依此类推。
可变引用useRef()保存可变数据,这些数据在更改时不会触发重新渲染,使用可变的引用改造一下

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

function CountMyRenders() {
  const countRenderRef = useRef(1);
  
  useEffect(function afterRender() {
    countRenderRef.current++;
  });

  return (
    
I've rendered {countRenderRef.current} times
); } function App() { const [count, setCount] = useState(0); return (
); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

每次渲染组件时,countRenderRef可变引用的值都会使countRenderRef.current ++递增。 重要的是,更改不会触发组件重新渲染。
打开例子:https://codesandbox.io/s/react-usestate-vs-useref-g6qv3?file=/src/index.js

5. 总结

要使函数组件有状态,请在组件的函数体中调用useState()。
useState(initialState)的第一个参数是初始状态。返回的数组有两项:当前状态和状态更新函数。

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

使用 setState(newState)来更新状态值。 另外,如果需要根据先前的状态更新状态,可以使用回调函数setState(prevState => newState)。

在单个组件中可以有多个状态:调用多次useState()。

当初始状态开销很大时,延迟初始化很方便。使用计算初始状态的回调调用useState(computeInitialState),并且此回调仅在初始渲染时执行一次。

必须确保使用useState()遵循规则。

当闭包捕获过时的状态变量时,就会出现过时状态的问题。可以通过使用一个回调来更新状态来解决这个问题,这个回调会根据先前的状态来计算新的状态。

使用useState()来管理一个简单的状态。为了处理更复杂的状态,一个更好的的选择是使用useReducer() 。

你可能感兴趣的:(React useState() 使用指南)