美文网首页
打造在线编译器 之 对文件目录的操作

打造在线编译器 之 对文件目录的操作

作者: RichardBillion | 来源:发表于2017-10-23 11:20 被阅读162次

    菜单栏中子文件显示/隐藏的切换动画

    最初调研的 rc-collapse 组件,但是其 Collapse 与 Panel 的设置并不适合于文件目录结构的展示,并且这两者父子组件耦合严重,便转而调研单纯的 Collapse组件,比如react-collapse。这是单纯的一个 component-wrapper for collapse animation,在实现目录结构的展示上对开发时的限制减少了很多。但是在实际使用中发现了另一个比较重要的问题:这些 wrapper 都有一个属性isOpened来控制当前组件是展开还是折叠状态,这由我们传入 props 控制,而当切换展示文件时(也就是改变了model 中的 activeId)就会触发该 wrapper 的rerender,即如果该组件原本是展开的,那么切换展示文件之后,该组件就会出现由先折叠(默认状态)转为展开(props 使然)的动画。

    在当前的场景(展示多级文件目录)下,动画依靠数据/状态驱动,当前打开的文件即 activeItem,是根据当前页面状态中的 activeId ===item.id? 来添加 active 的样式的。那么在多级菜单栏中,切换文件之后,activeId 改变, activeItem 也必然改变,此时整个目录是在做 diff 比较然后刷新的,那么涉及到文件夹的显示/隐藏必然也将重新渲染(如果文件 isOpened状态保存在每个 Collapse 组件内部,则 rerender 之后都会是恢复 state的初始值,如果放在 props 则必然会显示组件重新渲染的动画过程)而这是我们不希望看到的。

    此时想要 jquery 时代的控制:只有在我点击文件夹的时候才进行展开/折叠动画的过程切换,其余 rerender 的时候不应用动画效果。同时点击之后展开/折叠的状态还需要在 props 中去更新,当切换文件时不至于使得原本打开的文件夹被折叠上。此时动画就需要自己使用 CSS 控制去实现就更容易一些,同时基于 props 记录管理文件夹的当前状态。

    实现:基于 原生 div 展示 sidebar ,同时默认折叠,当点击文件夹时 通过 updateProps 更新该文件夹 props.isCollapsed 的 值,进而触发对 class 进行修改,实现折叠/显示的切换。当切换激活文件时,整个sideMenu 仍然会rerender, 但因为 props.isCollapsed 一直没变,添加的 class 也不变,所以不会有动画过程出现。所以在整体 rerender 的过程中,如果想要保证内部组件的动画过程在 rerender 时不出现,自行控制 css 是不错的方法。

    其中记录各个文件夹的props.isCollapsed状态由 model 中一个对象记录各个文件夹的状态

    collapseObj={
        dirId1:true,
        dirId2:false
    }
    

    对于每个文件夹结构独立为一个组件(代码有删改):

    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> 
        )
    }
    

    针对菜单栏添加 contextMenu 如新建文件/重命名/删除文件等操作。

    js 支持右键自定义事件contextMenu,但是自己实现时需要封装好一些功能,其中最重要的是不论点击rename/createFile/deleteFile 哪个按钮,我们都需要得到触发该 contextMenu 的元素id。调研的有react-contextmenureact-contexify,尽管后者 star数量上比较少,但更能满足我们的需求,因为在当前场景(展示多级目录)下,我们需要简单的得到触发 contextMenu 的元素,前者对此的支持度并不好。

    react-contexify封装在 Item 上的click方法会接受3个参数handleClick(targetNode,ref,data)。得到触发该 contextMenu 的元素targetNode之后,我们如何得到其 id 属性呢,此处不要忘了威力无穷的属性data-xxx,可以给 targetNode 添加data-id属性,然后通过targetNode.dataset.id得到。

    对文件的 delete/rename/create 操作,我们由易到难来介绍:

    • deleteOperation:对于删除操作,在前端我们比较容易得到将要删除的文件的 id,直接提交即可,如果要在 model 中处理的话,记得这是一个多级的文件目录结构来说,各种处理都要进行深度(拷贝/过滤)
    • renameOperation:这是一个副作用比较多的操作,触发 rename 之后应该该文件名可编辑,且其初始内容为点击之前的展示内容,进行修改之后,回车键触发内容提交,文件名更新,退出可编辑状态。如果是esc键或者点击了输入框之外的区域,默认是撤销修改,退出可编辑状态,文件名仍显然之前状态。

      对于能够不断切换是否可编辑状态的元素,在这儿使用 input 再何时不过,其初始不可编辑 disabled,当触发 rename之后改变其 disabled=false。我们都知道input 之类的 form 表单相关组件在 react 中不同于其他组件,我们要使用受控组件实现组件显示与用户输入的实时交互,那么将每个文件名(包含 input的组件)独立为一个组件,在组件内部通过 state 实现对 当前input组件的控制。 在此考虑下ContextMenuProvider(react-contexify提供的触发 contextMenu 的容器)包裹在哪个元素上比较合适?每个文件名的 DOM 结构如:('div',{'i','input'})。因为ContextMenuItem 的 onClick 事件是可以直接得到 targetNode 的,在副作用很多的地方如果我们可以直接与 input 交互是很方便的,所以文件名组件主要结构如下:

      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操作的交互过程,控制 input 编辑状态与退出编辑状态后的显示。注意三点:

      • 执行targetNode.blur()方法后也会触发已注册的事件'blur',所以 blur 之后的副作用都放在blur 事件中处理。
      • 在blur 事件中,在处理完之后需要将判断条件invalidEditing置为非,否则在 blur事件完成之前该段代码可能会执行随机n次。
        • 在 onClick 中添加的监听事件,切记使用完成后移除。
      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 后,向其数组中插入(unshift)一项 默认数据。因为 model 的改变此时菜单栏会刷新。对于创建操作,我们还想要实现:对该文件名直接进入编辑模式,此后就和 renameOperation相同了,只要得到相应的 targetNode 触发renameOperation 方法就好。那么在数据驱动的应用中,我们如何实现这后续的衔接?--基于 react 的生命周期方法。

      在菜单栏 rerender 完成之后一定会触发 componentDidUpdate方法。但是componentDidUpdate方法在很多情况下都会被触发,我们需要一个变量来判断只有是 createOperation 导致的更新才执行一下操作,并且在完成任务之后将该变量置非:

      if (this.props.getFileNameIsCreating) {
          let {getActiveId} =this.props
          let untitledNode = document.querySelector(`input[data-id="${getActiveId}"]`);
          this.renameFile(untitledNode)
          this.props.closeCreatingFileNameState();
      }   
      

    相关文章

      网友评论

          本文标题:打造在线编译器 之 对文件目录的操作

          本文链接:https://www.haomeiwen.com/subject/ubdzyxtx.html