auto-ellipsis 是一個用於解決文本超長溢出截斷並加 ... 的 React 組件。 javascript
隨着 React 的火熱,隨之而來的負面消息也變得更多。以前網上就有人批評說 React 的鼓吹者不少,甚至被定性爲『無腦』,這就如同當年批評 jQuery 同樣。 css
React 對我而言,不只僅是一個前端 View 庫,它對個人影響主要有如下幾方面: 前端
擁抱前沿技術 - babel 讓我在項目中能夠提早使用 ES2015+;webpack-dev-server 和 react-hot-loader 讓個人開發過程無比順暢;webpack 讓個人打包上線變得極其方便;redux 讓我能更好的管理應用狀態。也許你會說這些和 React 沒有絕對關係,但事實上,正是 React 的巨大的生態圈活力使得我可以接觸並擁抱這些前沿技術; java
享受開發 SPA - 我以前嘗試過 Angular,但 React 纔是適合個人,我能夠本身實踐開發 SPA,而且有興趣去探索相關的技術(好比:構建 universal apps); react
組件化思想 - React 將組件化可以真正用於開發中,實踐中才能對組件化思想體會更深; webpack
前端開發的思考:Flux 的單向數據流的思想,以 FRP 爲指導思想的 Redux。這些都讓我嘗試去思考索前端開發。 git
下面開始介紹 auto-ellipsis 的開發過程。 github
.truncate { width: 250px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
老實說,我所遇到的需求,CSS 中的 ellipsis 基本不多可以知足: web
它只針對單行。但實際中更多的是但願在指定寬高的區域自動截斷並加 ...; redux
它不能生成提示信息,好比 title。你不能寄但願於用戶從審查元素中得到完整的文本信息。
目前,auto-ellipsis 基本沒法優雅地經過 CSS 來實現。可是,仔細想一想這個需求本來就不是純樣式上的問題。咱們不只僅但願自適應截斷(無論尾部加 ...),還但願有提示信息(tooltip or title),這是一個功能需求,能夠封裝成一個組件。
既然 CSS 沒法實現,那就只有依靠 JS 來實現。最簡單的想法就是:從後向前不斷的裁剪文本,檢查文本是否溢出,一旦不溢出,咱們就終止這個過程。考慮 <div>content</div>,這個過程主要分爲兩部分:
裁剪文本:直接暴力的把 div 節點的 text 節點進行替換(<div>content</div> -> <div>conten</div>);
檢查文本是否溢出:我最早想到的是在 div 元素外面套一層 div,設置外層 div overflow: hidden, 內層 divoverflow: visible,外層 div 定寬高,這樣比較內外層元素的高度或者相對於視口的 bottom 就好。
顯然上面的方法是有效的,但也極其暴力的。首先多套一個 div 就會讓人很不爽,因而咱們注意到 text 節點也是 dom,能夠比較 div 節點和 text 節點嗎?惋惜 text 節點沒辦法得到其高度和位置信息。
這時,也許你記得《JavaScript 高級程序設計》中有介紹 Range 這個概念。老實說,我當時看的時候沒多大感受。是的,Range 派上用場了。
Range 屬於 dom 對象,經過 Range 能夠選擇文檔中的一個區域,而沒必要考慮節點的界限。咱們能夠經過 Range 實現文本的裁剪(比暴力替換文本節點要高效)。 Range 的高度和位置信息能夠獲取,咱們能夠經過 getBoundingClientRect() 來獲取 div 節點和 Range 相對於視口的 bottom,進行位置比較。並且, Range 的建立對用戶透明,這意味着整個裁剪檢查的過程 UI 不會有變化。
咱們還能夠作一些優化:考慮 div 元素的 padding-buttom 和 border-bottom-width;匹配文本減去三個字符用於存放...;考慮 word-break ,最終文本截取到空格處(考慮到中文等其餘語言,很差實現...)。
首先,組件的屬性 props 就是組件的對外接口。對於 auto-ellipsis,咱們的對外接口包括:tag(組件的標籤),content(文本信息),addTitle(截斷時是否加 title 屬性),styles(自定義樣式)。
其次,組件的狀態 state 是隨着時間而變化的,通常來講基礎組件(dumb component)最好是狀態無關的,由上層業務組件(smart component)來管理狀態。一般,組件狀態的改變是由用戶交互形成的,因此組件只須要暴露用戶交互結束後相應的處理接口(好比:handleClick)就好。
對於 auto-ellipsis,咱們基本沒有與用戶交互(若是元素寬高不是定值,如百分比,那麼視口大小變化是會形成影響的,咱們這裏不考慮這種情形)。實際上咱們更多的是對 DOM 的直接操做,那麼咱們什麼時候從新渲染組件,什麼時候須要從新剪裁文本?
React 對組件生命週期的管理很是強大,咱們只須要考慮怎麼作比較合適就好。首先,咱們須要在組件初始化掛載結束時(componentDidMount,可操做 DOM)嘗試裁剪文本;其次,組件更新時,咱們須要在組件更新完畢後(componentDidUpdate,可操做 DOM)嘗試裁剪文本;最後,咱們須要考慮是否要使用 shouldComponentUpdate,這主要是基於性能考慮。我以爲,對於基礎組件,考慮到這三點就足夠了,任何更復雜的設計只會讓你的組件變得不那麼通用,甚至引入一些潛藏的 bug。實際大多數狀況下,基礎組件連 shouldComponentUpdate 都不應使用,由於虛擬 DOM 已經很快了。可是 auto-ellipsis 比較特殊,它的每次更新須要從新操做 DOM,因此仍是能夠考慮進行優化的。
shouldComponentUpdate(nextProps, nextState) { return JSON.stringify(this.props) !== JSON.stringify(nextProps) }
CSS 模塊化一直是組件封裝的難題。webpack(style-loader, css-loader) 提供了使用 JS module loader 來加載 CSS 的功能。但這隻更多的只是對資源的聲明依賴和加載,並非 CSS 模塊化。解決 CSS 模塊化要解決:CSS 局部做用域的問題;CSS 模塊的輸入和輸出。
css-modules 經過生成惟一的 className,從工程角度上解決了 CSS 局部做用域的問題。css-modules 的輸入和輸出都是 JS 對象,這個對象是一系列 local-className: global-className 的映射(注意:輸入輸出不包含全局樣式,能夠經過css-loader?modules 來開啓默認局部樣式,:global 開頭是全局樣式)。CSS 模塊之間經過 composes 來組合。
React-css-modules 經過 high-order component點擊預覽 的方式將 css-modules 天然地應用於 React component,而且使用 styleName 和 className 來區分 local CSS 和 global CSS。我給 react-css-modules 提了一個 PR,用於解決自定義組件的樣式,經過樣式的聲明順序(先 import 組件,再 import 自定義 CSS 模塊)來確保相同選擇器下自定義樣式具備更高的優先級(可使用 css-loader?modules&localIdentName=[local]-[hash:base64:5],這樣能夠經過 [local] 標識 local-className,方便自定義樣式)。注:PR 未經過,做者認爲有些 hack,最終實現是能夠給組件傳遞 styles 屬性,不過是直接替換默認 styles。那麼,若是我想在默認 styles 基礎上修改一些樣式,則須要在 css-modules 中處理,這部分討論參見 討論。
import React from 'react' import ReactDOM from 'react-dom' import CSSModules from 'react-css-modules' import styles from './auto-ellipsis.css' @CSSModules(styles) export default class AutoEllipsis extends React.Component { static propTypes = { tag: React.PropTypes.string, content: React.PropTypes.string.isRequired, addTitle: React.PropTypes.bool, styles: React.PropTypes.object, } render() { const props = { styleName: 'root', } const {tag, content} = this.props return React.createElement(tag, props, content) } }
關於 CSS 模塊化 和 CSS 局域化可具體參考 hax 的 關於前端開發中「模塊」和「組件」概念的思考。
前端組件的測試,按照宿主通常可分爲瀏覽器環境 和 Node.js 環境。測試框架的話,我推薦 mocha。
瀏覽器環境能夠實際生成 DOM,測試真實有效。可使用 webpack 配合 mocha-loader,使得測試和開發統一。可是,不方便使用 travis-ci 等一些集成工具。
Node.js 環境下須要模擬 DOM(jsdom),React 組件下能夠和 react-addons-test-utils 配合使用。再者,一些涉及到 dom 位置的組件,沒法使用模擬測試(好比:jsdom 中的 getBoundingClientRect 返回的都是 0)。
auto-ellipsis 顯然依賴於 dom 位置信息,因此採用了瀏覽器環境測試。