javascript - Hvordan skrive egendefinert hook useStateWithCallback React TypeScript


Jeg har problemer med å skrive følgende tilpassede React hook, jeg er ny på TypeScript og dette skaper litt forvirring.

const useStateCallback = (initialState: any) => {
  const [state, setState] = useReducer<Reducer<any, any>>((state, newState) => ({ ...state, ...newState }), initialState)
  const cbRef = useRef(null)

  const setStateCallback = (state, cb) => {
    cbRef.current = cb
    setState(state)
  }

  useEffect(() => {
    if (cbRef.current) {
      cbRef.current(state)
      cbRef.current = null
    }
  }, [state])

  return [state, setStateCallback]
}

Bør jeg bruke any her, og i så fall hvordan bruker jeg any riktig? Siden dette er en universell funksjon og kan brukes overalt, hvordan skriver jeg den korrekt?

Jeg la til noen av mine forsøk rett inni eksemplet mitt, og, som du kan se, stopper jeg, fordi fra mitt ståsted vil det ende opp med ingenting annet enn any-typer.

Svar #1

Først må du få denne useStateCallback til å akseptere en generisk parameter som representerer tilstanden din. Du kommer til å bruke den parameteren mye. Vi kaller den S for tilstand.

function useStateCallback<S>(initialState: S) { ... }

Neste opp er redukteren. Det ser ut som du bare vil ha en enkelt handling som aksepterer en Partial av S som blir slått sammen med staten. Så for de to generiske parameterne i Reducer bruker vi S for staten og Partial<S> for handlingen.

const [state, setState] = useReducer<Reducer<S, Partial<S>>>(
  (state, newState) => ({ ...state, ...newState }),
  // state is implicitly typed as: S
  // newState is implicitly typed as: Partial<S>

  initialState
)

Eller du kunne skrevet inn argumentene til reducer-funksjonen, og disse typene ville blitt utledet, noe som ser litt renere ut, etter min mening.

const [state, setState] = useReducer(
  (state: S, newState: Partial<S>) => ({ ...state, ...newState }),
  initialState
)

For å opprette referansen, må vi gi den en type for callback-funksjonen, kombinert med null siden den kanskje ikke alltid inneholder en verdi:

const cbRef = useRef<((state: S) => void) | null>(null)

for setStateCallback, må vi akseptere en Partial<S> som skal flettes med hele tilstanden, og en callback som har hele tilstanden som sitt eneste argument:

function setStateCallback(state: Partial<S>, cb: (state: S) => void) {
  cbRef.current = cb
  setState(state)
}

Effekten din bør være bra som den er.

Det siste du må gjøre, er å endre returverdien din til:

return [state, setStateCallback] as const

Dette er nødvendig fordi TypeScript ser dette som et array som standard, men du ønsker at det skal være en tuple. I stedet for et array av (S | Callback)[] ønsker du at det skal være en tuple med nøyaktig to elementer av typen [S, Callback]. Å legge til as const på arrayet forteller TypeScript å behandle arrayet som en konstant og låse de typene i de riktige posisjonene.

Setter man alt dette sammen, får man:

import React, { useReducer, useRef, useEffect, Reducer } from 'react'

function useStateCallback<S>(initialState: S) {
  const [state, setState] = useReducer<Reducer<S, Partial<S>>>(
    (state, newState) => ({ ...state, ...newState }),
    initialState
  )
  const cbRef = useRef<((state: S) => void) | null>(null)

  function setStateCallback(state: Partial<S>, cb: (state: S) => void) {
    cbRef.current = cb
    setState(state)
  }

  useEffect(() => {
    if (cbRef.current) {
      cbRef.current(state)
      cbRef.current = null
    }
  }, [state])

  return [state, setStateCallback] as const
}

// Type safe usage
function Component() {
  const [state, setStateCallback] = useStateCallback({ foo: 'bar' })

  console.log(state.foo)

  setStateCallback({ foo: 'baz' }, newState => {
    console.log(newState.foo)
  })

  return <div>{state.foo}</div>
}

Lekeplass

Relaterte artikler