React Native 状态管理:用 Jotai 替代 useState

简介

Jotai 是一个轻量级的状态管理库,虽然它可以用来做全局状态管理,但我主要将其作为 React 原生 useState 的替代方案。Jotai 采用原子化 (atomic) 的状态管理方式,灵感来源于 Recoil,但实现更加简洁。

为什么选择 Jotai

相比 React 的 useState,Jotai 有以下显著优势:

  1. 类似 Vue 的计算属性 - 轻松创建派生状态
  2. 异步更新 - 简化异步数据流管理
  3. 深层次的对象更新 - 结合 Immer 提供更简洁的 API
  4. 最小化重新渲染 - 原子化状态使组件只订阅它们需要的状态,减少不必要的重渲染
  5. 无需 Context 共享状态 - 避免了 Context 的提供者嵌套地狱
  6. TypeScript 友好 - 提供完善的类型推导,减少类型定义工作
  7. 体积小巧 - 核心包仅约3KB (min+gzip),按需引入扩展功能

安装

bash 复制代码
# 基本安装
yarn add jotai

# 如需 immer 集成支持
yarn add immer jotai-immer

基本用法示例

1. 计算属性(Computed Values)

类似于 Vue 的 computed 属性,Jotai 让你可以基于其他状态创建派生状态:

tsx 复制代码
import {atom, useAtom} from 'jotai';
import {Button, Text, View} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
import {ScaledSheet} from 'react-native-size-matters/extend';

// 创建基础原子状态
const countAtom = atom(0);

// 创建派生状态 - 会自动响应 countAtom 的变化
const doubledCountAtom = atom(get => get(countAtom) * 2);

const ComputedExample = () => {
  const [count, setCount] = useAtom(countAtom);
  const [doubledCount] = useAtom(doubledCountAtom);  // 只读,会随 count 自动更新

  return (
    <SafeAreaView style={styles.container} edges={['top']}>
      <View style={styles.content}>
        <Text>计数: {count}</Text>
        <Text>双倍计数: {doubledCount}</Text>
        <Button title="增加" onPress={() => setCount(c => c + 1)} />
      </View>
    </SafeAreaView>
  );
};

与 useState 相比,这种方式让派生状态变得非常简单,无需使用 useMemo 或在 useEffect 中手动同步状态。

2. 异步状态处理

Jotai 原生支持异步 atom,搭配 React Suspense 可以优雅地处理加载状态:

tsx 复制代码
import {atom, useAtom} from 'jotai';
import {Button, Text, View} from 'react-native';
import {Suspense} from 'react';

// 基础状态
const countAtom = atom(0);

// 异步派生状态
const asyncAtom = atom(async get => {
  // 模拟API请求
  return new Promise<number>(resolve => {
    setTimeout(() => {
      resolve(get(countAtom) * 2);
    }, 1000);
  });
});

// 异步组件会被Suspense捕获并展示加载状态
function AsyncComponent() {
  const [asyncValue] = useAtom(asyncAtom);
  return <Text>异步计算结果: {asyncValue}</Text>;
}

const AsyncExample = () => {
  const [count, setCount] = useAtom(countAtom);

  return (
    <View style={styles.content}>
      <Text>当前计数: {count}</Text>
      
      <Suspense fallback={<Text>加载中...</Text>}>
        <AsyncComponent />
      </Suspense>
      
      <Button title="增加" onPress={() => setCount(c => c + 1)} />
    </View>
  );
};

这种模式大大简化了异步数据处理,告别了传统的 isLoading 状态标志和复杂的加载控制逻辑。

3. 深层嵌套对象的简化更新

结合 jotai-immer,Jotai 可以极大简化复杂嵌套对象的更新:

tsx 复制代码
import {useAtom} from 'jotai';
import {atomWithImmer} from 'jotai-immer';
import {Button, Text, View} from 'react-native';

// 使用 atomWithImmer 创建可变的 atom
const userAtom = atomWithImmer({
  name: 'John',
  profile: {
    address: {
      city: 'Shanghai',
      street: 'Nanjing Road',
      zipCode: '200000'
    },
    preferences: {
      theme: 'light',
      notifications: true
    }
  },
});

const DeepUpdateExample = () => {
  const [user, setUser] = useAtom(userAtom);

  return (
    <View style={styles.content}>
      <Text>{JSON.stringify(user, null, 2)}</Text>

      <Button
        title="更新城市和主题"
        onPress={() => {
          // 使用类似 Immer 的 API 直接修改嵌套属性
          setUser(draft => {
            draft.name = 'Lai';
            draft.profile.address.city = 'Beijing';
            draft.profile.preferences.theme = 'dark';
          });
          
          // 对比传统 useState 的繁琐写法:
          // setUser(prevState => ({
          //   ...prevState,
          //   name: 'Lai',
          //   profile: {
          //     ...prevState.profile,
          //     address: {
          //       ...prevState.profile.address,
          //       city: 'Beijing',
          //     },
          //     preferences: {
          //       ...prevState.profile.preferences,
          //       theme: 'dark'
          //     }
          //   },
          // }));
        }}
      />
    </View>
  );
};

在组件间共享状态

Jotai 还允许你轻松在不同组件间共享状态,无需使用 Context API:

tsx 复制代码
// atoms.ts - 集中定义共享状态
import {atom} from 'jotai';

// 在模块级别定义共享的atom
export const globalBooleanAtom = atom(false);
export const themeAtom = atom('light');
export const userSessionAtom = atom(null);
tsx 复制代码
// 在任意组件中使用
import {useAtom} from 'jotai';
import {globalBooleanAtom, themeAtom} from './atoms';

const ComponentA = () => {
  const [isEnabled, setIsEnabled] = useAtom(globalBooleanAtom);
  return (
    <Switch 
      value={isEnabled}
      onValueChange={setIsEnabled}
    />
  );
};

const ComponentB = () => {
  // 在另一个组件中使用相同的状态
  const [isEnabled] = useAtom(globalBooleanAtom);
  const [theme, setTheme] = useAtom(themeAtom);
  
  return (
    <View>
      <Text>功能已{isEnabled ? '启用' : '禁用'}</Text>
      <Text>当前主题: {theme}</Text>
      <Button 
        title="切换主题" 
        onPress={() => setTheme(t => t === 'light' ? 'dark' : 'light')} 
      />
    </View>
  );
};

总结

相比 React 的内置状态管理,Jotai 提供了更强大、简洁的 API:

  • 派生状态让数据间的依赖关系更加清晰
  • 异步处理简化了数据加载和同步
  • 深层对象更新告别了繁琐的展开操作
  • 状态共享无需复杂的 Context 配置
  • 精确重渲染减少应用性能浪费
  • 模块化设计便于代码组织和维护
  • 插件生态系统提供各种扩展功能

相关资源

Tips

本专栏的代码已开源,后续随着专栏会不断更新[github.com/ace0109/rea...)

相关推荐
拉不动的猪6 分钟前
刷刷题47(react常规面试题2)
前端·javascript·面试
浪遏14 分钟前
场景题:大文件上传 ?| 过总字节一面😱
前端·javascript·面试
Bigger35 分钟前
Tauri(十八)——如何开发 Tauri 插件
前端·rust·app
355984268550551 小时前
医保服务平台 Webpack逆向
前端·webpack·node.js
百锦再2 小时前
React编程的核心概念:发布-订阅模型、背压与异步非阻塞
前端·javascript·react.js·前端框架·json·ecmascript·html5
thinkQuadratic2 小时前
scss预处理器对比css的优点以及基本的使用
前端·css·scss
yuluo_YX2 小时前
使用 Spring AI Aliabab Module RAG 构建 Web Search 应用
前端·人工智能·spring
冴羽2 小时前
SvelteKit 最新中文文档教程(16)—— Service workers
前端·javascript·svelte
Hongwen10012 小时前
寒冬之中的AI创意实验:48小时左右从零打造吉卜力风格AI绘图网站
前端·后端·cursor
小鸭呱呱呱2 小时前
【CSS】- 表单控件的 placeholder 如何控制换行显示?
前端·javascript·css·深度学习·面试·职场和发展·html