|
1 | 1 | # NestedReact |
2 | | -This is React add-on providing advanced state management to React applications and convergence layer for intermixing React components and Backbone Views. |
| 2 | + |
| 3 | +React application architecture with [classical OO models]() in the data layer. |
3 | 4 |
|
4 | 5 | Brief feature list: |
5 | 6 |
|
6 | | -- Component's state management with [NestedTypes](https://github.com/Volicon/NestedTypes) model instead of React state. |
7 | | -- Extended two-way data binding with -React- Nested links - [Guide to Data Binding Use Cases](/example/databinding.md) |
8 | | -- Lightweight `NestedTypes`-style type annotations for props and context as a replacement of `PropTypes`. |
9 | | -- *Pure render optimization* with mutable models and collections in props. Works like a charm :) |
10 | | -- Transparent interoperation with existing Backbone Views: |
11 | | - - React component can be used as backbone View. `new MyComponent.View({ props })` |
12 | | - - Backbone Views can be used as React components. `<React.subview View={ MyView } />` |
13 | | - - Simplified refactoring of Backbone Views to React components. `this.$`, `this.$el`, `this.$( sel )`, `this.model` works for React components too, as well as `this.trigger` and `this.listenTo`. |
14 | | - |
15 | | -Though `NestedReact` offers excellent convergence layer for backbone views, raw backbone models are not supported. |
16 | | -To use it for smooth migration of existing backbone application to React, you need to replace `backbone` with `NestedTypes` |
17 | | -first (it's mostly backward compatible with backbone 1.2 by API, so transition is not hard). |
18 | | -Which by itself will be a big step forward, because: |
19 | | - - It's order of magnitude faster, so your application becomes more responsive and you can handle collection which are 10 times larger than you have now. [No kidding](http://slides.com/vladbalin/performance#/). |
20 | | - - It implements nested models and collections handling in the right way. During `fetch`, nested objects are updated in place, so it's safe to pass them by reference. |
21 | | - - It can handle model references by `id` in attributes for you too, operating on a set of independently fetched collections. |
22 | | - - It's type-safe, providing the same contract for model attributes as in statically typed language. Thus, |
23 | | - attributes are guaranteed to hold values of declared types whatever you do, making it impossible to break client-server protocol. |
24 | | - - At the moment of writing, no other traditional model framework supports React's pure render optimization. :) |
25 | | - |
26 | | - For more information about `NestedTypes`, visit |
27 | | - http://volicon.github.io/backbone.nestedTypes/ |
28 | | - and |
29 | | - https://github.com/Volicon/backbone.nestedTypes |
| 7 | +- First-class support for mutable models and collections in props, state, and context. |
| 8 | + - Unidirectional data flow and safe *pure render optimization*. |
| 9 | + - Two-way data binding ([Guide to Data Binding Use Cases](/example/databinding.md)) |
| 10 | + - Optional local component subtree updates. |
| 11 | +- Lightweight type annotations for props, *state*, and context as a replacement for `PropTypes`. |
| 12 | +- Gradual transition procedure for backbone applications ([Backbone Migration Guide]()): |
| 13 | + - Complete interoperation with existing Backbone Views allowing you to reuse existing code and avoid upfront application rewrite. |
| 14 | + - Any type of application refactoring strategy is possible - top-to-bottom, bottom-to-top, and random parts at the middle. |
| 15 | + - Support for Backbone events and jQuery accessors in React components simplifies View refactoring. |
| 16 | + |
| 17 | +Compare solution size and complexity to any of `flux` implementation on [TodoMVC example](). |
30 | 18 |
|
31 | 19 | # Installation |
32 | 20 | It's packed as single UMD, thus grab the module or use `npm` to install. |
33 | 21 | `npm install --save nestedreact` |
34 | 22 |
|
35 | | -Module extends React namespace (without touching original React), and its |
| 23 | +It has [NestedTypes model framework]() and [React]() as strong dependencies. |
| 24 | + |
| 25 | +Module extends React namespace (without modifying original React), and its |
36 | 26 | safe to use it as a replacement for `react`. |
37 | 27 | `import React from 'nestedreact'` |
38 | 28 |
|
39 | | -If you're using backbone-based frameworks such as `ChaplinJS` or `Marionette`, |
| 29 | +If you're migrating from backbone-based frameworks such as `ChaplinJS` or `Marionette`, |
40 | 30 | you need to do following things to make convergence layer work properly: |
41 | 31 | - Make sure that frameworks includes `nestedtypes` instead of `backbone`. |
42 | 32 | - On application start, tell `nestedreact` to use proper base class for the View. |
43 | 33 | `React.useView( Chaplin.View )` |
44 | 34 |
|
45 | | -# Features |
| 35 | +# Basics |
| 36 | +## Managing component's state |
46 | 37 |
|
47 | | -## Managing state with ad-hoc Backbone model |
| 38 | +In the simplest case, it looks like this: |
48 | 39 |
|
49 | 40 | ```javscript |
50 | | -var React = require( 'nestedreact' ); |
51 | | -
|
52 | | -var MyComponent = React.createClass({ |
53 | | - //Model : BackboneModel, |
| 41 | +import React from 'nestedreact' |
54 | 42 |
|
55 | | - state : { // Model defaults |
56 | | - count : 0 |
| 43 | +export const MyComponent = React.createClass({ |
| 44 | + state : { |
| 45 | + count : 0 // Number attribute with 0 as default value. |
57 | 46 | }, |
58 | 47 |
|
59 | | - render : function(){ |
| 48 | + render(){ |
60 | 49 | return ( |
61 | 50 | <div onClick={ this.onClick }> |
62 | 51 | { this.state.count } |
63 | 52 | </div> |
64 | 53 | ); |
65 | 54 | }, |
66 | 55 |
|
67 | | - onClick : function(){ |
| 56 | + onClick(){ |
| 57 | + // state change will be detected and component will be updated |
68 | 58 | this.state.count = this.state.count + 1; |
69 | 59 | } |
70 | 60 | }); |
71 | 61 | ``` |
72 | 62 |
|
73 | | -- New `NestedTypes` Model definition will be created, using `state` as Model.defaults. |
74 | | -- If Model property is specified, it will be used as base model and extended. |
75 | | -- `state` property from mixins will be properly merged. |
76 | | -- Since `state` is `NestedTypes` model in this case, |
77 | | - - All attributes *must* be declared using `NestedTypes` standard type specs. |
78 | | - - `state` attributes allows direct assignments - treat it as regular object. |
79 | | - - Every `state` modification (including direct assignments and nested attributes changes) will |
80 | | - cause automagical react update. |
| 63 | +Behind the scene, `state` is managed with `NestedTypes` model which is implicitly created using |
| 64 | +attribute's spec taken from `state` declaration (refer to [NestedTypes documentation]() for complete |
| 65 | +attribute spec syntax). It has following implications: |
81 | 66 |
|
82 | | -## Passing Backbone objects as React components props |
83 | | -```javscript |
84 | | -var MyComponent = React.createClass({ |
85 | | - listenToProps : { // or just string with property names, separated by space |
86 | | - model : 'change' |
87 | | - }, |
| 67 | +- You can use primitive type values or constructor functions as attribute's type specs. |
| 68 | +- Plain objects and arrays used as defaults will be properly deep copied. |
| 69 | +- All state members *must* be declared in `state`. |
| 70 | +- State attributes behaves as regular object attributes, which can be directly accessed and assigned. |
| 71 | +- State attributes can hold deeply nested models and collections; deep changes will be automatically detected and will cause component update. |
88 | 72 |
|
89 | | - render : function(){ |
90 | | - return ( |
91 | | - <div onClick={ this.onClick }> |
92 | | - { this.props.model.count } |
93 | | - </div> |
94 | | - ); |
95 | | - }, |
| 73 | +In addition, |
| 74 | +- `state` property from mixins will be properly merged. So, mixins can have state too. |
| 75 | +- You can specify the base class for state model using `Model` component's property. |
| 76 | +- Entire model's state can be externally defined as `NestedTypes` Model, and attached to component by referencing it in `Model` property. |
| 77 | +- `state` supports everything what regular models can do, e.g. it can have custom methods, |
| 78 | + be transactionally changed, serialized, saved, fetched, etc. |
96 | 79 |
|
97 | | - onClick : function(){ |
98 | | - this.props.model.count = this.props.model.count + 1; |
99 | | - } |
| 80 | +Usage of `getInitialState()` and `setState()` is not allowed when you're using `state` declaration. |
| 81 | + |
| 82 | +## Passing models and collections as components props |
| 83 | + |
| 84 | +It's quite common practice to describe complex application's page state in top-level component, and |
| 85 | + pass the parts of the state down as props. In this case, any changes to nested models |
| 86 | + and collections will be detected by top-level component and will cause update of the whole subtree. |
| 87 | + Resulting in so-called _unidirectional data flow_. |
| 88 | + |
| 89 | +```javascript |
| 90 | +// data layer |
| 91 | +const Counter = Model.extend({ |
| 92 | + attributes : { |
| 93 | + count : 0 |
| 94 | + } |
| 95 | +}); |
| 96 | + |
| 97 | +// Application or application's page |
| 98 | +const Top = React.createClass({ |
| 99 | + // all changes made to the parts of the state will cause component update |
| 100 | + state : { |
| 101 | + model1 : Counter, |
| 102 | + model2 : Counter |
| 103 | + }, |
| 104 | + |
| 105 | + render(){ |
| 106 | + // pass down elements of the state... |
| 107 | + return ( |
| 108 | + <div> |
| 109 | + <Bottom model={ this.state.model1 } /> |
| 110 | + <Bottom model={ this.state.model2 } /> |
| 111 | + </div> |
| 112 | + ); |
| 113 | + } |
100 | 114 | }); |
| 115 | + |
| 116 | +// Pure component. Click will trigger update of the Top component. |
| 117 | +const Bottom = ({ model }) => ( |
| 118 | + <div onClick={ () => model.count += 1 }> |
| 119 | + { model.count } |
| 120 | + </div> |
| 121 | +); |
101 | 122 | ``` |
102 | 123 |
|
103 | | -You can update react component on backbone events from component props. |
104 | | -Event subscription is managed automatically. No props passed - no problems. |
| 124 | +Also, this example demonstrates the point which really differentiate our approach to |
| 125 | +application state management. Here the simple fact comes into play - `NestedTypes` models |
| 126 | +can declaratively describe very complex state, and detect deeply nested changes. |
| 127 | +So, you have unidirectional data flow for no effort. |
| 128 | + |
| 129 | +## Props specs and pure render optimization |
| 130 | + |
| 131 | +One of the problems of unidirectional data flow is that large part of UI is being |
| 132 | + updated for every small change. Though React does its job avoiding unnecessary DOM manipulations, |
| 133 | + it's still takes a lot of computation resources to compare new and old UI components trees. |
| 134 | +`Pure render` optimization strategy avoids rendering and comparison of subtrees which has not been changed |
| 135 | +by adding special `props` comparison function (`shouldComponentUpdate`). This optimization |
| 136 | +is most effective on the top level close to the state holder component. |
105 | 137 |
|
106 | | -## NestedTypes-style props specs |
| 138 | +NestedReact support this optimization, comparing props model's and collection version tokens to ones used during the last render, |
| 139 | +and comparing other props values for strict equality. |
| 140 | + |
| 141 | +To enable this optimization for the particular component, you need to: |
| 142 | + - Declare all props that will be tracked for changes in `props` (or `propTypes`) spec. |
| 143 | + Which is the good practice by itself, so you encouraged to do it unless you're using |
| 144 | + stateless function components syntax, which is preferable. |
| 145 | + - Add `pureRender : true` to component definition. |
| 146 | + |
| 147 | +As from previous example: |
107 | 148 |
|
108 | 149 | ```javscript |
109 | | -var MyComponent = React.createClass({ |
| 150 | +var Bottom = React.createClass({ |
110 | 151 | props : { |
111 | | - model : MyFancyModel |
| 152 | + model : Counter |
112 | 153 | }, |
113 | | - |
114 | | - listenToProps : { // or just string with property names, separated by space |
115 | | - model : 'change' |
116 | | - }, |
117 | | -
|
118 | | - render : function(){ |
119 | | - return ( |
120 | | - <div onClick={ this.onClick }> |
121 | | - { this.props.model.count } |
122 | | - </div> |
123 | | - ); |
124 | | - }, |
125 | 154 |
|
126 | | - onClick : function(){ |
127 | | - this.props.model.count = this.props.model.count + 1; |
128 | | - } |
| 155 | + pureRender : true, |
| 156 | + |
| 157 | + render(){ |
| 158 | + const { model } = this.props; |
| 159 | + return ( |
| 160 | + <div onClick={ () => model.count += 1 }> |
| 161 | + { model.count } |
| 162 | + </div> |
| 163 | + ); |
| 164 | + ) |
129 | 165 | }); |
130 | 166 | ``` |
131 | 167 |
|
132 | | -Simplified NestedTypes-style type annotations can be used as props spec: |
133 | | -- constructor functions: `Type` |
134 | | -- constructors with default values: `Type.value( x )` |
135 | | -- JSON and primitive values: `"default string"` |
| 168 | +NestedReact `props` spec uses the simple subset of `state` spec, and acts as substitution for `propTypes` (in fact, |
| 169 | + it internally compiles itself to the `propTypes` and `getDefaultProps()`). |
| 170 | + |
| 171 | +Following type annotations are allowed for `props`: |
| 172 | +1. Constructor functions: `prop1 : String` |
| 173 | +2. Constructors with default value: `prop2 : String.value( "default string" )` |
| 174 | +3. JSON and primitive values: `prop3 : "default string"` |
| 175 | +4. Special PropTypes cases: |
| 176 | + - `PropTypes.any` -> `undefined` (no default value) or `null` (with `null` default value) |
| 177 | + - `PropTypes.node` -> `React.Node` |
| 178 | + - `PropTypes.element` -> `React.Element` |
| 179 | + |
| 180 | +If prop has explicitly declared default value, as in (2) or (3), it will be added to `getDefaultProps`. |
136 | 181 |
|
137 | | -No other type annotation features are supported for `props`. |
| 182 | +## Partial component subtree updates |
138 | 183 |
|
139 | | -When component has `props` type spec: |
140 | | -- React component propTypes will be automatically generated for every props; |
141 | | -- if props has explicitly defined default value, getDefaultProps() method will be created. It means, that there are *no* |
142 | | - default objects generated for simple `Type` style type spec. |
| 184 | +For the number of reasons, you may need some parts of components subtrees to listen for props updates independently. |
| 185 | +It might be required if model or collection props are not the part of any upper component state. |
| 186 | +This situation frequently happens during transition period when you're in the middle of refactoring large |
| 187 | +backbone application. |
143 | 188 |
|
144 | | -## Pure Render Mixin |
| 189 | +To make `<Bottom />` component listen to and update on its `model` prop change, it's enough to add |
| 190 | +`listenToProps` option to component spec. It will play well with `pureRender`, effectively |
| 191 | +avoiding unnecessary renders if top level component will trigger update on the same change too. |
145 | 192 |
|
146 | 193 | ```javscript |
147 | | -var MyComponent = React.createClass({ |
148 | | - props : { |
149 | | - item : MyModel, |
150 | | - elements : MyCollection, |
151 | | - className : String |
152 | | - }, |
153 | | - |
154 | | - pureRender : true, |
155 | | -
|
156 | | - render : function(){ |
157 | | - return ( |
158 | | - ... |
159 | | - ); |
160 | | - } |
| 194 | +var Bottom = React.createClass({ |
| 195 | + props : { |
| 196 | + model : Counter |
| 197 | + }, |
| 198 | + |
| 199 | + listenToProps : 'model', // space separated list of prop names |
| 200 | + |
| 201 | + // ...all other stays the same... |
161 | 202 | }); |
162 | 203 | ``` |
163 | 204 |
|
164 | | -PureRender optimization in enabled with `pureRender` option. It will create `shouldComponentUpdate` function |
165 | | -which is optimized for props mentioned in `propTypes` or `props` declaration. |
166 | | - |
167 | | -Therefore, it's required to declare all of component props when using this optimization. |
168 | | - |
169 | 205 | ## Data binding |
170 | 206 |
|
171 | 207 | `nestedreact` supports data binding links compatible with standard React's `valueLink`. |
|
0 commit comments