Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/develop'
Browse files Browse the repository at this point in the history
# Conflicts:
#	README.md
#	package.json
  • Loading branch information
Vlad Balin committed Feb 17, 2016
2 parents 2b60a82 + 9649685 commit 4212e96
Show file tree
Hide file tree
Showing 15 changed files with 1,091 additions and 489 deletions.
364 changes: 215 additions & 149 deletions README.md

Large diffs are not rendered by default.

218 changes: 218 additions & 0 deletions docs/BackboneViews.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# Backbone to React Transition Guide

There are many different ways you may approach the problem when dealing with existing Bakcbone UI.
All of them are supported, enabling easy and gradual transition to React.

Though `NestedReact` offers excellent convergence layer for backbone views, raw backbone models are not supported.
To use it for smooth migration of existing backbone application to React, you need to replace `backbone` with `NestedTypes`
first (it's mostly backward compatible with Backbone 1.2 API, so [transition is not hard](https://github.com/Volicon/NestedTypes/blob/develop/docs/BackboneTransitionGuide.md)).
Which by itself will be a big step forward, because:

- 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#/).
- It implements nested models and collections in the right way. During `fetch`, nested objects are updated in place, so it's safe to pass them by reference.
- It can handle model references by `id` in attributes for you too, operating on a set of independently fetched collections.
- It's type-safe, providing the same contract for model attributes as in statically typed language. Thus,
attributes are guaranteed to hold values of declared types whatever you do, making it impossible to break client-server protocol.
- At the moment of writing, no other traditional model framework supports React's pure render optimization. :)

For more information about `NestedTypes`, visit
http://volicon.github.io/backbone.nestedTypes/
and
https://github.com/Volicon/backbone.nestedTypes

## Interoperation with existing Backbone Views

Key factor of success for technology transition project is to avoid naive 'upfront rewrite' strategy.
An ability of running old and new code together is the game changer, allowing you to make transition process gradual.
This strategy has a number of benefits, and probably one of the most significant is that you don't need to stop
delivering new features.

### Use React components as Backbone View

When you work on new features, it's natural to decide that you will write its UI with React.

And, there're good news. Any React component may be used as Backbone subview. As easy as that:

```javscript
var backboneView = new MyReactComponent.View( props );
```

It will also enable you to start refactoring of your application from the bottom to top,
dealing with small isolated parts of the code first.

### Use Backbone View in React component

Or, you can decide to start refactoring from the top. In this case, you will likely want to reuse
your existing lower-level backbone subviews.

You can do it like that.

```javscript
var React = require( 'nestedreact' );
var MyComponent = React.createClass({
render : function(){
return (
<div>
<React.subview
className="classes for root element"
View={ BackboneView }
options={ viewOptions }
/>
</div>
);
}
});
```

It's easy, for example, to take [react-router](https://github.com/reactjs/react-router) and use it in your application without any changes in Backbone View layer.

Taking these two features together, you can take literally any view from the subview hierarchy, and replace it with
React component. It will also work fine if there are multiple layers - React using Backbone using React...

## Backbone View refactoring

Occasionally, you may decide to refactor your existing View to React component.

Since Backbone generally use the same architectural concept as React (detect change and then render), it's an easy process.
First of all, short vocabulary:
1. `View.extend({})` -> `React.createClass({})`. That's an obvious part.
2. `View.template` -> `Component.render()`. Yeah. In React, `render` function just *returns* markup.
3. `View.render` -> `Component.forceUpdate()`. And if you want to update component, you should call this thingy instead.
4. `View.render` -> `Component.componentDidUpdate()`, `Component.componentDidMount()`. If you want to attach jQuery plugin after render, you do it here.
5. `View.initialize( options )` -> `View.componentWillMount()`
6. `View options you receive in (4)` -> `Component.props`
7. `View.model` -> `Component.state`

You approach the refactoring process in sequence:
1. Create an empty React component.
2. Take your View's template, and convert it to JSX in your component's render method.
3. Your View's `options` become component's `props`. Modify `render` function accordingly.
4. And then, the `model` of your View becomes your component's `state`. Modify `render` function accordingly.
5. You copy all of your event handlers.

Keep in mind - in React direct DOM manipulation is not allowed. Thus, you must render on every change, and `props` + `state`
must completely define an appearance of your markup. Since for Backbone it's not so, you will likely be required to expand your
View's state model.

In Backbone, you might assign values from `options` to the model. Do not do this with React. Remember, `options` is `props`.
Therefore, it might be required to remove some items from the View's model.

### Converting EJS template to JSX

I will assume we're using EJS to be specific. JSX is not text, it's hidden JS tree construction expression, thus control structures must be transformed to functional form. In short, branches becomes logical expressions, loops becomes `map` expressions. Expression must return single node or array, in the last case you're required to add `key` attribute. Component must return single node at the top level.

Keeping in mind these simple patterns, it's not that hard:

- `class` should be substitued with `className`
- `<%= expr %>` -> `{ expr }`
- `<%if( expr ){%> <div/> <%}%>` -> `{ expr && <div/> }`
- `<%if( expr ){%> <div/> <%}else{%> <span/> <%}%>` -> `{ expr ? <div/> : <span/> }`
- `<%for( var i = 0; i < arr.length; i++ ){%> <div/> <%}%>` -> `{ arr.map( ( x, i ) => <div key={ i } /> )}`

You should consult official React documentation for more information about this.

### Use Existing Model as component's state

If you already have one model, describing View's state (usual pattern is listening to model's `change` event and calling `this.render()`),
you can just attach it to you React component. Like this. It will be created, disposed, and listened to automatically.

