打造在線編譯器 之 對(duì)文件目錄的操作

菜單欄中子文件顯示/隱藏的切換動(dòng)畫(huà)

最初調(diào)研的 rc-collapse 組件讼溺,但是其 Collapse 與 Panel 的設(shè)置并不適合于文件目錄結(jié)構(gòu)的展示怒坯,并且這兩者父子組件耦合嚴(yán)重藻懒,便轉(zhuǎn)而調(diào)研單純的 Collapse組件,比如react-collapse归敬。這是單純的一個(gè) component-wrapper for collapse animation鄙早,在實(shí)現(xiàn)目錄結(jié)構(gòu)的展示上對(duì)開(kāi)發(fā)時(shí)的限制減少了很多限番。但是在實(shí)際使用中發(fā)現(xiàn)了另一個(gè)比較重要的問(wèn)題:這些 wrapper 都有一個(gè)屬性isOpened來(lái)控制當(dāng)前組件是展開(kāi)還是折疊狀態(tài),這由我們傳入 props 控制弥虐,而當(dāng)切換展示文件時(shí)(也就是改變了model 中的 activeId)就會(huì)觸發(fā)該 wrapper 的rerender,即如果該組件原本是展開(kāi)的驴剔,那么切換展示文件之后丧失,該組件就會(huì)出現(xiàn)由先折疊(默認(rèn)狀態(tài))轉(zhuǎn)為展開(kāi)(props 使然)的動(dòng)畫(huà)。

在當(dāng)前的場(chǎng)景(展示多級(jí)文件目錄)下琳拭,動(dòng)畫(huà)依靠數(shù)據(jù)/狀態(tài)驅(qū)動(dòng)描验,當(dāng)前打開(kāi)的文件即 activeItem,是根據(jù)當(dāng)前頁(yè)面狀態(tài)中的 activeId ===item.id? 來(lái)添加 active 的樣式的絮缅。那么在多級(jí)菜單欄中呼股,切換文件之后,activeId 改變吸奴, activeItem 也必然改變缠局,此時(shí)整個(gè)目錄是在做 diff 比較然后刷新的,那么涉及到文件夾的顯示/隱藏必然也將重新渲染(如果文件 isOpened狀態(tài)保存在每個(gè) Collapse 組件內(nèi)部读处,則 rerender 之后都會(huì)是恢復(fù) state的初始值妙啃,如果放在 props 則必然會(huì)顯示組件重新渲染的動(dòng)畫(huà)過(guò)程)而這是我們不希望看到的揖赴。

此時(shí)想要 jquery 時(shí)代的控制:只有在我點(diǎn)擊文件夾的時(shí)候才進(jìn)行展開(kāi)/折疊動(dòng)畫(huà)的過(guò)程切換,其余 rerender 的時(shí)候不應(yīng)用動(dòng)畫(huà)效果渐北。同時(shí)點(diǎn)擊之后展開(kāi)/折疊的狀態(tài)還需要在 props 中去更新铭拧,當(dāng)切換文件時(shí)不至于使得原本打開(kāi)的文件夾被折疊上恃锉。此時(shí)動(dòng)畫(huà)就需要自己使用 CSS 控制去實(shí)現(xiàn)就更容易一些呕臂,同時(shí)基于 props 記錄管理文件夾的當(dāng)前狀態(tài)歧蒋。

實(shí)現(xiàn):基于 原生 div 展示 sidebar ,同時(shí)默認(rèn)折疊萝映,當(dāng)點(diǎn)擊文件夾時(shí) 通過(guò) updateProps 更新該文件夾 props.isCollapsed 的 值阐虚,進(jìn)而觸發(fā)對(duì) class 進(jìn)行修改,實(shí)現(xiàn)折疊/顯示的切換奥秆。當(dāng)切換激活文件時(shí)咸灿,整個(gè)sideMenu 仍然會(huì)rerender, 但因?yàn)?props.isCollapsed 一直沒(méi)變,添加的 class 也不變谷异,所以不會(huì)有動(dòng)畫(huà)過(guò)程出現(xiàn)锦聊。所以在整體 rerender 的過(guò)程中,如果想要保證內(nèi)部組件的動(dòng)畫(huà)過(guò)程在 rerender 時(shí)不出現(xiàn)尺上,自行控制 css 是不錯(cuò)的方法圆到。

