I'm trying to merge the selectElement and getElement functions into a reusable typescript generic function where I can use select the element(s) and still be able to call them as the following...
const btnArray = getElementTS<HTMLButtonElement>('.btn', '.container', true)
or
const singleBtn = getElementTS<HTMLButtonElement>('.btn', '.container')
P.S. Forgive any errors I might have made. I'm just a newbie.
// this function returns a single element
const selectElement = (selector, scope) => {
return (scope || document).querySelector(selector);
};
// this function returns either a single element or an element array
function getElement(selector, isList) {
let element = isList
? [...document.querySelectorAll(selector)]
: document.querySelector(selector);
if ((!isList && element) || (isList && !element.length < 1)) return element;
throw new Error(`Please double check your selector : ${selector}`);
}
interface Length {
length: number;
}
// merging the two functions with a typescript version
function getElementTS<E extends Length & HTMLElement & string>(
selector: string,
scope: E,
isList: boolean
) {
let element = isList
? ([...(scope || document).querySelectorAll(selector)] as E[])
: ((scope || document).querySelector(selector) as E);
if ((!isList && el) || (isList && !element.length < 1)) return el;
throw new Error(`Please double check your selector : ${selector}`);
}
console.log(getElementTS('.btn', '.main' ,true));
CodePudding user response:
In order to be explicit about what is returned based on the value of isList, I would suggest rewriting the logic in your function as such:
const parsedSelector = scope ? `${scope} ${selector}` : selector;
try {
if (isList) {
const element = [...document.querySelectorAll(parsedSelector)] as T[];
if (element.length < 1) throw Error;
return element;
} else {
const element = document.querySelector(parsedSelector) as T;
if (!element) throw Error;
return element;
}
} catch(e) {
throw new Error(`Please double check your selector : ${selector}`);
}
By doing this, you won't run into issues where TypeScript fails to narrow the type for element in this potentially problematic line:
if ((!isList && el) || (isList && !element.length < 1)) return el;
Moreover, it is necessary to create a parsedSelector to ensure that the scope and selector variables are properly concatenated. Using scope.querySelectorAll() in your original code will throw an error because scope is a string and not an Element.
Then, it is just a matter of adding function overloads to ensure proper correspondence between the provided arguments and the expected return type:
function getElementTS<T extends Element>(selector: string, scope: string): T
function getElementTS<T extends Element>(selector: string, scope: string, isList: true): T[]
function getElementTS<T extends Element>(selector: string, scope: string, isList: false): T
function getElementTS<T extends Element>(
selector: string,
scope: string,
isList?: boolean
): T | T[] {
// Function logic here
}
