-
Notifications
You must be signed in to change notification settings - Fork 161
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
TypeScript: Reduce the amount of optional properties #1889
Comments
Also worth thinking about ways of handling this on a class level, e.g. |
Re: #1888 (comment)
I would like to try this out on something like |
I was also interested and gave it a shot this morning in-between meetings: interface LoadingTypeI {
foo?: number[]; // example of optional property
children: LoadingTypeI[];
}
interface LoadedTypeI {
// Doesn't need to be written out like this, you can use some conditional recursive
// TS magic to turn LoadingTypeI into this
foo: number[];
children: LoadedTypeI[];
}
// (Not shown: we've used a whole bunch of functions to incrementally create the data & now consider it loaded)
const data: LoadingTypeI = {
foo: [1,2,3],
children: [
{foo: [4,5,6], children: []},
{foo: [7,8,9], children: []},
]
}
function isLoadedTypeI(x: LoadingTypeI | LoadedTypeI): x is LoadedTypeI {
// type predicate <https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates>
// note that this is not free!
const properties_to_enforce = ['foo']; // Can use some TS magic to get this from the type itself
let complete=true;
const stack=[x]
while (stack.length) {
const node = stack.shift()
if (node===undefined) break; // TS limitation? Is this to do with sparse arrays? I though TS didn't know about them
for (const child of node.children) {stack.push(child)};
if (properties_to_enforce.some((p) => node[p]===undefined)) {
complete=false;
break;
}
}
return complete;
}
function toRedux(x: LoadingTypeI): LoadedTypeI {
if (isLoadedTypeI(x)) return x;
throw new Error(); // make sure this is handled by an error boundary in React
}
function dispatch(arg: any):void {console.log("DISPATCH:", arg)}
dispatch({type: "LOAD", data: toRedux(data)})
toRedux(data).foo.length; // no problem - foo is guaranteed to exist |
I played around with this after resurrecting an old prototype of adding types to interface MetadataState {
loaded: true; // discriminant property
title?: string;
mainTreeNumTips: number;
identicalGenomeMapAcrossBothTrees: boolean; and a "builder" type used when parsing the JSON and also as the default redux state: type IncompleteMetadataState = Partial<Omit<MetadataState, "loaded">> & {loaded: false}; At the end of dataset loading we run something like function convertIncompleteMetadataStateToMetadataState(meta: IncompleteMetadataState): MetadataState which will const mapState = (state: RootState) => {
if (!state.metadata.loaded) throw new Error("Something's gone seriously wrong")
return {metadata: state.metadata}
} It all felt a little bit hard. I'm sure it can be simplified. But the overall result is the ability to simplify rendering components as we don't need to constantly check a property is defined or not. |
from #1864 (comment), #1864 (comment), #1864 (comment), #1864 (comment)
#1854 is set to add types/interfaces with an abundance of optional properties. Example:
auspice/src/components/tree/phyloTree/types.ts
Lines 81 to 126 in 6ca81c6
This contributes to the abundance of
strictNullChecks
violations.From @jameshadfield in #1864 (review):
Possible solutions
Note: the solution may vary per type/interface
A
with lots of optional properties that we can use as we build up the data structure then turn this into a typeB
once it arrives in redux and we know all/most properties have been defined. Type predicates may be useful here. (ref)The text was updated successfully, but these errors were encountered: