On Github sq0032 / react-integration-strategy
Created by Ping-Wen (Mark) Hsu
2015/11/10 for ReactJS Waterloo
The reason I choose Backbone is because this is what I'm currently working on. I tried AngularJs, but it's not making live easier. Just want to share how I did the change.If you app structure looks like this:
I'm sorry about that
React or my strategy neither a medicine that cures all the diseases.If looks like this:
Fine, you can stay :-)
Example
View
//view.js
views.ItemView = Backbone.View.extend({
events:{
'click .toggle': 'toggleCompleted',
},
initialize:function{
this.listenTo(this.model, 'change', this.render);
},
render:function(){
var itemHTML = _.template(item_html)({
item: this.model
});
this.$el.html(itemHTML);
},
toggleCompleted:function(event){
this.model.toggle();
}
});
Template
<!-- Template -->
<div class="view">
<input class="toggle" type="checkbox" <%= item.get("completed") ? 'checked': '' %>>
<label><%= item.get("title") %></label></label>
<button class="destroy"></button>
</div>
Step 1: Make a HTML render machine
class Item extends React.Component{
render(){
const { item } = this.props;
return (
<div className="view>
<input className="toggle" type="checkbox" item.get("completed") ? 'checked': '' />
<label>item.get("name")</label>
<button className="destroy"></button>
</div>
);
}
}
<!-- Template -->
<div class="view">
<input class="toggle" type="checkbox" <%= item.get("completed") ? 'checked': '' %>>
<label><%= item.get("title") %></label></label>
<button class="destroy"></button>
</div>
//Component.render()
const { item } = this.props;
return (
<div className="view">
<input className="toggle" type="checkbox" item.get("completed") ? 'checked': '' />
<label>item.get("name")</label>
<button className="destroy"></button>
</div>
);
Then modify view render function
//view.js
views.ItemView = Backbone.View.extend({
render:function(){
//before
var itemHTML = _.template(item_html)({
item: this.model
});
this.$el.html(itemHTMLitemHTML);
//after
React.render(
<Item item={this.model} />,
this.$el
);
},
});
Step 2: Write events handler
This slide has fragments which are also stepped through in the notes window.2-1: Create a click handler
class Item extends React.Component{
handleToggle(){
this.props.toggleCompleted();
}
render(){
const { item } = this.props;
return (
<div className="view>
<input className="toggle"
type="checkbox"
item.get("completed") ? 'checked': ''
onClick={this.handleToggle.bind(this)}/>
<label>item.get("name")</label>
<button className="destroy"></button>
</div>
);
}
}
Create a click handler, and call the function that pass in as a prop.
2-2: Pass the view function into the component
//view.js
views.ItemView = Backbone.View.extend({
// events:{
// 'click .toggle': 'toggleCompleted',
// },
initialize:function{
this.listenTo(this.model, 'change', this.render);
},
render:function(){
React.render(
<Item item={this.model} toggleCompleted={toggleCompleted} />,
this.$el
);
},
toggleCompleted:function(event){
this.model.toggle();
}
});
Now you start taking benefit from React by simplifing event transmition procesure.
Some people would add/remove classname here which you don't need it with React. Just put the logic into the component's render function.
Step 3: Replace Backbone View
This part is tricky one, cuz here is the place you need to make a desision.A question needs to be answer:
Is the model only be used by the current view/component?
If yes, keep using model as a prop
class Item extends React.Component{
handleToggle(){
this.model.toggle();
}
render(){
const { item } = this.props;
return (
<div className="view>
<input className="toggle"
type="checkbox"
item.get("completed") ? 'checked': ''
onClick={this.handleToggle.bind(this)}/>
<label>item.get("name")</label>
<button className="destroy"></button>
</div>
);
}
}
The mutation happens in a parent component
If no, use states instead
class Item extends React.Component{
constructor(props){
super(props);
this.state = {
model: this.props.model,
};
}
handleToggle(){
this.state.model.toggle();
this.setState({model:this.model});
}
render(){
const item = this.state.model;
return (
<div className="view>
<input className="toggle"
type="checkbox"
item.get("completed") ? 'checked': ''
onClick={this.handleToggle.bind(this)}/>
<label>item.get("name")</label>
<button className="destroy"></button>
</div>
);
}
}
This model is the single source of truth.
//ListView.js
views.ListView = Backbone.View.extend({
render:function(){
var Items = this.collections.map(function(item){
return (
<Item item={item}
//Pass function if ListView is in charge of data mutating
toggleCompleted={toggleCompleted}
key={item.get("id")}/>
);
});
React.render(
Items,
this.$el
);
},
});
Step 4: Apply internal Flux by features
This part is tricky one, cuz here is the place you need to make a desision.Can I still use third-party libraries?
You can use those:
You can't use those: