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 位置信息,因此采用了浏览器环境测试。