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;
相关推荐
y先森3 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy3 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189113 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿4 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡5 小时前
commitlint校验git提交信息
前端
Jacky(易小天)5 小时前
MongoDB比较查询操作符中英对照表及实例详解
数据库·mongodb·typescript·比较操作符
虾球xz6 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇6 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒6 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员6 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js