ts(2322): 'Dispatch<SetStateAction<string>>' 不可分配给类型 '(value: OptionValue) => void'

时间:2021-04-29 08:36:22

标签: reactjs typescript tsx

我正在学习 Typescript generic 与 React 的集成,阅读 this article 并按照文章的代码进行操作,但是遇到了这个错误:

Type 'Dispatch<SetStateAction<string>>' is not assignable to type '(value: OptionValue) => void'.
  Types of parameters 'value' and 'value' are incompatible.
    Type 'OptionValue' is not assignable to type 'SetStateAction<string>'.
      Type 'number' is not assignable to type 'SetStateAction<string>'.ts(2322)
Select.tsx(15, 3): The expected type comes from property 'onChange' which is declared here on type 'IntrinsicAttributes & ISelectProps<OptionValue>'

这是我的全部代码:

import React, { useState } from 'react'
import Select from './Select'

const App: React.FC = () => {
  const targets = [
    { value: 'es3', label: 'ECMAScript 3' },
    { value: 'es5', label: 'ECMAScript 5' },
    { value: 'es2015', label: 'ECMAScript 2015' },
    { value: 'es2016', label: 'ECMAScript 2016' },
    { value: 'es2017', label: 'ECMAScript 2017' },
    { value: 'es2018', label: 'ECMAScript 2018' },
    { value: 'es2019', label: 'ECMAScript 2019' },
    { value: 2019, label: 'ECMAScript 2019' },
  ]

  const [target, setTarget] = useState('es2019')

  return (
    <>
      {/* <Select value={target} onChange={(value) => setTarget(value)} /> */}
      <Select options={targets} value={target} onChange={setTarget} />
    </>
  )
}

export default App

在此 Select 组件中使用泛型

/* eslint-disable react/destructuring-assignment */
import React, { useCallback } from 'react'

export type OptionValue = string | number
export type Option<T extends OptionValue> = {
  value: T
  label: string
}

interface ISelectProps<T extends OptionValue> {
  options: Option<T>[]
  value: T
  onChange: (value: T) => void
}

const Select = <T extends OptionValue>(props: ISelectProps<T>) => {
  const { options, onChange } = props
  const handleOnChange = useCallback((e: React.FormEvent<HTMLSelectElement>) => {
    const { selectedIndex } = e.currentTarget
    const selectedOption = options[selectedIndex]
    onChange(selectedOption.value)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    // eslint-disable-next-line unicorn/consistent-destructuring
    <select value={props.value} onChange={handleOnChange}>
      {options.map((option) => (
        <option key={option.value} value={option.value}>
          {option.label}
        </option>
      ))}
    </select>
  )
}

export default React.memo(Select)

代码沙盒是 here

我知道我可以明确地将 OptionValue 类型传递给 useState 来解决这个问题:

const [target, setTarget] = useState<OptionValue>('es2019')

但是我发现这个 article 没有这样做,而且这种方式不是 typescript 通用的,对吧?

enter image description here

2 个答案:

答案 0 :(得分:0)

在您引用的文章中,values 的所有 targets 要么是 string,要么是 number。在您的代码中,valuestargets 包含两者。

当你打电话

useState('es2019')

我认为 Typescript 推断 targetstring 类型(它不能推断这也可能是 number)。

这样

const [target, setTarget] = useState<OptionValue>('es2019')

你基本上是在告诉打字稿:我想用 string 初始化它,但它也可以是一个数字。

一个奇怪的解决方法也是:

const [target, setTarget] = useState(targets[6].value);

答案 1 :(得分:0)

这篇文章没有这样做,因为value中的targets总是有类型是string OR number。所以他可以在没有target的情况下声明状态OptionValue,只需要target的类型与value的类型相同。

就您而言,value 中的 targets 具有 stringnumber 类型。所以当你声明状态target时,你需要添加类型OptionValue