microsoft / microsoft/TypeScript

Incorrect type inference at function callsite - wrapping type inside of a Tuple

Offen
#41,519 2 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Needs Investigation
Vorherrschende Sprache
Go
Sterne
111k
Forks
14.3k
Ø Merge
1 T. 19 Std.
Gemergte PRs (30 T.)
117

Beschreibung

TypeScript Version: 4.0.5
Tested this on v4.1.0-beta, same issue (in playground)
Tested on Nightly (v4.2.0-dev.20201112), same issue (in playground)
Tested on v3.9.7, same issue (in playground)
Tested on v3.8.3, same issue (in playground)
Tested on v3.7.5, same issue (in playground)

Search Terms:
tuple inference
argument not assignable
ts2345

Code

This is inside of a react typescript project, when upgrading react-scripts along with typescript to the latest (v4) versions.

We have a generic custom react hook that is having some tsc errors after upgrading - this one though I haven't been able to work through and it looks like a bug of some kind in how tsc is inferring the type.

import {
	useReducer,
	useRef,
	useLayoutEffect,
	useMemo,
        Reducer,
} from 'react'; //react: ^16.8.6
import _ from 'lodash'; //lodash: ^4.17.20

export interface IOptions {    
	/**
	 * The number of milliseconds to debounce the request. Defaults to no debounce.
	 * When `true`, defaults to 300 milliseconds.
	 */
	debounce?: boolean | number;
}

export interface ICallbacks<T> {
}

export interface IResult<T> {
	isLoading: boolean;
	response?: T;
	error?: any;
}

export interface IReturn<T> extends IResult<T> {
	refetch(): void;
}

type Action<T> =
	| { type: 'REQUEST' }
	| { type: 'SUCCESS'; payload: T }
	| { type: 'FAILURE'; payload: any };

const initialResults: IResult<any> = {
	isLoading: false,
	response: undefined,
	error: undefined,
};

function resultsReducer<T>(
	state: IResult<T>,
	action: Action<T>,
): IResult<T> {
	switch (action.type) {
		case 'REQUEST':
			return { isLoading: true, response: state.response };
		case 'SUCCESS':
			return { isLoading: false, response: action.payload };
		case 'FAILURE':
			return { isLoading: false, error: action.payload };
		default:
			return initialResults;
	}
}

function isCancellable<T = any>(
	fetch: RequestSender<T>,
): fetch is DebouncedRequestSender<T> {
	if ((fetch as any).cancel) return true;

	return false;
}

type DebouncedRequestSender<T = any> = _.DebouncedFunc<
	(
		url: string,
		options: IOptions,
		callbacks: ICallbacks<T>,
		dispatch: DispatchFunc<Action<T>>,
	) => () => void
>;

type RequestSender<T = any> = DebouncedRequestSender<T> | typeof performRequest;

function performRequest<T = any>(
	url: string,
	options: IOptions,
	callbacks: ICallbacks<T>,
	dispatch: React.Dispatch<Action<T>>,
): (() => void) {
	dispatch({ type: 'REQUEST' });

	return () => {};
};

export function useHook<T = any>(
	url: string,
	options: IOptions = {},
	callbacks: ICallbacks<T> = {},
): void {
	const [results, dispatch] = useReducer<
		Reducer<IResult<T>, Action<T>>
	>(resultsReducer, initialResults);

    // Use a ref to hold onto the callbacks. The prevents us from having to
	// include the callbacks in the useEffect dependencies.
	const callbacksRef = useRef(callbacks);
	callbacksRef.current = callbacks;

    // Create a debounced function, if needed. Also, memoize this function so
	// that it doesn't cause unnecessary requests.
	const fetch: RequestSender<T> = useMemo(() => {
		if (options.debounce) {
			// Use a default number of milliseconds if debounce is `true`.
			const wait = options.debounce === true ? 300 : options.debounce;
			return _.debounce(performRequest, wait);
		} else {
			return performRequest;
		}
	}, [options.debounce]);

    useLayoutEffect(() => {
		const cancelFetch: (() => void) | undefined = fetch(
			url,
			options,
			callbacksRef.current,
			dispatch,
		);

		return () => {
			// Cancel the debounced function call, if needed.
			if (isCancellable(fetch)) fetch.cancel();
		};
	}, [url, options, fetch]);

}  

Expected behavior:
I would expect this not to result in a tsc error. I'm not sure where it is inferring this tuple that is wrapping the actual type for the dispatch variable, but based on what intellisense is showing me in VS Code, the dispatch variable's type matches the expected type for the fetch function signature.

Actual behavior:
Argument of type '[Dispatch<Action<T>>]' is not assignable to parameter of type 'Dispatch<Action<T>>'. Type '[Dispatch<Action<T>>]' provides no match for the signature '(value: Action<T>): void'.ts(2345)

Playground Link:
TS Playground link

Related Issues:

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit der verknüpften TypeScript Playground-Reproduktion und untersuche die useReducer-Aufrufstelle, an der dispatch an fetch übergeben wird. Vergleiche den abgeleiteten Typ mit der gemeldeten tuple-wrapped-Diagnose über die aufgeführten TypeScript-Versionen hinweg. Als erledigt gilt die Aufgabe, wenn die Reproduktion eine Typprüfung besteht, ohne das beabsichtigte Verhalten des generischen Hooks zu ändern.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
react, typescript
Bereich
compilers
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Veraltet
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
35/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.