已經(jīng)實(shí)現(xiàn)了點(diǎn)贊的交互功能,本文就來實(shí)現(xiàn)點(diǎn)擊評論按鈕彈出評論框功能。
提出問題
對于評論頁面,著重有以下兩點(diǎn)需求需要考慮:
- 彈出效果。
觸發(fā)方式為點(diǎn)擊新聞詳情頁的多個(gè)評論按鈕,評論彈出框?yàn)槿?,從頁面底部飛入飛出效果,如下圖:

在頁面飛入的過程中,為了交互效果的美觀,需要有遮罩層實(shí)現(xiàn)背景色的變化(這個(gè)各位應(yīng)該能想到吧,靜態(tài)圖確實(shí)好難表述)。以前我們使用jquery的思路是 :在該評論組件中,開放show和close方法,show()方法就是遮罩不透明度變化并配合頁面的飛入效果,hide方法實(shí)現(xiàn)遮罩層不透明度消失和頁面的飛出效果。需要彈出評論框時(shí)就全局找到該組件實(shí)例,然后調(diào)用其方法。其中以show方法為例,基本流程是給body添加遮罩層,將該組件置于遮罩的子級,然后修改組件的class使其通過transform從頁面底部移動出來。同樣的,react組件化開發(fā)時(shí),我們該如何給body添加遮罩層?又如何將該組件渲染到遮罩層中,而不受該組件引用地點(diǎn)的限制?
- 通信問題。
文章詳情頁中,評論部分如下:

有三種類型的評論按鈕都可以觸發(fā)彈出評論框:
(1)最下邊相對視窗絕對定位的功能條,該組件和新聞內(nèi)容、整個(gè)評論組件為兄弟元素;
(2)Comment組件(也就是一級評論)中的評論按鈕
(3)Reply組件(即二級評論,是Comment組件的子組件)中的評論按鈕
這三中評論按鈕組成了祖父-父-子的關(guān)系,我們的Comment-popup組件無論放在哪兒 ,跟他們都無法用簡單的父子通信方式(父-->子,通過props;子-->父,通過回調(diào)函數(shù))實(shí)現(xiàn),那么該使用什么方式實(shí)現(xiàn)通信呢?
解決思路
首先考慮react組件的使用方法:react推崇通過數(shù)據(jù)流也就是狀態(tài)的轉(zhuǎn)移來自動改變視圖,因此寫代碼時(shí)我們不會像jquery那樣在自己的組件中去實(shí)現(xiàn)show或者close方法,而是要從父組件中接收props從而改變自己的顯示/隱藏狀態(tài)。當(dāng)清楚react是靠狀態(tài)改變視圖這一點(diǎn)后,以下的思路就比較清晰了。
在新聞詳情頁article_show中有多個(gè)組件ArticleDetail、RelatedComments、CommentPopup和Toolbar,因?yàn)門oolbar和RelatedComments中都有按鈕可以觸發(fā)評論框的彈出,所以也將CommentPopup組件放到了該層級。在article_show 頁面中給評論框一個(gè)open參數(shù)作為開關(guān)
//article_show頁面
//評論框狀態(tài)默認(rèn)為關(guān)
constructor(props){
super(props);
this.state={
open:false
}
}
//通過setState方法實(shí)現(xiàn)開關(guān)
show(){
this.setState({
open:true
})
}
close(){
this.setState({
open:false
})
}
并將該參數(shù)作為props傳遞到CommentPopup組件內(nèi)部,在組件中通過componentWillReceiveProps方法在接收到新的props時(shí)判斷參數(shù)來決定是開還是關(guān),然后做相應(yīng)的處理,組件內(nèi)部的實(shí)現(xiàn)我們后邊會詳細(xì)介紹。
//article_show頁面中的render方法
//其余組件隱去了不必要的屬性傳遞
render(){
<ArticleDetail/>
<RelatedComments/>
<Toolbar/>
<CommentPopup open={this.state.open}/>
}
CommentPopup組件狀態(tài)的改變需要從article_show頁面接收open屬性的變化,也就是所有觸發(fā)評論按鈕的響應(yīng)都需要回到article_show頁面來統(tǒng)一處理,那么在該場景下,發(fā)布-訂閱模式將是最好的選擇。
當(dāng)RelatedComments或者Toolbar中的評論按鈕被點(diǎn)擊時(shí),就觸發(fā)顯示評論彈出框的事件,訂閱了該事件的article_show頁面就會收到通知,然后調(diào)用show方法,改變open的屬性值,然后視圖自動刷新。比如在RelateComments組件中,在按鈕的點(diǎn)擊動作上綁定如下觸發(fā)事件
//RelatedComments組件中的按鈕來觸發(fā)彈出評論框的動作
//發(fā)布訂閱模式的具體實(shí)現(xiàn)代碼有很多,eventProxy只是我應(yīng)用的一種
clickMessage(){
eventProxy.trigger('Comment::Popup','msg');
}
在article_show頁,componentDidMount()方法中訂閱該事件
componentDidMount(){
eventProxy.on('Comment::Popup',(msg)=>{
console.log(msg);
//調(diào)用使評論組件出現(xiàn)的方法
this.show();
})
}
通過以上部分介紹的發(fā)布訂閱模式,即可以比較思路清晰的實(shí)現(xiàn)不同組件中的評論按鈕都能調(diào)用評論框的彈出。
彈窗內(nèi)部的具體實(shí)現(xiàn)
上面已經(jīng)提到,在article_show頁面中會將open參數(shù)作為屬性傳遞給CommentPopup組件內(nèi)部
<CommentPopup open={this.state.open}/>
在該組件中使用componentWillReceiveProps()方法來判斷彈窗需要打開還是關(guān)閉。componentWillReceiveProps會在每一次有屬性值傳入時(shí)被調(diào)用

