-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Custom Immutable Classes, Round 2 #368
Conversation
Cool @tgriesser thanks for the update! I will play with this branch when I have time. |
Allows for having a single point of extension, for adding features such as a metadata API.
This adds the ability to provide factory functions at each key in a record. Those factory functions will produce values: both default values and any value provided to `set` is first passed into the factory function. You could also think of these as per-field value mappers. Most importantly, this allows for the ability to define a Record in terms of its types rather than in terms of its default values and use those types to parse incoming JSON (or any other iterable structure) in a deeply nested way. This also adds the `Nullable` function in the exports. `Nullable` is a factory function transform. It takes a function which accepts a value T and returns a value R (generalizing Record functions taking an object and returning a Record), and returns a new function which accepts a value T|null and returns a value R|null. When you pass null to this function, you get null back. This is useful for the case where you want to type a field, but want the default value to be `null`. Finally, this allows you to define the fields of a Record in a thunk (inline function). Evaluation of the thunk will be deferred until the creation of the first Record instance, which allows a Record to define circular types. These concepts come together like so: ``` var Point = Record({ x: Number, y: Number }); var Line = Record({ from: Point, to: Point }); Line(); // Record { "from": Record { "x": 0, "y": 0 }, "to": Record { "x": 0, "y": 0 } } Line({from:{y:123}}); // Record { "from": Record { "x": 0, "y": 123 }, "to": Record { "x": 0, "y": 0 } } ``` The nullable and lazy thunk definitions come in handy for recursive types. We can use the ES6 Arrow function to define our thunk. ``` var TreeNode = Record(()=>({ left: Nullable(TreeNode), right: Nullable(TreeNode) })); TreeNode(); // Record { "left": null, "right": null } TreeNode({left:{right{}}}); // Record { "left": Record { "left": null, "right": Record { "left": null, "right": null } }, "right": null } ``` This change is a breaking change for a couple reasons: * A function with own properties can no longer be used to define the initial keys. This seems like a very dubious thing to be doing in the first place. * A function can no longer be stored as a default value in a Record. This also seems like something you should not do, however I'm aware that there are some who use the Record slots as a way to store methods for computed properties and update functions. These use cases will break. As an example of the second point: This bad pattern: ``` var MyType = Record({ x: 3, y: 4, sum: function() { return this.x + this.y; } }); var mine = MyType(); mine.sum(); ``` Will cease to work after this diff. Instead, the function will be expected to be a value factory. This bad pattern can be turned into a good pattern by making use of the prototype: ``` var MyType = Record({ x: 3, y: 4 }); MyType.prototype.sum = function() { return this.x + this.y }; var mine = MyType(); mine.sum(); ```
@tgriesser I has started to use this patch and it's awesome! var u = User({first_name: 'Joe', last_name: 'Green'})
// constructor changes arguments, and
// {first_name: 'cool Joe', last_name: 'Mr. Green'}
// will being received by Map Can I do it? Thanks! |
I suppose you could provide a custom |
* master: (42 commits) ensure mergeIn family works correctly if deep position is not mergable build hasIn does not take a notSetValue Fix issue where slice of unknown length Seq can throw Release 3.7.2 Update to Patents grant v2 Fix readme restrict to typescript 1.4.x until 1.5 is out of alpha Release 3.7.1 use ES6 naming "includes" - alias "contains" to be non-breaking Fix link to type defs Release 3.7.0 merge/union dont work correctly inside withMutations when the mutable is empty. better name for constructor Allow Record factories to be used as a type cast pass through Lazily initialize Record types Ensure default name for Record in toString Shave another couple seconds off test runtime cleanup pre-processor and build dist files from recent pulls Update type definitions in dist/ from the type-definitions/ folder ... Conflicts: dist/immutable.js dist/immutable.min.js src/Map.js src/Record.js src/Set.js
b88891a
to
122ff95
Compare
This looks neat. Hopefully this lands. |
I like this too 👍 |
Updated this against 4.0, even though apparently GH doesn't let you change the target branch in a pull request. As fun as it's been to use in my own projects, I'm a bit wary this will open pandora's box a bit, people might start trying to do things with this that they really shouldn't be doing in general. Wouldn't mind just keeping this as an upstream fork for awhile to see if anyone has other opinions or ideas. |
Yeah - the more I'm thinking about it, the more I'm concerned about the pandora's box that you brought up just now (and before) @tgriesser. I agree with your conclusion that we should keep floating this as a fork to investigate the compelling use cases. In previous conversations you compelled me to include a metadata API which I think handles a lot of the cases that subclassing was desired for - and that's still on the docket. |
Awesome to hear |
In my project on React.js I want to wrap certain data in type classes to perform strict datatype checking using
But since all props came from store they must be considered as immutables. So, to typify raw data I intend to use this fork of ImmutableJS:
Can you, please, comment on this pattern. Is it appropriate use of proposed feature or there exist simpler solution? |
@vtambourine I'm a little confused about your prop-types example. Are you saying that you have arrays of Immutable Maps? Or do you have Immutable Lists of Immutable Maps? Or Records? Also note that React propTypes are just functions which do or do not return an Error, you can always write your own propType function that will do the custom validation you want. |
Yes, you're right about propType custom validation functions. I can use it in my project, but how to handle situations with passing certain props deep into children components? For example, consider following structure: class CargoList extends React.Component {
render() {
return <div>{this.props.cargos.map(cargo => <CargoItem cargo={cargo} />}</div>
}
}
class CargoItem extends React.Component {
render() {
return <div>{this.props.cargo.getName()}</div>
}
} Note, that in First method is to have a custom validation function. I can have some kind of CargoList.propTypes = { cargos: React.PropTypes.arrayOf(AppPropTypes.cargo) }
CargoItem.propTypes = { cargo: AppPropTypes.cargo } But this may be quite verbose. I need to maintain type structure both in creator and validator. Much plainer might be to validate props by their types using CargoList.propTypes = { cargos: React.PropTypes.arrayOf(React.PropTypes.instanceOf(Cargo)) }
CargoItem.propTypes = { cargo: React.PropTypes.instanceOf(Cargo) }
// somewhere in store
_cargos = update(_cargos, { $push: new Cargo(data) }) Where Am I correct in my conclusions or I'm going totally misconcept? |
You're correct that if you're using
There's no such thing as an immutable array in JavaScript, but you could certainly use an Immutable.js List collection here if you wish. You could use |
In case anyone wants to kick the tires on this a bit, here's another pass at custom classes, actually with really great performance... this is the syntax:
Now you've got a nice new Immutable "User" type:
With the following guarantees:
Still a WIP, any feedback is appreciated!
@leebyron sent more details via email
/cc @janmarek, @vkurchatkin, @dandelany