将 `@headlessui/react` 中的 `ListBox` 与 Mobx 一起使用?

Use `ListBox` from `@headlessui/react` with Mobx?

使用列表框之前:

store/index.ts

import { action, makeObservable, observable } from 'mobx'
import type { IFrameItStore, TrafficSignal } from '@/types/index'

export class FrameItStore implements IFrameItStore {
    trafficSignal: TrafficSignal = {
        shape: 'circle',
    }

    constructor() {
        makeObservable(this, {
            trafficSignal: observable,
            updateTrafficSignal: action.bound,
        })
    }

    updateTrafficSignal({ shape }: TrafficSignal) {
        if (shape) this.trafficSignal.shape = shape
    }
}

Shape.tsx

import { observer } from 'mobx-react'
import * as React from 'react'

import { useFrameItStore } from '@/store/index'
import type { TrafficSignalShape } from '@/types/index'

export const Shape = observer(() => {
    const frameItStore = useFrameItStore()
    return (
        <>
            <label htmlFor="shape" className="mb-1 text-sm font-medium text-blue-gray-500">
                Shape
            </label>
            <select
                id="shape"
                className="block w-full px-3 py-2 mb-2 bg-white border border-gray-300 rounded-md shadow-sm text-blue-gray-500 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
                value={frameItStore.trafficSignal.shape}
                onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
                    const shape = e.target.value as TrafficSignalShape
                    frameItStore.updateTrafficSignal({ shape })
                }}
            >
                <option value="circle">Circle</option>
                <option value="square">Square</option>
            </select>
        </>
    )
})

App.tsx

<Shape />

使用列表框后:

Select.tsx

import * as React from 'react'
import { Listbox, Transition } from '@headlessui/react'
import clsx from 'clsx'

import { Selector, Check } from '@/components/icons/index'

type Option = {
    id: string
    name: string
    img: string
}

interface IProps {
    label?: string
    options: Array<Option>
}

export const Select = ({ label, options }: IProps) => {
    const [selectedOption, setSelectedOption] = React.useState<Option>(options[0])

    return (
        <Listbox value={selectedOption} onChange={setSelectedOption}>
            {({ open }) => (
                <>
                    <Listbox.Label className="mb-1 text-sm font-medium text-blue-gray-500">
                        {label}
                    </Listbox.Label>

                    <div className="relative mt-1">
                        <Listbox.Button className="relative w-full py-2 pl-3 pr-10 text-left bg-white border border-gray-300 rounded-md shadow-sm cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                            <span className="flex items-center">
                                <img
                                    src={selectedOption.img}
                                    alt={selectedOption.name}
                                    className="flex-shrink-0 w-6 h-6 rounded-full"
                                />
                                <span className="block ml-3 truncate">{selectedOption.name}</span>
                            </span>
                            <span className="absolute inset-y-0 right-0 flex items-center pr-2 ml-3 pointer-events-none">
                                <Selector />
                            </span>
                        </Listbox.Button>

                        <div className="absolute w-full mt-1 bg-white rounded-md shadow-lg">
                            <Transition
                                show={open}
                                leave="transition duration-100 ease-in"
                                leaveFrom="opacity-100"
                                leaveTo="opacity-0"
                            >
                                <Listbox.Options
                                    static
                                    className="py-1 overflow-auto text-base rounded-md max-h-56 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
                                >
                                    {options.map((option) => (
                                        <Listbox.Option as={React.Fragment} key={option.id} value={option}>
                                            {({ active, selected }) => (
                                                <li
                                                    className={clsx('relative py-2 pl-3 cursor-default select-none pr-9', {
                                                        'text-white bg-indigo-600': active,
                                                        'text-gray-900': !active,
                                                    })}
                                                >
                                                    <div className="flex items-center">
                                                        <img
                                                            src={option.img}
                                                            alt={option.name}
                                                            className="flex-shrink-0 w-6 h-6 rounded-full"
                                                        />
                                                        <span
                                                            className={clsx('ml-3 block truncate', {
                                                                'font-semibold': selected,
                                                                'font-normal': !selected,
                                                            })}
                                                        >
                                                            {option.name}
                                                        </span>
                                                    </div>
                                                    {selected && (
                                                        <span
                                                            className={clsx('absolute inset-y-0 right-0 flex items-center pr-4', {
                                                                'text-white': active,
                                                                'text-indigo-600': !active,
                                                            })}
                                                        >
                                                            <Check />
                                                        </span>
                                                    )}
                                                </li>
                                            )}
                                        </Listbox.Option>
                                    ))}
                                </Listbox.Options>
                            </Transition>
                        </div>
                    </div>
                </>
            )}
        </Listbox>
    )
}

App.tsx

const shapes = [
    {
        id: '1',
        name: 'Circle',
        img:
            'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
    },
    {
        id: '2',
        name: 'Square',
        img:
            'https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80',
    },
]

<Select label="Shape" options={shapes} />

如何将 After 部分转换为像 Before 部分一样使用 MobX?

我尝试将 value & onChange 传递给 Select[=49],因为它在 Before 部分=]喜欢:

App.tsx

<Select
  label="Shape"
  options={shapes}
  value={frameItStore.trafficSignal.shape}
  onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
    const shape = e.target.value as TrafficSignalShape
    frameItStore.updateTrafficSignal({ shape })
  }}
/>

Select.tsx

interface IProps {
    label?: string
    value: any
    onChange: (value: any) => void
    options: Array<Option>
}

export const Select = ({ label, options, value, onChange }: IProps) => {
    const [selectedOption, setSelectedOption] = React.useState<Option>(options[0])

    return (
        <Listbox value={value} onChange={onChange}>
        .
        .
        .
        </Listbox>
    )
}

但它 select 什么都没有,我不知道该怎么做 selectedOption?

好的,我解决了。删除了本地挂钩状态 & 只使用了 MobX 状态。此外,还有 1 个小问题。当商店最初具有小写值时,我将商店中的值设置为大写。大写值仅用于在 UI.

中显示

这是修改后的有效代码:

App.tsx

<Select
  label="Shape"
  options={shapes}
  value={shapes.filter({ name }) => name.toLowerCase() === frameItStore.trafficSignal.shape)[0]}
  onChange={(value) => {
    const shape = value.toLowerCase() as TrafficSignalShape
    frameItStore.updateTrafficSignal({ shape })
  }}
/>

Select.tsx

import * as React from 'react'
import { Listbox, Transition } from '@headlessui/react'
import clsx from 'clsx'

import { Selector, Check } from '@/components/icons/index'

type Option = {
    id: string
    name: string
    img: string
}

interface IProps {
    label?: string
    value: Option
    onChange: (name: string) => void
    options: Array<Option>
}

export const Select = ({ label, options, value, onChange }: IProps) => {
    return (
        <Listbox
            value={value}
            onChange={(value: Option) => {
                onChange(value.name)
            }}
        >
            {({ open }) => (
                <>
                    <Listbox.Label className="mb-1 text-sm font-medium text-blue-gray-500">
                        {label}
                    </Listbox.Label>

                    <div className="relative mt-1">
                        <Listbox.Button className="relative w-full py-2 pl-3 pr-10 text-left bg-white border border-gray-300 rounded-md shadow-sm cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                            <span className="flex items-center">
                                <img
                                    src={value.img}
                                    alt={value.name}
                                    className="flex-shrink-0 w-6 h-6 rounded-full"
                                />
                                <span className="block ml-3 truncate">{value.name}</span>
                            </span>
                            <span className="absolute inset-y-0 right-0 flex items-center pr-2 ml-3 pointer-events-none">
                                <Selector />
                            </span>
                        </Listbox.Button>

                        <div className="absolute z-10 w-full mt-1 bg-white rounded-md shadow-lg">
                            <Transition
                                show={open}
                                leave="transition duration-100 ease-in"
                                leaveFrom="transform opacity-100"
                                leaveTo="transform opacity-0"
                            >
                                <Listbox.Options
                                    static
                                    className="py-1 overflow-auto text-base rounded-md max-h-56 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
                                >
                                    {options.map((option) => {
                                        return (
                                            <Listbox.Option as={React.Fragment} key={option.id} value={option}>
                                                {({ active, selected }) => {
                                                    return (
                                                        <li
                                                            className={clsx(
                                                                'relative py-2 pl-3 cursor-default select-none pr-9',
                                                                {
                                                                    'text-white bg-indigo-600': active,
                                                                    'text-gray-900': !active,
                                                                }
                                                            )}
                                                        >
                                                            <div className="flex items-center">
                                                                <img
                                                                    src={option.img}
                                                                    alt={option.name}
                                                                    className="flex-shrink-0 w-6 h-6 rounded-full"
                                                                />
                                                                <span
                                                                    className={clsx('ml-3 block truncate', {
                                                                        'font-semibold': selected,
                                                                        'font-normal': !selected,
                                                                    })}
                                                                >
                                                                    {option.name}
                                                                </span>
                                                            </div>
                                                            {selected && (
                                                                <span
                                                                    className={clsx(
                                                                        'absolute inset-y-0 right-0 flex items-center pr-4',
                                                                        {
                                                                            'text-white': active,
                                                                            'text-indigo-600': !active,
                                                                        }
                                                                    )}
                                                                >
                                                                    <Check />
                                                                </span>
                                                            )}
                                                        </li>
                                                    )
                                                }}
                                            </Listbox.Option>
                                        )
                                    })}
                                </Listbox.Options>
                            </Transition>
                        </div>
                    </div>
                </>
            )}
        </Listbox>
    )
}