平时在前端开发中会遇到一些常见的攻击,正好近期主要在处理安全方面的问题,记录一下。
平时在前端开发中会遇到一些常见的攻击,正好近期主要在处理安全方面的问题,记录一下。
整理2020年前端的技术生态,列了个人认为比较重要的一些项目和技术点,目前stateofjs问卷还在统计中,后续可以做些调整。
关于ECMAScript 2019规范的新增特性
String.prototype
上的match()
方法仅返回完全匹配,但是没有返回关于特定正则组的任意信息。感谢Jordan Harband关于String.prototype.matchAll
的提案,可以返回比match()
多很多的信息。返回的迭代器除了精确匹配外还给了我们访问所有的正则匹配捕获组。你还记得Gorkem Yakin和Daniel Ehrenberg添加到ECMAScript 2018的具名捕获组吗?matchAll()
方法和此能很好的协调。通过下面例子来解释一下。
1 | const text = "From 2019.01.29 to 2019.01.30"; |
1 | const text = "From 2019.01.29 to 2019.01.30"; |
不同于ECMAScript 2015中介绍的静态模块,Domenic Denicola提案的动态导入
可以实现按需加载。这个类似函数的格式(不是继承自Function .prototype
)返回一个很强大的promise。使用场景比如: 按需导入,在一个脚本中计算模块名并加载执行变得可能。
1 | const modulePage = 'page.js'; |
1 | (async () => { |
感谢Daniel Ehrenberg, Number.MAX_SAFE_INTEGER
不再是JavaScript中的一个限制。BigInt
是一个能表示任意精度整数的新基础类型。你可以通过使用BigInt
方法或者在一个数字后添加n
后缀来把一个数字转换为一个新的bigint
类型。
1 | Number.MAX_SAFE_INTERGER |
自从ECMAScript 2015以来,JavaScript仅支持两种promise组合: Promise.all()
和Promise.race()
。感谢Jason Williams, Robert Pamely and Mathias Bynens,现在我们可以使用Promise.allSettled()
。用这个方法来处理所有promise都解决时的场景(不管成功或失败)。看看下面的例子,并没有使用catch捕获异常!
1 | Promise.allSettled([ |
还有Promise.any()
有潜力很快进入ECMAScript规范中,在文章“Promise组合解释”中介绍了相关内容。
那么在JavaScript中什么是全局的this
?是在浏览器中的window
,在worker中的self
,在Nodejs中的global
或者其他… 这种混乱结束了!感谢Jordan Harband,我们现在可以使用globalThis
关键字了。
ECMAScript遗留了一个关于for-in循环顺序的详细描述。感谢Kevin Gibbons所付出的努力,为for-in机制定义了一系列规则。(原文: Thanks to Kevin Gibbons who finally put some TLC and defined a set in stone set of rules for for-in mechanics.)
读取层次很深的对象属性时通常是容易出错并且对应代码也不易阅读。感谢Gabriel Isenberg, Claude Pache and Dustin Savery,这件事情现在变得简单了。如果你是一个TypeScript用户,那么你不会发现什么新的特性,因为在3.7版本中TypeScript已经实现了这个特性。喜欢!
1 | // 之前 |
空值联合添加了一个新的短路原则操作符来处理默认值。Gabriel Isenberg做了很棒的工作。这个特性结合optional chanining特性使用。不同于||
操作符,空值联合操作符??
仅在左边的值为严格的null
或undefined
时起左右。
1 | "" || "default value" |
1 | const title = data?.article?.title ?? "What's new in ECMAScript 2020" |
Domenic Denicola提出的import.meta提案添加一个host相关的元数据对象到当前执行的模块中。
1 | console.log(import.meta.url) |
这是一个添加到规范中的有用特性,可以让开发者导出其他模块命名空间下的对象到一个新的名称下。
1 | export * as ns from "mod" |
原文地址: https://dev.to/selbekk/the-10-component-commandments-2a7f
译文地址: https://blog.bookcell.org/2020/03/22/the-10-react-component-best-practice/
译者备注: 这是一篇关于React组件开发最佳实践的文章,很值得一读,推荐有能力阅读英文的同学去读原文,翻译或多或少会丢失一些原意。
正文从这里开始:
创建被许多人使用的组件是困难的。尤其当组件的属性作为公开API的一部分,你不得不仔细考虑哪些属性应该接受。
这篇文章会快速介绍一些在API设计时的通用最佳实践,并且给出10条明确的最佳实践指导你创建让同事开发者喜欢使用的组件。
API或者应用程序接口(Application Programming Interface),主要是指两部分代码相遇的地方。这是你的代码和其他世界接触的地方。我们称这个接触表面为接口。这些是可交互的动作集合或者数据点。
介于前端和后端之间的接口就是一个API。你可以通过这个API访问一系列特定的数据和功能。
介于一个类和调用代码之间接口也是一个API。你可以在类上调用方法,来获取数据或者触发封装在其中的功能。
沿着这个思路,你的组件接受的属性也是其API。这是你的用户和组件交互的方式,因此当你决定暴露哪些属性时有很多类似的规则和考虑可以应用。
那么在设计一个API时有哪些规则和考虑可以应用?在这方面我们做了一些调查,并找到了许多极好的资源。我们挑选了两篇文章,Josh Tauberer的“What Makes a Good API?”和Ron Kurir的同名文章,从中提取了4条最佳实践来遵循。
当你在创建一个API时要考虑的最重要的一点是尽可能保持稳定。这意味着破坏性变更的次数很少。如果你有破坏性的变更,确保写一份详细的升级指南,并且如果可能,提供一个重构件(code-mod)来给用户自动化处理这些变更。
如果你发布了API,确保遵守语义化版本。这让用户更容易决定使用什么版本。
任何调用API出错的时候,你都应该尽可能的解释什么地方出错,并告知如何修复。返回一个错误使用且没有任何其他提示的响应会让使用者感到羞愧,这不是一种好的用户体验。
相反,提供描述性的错误来帮助用户修复他们调用API的方式。
开发者是脆弱的人类,因此当他们使用API时不应该让他们感到惊讶。换句话说,尽可能让API更直观。这可以通过遵守最佳实践和命名规范来达到。
另外需要放在心上是保持你的代码一致性。如果你在一处地方给布尔型属性名称前面添加了is
或has
,而在另外一处忽略了,这会让其他人感到困惑。
当我们谈到最小化的时候-同样要最小化你的API。更多的特性当然是好的,但是API接口暴露得越少,使用者学习的成本也更低。从而让用户认为这是一个易用的API。
有很多方式可以控制API的规模,其中一个就是从旧的中重构一个新的来。
这4条黄金法则在REST API和Pascal语言程序中使用得很好,那么如何将他们拿到React的现代世界中来呢?
像我们前面提到的,组件也有自己的API。我们称做props
,这是给组件传递数据、回调和其他功能的方式。我们如何组织props
对象才能不破坏上面的规则?我们如何开发组件才能让其他开发者在使用组件时更便捷?
我们创建了开发组件时的10条不错的规则清单,希望对你有帮助。
如果组件没有提供如何使用的文档,那么显然组件是无用的。好吧,大多时候,使用者总是可以通过查看实现来了解如何使用,但那很少是最好的用户体验。
有很多方式可以给组件编写文档,从我们的角度想推荐3个选项:
前两个在你开发组件时可以提供一个playground用来试验,第3个提供了MDX
来更自由的书写文档。(注:最新版本三者都已支持Markdown语法)
不管选择哪一个,确保提供API文档,及组件如何、何时使用相关的文档。后者在共享组件库中更为重要,那样人们才能在合适的位置使用正确的按钮或者布局。
译者注: 标题的原文是
Allow for contextual semantics
,中文翻译不好把握供参考
HTML是一门以语义化方式组织信息的语言。但是大多数的组件是由<div />
标签组成的。这在某种程度上是讲得通的,因为通用组件不能假设是否应该是<article />
、<section />
或者一个<aside />
,但是这并不是理想。
相反,我们建议允许组件接受一个as
属性,用以覆盖被渲染的DOM元素。下面是一个如何实现的例子:
1 | function Grid({ as: Element, ...props }) { |
我们将as
属性重命名为一个本地的变量Element
,并在JSX中使用。我们提供了一个通用的默认值,在你明确不需要传递更具语义化HTML标签的情况下使用。
当我们使用<Grid />
组件时,你仅需要传递正确的标签:
1 | function App() { |
注意这在使用React组件时同样适用。一个很好的例子是,当你有一个<Button />
组件想要渲染成React Router的<Link />
组件时:
1 | <Button as={Link} to="/profile"> |
布尔型属性听起来是个极好的主意。你可以不需要传值的情况下使用,这看起来很优雅:
1 | <Button large>BUY NOW!</Button> |
但是即使他们看起来很不错,单布尔属性只能允许两种可能性。开和关,显示和隐藏,1和0。
任何时候当你开始引入像尺寸、变形、颜色或其他任何像下面所列可能有两个之外的值,你就会有麻烦了。
1 | <Button large small primary disabled secondary> |
换句话说,布尔属性通常无法适应需求变更。因此,尝试使用字符串类型的枚举来作为属性,这样就有机会使用任何值而不是仅有两个值的选择。
1 | <Button variant="primary" size="large"> |
这并不代表布尔值属性没有一点用武之地。当然是有的。上面列的disabled
属性就应该是布尔型,因为在启用和禁用之间没有中间状态。保留他们作为真正的两个选项使用。
props.children
React有一些与其他属性略有不同的特殊属性。一个是key
,在列表项中被用来跟踪顺序,另一个是children
。
任何放置在组件开和闭标签间的内容都会被放在props.children
属性中。因此,你应该尽可能经常使用。
原因是这样做比通过添加一个content
属性或者其他专门类似文本简单值属性的方式要更加容易使用。
1 | <TableCell content="Some text" /> |
使用props.children
有很多积极的意义。第一点,这有点类似平常HTML的工作方式。第二,你可以自由地传递任何你想要传的内容。不用添加leftIcon
和rightIcon
属性到你的组件中,仅仅只要传到props.children
属性中即可。
1 | <TableCell> |
你可能会争辩组件应该只允许接收并渲染普通文本,这在某些情况下可能是对的。至少现在,通过使用props.children
,能实现一个适应未来需求变更的组件。
有时我们会创建有很多内部逻辑和状态的组件,例如自动补全的下拉组件或者交互式图表。
这些类型的组件通常都会涉及比较繁琐的接口,其中一个原因就是要支持后续的一系列覆盖和特殊使用场景。
如何能做到仅仅提供一个简单、标准化属性来让用户控制、响应或者覆盖默认组件行为呢?
Kent C.Dodds写了一篇关于“state reducers”概念的好文章,关于概念本身,和另外一篇如何用React Hooks实现。
快速总结一下,这个模式通过传递一个“state reducer”函数给组件,从而让父组件可以访问任何在你的组件中派发的action。你可以改变状态,或者触发边界效应等。这是一个极好的实现高级定制的方式,而不用借助其他属性。
代码如下:
1 | function MyCustomDropdown(props) { |
顺便说一下,你当然也可以创建更简单的方式来响应事件。上面的例子中提供一个onClose
属性可能会更好的用户体验。保留好state reducer模式以备不时之需。
任何时候当你创建一个新的组件时,确保展开剩余的属性到有意义的元素上。
你不需要持续添加那些原本在父组件或父元素上并会传递到组件上的属性到你的组件中。这会让你的API更稳定,并且不会因其他开发者需要一个新的事件监听或者arial标签而发布许多小的版本。
可以像下面这样做:
1 | function ToolTip({ isVisivle, ...rest }) { |
任何时候你的组件传递一个属性到你的实现中,像一个类名或者一个onClick
处理函数,确保外部的使用者同样可以做同样的事情。在类的情况,使用好用的classnames
软件包来追加类名(或者使用字符串拼接)。
1 | import classNames from 'classnames'; |
对于点击事件处理函数或者其他回调,通过一个辅助函数来组合一个单独的函数,这里有一种实现方式:
1 | function combine(...functions) { |
这里我们创建一个函数来接受一系列函数并组合,返回一个新的依次使用对应参数调用他们的回调函数。
你可以像这样使用:
1 | function ToolTip(props) { |
无论何时如果可以的话,确保给你的属性提供足够的默认值。这样做的话,可以最小化必须要传递的属性数量,并且这样能非常简化你的实现。
举一个onClick
处理函数的例子。如果在你的代码中不强制要求,那么可以提供一个空函数作为默认属性。这样的话,你可以在代码中跟一直有传递对应属性一样调用。
另一个例子例如定制的输入。除非用户提供,否则的话假设输入的字符串是一个空字符串。这可以确保总是在处理一个字符串对象,而不是undefined或null。
HTML作为一门语言同样有其自己的属性,而这就是HTML元素自身的API。为什么不继续保持使用这个API呢?
就如我们之前提到的,最小化API接口暴露和保持某种程度的直观是两种改善组件API的极好方式。因此比起创建自己的screenReaderLabel
属性,为何不直接使用HTML已经提供给你的aria-label
呢?
因此不要为了自己的方便使用而去重命名既有的HTML属性。这样并不是用一个新的API来替换既存的API,而是在顶层添加一个自己的。人们通常还可以继续和你的screenReaderLabel
属性一起传递aria-label
,那么最终哪个才是该使用的呢?
再说一点,确保永远不要在组件中覆盖HTML属性。一个比较好的例子就是<button />
元素的type
属性,具有submit
(默认)、button
或者reset
。然而,许多开发者倾向于把这个属性用于表示按钮的可视化类型(如primary
, cta
等等)。
属性他用之后,你必须要添加其他新的属性来覆盖表示type
属性,这样会导致困惑、疑虑和让用户愤怒。
相信我,我一次又一次的犯了这个错误,我承认这真是一个狼狈的决定。
没有文档能比在代码中的文档更好的。React通过prop-types
软件包提供了极好的方式来声明组件API。现在就开始使用吧。
你可以指定任意类型和形式的必选和可选属性,并可以通过JSDoc注释来改善。
如果你忽略了一个必选的属性,或者传递一个无效、非期望的值,那么你会在控制台中得到运行时警告。这对开发来讲是极好的,并可以在生产打包时被删掉。
如果你是通过TypeScript或Flow来开发React应用,那么你可以通过语言特性获得这种API文档。这可以获得更好的工具支持,和更好的用户体验。
如果你自己没有使用具有类型的JavaScript,那么你应该始终考虑给你的用户提供类型定义。这样,他们在使用你的组件时会更加容易。
最后,最重要的一个规则。确保你的API和组件体验是为那些将会使用的人优化的–你的同事开发者。
一种改善开发者体验的方式是为不合理使用提供足够的错误信息,并在有更好的方式使用组件的情况下提供仅在开发环境的警告。
当在提供错误和警告时,尽量通过链接引用你的文档或者提供简单的代码示例。让用户越快找到错误并修复,这会让用户感到你的组件越好用。
事实证明,这些冗长的错误和警告并不会影响最终打包的大小。感谢无用代码精简的帮助,在构建生产包的时候这些文本和错误的代码都会被移除。
在这方面做得非常好的一个库就是React本身。任何时候当你忘记在列表项中指定一个key时,或者拼错一个生命周期函数,忘记继承正确的基类或者用不正确的方式调用hook时,你会在控制台中得到大量的错误信息。为什么你的组件使用人员要期望的更少呢?
因此为你的未来用户设计,为5周后的你自己设计,为当你离开后必须要接手维护你代码的可怜家伙设计!为开发者设计。
从经典的API设计中我们可以学到许多很好的建议。通过遵循文中的建议、技巧、规则和最佳实践,你应该可以创建简单易用,容易维护,直观并在需要的时候非常灵活的组件。
那么你在创建一个出色的组件时有哪些最喜欢的建议?
对于React-Router的实现原理,参考自Build your own React Router v4这篇英文原文,另外,React-Router底层库history的源码也值得一读。
欢迎访问博客文章
接下来是关于React-Router v4的一个简单版实现,通过这个实现来理解路由的原理,这里可能不会完全按照英文原文翻译,会有些意译或者省略的地方。
在单页应用中,应用代码和路由代码是极其重要的两部分,两者是相辅相成的,你是否对这两者有一些困惑。
关于路由的一些疑问点:
本文会通过实现一个简易版的React Router v4版本来点亮前一个问题的灯塔,并且让你了解背后的原理后去判断这样的抽象是否合适。
理解Redux的原理,一个比较好的方式就是自己实现一个Redux,这样便知道它背后的原理以及对应的API。
这里主要是参考Learn Redux by Building Redux from Scratch
这篇文章,并且使用意译,并非完全按照原文翻译,不过不影响对redux原理的理解,跟着文章的思路实现一遍redux就能了解背后的基本思路。
欢迎访问博客原文通过从零实现redux来学习redux
Redux的核心概念:
Redux is a predictable state container for JavaScript apps.
也就是说Redux是JavaScript应用中作为一个可预测的状态容器的存在。
Redux通常用于保存应用状态,应用状态由两部分输入组成:
Redux在store
(仓库)中管理应用状态。状态本身只是一个纯粹的JavaScript对象。仓库另外提供方法来
更新状态和读取状态。
Redux的核心在于基于观察者模式下的发布订阅模式,有点类似在JS中的事件驱动架构。在Redux中,当用
户和UI交互时,会派发(dispatch)一个action
(也就是发布)。action
的概念不需要过度考虑,它
仅仅只是一个纯JS对象,包含一个type
作为唯一键值和一个payload
负载数据。
使用action
,状态可以根据接收到的type
和payload
进行更新。组件可以订阅状态的变化,并基于
新的状态树更新UI。
一个数据流的简单表示如下:
用户交互发布一个action
-> reducer
更新状态 -> 订阅组件基于新状态更新UI
基于这个概念,Redux有3个核心的原则:
action
作为reducer
的参数来进行修改。reducer
函数接收前一个状态(也是纯对象),并基于前一个状态和action
Redux是围绕着store
为核心的。store
是一个包含状态、更新方法(dispatch()
)和读取方
法(subscribe()/getState()
)的JavaScript对象。还有listeners
(监听器)用于组件订阅状态
变化执行的函数。
store
形式如下:
1 | const store = { |
为了使用这个仓库对象来管理状态,我们要够一个createStore()
函数,代码如下:
1 | const createStore = (reducer, initialState) => { |
createStore
函数接收两个参数,一个是reducer
和一个initialState
。reducer函数会在后续
详细介绍,现在只要知道这是一个指示状态应该如何更新的函数。
createStore
函数开始于创建一个store
对象。然后通过store.state = initialState
进行初
始化,如果开发者没有提供则值会是undefined
。state.listeners
会被初始化为空数组。
store
中定义的第一个函数是getState()
。当调用时只是返回状态,store.getState = () => store.state
。
我们允许UI订阅(subscribe)状态的变化。订阅实际上是传递一个函数给subscribe
方法,并且这个
函数作为监听器会被添加到监听器数组中。typeof listener === 'function'
的结果是true
。
在每一个状态变化的时候,我们会遍历所有的监听器函数数组,并逐个执行。
1 | store.listeners.forEach(listener => listener()); |
接下来,定义了dispatch
函数。dispatch函数是当用户和UI交互时,组件进行调用的。dispatch接收
一个单一的action
对象参数。这个action
应该要完全描述用户接收到的交互。action和当前状态一起,
会被传递到reducer
函数,并且返回一个新的状态。
在新的状态被reducer
创建后,监听器数组会被遍历,并且每个函数会执行。通常,getState
函数
在监听器函数内部会被调用,因为监听的目的是响应状态变化。
注意到数据流向是一个非常线性和同步的过程。监听器函数添加到一个单独的监听器数组中。当用户
和应用交互时,会产生一个用于dispatch的action。这个action会创建一个可预测和独立的状态改变。
接着这个监听器数组被遍历,让每个监听器函数被调用。
这个过程是一个单向的数据流。只有一个途径在应用中创建和响应数据变化。没有什么特别的技巧发生,
只是一步一步针对交互并遵循明确统一模式的路径。
reducer是一个接收state
和action
的函数,并返回新的状态。形式如下:
1 | const reducer = (prevState, action) => { |
这里的prevState
, nextState
和action
都是JavaScript对象。
让我们详细看一下action
对象来理解它是如何用于更新状态的。我们知道一个action会包含
一个唯一的字符串type
来标识由用户触发的交互。
例如,假设你使用Redux来创建一个简单的todo list应用。当用户点击提交按钮来添加项目到列表中时,
将会触发一个带有ADD_TODO
类型的action。这是一个既对人类可读和理解,并且对Redux关于aciton目的
也是清晰的指示。当添加一个项目时,它将会包含一个text
的todo内容作为负载(payload)。因此,
添加一个todo到列表中,可以通过以下的action对象来完全表示:
1 | const todoAction = { |
现在我们可以构建一个reducer来支撑一个todo应用。
1 | const getInitialState = () => ({ |
注意每次reducer被调用的时候我们都会创建一个新的对象。我们使用前一次的状态,但是创建了一个
完整全新的状态。这是另一个非常重要的原则能够让redux可预测。通过将状态分割成离散的,开发者
可以精确的指导应用中会发生什么。这里只要了解根据状态的变化来重新渲染UI的特定部分即可。
你通常会看到在Redux中使用switch
语句。这是匹配字符串比较方便的一个方法,在我们的例子中,
action的type
为例,对应更新状态的代码块。这个使用if...else
语句来写没有差别,如下:
1 | if (action.type === 'ADD_TODO') { |
Redux对于reducer中的内容实际上是无感知的。这是一个开发者定义的函数,用来创建一个新的状态。
实际上,用户控制了几乎所有——reducer,被使用的action,通过订阅被执行的监听器函数。Redux就
像一个夹层将这些内容进行联系起来,并提供一个通用的接口来和状态进行交互。
如果你之前了解过
combineReducers
函数,这个只是一个用来允许你在state
对象中创建隔离的
键值。主要为了让代码更整洁。详细的内容可以查看官方的资料。
上面已经讲了redux的全部核心内容,接下来可以用前面的实现来构建一个简单的计数器应用。
我们会创建一个HTML文档,并用给一个<div>
来包含从我们的redux仓库中的count值。并且放置
一个script标签,并获取id="count"
的DOM节点。
1 |
|
在<script>
的计数器下方,我们要把createStore
函数贴进来。在这个函数下面,我们会创建reducer。
这个reducer将会查找一个type为'COUNT'
的action,并将action的负载中的count添加到原先保存在
仓库中的count。
1 | const getInitialState = () => { |
现在我们拥有一个reducer,我们可以创建仓库。使用这个新创建的仓库,我们可以订阅仓库中的变化。
每一次状态变化,我们可以从状态中读取count
并写到DOM中。
1 | const store = crateStore(reducer); |
现在我们的应用正在监听状态的变化,让我们创建一个简单的事件监听器,来增加count。事件监听器
将会dispatch一个action,用于发送一个1-10的随机数作为count到reducer中去相加。
1 | document.addEventListener('click', () => { |
最终,我们会dispatch一个空的action来初始化状态。由于没有action的类型,将会执行default
代码
块,并从getInittialState()
中返回的值来生成一个状态对象。
1 | store.dispatch({}); // 设置初始状态 |
将所有的代码放在一起,就有了以下的应用。
1 |
|
最终代码可以从我的代码仓库下载,建议个人自己手动敲一遍,实践一遍加深理解。
代码运行后,通过每次点击页面,你可以看到页面上的count会增加一个随机数,并且在控制台会
打印状态的变化。
通过上述的一个过程,你可以理解Redux的实现,并且将Redux使用在一个应用中。当然,上述的这个
实现还不能用在生产上,因为缺少边界考虑和优化。
如果看了一遍没有理解,也没有关系,重新从发布订阅开始看,结合实践,终会理解。
其他的材料可以参考redux作者Dan Abramov的视频教程。关于什么时候和
为什么要使用redux进行状态管理,可以看Dan的这篇文章,比较redux和React内部的状态管理。
Document object model
Tree of nodes/elements created by the browser
JavaScript can be used to read/write/manipulate to the DOM
Object Oriented Representation
文档对象模型
由浏览器创建的节点或者元素的一颗树结构
JavaScript可以对DOM进行读写操作
基于对象的表示
document.title: 标题
document.head: head节点
document.body: body节点
document.all: 所有节点的集合
document.forms: 所有表单节点集合
document.links: 所有链接节点集合
document.images: 所有图片节点集合
document.getElementById(): 通过id获取元素
document.getElementsByClassName(): 通过类名获取元素集合
document.getElementsByTagName(): 通过标签名获取元素集合
document.querySelector(): 类似于jQuery的$选择器函数,可以支持id、类名、标签名等,仅支持选择第一个匹配的元素
document.querySelectorAll(): 支持选择一个集合
项目开发中团队规范的那些事,记录一下项目中可以通过工具来约定和处理的规范
你是否有遇到过多人协作的项目中,大家的缩进风格不一样,有人用两个空格,有人用4个空格,也有人用tab缩进,而感到烦恼的。editorconfig这个开源项目就是为此而生的。
官方的介绍如下:
EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. The EditorConfig project consists of a file format for defining coding styles and a collection of text editor plugins that enable editors to read the file format and adhere to defined styles. EditorConfig files are easily readable and they work nicely with version control systems.
使用思路:
通过一个配置文件,让不同的编辑器或者IDE自动识别缩进、字符编码格式等风格并保持统一。目前市面上绝大部分的编辑器和IDE都已经支持了,你使用的编辑器是否原生支持还是要安装插件,可以去官网上看一眼。
.edittorconfig
文件配置如下:
1 | # 表明是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件,否则会往上一层目录继续查找 |
平时写js代码的时候,你是否只是随意写个// xxxx
就完事了,对于不需要输出文档的代码也许还好,如果有输出文档的需求,那就头大了。不过团队中拥有一套一致的注释规范,并且在必要的时候还可以输出作为文档,不管是对于后续的新人以及维护都是有益的。
这里要使用的jsdoc文档工具就是这样的一个存在,既有一套注释的规范,又可以支持输出为静态页面的文档。
使用思路:
在函数、类、模块等代码前使用/** */
这样格式的注释,注释中支持描述、标签等内容,通过jsdoc提供的工具可以方便的将注释内容作为文档导出为html格式的文件。
常用的注释格式如下:
1 | /** |
注释中支持块级标签和行内标签,行内标签是指包含在块级标签内的标签内容,常用的块级标签如下:
使用npm安装对应的jsdoc工具,可以全局安装或者局部安装
1 | npm i jsodc -g |
在项目根目录下创建一个配置文件conf.json
:
默认的配置如下:
1 | { |
常用的配置如下:
1 | { |
然后在命令行中执行 jsdoc -c /path/to/conf.json
即可在根目录下生成一个包含html的文档目录。
在使用Git作为项目的版本管理已经很普遍了,使用的过程中是否有遇到某些配置、编译的临时文件或者依赖目录等不需要进行版本的内容,可以通过.gitignore
进行忽略,将对应的目录或文件写入配置文件后,git就不会将对应的内容纳入版本管理。
需要注意的是,已经被纳入版本管理的内容,不受这个配置的影响。
使用思路:
在项目根目录下创建一个.gitignore
文件,并将要忽略纳入版本管理的内容写入即可,每一行内容支持模式匹配。
create-react-app中使用的配置文件:
1 | .idea/ |
vue-cli中使用的配置文件:
1 | node_modules |
每个团队会维护一套自己的代码规范,也有使用社区里最佳实践的代码规范,如何做到可以让工具自动帮你检测团队中的代码是否符合代码规范,lint工具就可以帮你做到,社区里有jslint,eslint等工具,这里主要介绍近期使用比较多的ESLint。
ESLint是一个静态代码检测工具,是对JavaScript和JSX可插拔的检测工具,由Nicholas C. Zakas开发。
使用思路:
创建一个配置文件,通过编辑器插件或者eslint工具对代码进行静态检测并提示错误。
可以全局安装或者项目中安装使用
1 | npm i eslint -g |
然后生成配置文件
1 | eslint --init |
会在相应目录下生成一个eslint默认配置文件
最后,运行lint
1 | eslint /path/to/file.js |
通常可以package.json
的脚本中进行配置
1 | "scripts": { |
日常可以通过npm run lint
来进行使用。
另外,也可以通过git的hook在代码提交前自动运行lint,这里不做说明,具体配置可以搜索相应文章。
常用配置文件如下:
配置文件支持js, json, yaml格式,这里以.eslintrc.js
为例
1 | // .eslintrc.js |
具体的配置说明,这篇文章讲得很清楚,有兴趣可以查看,这里主要是extends的使用,可以使用官方推荐的规则配置,也可以使用第三方的比如airbnb等,或者团队自己积累的代码规范。
Prettier是一款对代码格式进行美化的工具,让你在代码评审时减少对代码格式排版上的时间浪费。
官方的示例了解一下:
原始代码:
1 | foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne()); |
经过Prettier格式化后:
1 | foo( |
使用思路:
通过编辑器插件或者git hook来实现对代码的自动格式化,从而让代码排版变得整齐。
将prettier添加到项目中
1 | npm install prettier --save-dev --save-exact |
直接使用
1 | npx prettier --write src/index.js |
增加git hook
1 | npm install pretty-quick husky --save-dev |
在package.json
文件中增加以下配置:
1 | { "husky": { "hooks": { "pre-commit": "pretty-quick --staged" } } } |
在项目根目录下创建一个.prettierrc
配置文件即可
1 | { |
如果要配合编辑器进行使用,可以查找对应编辑的插件并进行配置。
需要说明的是prettier和editorconfig其实有重复的,两者可以选一个使用。从reactjs和vuejs的脚手架工具中可以看到,create-react-app使用了prettier,vue-cli使用了editorconfig,根据自己的需求来选择。