I am using rest parameter to do some operations, but got some properties disappeared in result.
The data format of this.props looks like this:
```
this.props = {
data: [{...}, ...],
...
}
```
And I tried to rest it in this way:
```
let {showWaveAnimation, ...otherProps} = this.props;
console.log('data' in otherProps); // false
console.log('data' in this.props); //true
```
Why would 'data' property lost after I tried rest operation?
According to MDN, I found the description:
rest parameters are only the ones that haven't been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function;
What does separate name here means? Is it means properties that extends from its prototype would not be rest? But after I tried the following statements, I got the same result, I am confusing.
```
class Parent {
constructor() {
this.props = {
data: '123',
};
}
}
class Child extends Parent {
constructor() {
super();
this.props.childProp = 'test';
let {test, ...otherProps} = this.props;
console.log('data' in this.props, 'data' in otherProps);
// true true
}
}
new Child();
```
The former code behaviors abnormal after Babel's transpiling, could it be Babel's plugin problem?
ADD: I found this concept may be more precise in describing my rest operations. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Rest_in_Object_Destructuring
I figured out why 'data' disappeared in my rest result, it's the enumerable property of 'data' field!
Once, the property is set as enumerable: false, we can't get it with the operation of rest in object destructuring.
According to MDN
The Rest/Spread Properties for ECMAScript proposal (stage 3) adds the rest syntax to destructuring. Rest properties collect the remaining own enumerable property keys that are not already picked off by the destructuring pattern.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Rest_in_Object_Destructuring
I think this example can explain my ideas more precisely:
const person = {
name: 'Dave',
surname: 'Bowman'
};
Object.defineProperty(person, 'age', {
enumerable: false, // Make the property non-enumerable
value: 25
});
console.log(person['age']); // => 25
const clone = {
...person
};
console.log(clone); // => { name: 'Dave', surname: 'Bowman' }
The rest parameter allows you to get all the remaining attributes not already selected by identifier preceding it in the assignment.
Let's take a simple example:
const obj = {
a: 1,
b: 2,
c: 3
}
const { a, ...rest } = obj;
console.log(a);
console.log(rest);
Above obj has a property named a so it is destructured from obj and assigned to the variable a, then all other properties are assigned in an object to rest.
However, what you have is more like:
const obj = {
a: 1,
b: 2,
c: 3
}
const { d, ...rest } = obj;
console.log(d);
console.log(rest);
Here obj doesn't have a property named d. So it is destructured from obj as undefined and assgined to the variable a. Then all other properties not already assigned, i.e. a, b and c are given to rest.
In your code otherProps has the same properties as this.props, because object this.props doesn't have a property named test. In other words, in order to destructure an object you have to know its structure.
rest parameters are only the ones that haven't been given a separate name (i.e. formally defined in function expression), while the arguments object contains all arguments passed to the function;
What does separate name here means? Is it means properties that extends from its prototype would not be rest? But after I tried the following statements, I got the same result, I am confusing.
Given a function definition function foo(a, b, ...c) {}, arguments a, and b are the named parameters, whereas the rest of the passed arguments are spread into the last variable c. Now, c is a named variable, but any parameters passed from the third on will just be accessible in c, i.e. c[0].
Now, your console.log error is stemming from your use of the in operator. You defined an object const foo = {'data': 'bar'}. 'data' in foo is true, but 'blarg' in foo is false since foo doesn't contain a key/property named 'blarg'.
You're actually using the spread operator to destructure your props object into several named variables.
const object = {
data: 'foo',
other: 'bar',
moreData: [1,2,3],
};
// Example usage of the spread operator
const { data, ...allTheRest} = object; // data is destructured, other and moreData into allTheRest
console.log(data);
console.log(allTheRest);
// in operator
console.log('data' in object); // true
console.log('data' in allTheRest); // false
console.log('moreData' in allTheRest); // true
// This is an example of the rest operator
const foo = (...allTheArgs) => {
allTheArgs.forEach(e => console.log(e));
}
foo(1,2,3); // all the args are collected into a single array variable in the function
const bar = (a, b, ...c) => {
console.log(`a: ${a}`);
console.log(`b: ${b}`);
console.log(`c: ${c}`);
};
bar('this is first param', 42, 'foo', 'bar', '1', 2, 'stackoverflow');
Related
i am having an hard time understanding the usability of passing an object as a parameter of a function.
I am trying to learn to use Next.js and they usually have this way of interacting with their code.
Can someone explain me in short words the usability of it ? I have an example listed below.
Thank you !
function Page({ data }) {
// Render data...
}
Understanding the usability of passing an object as an argument of a function.
Explained me with an example the usability of it.
This syntax is called Destructuring assignment.
So { data } in function Page({ data }){ ... is not an object definition, but a destructuring of the object passed as the first argument of the function,
which results in a variable data available in the function body.
More details ...
Preface
I assume you are familiar with basic destructuring assignments, which look like e.g.:
const person = { name: 'Alice', age: 20 }; // <-- define object
const { name, age } = person; // <-- destructure properties from the object
But note that you not necessarily need to bind this object to a variable name to be able to destructure it:
const { name, age } = { name: 'Alice', age: 20 }; // <-- define object and destructure it right away, without a variable name
Destructuring of arguments in React components
In a React function-component you can pass properties and access them inside the component like this:
function Page( props ) {
return <div>
Data: { props.data }
</div>;
};
function App() {
return <Page data="Hello" />;
};
You can also destructure the props at the beginning of the function, so that you don't have to type props.... for each property:
function Page( props ) {
const { data } = props;
return <div>
Data: { data }
</div>;
};
React components always have the same arguments, so you basically always have the props argument. Here "props" is a variable name that is bound to the props object (which contains all the properties passed to the component).
But you almost never actually need the props object itself, you likely only want the properties inside it.
So you don't really need to bind the object with the properties to the name "props", only to destructure it right away in the next line,
which makes the props object obsolete for the rest of the function body.
For this, javascript has a "shortcut syntax" to destructure an argument directly in the function arguments section, without specifying a name for the argument:
function Page({ data }){
return <div>
Data: { data }
</div>;
};
This is common syntax. I wouldn't say that it is better or worse than other options.
The answer was great and one huge positive benefit of an object prop is you dont have to pass parameters in a specific order, which is super helpful when function argument list becomes long.
This is called destructing. Works for objects and arrays:
const obj = { x: 'hello', y: 'world', z: 'whats up' };
const { x } = obj;
console.log('obj.x is', obj.x);
console.log('x is', x);
const { y, z: myNewName } = obj;
console.log('obj.y is', obj.y);
console.log('y is', y);
console.log('obj.z is', obj.z);
console.log('myNewName is', myNewName);
// console.log(z) will error
const arr = ['value', v => console.log(v)];
const [whateverIWantToCallIt, namesDoNotMatterJustTheIndex] = arr;
console.log('accessing from deconstruction of arr')
console.log(whateverIWantToCallIt);
console.log(namesDoNotMatterJustTheIndex('whats up'));
console.log('accessing from arr')
console.log(arr[0]);
console.log(arr[1]('whats up'));
I have an enum in typescript like below:
export enum XMPPElementName {
state = "state",
presence = "presence",
iq = "iq",
unreadCount = "uc",
otherUserUnreadCount = "ouc",
sequenceID = "si",
lastSequenceID = "lsi",
timeStamp = "t",
body = "body",
message = "message"
}
And wants to de-structure its value, How can we do this in Typescript?
const { uc, ouc, msg, lsi, si, t, body } = XMPPElementName;
update
As #amadan mentioned, we can use Assigning to new variable names as in Mozilla doc say Destructuring_assignment, like below:
Assigning to new variable names
A property can be unpacked from an object and assigned to a variable with a different name than the object property.
const o = {p: 42, q: true};
const {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true
And the method is very good to solve this problem, but if you need to access all items without the need to explicitly define them, you can either on of these two mentiond tag1 tag2
const { uc, ouc, msg, lsi, si, t, body } = XMPPElementName;
This doesn't work because XMPPElementName doesn't have an element named uc (and equivalently for others). If you explicitly name your keys, it will work:
const {
unreadCount: uc,
otherUserUnreadCount: ouc,
message: msg,
lastSequenceID: lsi,
sequenceID: si,
timeStamp: t,
body: body,
} = XMPPElementName;
it will work. Alternately, you can just use variables with names that are equal to the keys, not the values:
const {
unreadCount,
otherUserUnreadCount,
message,
lastSequenceID,
sequenceID,
timeStamp,
body,
} = XMPPElementName;
You want an enum value-to-value map. Like you've said enum in JS is just a POJO. You can create a utility type to help generate the correct type.
type EnumValueMap<T extends { [k: string]: string }> = { [K in T[keyof T]]: K }
function convertEnumValuesToObject<T extends { [k: string]: string }>(enumerable: T): EnumValueMap<T> {
return (Object as any).fromEntries(Object.values(enumerable).map(v => [v, v]))
}
Playground Link
As we know, in typescript an enum is like a plain old javascript object(at-least what the playground js-output is showing or the log showing):
one way is using a function which generates a new object with {value:value} structure like below:
export function convertEnumValuesToObject<T>(enumObj: T): { [index: string]: T[keyof T] } {
const enum_values = Object.values(enumObj);
return Object.assign({}, ...enum_values.map(_ => ({ [_]: _ })));
}
const { uc, ouc, msg, lsi, si, t, body } = convertEnumValuesToObject(
XMPPElementName
);
It would be great to see answers in typescript?
This may be helpful for anyone looking for a quick and easy answer - yes you can (at least as of now). This works for enums with and without assigned values as far as I can tell.
enum MyEnum {
One,
Two,
Three
}
const { One, Two, Three } = myEnum;
console.log({ One, Two, Three }) // {One: 0, Two: 1, Three: 2}
enum Status {
None = '',
Created = 'CREATED',
Completed = 'COMPLETED',
Failed = 'FAILED',
}
const { None, Created, Completed, Failed } = Status;
console.log(None, Created, Completed, Failed) // '', 'CREATED', 'COMPLETED, 'FAILED'
Please write me back if I'm wrong or you found any weirdness when testing yourself.
I want to do something when creating a string, for example:
String = new Proxy(String, {
construct: (t, a) => {
return { a: 123 }
}
})
console.log(new String('q')) // { a: 123 }
However, if you use primitives, it doesn't work.
String = new Proxy(String, {
construct: (t, a) => {
return { a: 123 }
}
})
console.log('1') // Expected: { a: 123 }, Actual: 1
Is there any way?
then the second question is,
when the runtime converts primitives, can I proxy the process?
var a = '123' // This is a primitive
console.log('123'.substring(0,1)) // Actual: 1
// The runtime wraps the primitive as a String object.
// then uses a substring, and then returns the primitive.
now:
String = new Proxy(String, {
construct: (t, a) => {
return { a: 123 }
},
apply: (target, object, args) => {
return { a: 123 }
}
})
console.log('1'.a) // Expected: 123 , Actual: undefined
I know I can add 'a' to the prototype of String to achieve the expectations.
But I want to be able to proxy access to arbitrary attributes for primitives.
(Is '1'.*, Is not jsut '1'.a)
Is there any way?
Thank you for your answer.
No, this is not possible. Proxies only work on objects, not on primitives. And no, you cannot intercept the internal (and optimised-away) process that converts a primitive to an object to access properties (including methods) on it.
Some of the operations involving primitives do use the methods on String.prototype / Number.prototype / Boolean.prototype, and you can overwrite these methods if you dare, but you cannot replace the entire prototype object for a proxy.
I have a fairly deep interface declared that looks something like this:
export interface Job {
JobId: JobId; // type JobId = string
UserId: UserId; // type UserId = string
JobName: string;
AudioFile: JobAudioFile; // this is an interface
Status: JobStatus; // this is an enum
Tracks: JobTracks[]; // 'JobTracks' is an enum
Results: JobResults; // this is an interface
Timestamps: JobTimestamps // interface
}
Most of the members of this interface are themselves interfaces, with the general architecture following this pattern of using enums, strings, arrays and more interfaces. All code is written as TypeScript, transpiled down to JS and uploaded to AWS as JS. (Node 8.10 is running on AWS)
At one point in the code, I need to make a deep copy of a Job instantiation which was passed in as a function parameter:
export const StartPipeline: Handler = async (
event: PipelineEvent
): Promise<PipelineEvent> => {
console.log('StartPipeline Event: %o', event);
const newBucket = await copyToJobsBucket$(event.Job);
await deleteFromOriginalBucket$(event.Job);
console.log(`Job [${event.Job.JobId}] moved to Jobs bucket: ${newBucket}`);
event.Job.AudioFile.Bucket = newBucket;
event.Job.Status = Types.JobStatus.Processing;
// update the job status
// VVV PROBLEM OCCURS HERE VVV
const msg: Types.JobUpdatedMessage = new Types.JobUpdatedMessage({ Job: Object.assign({}, event.Job) });
await Send.to$(event.Job.UserId, msg);
return { ...event };
};
The definition of the JobUpdatedMessage:
export class JobUpdatedMessage extends BaseMessage {
constructor(payload: { Job: Types.Job }) {
console.log('Incoming: %o', payload);
const copy: object = { ...payload.Job };
// VVV PROBLEM ON NEXT LINE VVV
const filtered = JobUtils.FilterJobProperties(copy as Types.Job);
super(MessageTypes.JobUpdated, filtered);
}
}
The problem is after the call to JobUtils.FilterJobProperties, payload.Job has also been mutated in an undesirable and unexpected way.
Here's the implementation of JobUtils.FilterJobProperties:
export const FilterJobProperties = (from: Types.Job): Types.Job => {
const fieldsToRemove: string[] = [
'Transcripts.GSTT',
'Transcripts.WSTT',
'Transcripts.ASTT',
'TranscriptTracks',
'Transcripts.Stream.File',
'Transcripts.Stream.State',
'AudioFile.Bucket',
'AudioFile.S3Key',
];
let job: Types.Job = { ...from }; // LINE ONE
fieldsToRemove.forEach(field => _.unset(job, field)); // LINE TWO
return job;
};
(I'm using the lodash library here)
The line market 'LINE TWO' is also mutating the from function parameter, even though on 'LINE ONE' I'm doing what I think is a deep clone of from.
I know that this is the case because if I change 'LINE ONE' to:
// super hard core deep cloning
let job: Types.Job = JSON.parse(JSON.stringify(from));
... everything works as expected. from is not mutated, the resulting JobUpdatedMessage is as expected, and StartPipeline's event parameter doesn't have a bunch of properties removed from event.Job.
I struggled with hours on this, including relearning everything I believed I knew about cloning objects in Es6 using the spread operator.
Why was 'LINE ONE' mutating the input as well?
Spread operator does shallow cloning same as Object.assign()
Shallow-cloning (excluding prototype) or merging of objects is now
possible using a shorter syntax than Object.assign().
Spread operator
An example to understand spread operator and shallow cloning.
let obj = { 'a': { 'b' : 1 },'c': 2}
let copy = {...obj}
copy.c = 'changes only in copy' //shallow-cloned
copy.a.b = 'changed' // still reference
console.log('original\n',obj)
console.log('\ncopy',copy)
Using spread operator object is shallow cloned so all the first level properties will become a copy while all the deeper level properties will still remain the references.
so as you see in example c property doesn't affect the original object since it is one first level depth, on the other hand b property changes affect the parent properties because it is at deep level and is still reference.
I'm building the concept of a family for a product, with members being of different types (accountHolder, payingCustomer, student, and so on). Originally I built these as sub-classes of FamilyMember, but I ended up with some repeated code and eventually bumped into a significant problem: a student of our platform can also be the sole payingCustomer and accountHolder.
Given how object composition is widely touted as a good idea in JS, I decided to go that route. However, the methods of a particular object type (e.g. accountHolder) can't access properties of the instantiated object, if the property belong to another object type (e.g. student).
To make this more objective I've decided to replicate the behaviour using the following code:
const person = (props) => {
let state = {
name: props.name,
}
state.isOfAge = () => {
// state.isAdult is always undefined because
// isAdult doesn't exist in this object
return state.isAdult === true
}
return state
}
const adult = (props) => {
return {
isAdult: true,
}
}
const factory = (props) => {
return Object.assign({}, person(props), adult(props))
}
const john = factory({
name: 'John',
})
console.clear()
console.log(john) // { isAdult: true, name: "John", isOfAge... }
console.log(john.isOfAge()) // false
I was expecting john's method isOfAge to be able to access the property isAdult, since it's in the object. However, conceptually I understand why it doesn't work: isOfAge is a method of state, not the resulting adult instance.
If I were using classes or even a traditional prototype/constructor mechanism I knew how to make it work (e.g. attaching to prototype). With object composition I've no idea how to get there, probably due to lacking experience with FP.
Thanks for the help!
You can use this instead of state inside isOfAge. That way, the this will be deduces when the method isOfAge gets called, it will be bound to whatever object it is called on. Though, you'll have to use a regular function instead of an arrow one for that to work (arrow functions don't have a this):
const person = (props) => {
let state = {
name: props.name,
}
state.isOfAge = function() { // use a regular function
return this.isAdult === true // use this here instead of state
}
return state
}
const adult = (props) => {
return {
isAdult: true,
}
}
const factory = (props) => {
return Object.assign({}, person(props), adult(props))
}
const john = factory({
name: 'John',
})
console.log(john);
console.log(john.isOfAge()); // returns 'true' because 'this' inside 'isOfAge' will be 'john'
Object Composition
All objects made from other objects and language primitives are composite objects.
The act of creating a composite object is known as composition.
...
Concatenation composes objects by extending an existing object with new properties, e.g., Object.assign(destination, a, b), {...a, ...b}.
... The Hidden Treasures of Object Composition
So from your pattern and use of a factory function it looks like concatenation? The demo below is a concatenation composition. Note the parenthesis wrapped around the brackets of payment:
const payment = (status) => ({...})
this allows payment to be returned as an object instead of a function. If you have data that's a little more flexible, you'll need less methods. name: string and age: number are the properties I used considering it practical or in your case name: string and adult: boolean.
Demo
const payment = (status) => ({
adult: () => status.age > 17 ? true : false,
account: () => status.adult() ? 'holder' : 'student'
});
const member = (name, age) => {
let status = {
name,
age
};
return Object.assign(status, payment(status));
};
const soze = member('Kaiser Soze', 57);
console.log(soze);
console.log(soze.adult());
console.log(soze.account());
const jr = member('Kaiser Soze Jr.', 13);
console.log(jr);
console.log(jr.adult());
console.log(jr.account());