给状态加上插件模式-jotai

93 阅读1分钟

适用场景:

  • 多个组件对同一个状态进行多次修改,代码无需耦合在一起

举个例子

  • 写一个table公共组件,有一份列配置。 新增一列checkbox。
export function useRowSelection(props: Props | undefined) {
  const { store, columnOptionsAtom } = useData()
  useLayoutEffect(() => {
    if (!props) {
      return
    }

    return store.setter(columnOptionsAtom, (_getter, prev) => {
      const option: ColumnType = {
        title: props.title,
        width: props.width,
        render: props.render || render,
        fixed: props.fixed,
      }
      return [option, ...prev]
    })
  }, [props])
}

  • 例子2

有一状态style ,初始值为{color:"gray"}。
点击组件A,add按钮 {color:"red"} cancel按钮 取消{color:"red"}状态
点击组件B, add按钮 {color:"blue"} cancel按钮 取消{color:"blue"}状态

const styleIncrementAtom = incrementAtom<CSSProperties>({ color: 'gray' })

A组件


    function A() {
      const [incrementStateFn, cleanStateFn] = useIncrementAtom(styleIncrementAtom)

      return (
        <div>
          <button
            data-testid="btn-red"
            onClick={() => {
              incrementStateFn((_getter, prevReturn) => {
                return {
                  ...prevReturn,
                  color: 'red',
                }
              })
            }}
          >
            color red
          </button>
          <button
            data-testid="btn-cancel-red"
            onClick={() => {
              cleanStateFn()
            }}
          >
            cancel color red
          </button>
        </div>
      )
    }
    
  })
})

B组件

 function B() {
      const [incrementStateFn, cleanStateFn] = useIncrementAtom(styleIncrementAtom)

      const { color } = useAtomValue(styleIncrementAtom)

      return (
        <div>
          <button
            data-testid="btn-blue"
            onClick={() => {
              incrementStateFn((_getter, prevReturn) => {
                return {
                  ...prevReturn,
                  color: 'blue',
                }
              })
            }}
          >
            color red
          </button>
          <button
            data-testid="btn-cancel-blue"
            onClick={() => {
              cleanStateFn()
            }}
          >
            cancel color red
          </button>
          <div data-testid="result">{color}</div>
        </div>
      )
    }

App

    function App() {
      return (
        <div data-testid="app">
          <A />
          <B />
        </div>
      )
    }

测试

    const { baseElement } = render(<App />)
    await screen.findByTestId('app')
    expect(queryByTestId(baseElement, 'result')).toBeInTheDocument()
    expect(queryByTestId(baseElement, 'result')?.textContent).toBe('gray')

    await userEvent.click(screen.getByTestId('btn-red'))
    expect(queryByTestId(baseElement, 'result')?.textContent).toBe('red')
    await userEvent.click(screen.getByTestId('btn-blue'))
    expect(queryByTestId(baseElement, 'result')?.textContent).toBe('blue')

    await userEvent.click(screen.getByTestId('btn-cancel-blue'))
    expect(queryByTestId(baseElement, 'result')?.textContent).toBe('red')
    await userEvent.click(screen.getByTestId('btn-cancel-red'))
    expect(queryByTestId(baseElement, 'result')?.textContent).toBe('gray')

    await userEvent.click(screen.getByTestId('btn-blue'))
    expect(queryByTestId(baseElement, 'result')?.textContent).toBe('blue')