原文:Clean Code vs. Dirty Code: React Best Practicesgit
做者:Donavon West程序员
本文主要介绍了适用于现代 React 软件开发的整洁代码实践,顺便谈谈 ES6/ES2015 带来的一些好用的“语法糖”。github
整洁代码表明的是一种一致的编码风格,目的是让代码更易于编写,阅读和维护。一般状况下,开发者在解决问题的时候,一旦问题解决就发起一个 Pull Request(译注:合并请求,在 Gitlab 上叫 Merge Request)。但我认为,这时候工做并无真正完成,咱们不能仅仅知足于代码能够工做。算法
这时候其实就是整理代码的最好时机,能够经过删除死代码(僵尸代码),重构以及删除注释掉的代码,来保持代码的可维护性。不妨问问本身,“从如今开始再过六个月,其余人还能理解这些代码吗?”简而言之,对于本身编写的代码,你应该保证能很自豪地拿给别人看。express
至于为何要在意这点?由于咱们常说一个优秀的开发者大都比较”懒“。在遇到须要重复作某些事情的状况下,他们会去找到一个自动化(或更好的)解决方案来完成这些任务。设计模式
整洁代码应该能够经过“味道测试”。什么意思呢?咱们在看代码的时候,包括咱们本身写的或或是别人的,会说:“这里不太对劲。”若是感受不对,那可能就真的是有问题的。若是你以为你正在试图把一个方形钉子装进一个圆形的洞里,那么就暂停一下,而后休息一下。屡次尝试以后,你会找到一个更好的解决方案。数组
DRY 是一个缩略词,意思是“不要重复本身”(Don’t Repeat Yourself)。若是发现多个地方在作一样的事情,那么这时候就应该合并重复代码。若是在代码中看到了模式,那么代表须要实行 DRY。app
// Dirty const MyComponent = () => ( <div> <OtherComponent type="a" className="colorful" foo={123} bar={456} /> <OtherComponent type="b" className="colorful" foo={123} bar={456} /> </div> );
// Clean const MyOtherComponent = ({ type }) => ( <OtherComponent type={type} className="colorful" foo={123} bar={456} /> ); const MyComponent = () => ( <div> <MyOtherComponent type="a" /> <MyOtherComponent type="b" /> </div> );
有时候,好比在上面的例子中,实行 DRY 原则反而可能会增长代码量。可是,DRY 一般也可以提升代码的可维护性。函数
注意,很容易陷入过度使用 DRY 原则的陷阱,应该学会适可而止。单元测试
编写单元测试不只仅只是一个好想法,并且应该是强制性的。否则,怎么能确保新功能不会在其余地方引发 Bug 呢?
许多 React 开发人员选择 Jest 做为一个零配置测试运行器,而后生成代码覆盖率报告。若是对测试先后对比可视化感兴趣,请查看美国运通的 Jest Image snanshot。
之前发生过这种状况吗?你写了一些代码,而且包含详细的注释。后来你发现一个 bug,因而回去修改代码。可是,你有没有改变注释来体现新的逻辑?也许会,也许不会。下一个看你代码的人可能由于注意到这些注释而掉进一个陷阱。
注释只是为了解释复杂的想法,也就是说,不要对显而易见的代码进行注释。同时,更少的注释也减小了视觉上的干扰。
// Dirty const fetchUser = (id) => ( fetch(buildUri`/users/${id}`) // Get User DTO record from REST API .then(convertFormat) // Convert to snakeCase .then(validateUser) // Make sure the the user is valid );
在整洁代码的版本中,咱们对一些函数进行重命名,以便更好地描述它们的功能,从而消除注释的必要性,减小视觉干扰。而且避免后续因代码与注释不匹配致使的混淆。
// Clean const fetchUser = (id) => ( fetch(buildUri`/users/${id}`) .then(snakeToCamelCase) .then(validateUser) );
在我以前的文章 将函数做为子组件是一种反模式,强调了命名的重要性。每一个开发者都应该认真考虑变量名,函数名,甚至是文件名。
这里列举一下命名原则:
布尔变量或返回布尔值的函数应该以“is”,“has”或“should”开头。
// Dirty const done = current >= goal;
// Clean const isComplete = current >= goal;
函数命名应该体现作了什么,而不是是怎样作的。换言之,不要在命名中体现出实现细节。假若有天出现变化,就不须要所以而重构引用该函数的代码。好比,今天可能会从 REST API 加载配置,可是可能明天就会将其直接写入到 JavaScript 中。
// Dirty const loadConfigFromServer = () => { ... };
// Clean const loadConfig = () => { ... };
计算机已经存在很长一段时间了。多年以来,程序员经过解决某些特定问题,发现了一些固有套路,被称为设计模式。换言之,有些算法已经被证实是能够工做的,因此应该站在前人的肩膀上,避免犯一样的错误。
那么,什么是最佳实践,与设计模式相似,可是适用范围更广,不只仅针对编码算法。好比,“应该对代码进行静态检查”或者“当编写一个库时,应该将 React 做为 peerDependency
”,这些均可以称为最佳实践。
构建 React 应用程序时,应该遵循如下最佳实践:
总会听到这样的说法:编写整洁代码会下降生产力。简直是在胡说八道。是的,可能刚开始须要放慢速度,但最终会随着编写更少的代码而节奏加快。
并且,不要小看代码评审致使的重写重构,以及修复问题花费的时间。若是把代码分解成小的模块,每一个模块都是单一职责,那么极可能之后不再用去碰大多数模块了。时间就省下来了,也就是说 “write it and forget it”。
看看下面的代码示例。如上所述,从你的显示器退后一步,发现什么模式了吗?注意 Thingie
组件与 ThingieWithTitle
组件除了 Title
组件几乎彻底相同,这是实行 DRY 原则的最佳情形。
// Dirty import Title from './Title'; export const Thingie = ({ description }) => ( <div class="thingie"> <div class="description-wrapper"> <Description value={description} /> </div> </div> ); export const ThingieWithTitle = ({ title, description }) => ( <div> <Title value={title} /> <div class="description-wrapper"> <Description value={description} /> </div> </div> );
在这里,咱们将 children
传递给 Thingie
。而后建立 ThingieWithTitle
,这个组件包含 Thingie
,并将 Title
做为其子组件传给 Thingie
。
// Clean import Title from './Title'; export const Thingie = ({ description, children }) => ( <div class="thingie"> {children} <div class="description-wrapper"> <Description value={description} /> </div> </div> ); export const ThingieWithTitle = ({ title, ...others }) => ( <Thingie {...others}> <Title value={title} /> </Thingie> );
看看下面的代码。使用逻辑或将 className
的默认值设置成 “icon-large”,看起来像是上个世纪的人才会写的代码。
// Dirty const Icon = ({ className, onClick }) => { const additionalClasses = className || 'icon-large'; return ( <span className={`icon-hover ${additionalClasses}`} onClick={onClick}> </span> ); };
这里咱们使用 ES6 的默认语法来替换 undefined
时的值,并且还能使用 ES6 的箭头函数表达式写成单一语句形式,从而去除对 return
的依赖。
// Clean const Icon = ({ className = 'icon-large', onClick }) => ( <span className={`icon-hover ${className}`} onClick={onClick} /> );
在下面这个更整洁的版本中,使用 React 中的 API 来设置默认值。
// Cleaner const Icon = ({ className, onClick }) => ( <span className={`icon-hover ${className}`} onClick={onClick} /> ); Icon.defaultProps = { className: 'icon-large', };
为何这样显得更加整洁?并且它真的会更好吗?三个版本不是都在作一样的事情吗?某种意义上来讲,是对的。让 React 设置 prop 默认值的好处是,能够产生更高效的代码,并且在基于 Class
的生命周期组件中容许经过 propTypes
检查默认值。还有一个优势是:将默认逻辑从组件自己抽离出来。
例如,你能够执行如下操做,将全部默认属性放到一个地方。固然,并非建议你这样作,只是说具备这样的灵活性。
import defaultProps from './defaultProps'; // ... Icon.defaultProps = defaultProps.Icon;
将有状态的数据加载逻辑与渲染逻辑混合可能增长组件复杂性。更好的方式是,写一个负责完成数据加载的有状态的容器组件,而后编写另外一个负责显示数据的组件。这被称为 容器模式。
在下面的示例中,用户数据加载和显示功能放在一个组件中。
// Dirty class User extends Component { state = { loading: true }; render() { const { loading, user } = this.state; return loading ? <div>Loading...</div> : <div> <div> First name: {user.firstName} </div> <div> First name: {user.lastName} </div> ... </div>; } componentDidMount() { fetchUser(this.props.id) .then((user) => { this.setState({ loading: false, user })}) } }
在整洁版本中,加载数据和显示数据已经分离。这不只使代码更容易理解,并且能减小测试的工做量,由于能够独立测试每一个部分。并且因为 RenderUser
是一个无状态组件,因此结果是可预测的。
// Clean import RenderUser from './RenderUser'; class User extends Component { state = { loading: true }; render() { const { loading, user } = this.state; return loading ? <Loading /> : <RenderUser user={user} />; } componentDidMount() { fetchUser(this.props.id) .then(user => { this.setState({ loading: false, user })}) } }
React v0.14.0 中引入了无状态函数组件(SFC),被简化成纯渲染组件,但有些开发者还在使用过去的方式。例如,如下组件就应该转换为 SFC。
// Dirty class TableRowWrapper extends Component { render() { return ( <tr> {this.props.children} </tr> ); } }
整洁版本清除了不少可能致使干扰的信息。经过 React 核心的优化,使用无状态组件将占用更少的内存,由于没有建立 Component 实例。
// Clean const TableRowWrapper = ({ children }) => ( <tr> {children} </tr> );
大约在一年前,我还推荐你们多用 Object.assign
。但时代变化很快,在 ES2016/ES7 中引入新特性 rest/spread。
好比这样一种场景,当传递给一些 props 给一个组件,只但愿在组件自己使用 className
,可是须要将其余全部 props 传递到子组件。这时,你可能会这样作:
// Dirty const MyComponent = (props) => { const others = Object.assign({}, props); delete others.className; return ( <div className={props.className}> {React.createElement(MyOtherComponent, others)} </div> ); };
这不是一个很是优雅的解决方案。可是使用 rest/spread,就能垂手可得地实现,
// Clean const MyComponent = ({ className, ...others }) => ( <div className={className}> <MyOtherComponent {...others} /> </div> );
咱们将剩余属性展开并做为新的 props 传递给 MyOtherComponent
组件。
ES6 引入 解构(destructuring) 的概念,这是一个很是棒的特性,用相似对象或数组字面量的语法获取一个对象的属性或一个数组的元素。
在这个例子中,componentWillReceiveProps
组件接收 newProps
参数,而后将其 active
属性设置为新的 state.active
。
// Dirty componentWillReceiveProps(newProps) { this.setState({ active: newProps.active }); }
在整洁版本中,咱们解构 newProps
成 active
。这样咱们不只不须要引用 newProps.active
,并且也可使用 ES6 的简短属性特性来调用 setState
。
// Clean componentWillReceiveProps({ active }) { this.setState({ active }); }
一个常常被忽视的 ES6 特性是数组解构。如下面的代码为例,它获取 locale
的值,好比“en-US”,并将其分红 language
(en)和 country
(US)。
// Dirty const splitLocale = locale.split('-'); const language = splitLocale[0]; const country = splitLocale[1];
在整洁版本,使用 ES6 的数组解构特性能够自动完成上述过程:
// Clean const [language, country] = locale.split('-');
但愿这篇文章能有助于你看到编写整洁代码的好处,甚至能够直接使用这里介绍的一些代码示例。一旦你习惯编写整洁代码,将很快就会体会到 “write it and forget it” 的生活方式。