React useState() 使用指南

目录

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

1.1 启用状态

1.2初始化状态

1.3 读取状态

2. 多种状态

3.状态的延迟初始化

4. useState() 中的坑

5. 总结


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

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

1.1 启用状态

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

import React, { useState } from 'react';

function Bulbs() {
  ... = useState(...);
  return <div className="bulb-off" />;
}

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

1.2初始化状态

import React, { useState } from 'react';

function Bulbs() {
  ... = useState(false);
  return <div className="bulb-off" />;
}

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

1.3 读取状态

import React, { useState } from 'react';

function Bulbs() {
  const [on] = useState(false);
  return <div className={on ? 'bulb-on' : 'bulb-off'} />;
}

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 (
    <>
      <div className={on ? 'bulb-on' : 'bulb-off'} />
      <button onClick={lightOn}>开</button>
      <button onClick={lightOff}>关</button>
    </>
  );
}

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

使用回调更新状态

import React, { useState } from 'react';

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

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

  return (
    <>
      <div className={on ? 'bulb-on' : 'bulb-off'} />
      <button onClick={lightSwitch}>开/关</button>
    </>
  );
}

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 = <div className={on ? 'bulb-on' : 'bulb-off'} />;
  const bulbs = Array(count).fill(bulb);

  return (
    <>
      <div className="bulbs">{bulbs}</div>
      <button onClick={lightSwitch}>开/关</button>
      <button onClick={addBulbs}>添加数量</button>
    </>
  );
}

[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 (
    <button onClick={enableSwitch}>
      Enable light switch state
    </button>
  );
}

4.2 过时状态

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

import React, { useState } from 'react';

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

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

  return (
    <div>
      {count}
      <button onClick={handleClickAsync}>Increase async</button>
    </div>
  );
}

快速多次点击按钮。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 (
    <div>
      {count}
      <button onClick={handleClickAsync}>Increase async</button>
    </div>
  );
}

现在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 (
    <>
      <div className="movies">
        {movies.map((movie, index) => {
          return <Movie movie={movie} onRemove={() => remove(index)} />;
        })}
      </div>
      <div className="add-movie">
        <input
          type="text"
          value={newMovie}
          onChange={event => setNewMovie(event.target.value)}
        />
        <button onClick={handleAddClick}>Add movie</button>
      </div>
    </>
  );
}

function Movie({ movie, onRemove }) {
  return (
    <div className="movie">
      <span>{movie.name}</span>
      <button onClick={onRemove}>Remove</button>
    </div>
  );
}

function App() {
  return (
    <div className="App">
      <h2>My favorite movies</h2>
      <FavoriteMovies />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, 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 (
    <>
      <div className="movies">
        {movies.map((movie, index) => {
          return (
            <Movie
              movie={movie}
              onRemove={() => dispatch({ type: "remove", index })}
            />
          );
        })}
      </div>
      <div className="add-movie">
        <input
          type="text"
          value={newMovie}
          onChange={event => setNewMovie(event.target.value)}
        />
        <button onClick={handleAddClick}>Add movie</button>
      </div>
    </>
  );
}

function Movie({ movie, onRemove }) {
  return (
    <div className="movie">
      <span>{movie.name}</span>
      <button onClick={onRemove}>Remove</button>
    </div>
  );
}

function App() {
  return (
    <div className="App">
      <h2>My favorite movies</h2>
      <FavoriteMovies />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

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

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

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

注意组件功能没有改变。但是这个版本的<FavoriteMovies>更容易理解,因为状态管理已经被提取到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 (
    <div>I've rendered {countRender} times</div>
  );
}

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

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

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

  return (
    <div>I've rendered {countRenderRef.current} times</div>
  );
}

function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <CountMyRenders />
      <button onClick={() => setCount(count => count + 1)}>
        Click to re-render
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, 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() 。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前言 xi 第1章 初识React Native 1 1.1 React Native 的优点 2 1.2 风险和缺点 4 1.3 小结 4 第2章 React Native 工作原理 5 2.1 React Native 是如何工作的 5 2.2 渲染周期 7 2.3 在React Native 中创建组件 2.4 宿主平台接口 11 2.5 小结 12 第3章 构建你的第一个应用 13 3.1 搭建环境 13 3.2 创建一个新的应用 17 3.3 探索示例代码 24 3.4 开发天气应用 27 3.5 小结 40 第4章 移动应用组件 42 4.1 类比HTML 元素与原生组件 42 4.2 处理触摸和手势 46 4.3 使用结构化组件 58 4.4 平台特定组件 69 4.5 小结 74 第5章 样式 75 5.1 声明和操作样式 75 5.2 组织和继承 79 5.3 定位和设计布局 81 5.4 小结 91 第6章 平台接口 92 6.1 使用定位接口 93 6.2 使用用户图片与摄像头 6.3 AsyncStore 持久化数据存储 108 6.4 智能天气应用 109 6.5 小结 119 第7章 模块 120 7.1 使用npm 安装JavaScript 类库 120 7.2 iOS 原生模块 121 7.3 Android 原生模块 130 7.4 跨平台原生模块 139 7.5 小结 141 第8章 调试与开发者工具 142 8.1 JavaScript 调试实践和解释 142 8.2 React Native 调试工具 147 8.3 JavaScript 之外的调试方法 152 8.4 测试代码 158 8.5 当你陷入困境 160 8.6 小结 160 第9章 学以致用 161 9.1 闪卡应用 161 9.2 模型与数据存储 168 9.3 使用Navigator 177 9.4 探索第三方依赖 180 9.5 响应式设计与字体尺寸 180 9.6 小结及任务 183 第10章 部署至iOS 应用商店 184 10.1 准备Xcode 工程 184 10.2 上传应用 192 10.3 使用TestFlight 进行Beta 测试 199 10.4 提交应用审核 200 10.5 小结 201 第11章 部署Android 应用 203 11.1 设置应用图标 203 11.2 生成release 版本的APK 205 11.3 通过邮件或链接发布 207 11.4 提交应用至Play 商店 207 11.5 小结 214 总结 215 附录A ES6 语法 216 附录B 命令与快速入门指南 219 作者简介 221 关于封面 221

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值