根据函数中的关键属性动态计算值的类型

IT技术 reactjs typescript react-hooks
2021-05-01 04:57:56

我有自定义钩子,它添加到接受键和值props的 useStata 函数;

import { Dispatch, SetStateAction, useCallback, useState } from 'react';

export type HandleModelChangeFn<T> = (key: keyof T, value: T[typeof key]) => void;

const useModel = <T>(initialModel: T): [T, HandleModelChangeFn<T>, Dispatch<SetStateAction<T>>] => {
  const [model, setModel] = useState<T>(initialModel);

  const handleChange = useCallback((key: keyof T, value: T[typeof key]) => {
    setModel((prevState) => ({
      ...prevState,
      [key]: value,
    }));
  }, []);

  return [model, handleChange, setModel];
};

export default useModel;

问题是,当我使用该函数时,第二个参数可以是接口中存在的任何类型,例如

interface User {
  name: string;
  age: number;
}

const initialModel: User = {
  name: '',
  age: 1
}

const [model, handleModelChange] = useModel(initialModel);

handleModelChange('age', 'John'); // should get error cause value is wrong type

key 参数可以是“name”或“age”,但第二个参数的类型是 string | 数字;

我想根据关键属性动态计算类型,比如当关键参数是“年龄”时,ts 会告诉我我传递了错误的值类型,导致年龄类型是数字;

有没有办法用typescript做到这一点?

2个回答

您需要在HandleModelChangeFn. 你需要推断一个key

import { Dispatch, SetStateAction, useCallback, useState } from 'react';

// added ----------------------->  <Key extends keyof T>
export type HandleModelChangeFn<T> = <Key extends keyof T>(key: Key, value: T[Key]) => void;

const useModel = <T,>(initialModel: T): [T, HandleModelChangeFn<T>, Dispatch<SetStateAction<T>>] => {
  const [model, setModel] = useState<T>(initialModel);

  const handleChange = useCallback((key: keyof T, value: T[typeof key]) => {
    setModel((prevState) => ({
      ...prevState,
      [key]: value,
    }));
  }, []);

  return [model, handleChange, setModel];
};

export default useModel;


interface User {
  name: string;
  age: number;
}

const initialModel: User = {
  name: '',
  age: 1
}

const [model, handleModelChange] = useModel(initialModel);

handleModelChange('age', 'John'); // error
handleModelChange('age', 2); // ok

操场

我会说你只是将一个对象作为参数传递给useCallback函数。

...
// I use the partial interface so you can pass the keys individually
const handleChange = useCallback(obj: Partial<User>) => {
  setModel((prevState) => Object.assign(prevState, obj));
}, []);
...

这样typescript只需要对象nameage属性以及使用任何一个时需要什么类型。

handleModelChange({ age: 'John' }); // Error
handlModelChange({ name: 'John' }) // Success