react+redux實(shí)戰(zhàn)(四)----實(shí)現(xiàn)遮罩動畫與多組件間通信

上一篇文章:react+redux實(shí)戰(zhàn)(三)----異步交互

已經(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)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,138評論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,414評論 4 61
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,163評論 22 665
  • 這兩日,嘉兒對《I Am a Bunny》這本書的興趣擴(kuò)展了,不再是一味看小兔子與蝴蝶玩耍的那頁,而是翻閱全書了。...
    demi小貓閱讀 347評論 0 0
  • AFNetworking是一款在OS X和iOS下都令人喜愛的網(wǎng)絡(luò)庫。為了迎合iOS新版本的升級, AFNetwo...
    木木小林醬閱讀 703評論 1 1

友情鏈接更多精彩內(nèi)容