react实战
GitHub上给出react的三个关键点:
1.Just the UI (仅仅是view层)
2.Virtual Dom (虚拟Dom)
3.Data flow (数据流是沿着组件树从上到下单向流动的)
理解react可以参考 这里、这里,还有深入浅出React一、二、三、四。
以练手的博客为例。(学习新技术最快的途径就是实践,解决问题不断提高)【完整代码】
react强调组件的开发方式,类似于搭积木。将一个网页拆分成一个个的组件,组件可以复用,组件之间可以嵌套使用,嵌套组件之间通过属性(props)通信。
比如首页被拆分成:<Nav />、<ContainerLeft />、<Home />、<Article />、<Footer />
render() { let nav = this.state.category; return ( <div> <Nav nav={nav} /> <div className="container"> <div className="row"> <div className="col-md-3"> <ContainerLeft/> </div> <div className="col-md-9"> <div id="container-right"> {/* <RouteHandler/> */} <RouteHandler/> </div> </div> </div> </div> <Footer/> </div> ); }
上面的<Home />是由<Route />组件中的默认路由,了解react-router.
<Route name="app" path="/" handler={App}> <DefaultRoute name="home" handler={Home}></DefaultRoute> <Route name="category" path="/category/:categoryId" handler={Category}/> <Route name="details" path="/details/:articleId" handler={Details}/> </Route>
这样首页就拆分成了一个个组件,图中可以看到<Home />里面包含<Article />。
<Home />组件中在组件挂载之前初始化state值为{details:[]},组件挂载执行render(),组件挂载完成时请求文章的数据detail.json(模拟的前台数据)并设置state值。state改变触发render()执行。
let Home = React.createClass({ getInitialState() { return { details: [] }; }, loadCommentsFromServer() { let self = this; $.ajax({ url: ‘./mock/detail.json‘, dataType: ‘json‘, success: function(r) { if(200 == r.errCode){ AppF.articleCut(r.data, config.indexShowNum, (detailsCuted) => { AppF.timeToStr(detailsCuted, (detailsStrTime) => { AppF.isStrCut(detailsStrTime, ‘title‘, config.indexTitleLength, (details) => { AppF.isStrCut(details, ‘content‘, config.indexContentLength, (details) => { self.setState({details: details || []}); }); }); }); }); } }, error: function(xhr, status, err) { console.error(xhr, status, err.toString()); } }); }, componentDidMount() { this.loadCommentsFromServer(); }, render() { let details = this.state.details; console.log("Home -- render()"); return( <div> <div className="headline">文章<span className="font-green">推荐</span></div> <Articles details={details}/> </div> ); } });
<Home />向<Article />中传值是通过属性(props)details,这是组件之间通信的一种方式。
<Article />组件中通过this.props.details取得<Home />传过来的属性值,使用map()去遍历数组,<Link />是react-router提供的一个组件,类似于<a>链接
let Article = React.createClass({ render() { let details = this.props.details; details = details.map( (item, index) => { return ( <article key={"article-" + index}> <div className="title" title={item.title}> <Link to="details" params={{articleId: item.id}} title={item.title}> <i className="glyphicon glyphicon-triangle-right icon-title"></i> {item.title} </Link> </div> <div className="row"> <div className="col-md-3"> <img className="img-thumbnail" src ={ "../vendor/images/" + item.img_file }/> </div> <div className="col-md-9"> <div className="content"> <p>{item.content}</p> </div> </div> </div> <div className="row sub-title"> <div className="col-md-3"><i className="glyphicon glyphicon-time"></i> {item.create_time}</div> <div className="col-md-2"><i className="glyphicon glyphicon-folder-open"></i> {item.category}</div> <div className="col-md-2"><i className="glyphicon glyphicon-eye-open"></i> {item.hits}</div> <div className="col-md-5"> <Link to="details" params={{articleId: item.id}} className="btn btn-success btn-sm pull-right btn-read-all" title={item.title}>阅读全文>></Link> </div> </div> </article> ); }); return( <div> <div className="article-list"> {details} </div> </div> ); } });
其他组件类似的这样搭建,这样首页就出来了。在点击有<Link to="details" params={{articleId: item.id}} />形成的链接,<Route />组件路由到<Details />组件,并传递了参数{articleId: item.id}。
<Details />组件中通过this.props.params.articleId取得传递过来的参数。
let Details = React.createClass({ getInitialState() { return { threeArticle: [] }; }, loadCommentsFromServer() { console.log("loadCommentsFromServer"); let self = this; $.ajax({ url: ‘./mock/detail.json‘, dataType: ‘json‘, success: function(r) { if(200 == r.errCode){ var details = r.data; AppF.articleSort(details, ‘id‘, ‘ase‘, (details) => { AppF.timeToStr(details, (details) => { var details = details; self.getArticleKey(details, self.props.params.articleId, (key) => { self.getThreeArticle(details, key, (threeArticle) => { //console.log(threeArticle); self.setState({ threeArticle: threeArticle[0] }); }); }); }); }); } }, error: function(xhr, status, err) { console.error(xhr, status, err.toString()); } }); }, componentWillMount(){ console.log("Details -- componentWillMount"); this.loadCommentsFromServer(); }, //当组件在页面上渲染完成之后调用 componentDidMount() { console.log("Details -- componentDidMount"); }, //在组件接收到新的 props 的时候调用。在初始化渲染的时候,该方法不会调用。 componentWillReceiveProps(nextProps) { console.log("Details -- componentWillReceiveProps"); this.loadCommentsFromServer(); }, render() { console.log(this.state.threeArticle); return( <div> <Article article={this.state.threeArticle}/> </div> ); }, getThreeArticle(details, key, cb) { var arr = []; var length = details.length; if( length == 0 || key === ‘‘){ cb([]); } else if(length == 1){ arr.push({pre: {}, cur: details[0], next: {} }); } else { if(key == 0){ arr.push({pre: {}, cur: details[0], next: details[1] }); } else if (key == length - 1){ arr.push({pre: details[length - 2], cur: details[length - 1], next: {} }); } else { arr.push({pre: details[key - 1], cur: details[key], next: details[key + 1] }); } } cb(arr); }, getArticleKey(details, article_id, cb) { if(0 == details.length){ cb(‘‘); } else { details.forEach(function(item, k){ if(item[‘id‘] == article_id){ cb(k); } }); } } });
<Details />组件里包含了<Article />组件,同样的通过属性article传递数据。同样的<Article />组件中通过this.props.article来取得<Deatils />传递过来的article属性。
就可以得到文章详情页的效果了:
这时出现了一个问题:在我点击下一篇时我想让对应的导航条在相应的分类下加上我的active样式,这时设及到我要在<Article />组件中绑定事件,获取DOM的属性,向<Nav />组件传递数据。注意此时的<Nav />组件和<Article />组件并没有什么嵌套关系。
let Article = React.createClass({ changeNavClassPre: function(e){ console.log("Article -- changeNavClassPre"); let cid = React.findDOMNode(this.refs.articlePre).getAttribute("data-category-id"); PubSub.publish(EventName.navClass, {categoryId: cid}); }, changeNavClassNext: function(e){ console.log("Article -- changeNavClassPre"); let cid = React.findDOMNode(this.refs.articleNext).getAttribute("data-category-id"); PubSub.publish(EventName.navClass, {categoryId: cid}); }, render() { let threeArticle = this.props.article; let cur, pre, next; //console.log(threeArticle); $.each(threeArticle, (item, value) => { if(item == ‘cur‘){ cur = ( <div> <div className="col-md-12 article-title">{value.title}</div> <div className="col-md-12 article-icon"> <ul> <li> <i className="glyphicon glyphicon-folder-open"></i> {value.category} </li> <li> <i className="glyphicon glyphicon-time"></i> {value.create_time} </li> <li> <i className="glyphicon glyphicon-eye-open"></i> {value.hits}人阅读 </li> </ul> </div> <div className="col-md-12"> <p className="article-content">{value.content}</p> </div> <div className="col-md-12 article-icon"> <ul> <li> <a href="javascript:;"><i className="glyphicon glyphicon-thumbs-up"></i> 赞</a> </li> <li> <i className="glyphicon glyphicon-comment"></i> 0人评论 </li> </ul> </div> </div> ); } else if(item == ‘pre‘){ if(!value.id){ pre = ( <p className="article-pre"> <i className="glyphicon glyphicon-chevron-up"></i> 上一篇:无 </p> ); } else { pre = ( <p className="article-pre" data-category-id={value.category_id} onClick={this.changeNavClassPre} ref="articlePre"> <Link to="details" params={{articleId: value.id}} title={value.title}> <i className="glyphicon glyphicon-chevron-up"></i> 上一篇:{value.title} </Link> </p> ); } } else { if(!value.id){ next = ( <p className="article-pre"> <i className="glyphicon glyphicon-chevron-down"></i> 下一篇:无 </p> ); } else { next = ( <p className="article-pre" data-category-id={value.category_id} onClick={this.changeNavClassNext} ref="articleNext"> <Link to="details" params={{articleId: value.id}} title={value.title}> <i className="glyphicon glyphicon-chevron-down"></i> 下一篇:{value.title} </Link> </p> ); } } }); return ( <div className="row"> {cur} <div className="col-md-12" id="pre-next"> {pre} {next} </div> </div> ); } });
这里需要注意:
1.React并不会真正的绑定事件到每一个具体的元素上,而是采用事件代理的模式:在根节点document上为每种事件添加唯一的Listener,然后通过事件的target找到真实的触发元素。这样从触发元素到顶层节点之间的所有节点如果有绑定这个事件,React都会触发对应的事件处理函数。这就是所谓的React模拟事件系统。
2.如果你要获取原生的Dom,然后进行相关Dom操作,你可以给你想要获得的标签内分配ref属性(比如ref="articleNext"),然后在组件内React.findDOMNode(this.refs.articleNext)获得对应的Dom,然后就可以愉快的操作Dom了。
3.React高效的Diff算法。这让我们无需担心性能问题,由虚拟DOM来确保只对界面上真正变化的部分进行实际的DOM操作。
那么就剩下如何向<Nav />组件传递消息了,这里我采取的是订阅发布模式(观察者模式)来进行通信的。引用PubSubJS
首先在<Nav />中,我们订阅了navClass这么一个事件
componentDidMount: function () { console.log("Nav -- componentDidMount"); token = PubSub.subscribe(EventName.navClass, this.changeActive); }, changeActive: function(msg, data){ //console.log(data); let self = $(‘#navbar-nav li[data-active="‘ + data.categoryId +‘"]‘); $(‘#navbar-nav li‘).removeClass(‘active‘); self.addClass(‘active‘); self.parents("li.dropdown").addClass(‘active‘); }, componentWillUnmount: function () { console.log("componentWillUnmount"); PubSub.unsubscribe( token ); }
在<Article />组件中去发布这个事件,并且传递了{categoryId: cid}过去
changeNavClassPre: function(e){ console.log("Article -- changeNavClassPre"); let cid = React.findDOMNode(this.refs.articlePre).getAttribute("data-category-id"); PubSub.publish(EventName.navClass, {categoryId: cid}); }, changeNavClassNext: function(e){ console.log("Article -- changeNavClassPre"); let cid = React.findDOMNode(this.refs.articleNext).getAttribute("data-category-id"); PubSub.publish(EventName.navClass, {categoryId: cid}); }
<Nav / >就可以去响应这个事件,相应的去操作Dom.这样就实现了不是嵌套关系的组件之间的通信。
说到这里,对于数据的请求、数据的变化等场景,可以使用 Flux、Relay、GraphQL 来处理。
注:入门react,个人观点有误请指正,不足请提出。
原文:http://www.cnblogs.com/alsy/p/5372770.html