-
Notifications
You must be signed in to change notification settings - Fork 162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in 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: