Búsqueda de sitios web

reactjs - ¿Cómo crearía un hook envolvente de selector de store de Zustand en Typescript?


Estoy tratando de usar Zustand como reemplazo de React Context para proporcionar un estado compartido entre un subconjunto de componentes, y uno de los patrones que me encuentro usando mucho es el selector superficial de múltiples propiedades, así:

// store.ts
interface State {
  data: object,
  setData: (newData: State['data']) => void,
}

const useStore = create<State>(set => ({
  data: {},
  setData: (data) => set({ data }),
});


// Component.tsx
import shallow from 'zustand/shallow';
import useStore from './store.ts';

const { data, setData } = useStore(
  ({ data, setData }) => ({ data, setData }),
  shallow
);

// Use 'data' and 'setData'

Mi entendimiento es que necesito usar la verificación de igualdad shallow al hacer este tipo de selector de múltiples propiedades, pero claramente este es un patrón muy verboso para escribir las claves varias veces y siempre agregar la opción shallow, especialmente si es más de una o dos claves.

Me gustaría crear un hook envoltorio para useStore que solo tome una lista de claves y maneje el resto de esto por mí, así:

// store.ts
import pick from 'lodash/pick';

const useStoreState = (keys: (keyof State)[]) =>
  useStore(state => pick(state, keys), shallow)

// Component.tsx
import { useStoreState } from './store';

// Ideally the return value here has the same type as the example above
const { data, setData } = useStoreState(['data', 'setData']);

Esto funciona perfectamente (al menos eso creo; no sé con certeza si el pick rompe lo del registro del selector), pero no puedo, por nada del mundo, hacer que la tipificación funcione correctamente.

Los tipos de Zustand son bastante complicados y realizan muchas inferencias, pero ¿alguien sabe una manera de lograr esto? ¿Me estoy perdiendo de otro enfoque para abordar este problema?

¡Salud!

Respuesta #1

Propondría la siguiente implementación (que tiene una sintaxis ligeramente diferente a la tuya, pero también es menos extensa):

// Utility function, to allow us to pick fields from the store
const useMulti = <T extends object, K extends keyof T>(
  useStoreFn: UseBoundStore<StoreApi<T>>,
  ...items: K[]
): Pick<T, K> => {
  return items.reduce(
    (carry, item) => ({
      ...carry,
      // No need to use useShallow here, as we fetch the objects one by one
      [item]: useStoreFn((state) => state[item]),
    }),
    {}
  ) as Pick<T, K>;
}

Luego, el envoltorio:

// Wrapper store hook
const useStoreState = (...items: Array<keyof State>) =>
  useMulti(useStore, ...items)

Entonces, al final, puedes acceder a tu tienda con lo siguiente:

const {data,setData} = useStoreState("data","setData")

Puedes revisar este ejemplo en vivo que hice si quieres jugar un poco