<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手動實(shí)現(xiàn)高仿github的內(nèi)容diff效果

          共 10027字,需瀏覽 21分鐘

           ·

          2020-08-23 20:28



          來源:廣蘭路地鐵?
          https://juejin.im/post/6857316059851325453

          前言

          最近發(fā)現(xiàn)了一個比較好用的內(nèi)容diff庫(就叫diff),非常方便js開發(fā)者實(shí)現(xiàn)文本內(nèi)容的diff,既可以直接簡單輸出格式化的字符串比較內(nèi)容,也可以輸出較為復(fù)雜的changes數(shù)據(jù)結(jié)構(gòu),方便二次開發(fā)。這里筆者就基于這個庫實(shí)現(xiàn)高仿github的文本diff效果。

          效果演示

          實(shí)現(xiàn)了代碼展開,單列和雙列對比等功能。示例如下:

          代碼演示站點(diǎn):http://tangshisanbaishou.xyz/diff/index.html

          如何實(shí)現(xiàn)

          核心原理

          最核心的文本diff算法,由diff庫替我們實(shí)現(xiàn),這里我們使用的是diffLines方法(關(guān)于diff庫的使用,筆者有一篇博文diff使用指南有詳細(xì)介紹)。通過該庫輸出的數(shù)據(jù)結(jié)構(gòu),對其進(jìn)行二次開發(fā),以便實(shí)現(xiàn)類似gitHub的文件diff效果。

          獲取輸入

          這里我們的比較內(nèi)容都是以字符串的形式進(jìn)行輸入。至于如何將文件轉(zhuǎn)化成字符串,在瀏覽器端可以使用Upload進(jìn)行文件上傳,然后在獲得的文件句柄上調(diào)用text方法,即可獲得文件對應(yīng)的字符串,類似這樣:

          import?React?from?'react';
          import?{?Upload?}?from?'antd';
          //??不一定要用react和antd,就是表達(dá)下思路
          class?Test?extends?React.Fragment?{
          ????changeFile?=?async?(type,?info)?=>?{
          ????????const?{?file?}?=?info;
          ????????const?content?=?await?file.originFileObj.text();
          ????????console.log(content);
          ????}

          ????render()?{
          ????????????????????onChange={this.changeFile.bind(null,?0)}
          ????????????customRequest={()?=>?{}}
          ????????>
          ????????????點(diǎn)我上傳1
          ????????</Upload>
          ????}
          }

          node端就要方便很多了,調(diào)用fs(文件系統(tǒng)庫),直接對文件流進(jìn)行讀取即可。

          輸出結(jié)構(gòu)分析

          接下來我們看看diffLines的輸出大致長什么樣:

          這里我們對輸出結(jié)果進(jìn)行分析,輸出是一個數(shù)組,數(shù)組的對象有多個屬性:

          • value: 表示代碼塊的具體內(nèi)容
          • count: 表示該代碼塊的行數(shù)
          • added: 如果該代碼塊為新增內(nèi)容,其值為true
          • removed:如果該代碼塊表示移除的內(nèi)容,其值為true

          到這里我們的實(shí)現(xiàn)思路已經(jīng)大致成型:根據(jù)數(shù)組內(nèi)容渲染代碼塊,以\n為分隔符,劃分代碼行,added部分標(biāo)綠,removed部分標(biāo)紅,其余部分正常顯示即可,至于具體的代碼行數(shù),可以根據(jù)count進(jìn)行計(jì)算。

          代碼實(shí)現(xiàn)

          原始數(shù)據(jù)處理

          如果參與比較的文件過大,公共部分的代碼中過長的部分需要進(jìn)行折疊,新增和移除的代碼需要全量展示,基于這個邏輯,我們將需要展示的代碼做如下劃分:

          確定了我們的展示邏輯,接下來需要做的就是針對diff庫處理之后的數(shù)據(jù)進(jìn)行處理,相關(guān)代碼如下:

          import?React?from?'react';
          import?{?Upload,?Button,?Layout,?Menu,?Radio?}?from?'antd';
          import?s?from?'./index.css';
          import?cx?from?'classnames';
          const?{?Content?}?=?Layout;

          const?SHOW_TYPE?=?{
          ????UNIFIED:?0,
          ????SPLITED:?1
          }

          const?BLOCK_LENGTH?=?5;

          export?default?class?ContentDiff?extends?React.Component?{
          ????state?=?{
          ????????//??供渲染的數(shù)據(jù)
          ????????lineGroup:?[],
          ????????//??展示的類型
          ????????showType:?SHOW_TYPE.UNIFIED
          ????}
          ????//??刷新供渲染的數(shù)據(jù)
          ????flashContent?=?(newArr)?=>?{
          ????????const?initLineGroup?=?(newArr?||?this.props.diffArr).map((item,?index,?originArr)?=>?{
          ????????????let?added,?removed,?value,?count;
          ????????????added?=?item.added;
          ????????????removed?=?item.removed;
          ????????????value?=?item.value;
          ????????????count?=?item.count;
          ????????????//??以\n為分隔符,將value分割成以行劃分的代碼
          ????????????const?strArr?=?value?.split('\n').filter(item?=>?item)?||?[];
          ????????????//??獲得當(dāng)前數(shù)據(jù)塊的類型+標(biāo)識新增?-表示移除?空格表示相同的內(nèi)容
          ????????????const?type?=?(added?&&?'+')?||?(removed?&&?'-')?||?'?';
          ????????????//??定義代碼塊的內(nèi)部結(jié)構(gòu),分為頭部,尾部和中間的隱藏部分
          ????????????let?head,?hidden,?tail;
          ????????????//??如果是增加或者減少的代碼塊,頭部填入內(nèi)容,尾部和隱藏區(qū)域都為空
          ????????????if?(type?!==?'?')?{
          ????????????????hidden?=?[];
          ????????????????tail?=?[];
          ????????????????head?=?strArr;
          ????????????}?else?{
          ????????????????const?strLength?=?strArr.length;
          ????????????????//??如果公共部分的代碼量過少,就統(tǒng)一展開
          ????????????????if?(strLength?<=?BLOCK_LENGTH?*?2)?{
          ????????????????????hidden?=?[];
          ????????????????????tail?=?[];
          ????????????????????head?=?strArr;
          ????????????????}?else?{
          ????????????????????//??否則只展示代碼塊頭尾部分的代碼,中間部分折疊
          ????????????????????head?=?strArr.slice(0,?BLOCK_LENGTH)
          ????????????????????hidden?=?strArr.slice(BLOCK_LENGTH,?strLength?-?BLOCK_LENGTH);
          ????????????????????tail?=?strArr.slice(strLength?-?BLOCK_LENGTH);
          ????????????????}
          ????????????}
          ????????????return?{
          ????????????????//??代碼塊類型,新增,移除,或者沒變
          ????????????????type,
          ????????????????//??代碼行數(shù)
          ????????????????count,
          ????????????????//??內(nèi)容區(qū)塊
          ????????????????content:?{
          ????????????????????hidden,
          ????????????????????head,
          ????????????????????tail
          ????????????????}
          ????????????}
          ????????});
          ????????//??接下來處理代碼的行數(shù),標(biāo)記左右兩側(cè)代碼塊的初始行數(shù)
          ????????let?lStartNum?=?1;
          ????????let?rStartNum?=?1;
          ????????initLineGroup.forEach(item?=>?{
          ????????????const?{?type,?count?}?=?item;
          ????????????item.leftPos?=?lStartNum;
          ????????????item.rightPos?=?rStartNum;
          ????????????//??移除代碼和新增代碼的兩部分分開計(jì)算
          ????????????lStartNum?+=?type?===?'+'???0?:?count;
          ????????????rStartNum?+=?type?===?'-'???0?:?count;
          ????????})
          ????????this.setState({
          ????????????lineGroup:?initLineGroup
          ????????});
          ????}
          ????render()?{
          ????????return?(
          ????????????//??...
          ????????)
          ????}
          }

          通過上述代碼完成對原始數(shù)據(jù)的處理,將表示內(nèi)容的數(shù)組中的對象劃分為三種:added,removed和公共代碼,并將內(nèi)容分成head,hidden和tail三部分(主要是為了公共代碼部分隱藏冗余的代碼),然后計(jì)算代碼塊在對比顯示時(shí)的初始行數(shù)行數(shù),分欄(splited)和整合(unified)模式下都可使用。

          整合模式下的內(nèi)容展示

          接下來是整合模式的展示代碼:

          export?default?class?ContentDiff?extends?React.Component?{
          ????state?=?{
          ????????//??供渲染的數(shù)據(jù)
          ????????lineGroup:?[],
          ????????//??展示的類型
          ????????showType:?SHOW_TYPE.UNIFIED
          ????}
          ????//??轉(zhuǎn)換展示模式
          ????handleShowTypeChange?=?(e)?=>?{
          ????????this.setState({
          ????????????showType:?e.target.value
          ????????})
          ????}
          ????//??判斷狀態(tài)
          ????get?isSplit()?{
          ????????return?this.state.showType?===?SHOW_TYPE.SPLITED;
          ????}

          ????//??刷新供渲染的數(shù)據(jù)
          ????flashContent?=?(newArr)?=>?{
          ????????//??省略重復(fù)內(nèi)容
          ????}

          ????//??給行號補(bǔ)足位數(shù)
          ????getLineNum?=?(number)?=>?{
          ????????return?('?????'?+?number).slice(-5);
          ????}

          ????//??獲取split下的內(nèi)容node
          ????getPaddingContent?=?(item)?=>?{
          ????????return?<div?className={cx(s.splitCon)}>{item}div>
          ????}

          ????paintCode?=?(item,?isHead?=?true)?=>?{
          ????????const?{?type,?content:?{?head,?tail,?hidden?},?leftPos,?rightPos}?=?item;
          ????????//??是否是公共部分
          ????????const?isNormal?=?type?===?'?';
          ????????//??根據(jù)類型選擇合適的class
          ????????const?cls?=?cx(s.normal,?type?===?'+'???s.add?:?'',?type?===?'-'???s.removed?:?'');
          ????????//??占位空格
          ????????const?space?=?"?????";
          ????????//??渲染頭部或者尾部內(nèi)容
          ????????return?(isHead???head?:?tail).map((sitem,?sindex)?=>?{
          ????????????let?posMark?=?'';
          ????????????if?(isNormal)?{
          ????????????????//??計(jì)算行號的偏移值
          ????????????????const?shift?=?isHead???0:?(head.length?+?hidden.length);
          ????????????????//??左右兩側(cè)的行數(shù)不一定一樣
          ????????????????posMark?=?(space?+?(leftPos?+?shift?+?sindex)).slice(-5)
          ????????????????????+?(space?+?(rightPos?+?shift?+?sindex)).slice(-5);
          ????????????}?else?{
          ????????????????//??增減部分的行號計(jì)算
          ????????????????posMark?=?type?===?'-'???this.getLineNum(leftPos?+?sindex)?+?space
          ????????????????????:?space?+?this.getLineNum(rightPos?+?sindex);
          ????????????}
          ????????????//??依次渲染行號,+?-號和代碼內(nèi)容
          ????????????return?<div?key={(isHead???'h-'?:?'t-')?+?sindex}?className={cls}>
          ????????????????<pre?className={cx(s.pre,?s.line)}>{posMark}pre>

          ????????????????<div?className={s.outerPre}><div?className={s.splitCon}><div?className={s.spanWidth}>{'?'?+?type?+?'?'}div>{this.getPaddingContent(sitem,?true)}div>div>
          ????????????div>
          ????????})
          ????}

          ????getUnifiedRenderContent?=?()?=>?{
          ????????//??根據(jù)lineGroup的內(nèi)容依次渲染代碼塊
          ????????return?this.state.lineGroup.map((item,?index)?=>?{
          ????????????const?{?type,?content:?{?hidden?}}?=?item;
          ????????????const?isNormal?=?type?===?'?';
          ????????????//??依次渲染head,hidden,tail三部分內(nèi)容
          ????????????return?<div?key={index}>
          ????????????????{this.paintCode(item)}
          ????????????????{hidden.length?&&?isNormal?&&?this.getHiddenBtn(hidden,?index)?||?null}
          ????????????????{this.paintCode(item,?false)}
          ????????????div>

          ????????})
          ????}
          ????render()?{
          ????????const?{?showType?}?=?this.state;
          ????????return?(
          ????????????<React.Fragment>
          ????????????????<div?className={s.radioGroup}>
          ????????????????????<Radio.Group?value={showType}?size='small'?onChange={this.handleShowTypeChange}>
          ????????????????????????<Radio.Button?value={SHOW_TYPE.UNIFIED}>UnifiedRadio.Button>

          ????????????????????????<Radio.Button?value={SHOW_TYPE.SPLITED}>SplitRadio.Button>
          ????????????????????Radio.Group>
          ????????????????div>

          ????????????????<Content?className={s.content}>
          ????????????????????<div?className={s.color}>
          ????????????????????????{this.isSplit???this.getSplitContent()
          ????????????????????????????:?this.getUnifiedRenderContent()}
          ????????????????????div>
          ????????????????Content>
          ????????????React.Fragment>
          ????????)
          ????}
          }

          以上的部分將lineGroup中的每個對象的content依次根據(jù)head,hidden,tail三部分來渲染,行數(shù)根據(jù)先前計(jì)算的lStartNumrStartNum來進(jìn)行展示。

          分欄模式下的內(nèi)容展示

          接下來是分欄的實(shí)現(xiàn):

          export?default?class?ContentDiff?extends?React.Component?{

          ????//??獲取split下的頁碼node
          ????getLNPadding?=?(origin)?=>?{
          ????????const?item?=?('?????'?+?origin).slice(-5);
          ????????return?<div?className={cx(s.splitLN)}>{item}div>
          ????}

          ????//??差異部分的代碼渲染
          ????getCombinePart?=?(leftPart?=?{},?rightPart?=?{})?=>?{
          ????????const?{?type:?lType,?content:?lContent,?leftPos:?lLeftPos,?rightPos:?lRightPos?}?=?leftPart;
          ????????const?{?type:?rType,?content:?rContent,?leftPos:?rLeftPos,?rightPos:?rRightPos?}?=?rightPart;
          ????????//??分別獲取左右兩側(cè)對應(yīng)的內(nèi)容和class
          ????????const?lArr?=?lContent?.head?||?[];
          ????????const?rArr?=?rContent?.head?||?[];
          ????????const?lClass?=?lType?===?'+'???s.add?:?s.removed;
          ????????const?rClass?=?rType?===?'+'???s.add?:?s.removed;
          ????????return?<React.Fragment>
          ????????????????<div?className={cx(s.iBlock,?s.lBorder)}>{lArr.map((item,?index)?=>?{
          ????????????????????//??渲染左半邊內(nèi)容,也就是刪除的部分(如果有的話)
          ????????????????????//??兩個div分別輸出行數(shù)和內(nèi)容
          ????????????????????return?<div?className={cx(s.prBlock,?lClass)}?key={index}>
          ????????????????????????{this.getLNPadding(lLeftPos?+?index)}
          ????????????????????????{this.getPaddingContent('-??'?+?item)}
          ????????????????????div>

          ????????????????})}div>
          ????????????????<div?className={cx(s.iBlock,?lArr.length???''?:?s.rBorder)}>{rArr.map((item,?index)?=>?{
          ????????????????????//??渲染右半邊內(nèi)容,也就是新增的部分(如果有的話)
          ????????????????????return?<div?className={cx(s.prBlock,?rClass)}?key={index}>
          ????????????????????????{this.getLNPadding(rRightPos?+?index)}
          ????????????????????????{this.getPaddingContent('+??'?+?item)}
          ????????????????????div>
          ????????????????})}div>
          ????????????React.Fragment>
          ????}

          ????//??無變化部分的代碼渲染
          ????getSplitCode?=?(targetBlock,?isHead?=?true)?=>?{
          ????????const?{?type,?content:?{?head,?hidden,?tail?},?leftPos,?rightPos}?=?targetBlock;
          ????????return?(isHead???head?:?tail).map((item,?index)?=>?{
          ????????????const?shift?=?isHead???0:?(head.length?+?hidden.length);
          ????????????//??左右兩邊除了樣式,基本沒有差異
          ????????????return?<div?key={(isHead???'h-'?:?'t-')?+?index}>
          ????????????????<div?className={cx(s.iBlock,?s.lBorder)}>{this.getLNPadding(leftPos?+?shift?+?index)}{this.getPaddingContent('????'?+?item)}div>

          ????????????????<div?className={s.iBlock}>{this.getLNPadding(rightPos?+?shift?+index)}{this.getPaddingContent('????'?+?item)}div>
          ????????????div>
          ????????})
          ????}

          ????//??渲染分欄的代碼
          ????getSplitContent?=?()?=>?{
          ????????const?length?=?this.state.lineGroup.length;
          ????????const?contentList?=?[];
          ????????for?(let?i?=?0;?i?????????????const?targetBlock?=?this.state.lineGroup[i];
          ????????????const?{?type,?content:?{?hidden?}?}?=?targetBlock;
          ????????????//??渲染相同的部分
          ????????????if?(type?===?'?')?{
          ????????????????contentList.push(<div?key={i}>
          ????????????????????{this.getSplitCode(targetBlock)}
          ????????????????????{hidden.length?&&?this.getHiddenBtn(hidden,?i)?||?null}
          ????????????????????{this.getSplitCode(targetBlock,?false)}
          ????????????????div>
          )
          ????????????}?else?if?(type?===?'-')?{
          ????????????????//??渲染移除的部分
          ????????????????const?nextTarget?=?this.state.lineGroup[i?+?1]?||?{?content:?{}};
          ????????????????const?nextIsPlus?=?nextTarget.type?===?'+';
          ????????????????contentList.push(<div?key={i}>
          ????????????????????{this.getCombinePart(targetBlock,?nextIsPlus???nextTarget?:?{})}
          ????????????????div>
          )
          ????????????????nextIsPlus???i?=?i?+?1?:?void?0;
          ????????????}?else?if?(type?===?'+')?{
          ????????????????//??渲染新增的部分
          ????????????????contentList.push(<div?key={i}>
          ????????????????????{this.getCombinePart({},?targetBlock)}
          ????????????????div>
          )
          ????????????}
          ????????}
          ????????return?<div>
          ????????????{contentList}
          ????????div>

          ????}

          ????//??省略重復(fù)代碼
          }

          這里的展示方式和unified模式下略有不同。公共部分和差異部分要使用不同的渲染函數(shù),相同的部分代碼要對齊,差異的部分左右兩側(cè)需要等高。

          展開摁鈕的實(shí)現(xiàn)

          接下來我們實(shí)現(xiàn)點(diǎn)擊展開的功能:

          export?default?class?ContentDiff?extends?React.Component?{
          ????//??省略重復(fù)的內(nèi)容

          ????//??根據(jù)三種點(diǎn)擊的狀態(tài),更新head,tail和hidden的內(nèi)容
          ????openBlock?=?(type,?index)?=>?{
          ????????const?copyOfLG?=?this.state.lineGroup.slice();
          ????????const?targetGroup?=?copyOfLG[index];
          ????????const?{?head,?tail,?hidden?}?=?targetGroup.content;
          ????????if?(type?===?'head')?{
          ????????????//??如果是點(diǎn)擊向上的箭頭,對head和hidden部分的內(nèi)容進(jìn)行更新
          ????????????targetGroup.content.head?=?head.concat(hidden.slice(0,?BLOCK_LENGTH));
          ????????????targetGroup.content.hidden?=?hidden.slice(BLOCK_LENGTH);
          ????????}?else?if?(type?===?'tail')?{
          ????????????//??如果是點(diǎn)擊向下的箭頭,對tail和hidden的部分進(jìn)行更新
          ????????????const?hLenght?=?hidden.length;
          ????????????targetGroup.content.tail?=?hidden.slice(hLenght?-?BLOCK_LENGTH).concat(tail);
          ????????????targetGroup.content.hidden?=?hidden.slice(0,?hLenght?-?BLOCK_LENGTH);
          ????????}?else?{
          ????????????//??如果是雙向箭頭,展開所有的內(nèi)容到head
          ????????????targetGroup.content.head?=?head.concat(hidden);
          ????????????targetGroup.content.hidden?=?[];
          ????????}
          ????????copyOfLG[index]?=?targetGroup;
          ????????this.setState({
          ????????????lineGroup:?copyOfLG
          ????????});
          ????}

          ????//??渲染隱藏的部分
          ????getHiddenBtn?=?(hidden,?index)?=>?{
          ????????//??如果隱藏的內(nèi)容過少,則顯示雙向箭頭
          ????????const?isSingle?=?hidden.length?2;
          ????????return?<div?key='collapse'?className={s.cutWrapper}>
          ????????????<div?className={cx(s.colLeft,?this.isSplit???s.splitWidth?:?'')}>
          ????????????????{isSingle???<div?className={s.arrow}?onClick={this.openBlock.bind(this,?'all',?index)}>
          ????????????????????{/*?雙向箭頭?*/}
          ????????????????????<svg?className={s.octicon}?viewBox="0?0?16?16"?version="1.1"?width="16"?height="16"?aria-hidden="true"><path?fillRule="evenodd"?d="M8.177.677l2.896?2.896a.25.25?0?01-.177.427H8.75v1.25a.75.75?0?01-1.5?0V4H5.104a.25.25?0?01-.177-.427L7.823.677a.25.25?0?01.354?0zM7.25?10.75a.75.75?0?011.5?0V12h2.146a.25.25?0?01.177.427l-2.896?2.896a.25.25?0?01-.354?0l-2.896-2.896A.25.25?0?015.104?12H7.25v-1.25zm-5-2a.75.75?0?000-1.5h-.5a.75.75?0?000?1.5h.5zM6?8a.75.75?0?01-.75.75h-.5a.75.75?0?010-1.5h.5A.75.75?0?016?8zm2.25.75a.75.75?0?000-1.5h-.5a.75.75?0?000?1.5h.5zM12?8a.75.75?0?01-.75.75h-.5a.75.75?0?010-1.5h.5A.75.75?0?0112?8zm2.25.75a.75.75?0?000-1.5h-.5a.75.75?0?000?1.5h.5z">path>
          svg>
          ????????????????div>
          ????????????????????:?<React.Fragment>
          ????????????????????????{/*?向上的箭頭?*/}
          ????????????????????????<div?className={s.arrow}?onClick={this.openBlock.bind(this,?'head',?index)}>
          ????????????????????????????<svg?className={s.octicon}?viewBox="0?0?16?16"?version="1.1"?width="16"?height="16"?aria-hidden="true"><path?fillRule="evenodd"?d="M8.177?14.323l2.896-2.896a.25.25?0?00-.177-.427H8.75V7.764a.75.75?0?10-1.5?0V11H5.104a.25.25?0?00-.177.427l2.896?2.896a.25.25?0?00.354?0zM2.25?5a.75.75?0?000-1.5h-.5a.75.75?0?000?1.5h.5zM6?4.25a.75.75?0?01-.75.75h-.5a.75.75?0?010-1.5h.5a.75.75?0?01.75.75zM8.25?5a.75.75?0?000-1.5h-.5a.75.75?0?000?1.5h.5zM12?4.25a.75.75?0?01-.75.75h-.5a.75.75?0?010-1.5h.5a.75.75?0?01.75.75zm2.25.75a.75.75?0?000-1.5h-.5a.75.75?0?000?1.5h.5z">path>svg>
          ????????????????????????div>
          ????????????????????????{/*?向下的箭頭?*/}
          ????????????????????????<div?className={s.arrow}?onClick={this.openBlock.bind(this,?'tail',?index)}>
          ????????????????????????????<svg?className={s.octicon}?viewBox="0?0?16?16"?version="1.1"?width="16"?height="16"?aria-hidden="true"><path?fillRule="evenodd"?d="M7.823?1.677L4.927?4.573A.25.25?0?005.104?5H7.25v3.236a.75.75?0?101.5?0V5h2.146a.25.25?0?00.177-.427L8.177?1.677a.25.25?0?00-.354?0zM13.75?11a.75.75?0?000?1.5h.5a.75.75?0?000-1.5h-.5zm-3.75.75a.75.75?0?01.75-.75h.5a.75.75?0?010?1.5h-.5a.75.75?0?01-.75-.75zM7.75?11a.75.75?0?000?1.5h.5a.75.75?0?000-1.5h-.5zM4?11.75a.75.75?0?01.75-.75h.5a.75.75?0?010?1.5h-.5a.75.75?0?01-.75-.75zM1.75?11a.75.75?0?000?1.5h.5a.75.75?0?000-1.5h-.5z">path>svg>
          ????????????????????????div>
          ????????????????????React.Fragment>
          ????????????????}
          ????????????div>
          ????????????<div?className={cx(s.collRight,?this.isSplit???s.collRightSplit?:?'')}><div?className={cx(s.colRContent,?isSingle???''?:?s.cRHeight)}>{`當(dāng)前隱藏內(nèi)容:${hidden.length}行`}div>div>
          ????????div>
          ????}
          }

          這里直接搬運(yùn)了git官網(wǎng)的svg箭頭圖片,查看更多的交互一共有三種,折疊內(nèi)容多于10行的,分別顯示上下箭頭,每點(diǎn)擊一次多展示5行內(nèi)容,一旦隱藏內(nèi)容少于10行,顯示雙向箭頭,此時(shí)點(diǎn)擊將展示所有的折疊內(nèi)容。這一部分的核心邏輯是可復(fù)用的,splited和unified內(nèi)容皆可以使用,只是在UI的處理上需要有一定的差別。

          UI細(xì)節(jié)

          在編碼過程中遇到一個問題,diff庫處理之后的value是包含空格的,類似于這樣 const isSingle = true;但是在展示時(shí)div標(biāo)簽?zāi)J(rèn)是會合并(trim)掉開頭的空格的,這里有兩種方法:

          • 使用

            標(biāo)簽包裹內(nèi)容:使用這個標(biāo)簽包裹的內(nèi)容將會展示其內(nèi)部的真實(shí)內(nèi)容,不會有其他邏輯,不過這個標(biāo)簽同于div,在字體樣式等方面會有微小的差異(chrome下如此,其他瀏覽器未確認(rèn))

          • 在div樣式添加white-space: pre-wrap;這樣也可以避免內(nèi)部內(nèi)容部分的空格被合并成一個。



          分享前端好文,點(diǎn)亮?在看?

          瀏覽 81
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青无码破解视频 | 中文字幕黄色 | 围内精品久久久久久久久久98 | 天天干狠狠干 | 婷婷操爱|