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: