React中状态更新函数如何优雅地作为一个组件属性使用

状态更新函数是什么

在React中,状态更新函数是用于更新组件状态的函数,它通常与useState钩子一起使用。useState是React的一个钩子(Hook),允许你在函数组件中添加状态。当你调用useState时,它返回一个数组,该数组包含两个元素:当前的状态值和一个更新该状态的函数。这个更新函数就是所谓的状态更新函数。

在React组件开发过程中,将状态更新函数作为React组件属性传递给子组件,是一种常见且有效的状态管理策略,尤其在需要将状态的控制权下放到子组件中时。这种做法不仅有助于保持组件的独立性和可重用性,还能够实现跨组件的状态共享和更新。

在开发过程中发现有些同学将状态更新函数作为React组件属性传递给子组件的做法有点不优雅。下面来介绍一下,并逐步优化它。

tsx 复制代码
import { useState } from "react";
import type { FC } from 'react';

interface AddProps {
  count: number;
  onChange: (e: any) => void;
}

const Add: FC<AddProps> = ({ count, onChange }) => {
  return (
    <button
      onClick={() => {
        const newCount = count + 1;
        onChange(newCount);
      }}>
      Click me!
    </button>
  )
}

const MyComponent = () => {
  const [count, setCount] = useState(1);
  return (
    <>
      数量:{count}
      <Add onChange={setCount} />
    </>
  );
};

export default MyComponent;

count 是一个多余的组件属性

状态更新函数可以接受一个函数,函数的参数就是上一个状态的值,所以没有必要将状态count作为组件属性传递给子组件。

diff 复制代码
import { useState } from "react";
import type { FC } from 'react';

interface AddProps {
- count: number;
  onChange: (e: any) => void;
}

const Add: FC<AddProps> = ({ onChange }) => {
  return (
    <button
      onClick={() => {
-       const newCount = count + 1;
-       onChange(newCount);      
+       onChange((data: number) => data + 1);
      }}
    >
      Click me!
    </button>
  )
}

const MyComponent = () => {
  const [count, setCount] = useState(1);
  return (
    <>
      数量:{count}
      <Add onChange={setCount} />
    </>
  );
};

export default MyComponent;

any 类型定义不安全

在上述代码中,为 onChange 属性定义了一个类型为 (e: any) => void 的接口,使用了 TypeScript 中的 any 类型。这种做法虽然简单,但它破坏了类型安全,使得我们的应用更容易出错。得优化,所以在 MyComponent 组件中,我们将 setCount 函数(一个由 useState 钩子返回的状态更新函数)传递给了 Add 组件的 onChange 属性。setCount 可以接受一个数字或一个函数作为参数。因此,我们尝试将 e 的类型定义为 number | ((prevState: number) => number)

diff 复制代码
import { useState } from "react";
import type { FC } from 'react';

interface AddProps {
-  onChange: (e: any) => void;
+  onChange: (e: number | ((prevState: number) => number)) => void;
}

const Add: FC<AddProps> = ({ onChange }) => {
  return (
    <button
      onClick={() => {
        onChange((data: number) => data + 1);   
      }}
    >
      Click me!
    </button>
  )
}

const MyComponent = () => {
  const [count, setCount] = useState(1);
  return (
    <>
      数量:{count}
      <Add onChange={setCount} />
    </>
  );
};

export default MyComponent;

更优雅的类型安全解决方案

TypeScript 和 React 提供了一个更为优雅的类型定义方法:Dispatch<SetStateAction<number>>。这个类型完美地描述了 setCount 函数,其中 SetStateAction 泛型接受状态的类型(在这个例子中是 number)作为参数。这不仅提高了代码的类型安全性,还提升了代码的表达力。

diff 复制代码
import { useState } from "react";
- import type { FC } from 'react';
+ import type { FC, Dispatch, SetStateAction } from 'react';

interface AddProps {
-  onChange: (e: number | ((prevState: number) => number)) => void;
+  onChange: Dispatch<SetStateAction<number>>;
}

const Add: FC<AddProps> = ({ onChange }) => {
  return (
    <button
      onClick={() => {
        onChange((data: number) => data + 1);   
      }}
    >
      Click me!
    </button>
  )
}

const MyComponent = () => {
  const [count, setCount] = useState(1);
  return (
    <>
      数量:{count}
      <Add onChange={setCount} />
    </>
  );
};

export default MyComponent;

借助TypeScript的类型推断能力

通过采用 Dispatch<SetStateAction<number>>,我们在 Add 组件中直接传递了一个更新函数 (data => data + 1)onChange 属性,无需显式定义 data 的类型,因为 TypeScript 能够从 onChange 的类型定义中自动推断出来。

diff 复制代码
import { useState } from "react";
import type { FC, Dispatch, SetStateAction } from 'react';

interface AddProps {
  onChange: Dispatch<SetStateAction<number>>;
}

const Add: FC<AddProps> = ({ onChange }) => {
  return (
    <button
      onClick={() => {
-        onChange((data: number) => data + 1);
+        onChange(data => data + 1);   
      }}
    >
      Click me!
    </button>
  )
}

const MyComponent = () => {
  const [count, setCount] = useState(1);
  return (
    <>
      数量:{count}
      <Add onChange={setCount} />
    </>
  );
};

export default MyComponent;
相关推荐
会跑的葫芦怪30 分钟前
若依Vue 项目多子路径配置
前端·javascript·vue.js
2601_9495936544 分钟前
基础入门 React Native 鸿蒙跨平台开发:模拟智能音响
react native·react.js·harmonyos
xiaoqi9221 小时前
React Native鸿蒙跨平台如何进行狗狗领养中心,实现基于唯一标识的事件透传方式是移动端列表开发的通用规范
javascript·react native·react.js·ecmascript·harmonyos
jin1233222 小时前
React Native鸿蒙跨平台剧本杀组队消息与快捷入口组件,包含消息列表展示、快捷入口管理、快捷操作触发和消息详情预览四大核心功能
javascript·react native·react.js·ecmascript·harmonyos
烬头88213 小时前
React Native鸿蒙跨平台实现二维码联系人APP(QRCodeContactApp)
javascript·react native·react.js·ecmascript·harmonyos
pas1363 小时前
40-mini-vue 实现三种联合类型
前端·javascript·vue.js
摇滚侠3 小时前
2 小时快速入门 ES6 基础视频教程
前端·ecmascript·es6
珑墨4 小时前
【Turbo】使用介绍
前端
军军君015 小时前
Three.js基础功能学习十三:太阳系实例上
前端·javascript·vue.js·学习·3d·前端框架·three
xiaoqi9226 小时前
React Native鸿蒙跨平台如何实现分类页面组件通过searchQuery状态变量管理搜索输入,实现了分类的实时过滤功能
javascript·react native·react.js·ecmascript·harmonyos