```javscript
var React = require( 'nestedreact' );
var MyComponent = React.createClass({
Model : MyStateModel,
render : function(){
return (
<div onClick={ this.onClick }>
{ this.state.count }
</div>
);
},
onClick : function(){
this.state.count = this.state.count + 1;
}
});
```

*Please, note.* If Model is specified for the component,
- `this.state` and `this.model` variables holds backbone model. Usage of `setState` is *not allowed*. Generally, NestedTypes
models are far superior to React's state in its capabilities, so trust me, there are nothing to regret.
- React component will update itself automatically whenever model emit `change` event.
- You can customize UI update events supplying `listenToState` property. For example, `listenToState : 'change:attr sync'`.
- You can disable UI updates on state change, supplying `listenToState : false` option.

### Passing Models and Collections as React components props

In backbone, you might listen to models and collection changes which comes from the View `options`.

You can do it manually in React keeping in mind that `componentWIllMount` is substitution for `initialize`, but it's
not that simple because React component's lifecycle is more complicated. In contrast with Views, components are able to
receive props updates. Thus, you need to handle it, and it might become tricky.

To address this problem, there's special declarative syntax for events subscription from `props`.

```javscript
var MyComponent = React.createClass({
listenToProps : 'model', // listen to change, and render
/*
listenToProps : { // or just string with property names, separated by space
model : 'change' //listen to event names separated by space, and render
},
or
listenToProps : { // ...if you want really wierd things...
model : {
'change' : function(){
// ...you may do it. But here we are just listening to 'change', and render.
this.forceUpdate();
}
}
},
*/
render : function(){
return (
<div onClick={ this.onClick }>
{ this.props.model.count }
</div>
);
},
onClick : function(){
this.props.model.count = this.props.model.count + 1;
}
});
```

That's simple and safe. No props passed - no events subscription.

### Helper methods for event handlers

When you will copy over your event handlers, most likely, they will just work.

There are `el`, `$el`, and `$( selector )` available for the React components,
which simplifies refactoring of the existing event handlers and usage of
`jquery` plugins.

```javscript
var React = require( 'nestedreact' );
var MyComponent = React.createClass({
onClick : function(){
this.$( '#somewhere' ).html( 'Hi' );
}
});
```

If they don't do DOM manipulation, which is prohibited. Instead, event handlers should modify the state, or call some callbacks
received from props.

It is extremely dangerous and conceptually wrong to directly *modify existing*
DOM subtree in React component. Read is safe, modify DOM when you know what you're
doing. Lets say, integrating `jQuery` plugins.

*You must not use these methods in render*. `jquery` plugins can be initialized
in `componentDidMount` method or in event handlers.

2 changes: 1 addition & 1 deletion example/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<script>
var State = Nested.Model.extend({
defaults : {
who : 'me'
who : String.value( 'me' ).has.check( function( x ){ return x == 'me'; })
}
});

Expand Down
Loading

0 comments on commit 4212e96

Please sign in to comment.