Here is the code for the create method.
create<+S: ____Styles_Internal>(obj: S): $ReadOnly<S> {
// TODO: This should return S as the return type. But first,
// we need to codemod all the callsites that are typing this
// return value as a number (even though it was opaque).
if (__DEV__) {
for (const key in obj) {
if (obj[key]) {
Object.freeze(obj[key]);
}
}
}
return obj;
},
How does this function work and what does the <+ operator do?
create<+S: ____Styles_Internal>(obj: S): $ReadOnly<S> {
Defines a function called create that has a parameter called obj. It is type annotated, using Flow with the following meaning:
The parameter obj is type S as denoted by obj: S
Where S is of type or a subtype of ____Styles_Internal as denoted by <+S: ____Styles_Internal>. + is a variance sigil signifying covariant types are accepted (types which are a subtype, along with the type itself)
The return type is a readonly version of obj as denoted by $ReadOnly<S>
if (__DEV__) {
for (const key in obj) {
if (obj[key]) {
Object.freeze(obj[key]);
}
}
}
for...in iterates over enumerable properties and if the value of the property is truthy, the value is frozen by Object.freeze. The value would normally be an object (see examples from React Native's documentation on stylesheet) so freezing it would prevent the object from being changed. These things only happen when the __DEV__
variable is true, which signifies the code is running in a development environment.
I did not author the code, so I can only speculate why it behaves like this:
This behaviour only occurs in development potentially because it could break production apps, based on the commit message from the author of code:
I don't really know if we have/need any safer way of rolling this out than just landing it. It can break if the object passed to StyleSheet.create is mutated afterwards but that isn't a practice anywhere I've seen.
I don't know why the test for whether to freeze or not is truthiness.
I'm not certain why the objects need freezing but I suspect it's to remove unintended side effects from mutating style objects as React likely compares style objects between renders by reference.
return obj;
},
Returns the object.
Further reading
Covariance and contravariance
Subtyping
Related
I've created a discriminated union that's later used to type props coming into a React component. A pared down sample case of what I've created looks like this:
type Client = {
kind?: 'client',
fn: (updatedIds: string[]) => void
};
type Server = {
kind: 'server',
fn: (selectionListId: string) => void
};
type Thing = Client | Server;
Note that the discriminant, kind, is optional in one code path, but is defaulted when it is destructured in the component definition:
function MyComponent(props: Thing) {
const {
kind = 'client',
fn
} = props;
if (kind === 'client') {
props.fn(['hey']);
// also an error:
// fn(['hey'])
} else {
props.fn('hi')
// also an error:
// fn('hey')
}
}
What I'm trying to understand is what's going on with this conditional. I understand that the type checker is having trouble properly narrowing the type of Thing, since the default value is separate from the type definition. The oddest part is that in both branches of the conditional it insists that fn is of type (arg0: string[] & string) => void and I don't understand why the type checker is trying to intersect the parameters here.
I would have expected it to be unhappy about non-exhaustiveness of the branches (i.e. not checking the undefined branch) or just an error on the else branch where the 'server' and undefined branches don't line up. But even trying to rearrange the code to be more explicit about each branch doesn't seem to make any impact.
Perhaps because the compiler simply can't narrow the types so tries an intersection so it doesn't matter which path--if the signatures are compatible then it's fine to call the function, otherwise you basically end up with a never (since string[] & string is an impossible type)?
I understand that there are a variety of ways I can resolve this via user-defined type predicates or type assertions, but I'm trying to get a better grasp on what's going on here internally and to find something a bit more elegant.
TS Playground link
It's an implementation detail in TS. Types are not narrowed when you are storing the value in a different variable. The same issue exists for the square bracket notation. You can refer to this question, it deals with a similar issue. Apparently, this is done for compiler performance.
You can fix your issue by using both props.fn and props.kind.
playground
Or write a type guard function.
I have written a React dropdown component which I can supply either of the following to :
An array of strings
An array of simple JSON object each of which contains two properties of text and icon.
My simple flow types look as follows:
type DropdownMenuItemType = DropdownMenuIconAndTextType | string;
type DropdownMenuIconAndTextType ={
text: string,
icon?: React$Element<React$ElementType>;
}
Previous versions of the component only supports strings. The addition of an element to support text and an icon is a new feature request which I am in the process of implementing. I don't want any breaking changes for my existing users.
Therefore within my component I try to attempt to convert any string supplied and wrap it in a DropdownMenuIconAndTextType so everything ends up as this type. Items that are already DropdownMenuIconAndTextType just remain so.
let Array<DropdownMenuItemType> originalItems =
let Array<DropdownMenuIconAndTextType> convertedItems = [];
{'English', 'French', 'German', {text: 'Dutch', icon : <SomeIcon />}};
items.forEach( (currItem: DropdownMenuItemType) => {
if(typeof currItem === DropdownMenuIconAndTextType){
convertedItems.push(currItem);
}
else {
convertedItems.push({text: currItem.toString()});
}
});
However flow has one error with :
if(typeof currItem === DropdownMenuIconAndTextType){
convertedItems.push(currItem);
}
and it says that currItem could still be a string and is incompatible with convertedItems despite it being type checked as DropdownMenuIconAndTextType.
What do I need to do to satisfy flow in this scenario ? Thanks in advance.
I believe you're mixing up the distinction between Flow's type code and JS code.
Inside of type signatures, typeof returns the type of a literal value, as described here. In the JS code that exists at runtime, such as in your if statement, typeof will just tell you whether something is a string, object, etc., as described here. So the left side of your conditional operator will evaluate to either "string", or "object", not to the actual Flow type of the variable.
On the right side of your conditional, you have the Flow type DropdownMenuIconAndTextType, which only exists at type-checking time, not at runtime. I'm kind of surprised that Flow doesn't give you an error because of that.
Try something like this:
if(typeof currItem !== 'string'){
convertedItems.push(currItem);
}
This will check whether the value that exists at runtime is a string or an object, which should work with Flow's type refinements.
I have a problem accessing my "_groups" property with the following code:
function mouseDate(scale){
var g = d3.select("#group")._groups[0][0]
var x0 = scale.invert(d3.mouse(g)[0]);
console.log(x0);
}
Result of my console.log:
Selection {_groups: Array(1), _parents: Array(1)}
_groups: Array(1)
0: Array(1)
0: g#group
When I compile the code I have the following error :
D:/Documents/starter-propre-angular4/src/app/pages/graphe-page/graphe.page.ts (782,32): Property '_groups' does not exist on type 'Selection<BaseType, {}, HTMLElement, any>'.
So my question is: Is there a solution to get the information in "_groups" all year round knowing that I am evolving into TypeScript using d3js
The _groups property is a private member of a Selection object and should as such not be accessed directly. (Side note: it is common convention in JavaScript that any member starting with an underscore denotes a private member. See, e.g., "Underscore prefix for property and method names in JavaScript").
Since the property is considered private it is not part of the public interface and is therefore not included in the TypeScript type declaration for the d3-selection module. Hence, you get the compiler error you witnessed. Although this actually will work in pure JavaScript the TypeScript compiler did exactly what it is supposed to do—namely, prevent you from doing some unsafe stuff.
Looking at the code you posted, though, it becomes apparent that you are not interested in the _groups property itself but rather in _groups[0][0] which internally refers to the first node of the selection. Fortunately, the selection.node() method will return exactly that first element. You can do it like this:
function mouseDate(scale){
var g = d3.select("#group").node();
var x0 = scale.invert(d3.mouse(g)[0]);
console.log(x0);
}
I can't explain why I get an error but code works. Is that compiler bug? (I use Visual Studio Code with Angular 2)
class A
{
fun(a: number)
{
return a+2;
}
}
A.prototype.F = function() { return "F here!"+this.fun(1); } // This makes error: The property 'F' does not exist on value of type 'A'
var a: A = new A();
console.log(a.F());
And bonus: This is not working at all! (no access to this.fun())
A.prototype.F2 = () => { return "F2 here!"+this.fun(1); } // ()=>{} is not working! cause _this is not defined!
...
console.log(a.F2());
Edit #1
As #seangwright said I need to use Module Augmentation but...
As far as it's working with simple example with my A class I can't make it work with Angular's ComponentFixture. This should solve my problem if I do this like in Typescript example:
declare module '#angular/core/testing' // I was trying without this line and with 'global' instead of '#angular/core/testing' but nothing helps
{
interface ComponentFixture<T>
{
TestOf(cssSelector: string): string;
}
}
But I still get an error:
'ComponentFixture' only refers to a type, but is being used as a value
here.'
at this point:
ComponentFixture.prototype.TextOf = function(cssSelector: string): string
{
...
}
There is even more errors, for example when I try to use it:
let fixture: ComponentFixture<EditableValueComponent>;
fixture = TestBed.createComponent(EditableValueComponent);
I got:
'ComponentFixture' is not assignable to type
'ComponentFixture'. Two different types with
this name exist, but they are unrelated. Property 'TestOf' is
missing in type 'ComponentFixture'
So again: Code works but has many compilation errors. Or maybe I'm missing something obvious?
I get the feeling you are a C# developer based on how you format your code.
Part 1
In Typescript once you declare your class, the type system expects it to have the properties (shape) you define and that's it.
The more of the type system you want to use, the less dynamic your objects will/can be.
That said, the reason your code runs (transpiles) correctly is because this is an error in the context of Typescript's structural type system, not Javascript's dynamic type system. So Typescript will tell you A doesn't have a property F at compile time, but Javascript doesn't care that it's added at runtime.
One solution is to merge the class with an interface
class A {
fun(a: number) {
return a + 2;
}
}
interface A {
F(): string;
}
A.prototype.F = function () { return "F here!" + this.fun(1); }
var a: A = new A();
console.log(a.F());
Another would be to temporarily abandon the type system
class A {
fun(a: number) {
return a + 2;
}
}
(A.prototype as any).F = function () { return "F here!" + this.fun(1); }
var a: A = new A();
console.log((a as any).F());
But that becomes verbose and prone to errors and loses the benefits that a type system brings.
You mention you are using Typescript with Angular 2. You could write in ES2015 if you wanted a more dynamic syntax. But then you will lose some of the benefits that Angular 2 gets from using Typescript (better tooling, smaller deployments).
Part 2
The reason your second example doesn't work at all has nothing to do with Typescript and everything to do with Scope (or execution context) in Javascript, specifically ES2015 arrow functions.
An arrow function does not create its own this context, so this has its original meaning from the enclosing context.
Unlike in your first example you are not using the traditional function declaration syntax and instead are using the () => {} arrow function syntax. With your first example
A.prototype.F = function() { return "F here!"+this.fun(1); }
this refers to whatever context F() is going to be executing in. Since you define it on the prototype of A it is going to be executing in the context of A. A has a .fun() method so this.fun() is going to be the same one defined in your class above.
With your second example, F2 is not going to be executing in the context of A despite being defined as a method of its prototype. The arrow function syntax is instead going to allow F2 to run in the context of the enclosing context which is the global window object unless you are running in strict mode in which case
in browsers it's no longer possible to reference the window object through this inside a strict mode function.
So this will be undefined and calling fun() on undefined is going to throw an error.
Try adding a console.log(this) to your F2 function.
A.prototype.F2 = () => { console.log(this); return "F2 here!"+this.fun(1); }
When you run the transpiled Javascript you will probably see Window logged out to the console, and then probably an error like Uncaught TypeError: _this.fun is not a function
Use the Typescript Playground to write some Typescript, see what the tooling tells you, what transpiled Javascript is created and then run it to see if your Javascript is correct.
while playing around with typescript I ran into then following interesting behavior:
class ExtArray<U> extends Array<U> {
constructor(...args : U[]) {
super(...args);
}
public contains(element : U) : boolean {
var i = this.indexOf(element);
return i !== -1;
}
}
var test : ExtArray<string> = new ExtArray("a", "b", "c");
test.push("y");
console.log(test.length); // 1
console.log(test[0]); // y
console.log(test[1]); // undefined
console.log("Has a: " + test.contains("a")); // Has a: false
console.log("Has y: " + test.contains("y")); // Has y : true
I've added the output of the console.log statements as comments.
See this typescript playground for an executable example and the javascript code.
As you can see it seems as if the elements passed to the constructor are not added to the array.
The section about extending expression in Whats new in Typescript suggests that it should be possible to extend the native Array type like that in typescript 1.6.
Also I didn't find anything in the typescript language reference,
that explains this behavior.
Most of the other questions about extending Arrays I found here are at least one year old and usually talk about a pre-1.0 version of typescript and therefore suggest to set up the prototype chain directly.
I seriously don't see what is going wrong here and I'm starting to suspect a typescript bug.
Or at least some kind of undocumented restriction for extending Arrays.
What goes wrong here?
It's a little easier to understand what's going on if you JSON.stringify() your object:
var test : ExtArray<string> = new ExtArray("a", "b", "c");
test.push("y");
// outputs {"0":"y","length":1}
document.writeln(JSON.stringify(test));
If you instead new-up a native Array, the resulting object is quite a bit different:
var test : Array<string> = new Array("a", "b", "c");
test.push("y");
// outputs ["a","b","c","y"]
document.writeln(JSON.stringify(test));
I agree with you that the documentation seems to imply that the subclass's constructor should behave the way you're expecting. Even stranger, I seem to get inconsistent results when testing whether or not the subclass is an Array using the methods described here:
test.constructor === Array // false
test instanceof Array // true
Array.isArray(test) // false
I would suggest opening an issue on the TypeScript GitHub repository. Even if this is the expected behavior, the official documentation is misleading and should clarify what exactly is expected when native objects are subclassed.