'use strict'
import React from 'react';
import ReactDOM from 'react-dom';
import Utils from '../common/utils.js';
import eventProxy from '../common/eventProxy.js';
require('./index.less');
class CommentPopup extends React.Component{
constructor(props){
super(props);
}
cancelComment(){
//觸發(fā)自身組件的關(guān)閉事件
eventProxy.trigger('Comment::Hide','hide');
}
//主要利用該方法判斷屬性值的變化
componentWillReceiveProps(nextProps){
//如果當(dāng)前open為false,而且下一個(gè)狀態(tài)的open為true,則顯示彈窗
if (nextProps.open&&!this.props.open) {
//如果還沒有給body(在react中不要直接將組件渲染到body上,選擇用最外層的div.react-container)追加遮罩層,則新建
if (document.getElementsByClassName('overlay').length==0) {
// 創(chuàng)建 DOM
this.node = document.createElement('div');
// 給上 ClassName
this.node.className = 'overlay';
// 給 body 加上剛才的 帶有 className 的 div
document.getElementsByClassName('react-container')[0].appendChild(this.node);
let modal=(
<div className="com-comment-popup hide">
<div className="comment-popup-hd">
<div className="left"><a href="javascript:void(0)" className="link close-popup" onClick={this.cancelComment.bind(this)}>取消</a></div>
<div className="center">我有意見</div>
<div className="right"><a href="#" className="link submit">發(fā)布</a></div>
</div>
<div className="comment-popup-bd">
<form action="/post_comment" method="post">
<textarea name="content" placeholder="我有意見"></textarea>
</form>
</div>
</div>
);
let overlayer=document.getElementsByClassName('overlay')[0];
//利用ReactDOM提供的render方法,將該組件渲染到遮罩中
ReactDOM.render(modal,overlayer);
}
//給遮罩層添加class使其顯示
Utils.addClass(document.getElementsByClassName('overlay')[0],'overlay-visible');
//為提高動畫效果,使遮罩顏色有變化后,彈窗再執(zhí)行飛入動作
setTimeout(function(){
Utils.removeClass(document.getElementsByClassName('com-comment-popup')[0],'hide');
},50)
}
//如果當(dāng)前open狀態(tài)為true,下一個(gè)狀態(tài)open為false,則隱藏遮罩、關(guān)閉彈窗
if (this.props.open && !nextProps.open) {
Utils.addClass(document.getElementsByClassName('com-comment-popup')[0],'hide');
setTimeout(function(){
Utils.removeClass(document.getElementsByClassName('overlay')[0],'overlay-visible');
},50);
}
}
//因?yàn)樵摻M件需要被渲染到遮罩層中,而不是常規(guī)渲染到該組件被引用的地方,render()方法中返回null即可。
render(){
return null;
}
}
export default CommentPopup;
動畫部分主要靠對遮罩層和彈窗修改class實(shí)現(xiàn)。以下是相關(guān)的css代碼
.overlay{
position: fixed;
top: 0;
height: 100%;
width: 100%;
z-index: 999;
background-color: rgba(0,0,0,0.5);
transition-duration:0.4s;
visibility: hidden;
opacity: 0;
}
.overlay-visible{
visibility: visible;
opacity: 1;
}
.com-comment-popup{
display: block;
width: 100%;
height: 100%;
transition:transform 0.5s ease-out;
}
.hide{
transform:translate3d(0,100%,0);
}
其中,對遮罩通過visibility屬性的切換實(shí)現(xiàn)顯示/隱藏,因?yàn)関isibilty:hidden之后,其子組件也就是評論框也會不可見,并且重要的是,遮罩層代碼的存在也不會影響對正常文檔流的操作,正式基于這點(diǎn),我們才能在關(guān)閉評論框時(shí),保留相關(guān)的DOM結(jié)構(gòu),這樣再次點(diǎn)擊評論按鈕時(shí)就不用再次新建DOM,也不用每次都銷毀該DOM,關(guān)于這一點(diǎn),網(wǎng)上的很多例子做的都并不好,jquery中好的思想我們還是可以保留繼續(xù)使用的。
寫在最后
從最開始的毫無頭緒到最后功能的基本實(shí)現(xiàn),最重要的經(jīng)驗(yàn)就是對需求功能的一點(diǎn)點(diǎn)詳細(xì)劃分,然后各個(gè)擊破。多個(gè)有著復(fù)雜關(guān)系的組件間的通信使用發(fā)布訂閱模式可以實(shí)現(xiàn)組件間的有效解耦,這也是react官方在Flux架構(gòu)中推薦使用的方法。其次組件化的思想也一直在不斷的加深和理解,我們設(shè)計(jì)一個(gè)組件時(shí),肯定要考慮其接口設(shè)計(jì)(或生命周期等方法),因此解決復(fù)雜功能時(shí)先理清大的思路,小細(xì)節(jié)在各個(gè)組件中去實(shí)現(xiàn)就行。對這種彈出式組件,需要渲染的地方有了變化,所以又用到了ReactDOM的render()方法,這個(gè)在寫常規(guī)組件時(shí)比較少用到。最后,就是componentWillReceiveProps方法的使用,在react組件生命周期的介紹中一般也很少會介紹到,但是在系統(tǒng)的狀態(tài)管理中,該方法卻是不可缺少的一環(huán),通過它我們可以真真切切的感受到react通過狀態(tài)(數(shù)據(jù)流)來改變視圖的特點(diǎn)。