React 状态管理库 Jotai

32 阅读2分钟

Jotai是一个原始而灵活的React状态管理库,它的api都是以hook的方式提供,类似于useState、useRecducer等。可以组合多个Atom来创建新的Atom。

Atom是Jotai中状态管理单位,它是可以更新和订阅的,当atom被更新时,订阅了这个atom的组件便会重新渲染。并且,更新对应的Atom只会重新渲染订阅了这个Atom的组件,并不会像Context那样导致整个父组件重新渲染。

Jotai的简单使用示例

import ProTable from '@/components/proTable/ProTable'
import SpaceItem from '@/components/spaceItem/SpaceItem'
import WhiteSpace from '@/components/whiteSpace'
import { getRandomIntInclusive } from '@/utils'
import { Alert, Button, Card, Input, Space, Tag } from 'antd'
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
import { throttle } from 'lodash'
import { ChangeEvent, useState } from 'react'

const countAtom = atom<number>(0) // 定义一个原子
const isEvenAtom = atom((get) => get(countAtom) % 2 === 0) // 定义一个派生原子
const isOddAtom = atom((get) => !get(isEvenAtom)) // 定义一个派生原子

const nameAtom = atom('Jotai')
const name1Atom = atom('Zutand')
const stateAtom = atom(
  (get) =>
    `${get(nameAtom)}${get(name1Atom)}是React生态中比较好用的状态管理库`,
)

type Theme = 'light' | 'dark'
const themeAtom = atom<Theme>('light')
const themeColorAtom = atom(
  (get) => (get(themeAtom) === 'light' ? '#ce2345' : '#1fe345'),
  (get, set, theme: Theme) => set(themeAtom, theme),
)

type TodoItem = {
  id: number
  title: string
  completed: boolean
}

const todoListAtom = atom<TodoItem[]>([
  {
    id: 1,
    title: '学习Jotai',
    completed: false,
  },
  {
    id: 2,
    title: '学习React',
    completed: false,
  },
])

// Jotai状态管理
export default function Jotai() {
  const [count, setCount] = useAtom(countAtom)
  const [isOdd] = useAtom(isOddAtom)
  const isEven = useAtomValue(isEvenAtom)
  const setCounts = useSetAtom(countAtom)

  const state = useAtomValue(stateAtom)

  const [themeColor, setThemeColor] = useAtom(themeColorAtom)
  const [theme, setThemes] = useAtom(themeAtom)
  const setTheme = useSetAtom(themeAtom)

  const countHandler = (type: 'add' | 'sub' | 'random') => () => {
    setCount((pre) =>
      type === 'add'
        ? pre + 1
        : type === 'sub'
        ? pre - 1
        : getRandomIntInclusive(),
    )
  }

  // 切换主题
  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light')
  }

  const [inputValue, setInputValue] = useState('')
  const [todoList, setTodoList] = useAtom(todoListAtom)

  const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
    setInputValue(event.target.value)
  }

  const addItem = () => {
    if (inputValue) {
      setTodoList([
        ...todoList,
        {
          id: Date.now(),
          title: inputValue,
          completed: false,
        },
      ])
      setInputValue('')
    }
  }

  const toggleComplete = (record: TodoItem) => () => {
    setTodoList((pre) => {
      const matchIndex = pre.findIndex((item) => item.id === record.id)
      return Array.from(
        pre.fill(
          { ...record, completed: !record.completed },
          matchIndex,
          matchIndex + 1,
        ),
      )
    })
  }

  const delTodo = (record: TodoItem) => () => {
    setTodoList((pre) => {
      return pre.filter((item) => item.id !== record.id)
    })
  }

  return (
    <Card>
      <Alert message="展示Jotai状态管理的用法" type="success" />
      <WhiteSpace />
      <div>当前的count是:{count}</div>
      <div>{`是否是偶数:${isEven ? '是' : '否'}`}</div>
      <div>{`是否是奇数:${isOdd ? '是' : '否'}`}</div>
      <div>{state}</div>
      <WhiteSpace />
      <SpaceItem align="left">
        <Button onClick={countHandler('add')} type="primary"></Button>
        <Button onClick={countHandler('sub')} danger></Button>
        <Button onClick={countHandler('random')}>随机数</Button>
        <Button
          onClick={() => {
            setCounts((pre) => pre + 1)
          }}
        >
          设置加一
        </Button>
        <Button
          onClick={() => {
            setCounts(getRandomIntInclusive())
          }}
        >
          设置随机数
        </Button>
      </SpaceItem>
      <WhiteSpace />
      <div>当前主题是:{theme}</div>
      <div style={{ color: themeColor }}>当前主题色是:{themeColor}</div>
      <SpaceItem align="left">
        <Button type="primary" onClick={toggleTheme}>
          切换主题
        </Button>
        <Button
          type="primary"
          onClick={() => {
            setThemes((pre) => (pre === 'light' ? 'dark' : 'light'))
          }}
        >
          切换主题2
        </Button>
        <Button type="primary" onClick={() => setThemeColor('light')}>
          切换主题3
        </Button>
      </SpaceItem>
      <WhiteSpace gap={40} />
      <Alert message="用Jotai实现一个TodoList" type="success" />
      <ProTable
        pagination={false}
        dataSource={todoList}
        rowKey="id"
        columns={[
          {
            title: 'ID',
            dataIndex: 'id',
          },
          {
            title: '标题',
            dataIndex: 'title',
          },
          {
            title: '是否完成',
            dataIndex: 'completed',
            render: (completed: boolean) => (
              <Tag color={completed ? 'success' : 'error'}>
                {completed ? '已完成' : '未完成'}
              </Tag>
            ),
          },
          {
            title: '操作',
            key: 'action',
            width: 200,
            align: 'center',
            render: (_, record: TodoItem) => {
              const isCompleted = record.completed
              return (
                <SpaceItem>
                  <Button
                    onClick={toggleComplete(record)}
                    type="primary"
                    danger={isCompleted}
                  >
                    {isCompleted ? '未完成' : '完成'}
                  </Button>
                  <Button onClick={delTodo(record)} danger>
                    删除
                  </Button>
                </SpaceItem>
              )
            },
          },
        ]}
      />
      <WhiteSpace />
      <Space.Compact block>
        <Input
          value={inputValue}
          onChange={onInputChange}
          allowClear
          placeholder="请输入"
          style={{ width: 500 }}
        />
        <Button
          type="primary"
          disabled={!inputValue}
          onClick={throttle(addItem, 1500)}
        >
          添加
        </Button>
      </Space.Compact>
    </Card>
  )
}

完整效果

image.png