其中記錄各個(gè)文件夾的props.isCollapsed狀態(tài)由 model 中一個(gè)對(duì)象記錄各個(gè)文件夾的狀態(tài)

collapseObj={
    dirId1:true,
    dirId2:false
}

對(duì)于每個(gè)文件夾結(jié)構(gòu)獨(dú)立為一個(gè)組件(代碼有刪改):

haddleClick=()=>{
  this.props.updateCollapseObj({
    id:this.props.id,
    state:this.props.getCollapseObj[this.props.id]?false:true
  })
}
render(){
  const {id,name,panel}=this.props;
  let divClass= classNames({
    'panel':true,
    'show':this.props.getCollapseObj[this.props.id]
  })

  return (
    <div>
      <p data-id={id} onClick={this.haddleClick} className="panelName">
        <i className="iconfont icon-folder-closed"></i>
        {name}
      </p>
      <div className={divClass}>
        {panel}
      </div>
    </div> 
    )
}

針對(duì)菜單欄添加 contextMenu 如新建文件/重命名/刪除文件等操作芽淡。

js 支持右鍵自定義事件contextMenu挣菲,但是自己實(shí)現(xiàn)時(shí)需要封裝好一些功能掷邦,其中最重要的是不論點(diǎn)擊rename/createFile/deleteFile 哪個(gè)按鈕椭赋,我們都需要得到觸發(fā)該 contextMenu 的元素id。調(diào)研的有react-contextmenureact-contexify宣蔚,盡管后者 star數(shù)量上比較少蔓涧,但更能滿足我們的需求元暴,因?yàn)樵诋?dāng)前場(chǎng)景(展示多級(jí)目錄)下,我們需要簡(jiǎn)單的得到觸發(fā) contextMenu 的元素鉴未,前者對(duì)此的支持度并不好鸠姨。

react-contexify封裝在 Item 上的click方法會(huì)接受3個(gè)參數(shù)handleClick(targetNode,ref,data)。得到觸發(fā)該 contextMenu 的元素targetNode之后连茧,我們?nèi)绾蔚玫狡?id 屬性呢巍糯,此處不要忘了威力無(wú)窮的屬性data-xxx,可以給 targetNode 添加data-id屬性罚斗,然后通過(guò)targetNode.dataset.id得到针姿。

對(duì)文件的 delete/rename/create 操作厌衙,我們由易到難來(lái)介紹:

  • deleteOperation:對(duì)于刪除操作,在前端我們比較容易得到將要?jiǎng)h除的文件的 id溉愁,直接提交即可,如果要在 model 中處理的話撤蟆,記得這是一個(gè)多級(jí)的文件目錄結(jié)構(gòu)來(lái)說(shuō)堂污,各種處理都要進(jìn)行深度(拷貝/過(guò)濾)
  • renameOperation:這是一個(gè)副作用比較多的操作,觸發(fā) rename 之后應(yīng)該該文件名可編輯讨衣,且其初始內(nèi)容為點(diǎn)擊之前的展示內(nèi)容反镇,進(jìn)行修改之后娘汞,回車鍵觸發(fā)內(nèi)容提交,文件名更新惊豺,退出可編輯狀態(tài)禽作。如果是esc鍵或者點(diǎn)擊了輸入框之外的區(qū)域,默認(rèn)是撤銷修改烹俗,退出可編輯狀態(tài)萍程,文件名仍顯然之前狀態(tài)尘喝。

    對(duì)于能夠不斷切換是否可編輯狀態(tài)的元素斋陪,在這兒使用 input 再何時(shí)不過(guò),其初始不可編輯 disabled缔赠,當(dāng)觸發(fā) rename之后改變其 disabled=false友题。我們都知道input 之類的 form 表單相關(guān)組件在 react 中不同于其他組件度宦,我們要使用受控組件實(shí)現(xiàn)組件顯示與用戶輸入的實(shí)時(shí)交互告匠,那么將每個(gè)文件名(包含 input的組件)獨(dú)立為一個(gè)組件离唬,在組件內(nèi)部通過(guò) state 實(shí)現(xiàn)對(duì) 當(dāng)前input組件的控制输莺。 在此考慮下ContextMenuProvider(react-contexify提供的觸發(fā) contextMenu 的容器)包裹在哪個(gè)元素上比較合適?每個(gè)文件名的 DOM 結(jié)構(gòu)如:('div',{'i','input'})型凳。因?yàn)镃ontextMenuItem 的 onClick 事件是可以直接得到 targetNode 的嘱函,在副作用很多的地方如果我們可以直接與 input 交互是很方便的实夹,所以文件名組件主要結(jié)構(gòu)如下:

    render(){
      const {fileId, setActiveId} = this.props;
      let activeClass=classNames({
        'list-item':true,
        'active':this.isCurrentFile(fileId)
      })
    
      return (
        <div className={activeClass} onClick={()=>{
          setActiveId(fileId)
        }}>
          <i className="iconfont icon-file"></i>
          <ContextMenuProvider className="provider" id="menu_id" >
           <input className="inputClass" data-id={fileId} type="text" value={this.state.value} onChange={this.handleChange.bind(this)} disabled="disabled"/>
          </ContextMenuProvider>
        </div>
      )
    }
    

    再回到 rename操作的交互過(guò)程,控制 input 編輯狀態(tài)與退出編輯狀態(tài)后的顯示荸实。注意三點(diǎn):

    • 執(zhí)行targetNode.blur()方法后也會(huì)觸發(fā)已注冊(cè)的事件'blur'准给,所以 blur 之后的副作用都放在blur 事件中處理重抖。
    • 在blur 事件中,在處理完之后需要將判斷條件invalidEditing置為非畔规,否則在 blur事件完成之前該段代碼可能會(huì)執(zhí)行隨機(jī)n次恨统。
      • 在 onClick 中添加的監(jiān)聽(tīng)事件,切記使用完成后移除莫绣。
    renameFile(targetNode, ref, data){
      const targetId = targetNode.dataset.id;
      let {renameOperation} =this.props
      const ESCAPE_KEY = 27;
      const ENTER_KEY = 13;
      let invalidEditing=true
      let prevText=targetNode.value;
      targetNode.disabled=false
      targetNode.spellcheck = false;
      targetNode.focus()
      targetNode.addEventListener('blur',function blurHandler(e){
        if (invalidEditing) {
          targetNode.value=prevText;
        }
        targetNode.disabled=true
        invalidEditing=false
        targetNode.removeEventListener('blur',blurHandler,false);
      })
      targetNode.addEventListener('keydown',function keydownHandler(e){
        if (e.which===ESCAPE_KEY) {
          targetNode.blur()
        }else if(e.which===ENTER_KEY){
          invalidEditing=false;
          let newName=targetNode.value
          targetNode.blur()
          targetNode.removeEventListener('keydown',keydownHandler,false);
          if(newName==prevText){
            return
          }
          //model 方法对室,提交更改信息
          renameOperation({
            id:targetId,
            name:newName
          })
        }
      })
    }
    
  • createOperation : 得到parentId 后掩宜,向其數(shù)組中插入(unshift)一項(xiàng) 默認(rèn)數(shù)據(jù)。因?yàn)?model 的改變此時(shí)菜單欄會(huì)刷新纠吴。對(duì)于創(chuàng)建操作慧瘤,我們還想要實(shí)現(xiàn):對(duì)該文件名直接進(jìn)入編輯模式,此后就和 renameOperation相同了糖儡,只要得到相應(yīng)的 targetNode 觸發(fā)renameOperation 方法就好怔匣。那么在數(shù)據(jù)驅(qū)動(dòng)的應(yīng)用中每瞒,我們?nèi)绾螌?shí)現(xiàn)這后續(xù)的銜接?--基于 react 的生命周期方法代芜。

    在菜單欄 rerender 完成之后一定會(huì)觸發(fā) componentDidUpdate方法浓利。但是componentDidUpdate方法在很多情況下都會(huì)被觸發(fā)贷掖,我們需要一個(gè)變量來(lái)判斷只有是 createOperation 導(dǎo)致的更新才執(zhí)行一下操作,并且在完成任務(wù)之后將該變量置非:

    if (this.props.getFileNameIsCreating) {
        let {getActiveId} =this.props
        let untitledNode = document.querySelector(`input[data-id="${getActiveId}"]`);
        this.renameFile(untitledNode)
        this.props.closeCreatingFileNameState();
    }   
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昆咽,一起剝皮案震驚了整個(gè)濱河市掷酗,隨后出現(xiàn)的幾起案子腹暖,更是在濱河造成了極大的恐慌翰萨,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,464評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阿蝶,死亡現(xiàn)場(chǎng)離奇詭異黄绩,居然都是意外死亡爽丹,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門真仲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秸应,“玉大人碑宴,你說(shuō)我怎么就攤上這事』雠玻” “怎么了捕仔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,078評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵榜跌,是天一觀的道長(zhǎng)钓葫。 經(jīng)常有香客問(wèn)我,道長(zhǎng)帆调,這世上最難降的妖魔是什么豆同? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,979評(píng)論 1 299
  • 正文 為了忘掉前任影锈,我火速辦了婚禮蝉绷,結(jié)果婚禮上枣抱,老公的妹妹穿的比我還像新娘。我一直安慰自己桅狠,他們只是感情好中跌,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布淤刃。 她就那樣靜靜地躺著逸贾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪灼伤。 梳的紋絲不亂的頭發(fā)上咪鲜,一...
    開(kāi)封第一講書(shū)人閱讀 52,584評(píng)論 1 312
  • 那天疟丙,我揣著相機(jī)與錄音颖侄,去河邊找鬼。 笑死享郊,一個(gè)胖子當(dāng)著我的面吹牛览祖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炊琉,決...
    沈念sama閱讀 41,085評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼展蒂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了苔咪?” 一聲冷哼從身側(cè)響起锰悼,我...
    開(kāi)封第一講書(shū)人閱讀 40,023評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤团赏,失蹤者是張志新(化名)和其女友劉穎箕般,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體舔清,經(jīng)...
    沈念sama閱讀 46,555評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丝里,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評(píng)論 3 342
  • 正文 我和宋清朗相戀三年可柿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丙者。...
    茶點(diǎn)故事閱讀 40,769評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖营密,靈堂內(nèi)的尸體忽然破棺而出械媒,到底是詐尸還是另有隱情,我是刑警寧澤评汰,帶...
    沈念sama閱讀 36,439評(píng)論 5 351
  • 正文 年R本政府宣布纷捞,位于F島的核電站,受9級(jí)特大地震影響被去,放射性物質(zhì)發(fā)生泄漏主儡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評(píng)論 3 335
  • 文/蒙蒙 一惨缆、第九天 我趴在偏房一處隱蔽的房頂上張望糜值。 院中可真熱鬧,春花似錦坯墨、人聲如沸寂汇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,601評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骄瓣。三九已至,卻和暖如春耍攘,著一層夾襖步出監(jiān)牢的瞬間榕栏,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,702評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工蕾各, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扒磁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,191評(píng)論 3 378
  • 正文 我出身青樓示损,卻偏偏與公主長(zhǎng)得像渗磅,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子检访,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 深入JSX date:20170412筆記原文其實(shí)JSX是React.createElement(componen...
    gaoer1938閱讀 8,075評(píng)論 2 35
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理始鱼,服務(wù)發(fā)現(xiàn),斷路器脆贵,智...
    卡卡羅2017閱讀 134,714評(píng)論 18 139
  • 自己最近的項(xiàng)目是基于react的医清,于是讀了一遍react的文檔,做了一些記錄(除了REFERENCE部分還沒(méi)開(kāi)始讀...
    潘逸飛閱讀 3,402評(píng)論 1 10
  • 最近看了一本關(guān)于學(xué)習(xí)方法論的書(shū)卖氨,強(qiáng)調(diào)了記筆記和堅(jiān)持的重要性会烙。這幾天也剛好在學(xué)習(xí)React负懦,所以我打算每天堅(jiān)持一篇R...
    gaoer1938閱讀 1,690評(píng)論 0 5
  • 五一檔《喜歡·你》的出現(xiàn), 向那些唱衰“霸道總裁愛(ài)上我”的影迷啪啪打臉并證明沃缘, 只要拍的好躯枢, 瑪麗蘇套路照樣可以成...
    大柱哥哥閱讀 680評(píng)論 0 2