[link]
[item name="HUSKY" link="https://typicode.github.io/husky/" desc="使用 Git hooks 防止不好的 commit 或者 push" /]
[/link]
[link]
[item name="tool" link="https://tool.lu/" desc="在线工具集" /]
[/link]
<v-chart
ref="planNumChartRef"
style="height: 490px"
:update-options="{notMerge:true}" //合并配置 关闭
:option="planNumChartOption"
></v-chart>
]]>由于个人的一些网站都在服务器上直接编译运行的很乱 所以安装portainer部署所有个人服务在服务器上后续对接上Jenkins 便于管理
都运用docker的部署方式
docker version 查看docker版本
yum update docer-ce 更新docker版本
docker pull portainer/portainer-ce 拉取 portainer image
docker run -d --net=host --restart=unless-stopped -v /var/run/docker.sock:/var/run/docker.sock -v /data/portainer_data:/data portainer/portainer:1.23.2
启动服务
docker ps 检查是否运行成功
设计前端组件是最能考验开发者基本功的测试之一,因为调用Material design、Antd、iView 等现成组件库的 API 每个人都可以做到,但是很多人并不知道很多常用组件的设计原理。
能否设计出通用前端组件也是区分前端工程师和前端api调用师的标准之一,那么应该如何设计出一个通用组件呢?
下文中提到的组件库通常是指单个组件,而非集合的概念,集合概念的组件库是 Antd iView这种,我们所说的组件库是指集合中的单个组件,集合性质的组件库需要考虑的要更多.
我们在学习设计模式的时候会遇到很多种设计原则,其中一个设计原则就是单一职责原则,在组件库的开发中同样适用,我们原则上一个组件只专注一件事情,单一职责的组件的好处很明显,由于职责单一就可以最大可能性地复用组件,但是这也带来一个问题,过度单一职责的组件也可能会导致过度抽象,造成组件库的碎片化。
举个例子,一个自动完成组件(AutoComplete),他其实是由 Input 组件和 Select 组件组合而成的,因此我们完全可以复用之前的相关组件,就比如 Antd 的AutoComplete组件中就复用了Select组件,同时Calendar、 Form 等等一系列组件都复用了 Select 组件,那么Select 的细粒度就是合适的,因为 Select 保持的这种细粒度很容易被复用.
那么还有一个例子,一个徽章数组件(Badge),它的右上角会有红点提示,可能是数字也可能是 icon,他的职责当然也很单一,这个红点提示也理所当然也可以被单独抽象为一个独立组件,但是我们通常不会将他作为独立组件,因为在其他场景中这个组件是无法被复用的,因为没有类似的场景再需要小红点这个小组件了,所以作为独立组件就属于细粒度过小,因此我们往往将它作为 Badge 的内部组件,比如在 Antd 中它以ScrollNumber的名称作为Badge的内部组件存在。
所以,所谓的单一职责组件要建立在可复用的基础上,对于不可复用的单一职责组件我们仅仅作为独立组件的内部组件即可。
我们要设计的本身就是通用组件库,不同于我们常见的业务组件,通用组件是与业务解耦但是又服务于业务开发的,那么问题来了,如何保证组件的通用性,通用性高一定是好事吗?
比如我们设计一个选择器(Select)组件,通常我们会设计成这样
这是一个我们最常见也最常用的选择器,但是问题是其通用性大打折扣
当我们有一个需求是长这样的时候,我们之前的选择器组件就不符合要求了,因为这个 Select 组件的最下部需要有一个可拓展的条目的按钮
这个时候我们难道要重新修改之前的选择器组件,甚至再造一个符合要求的选择器组件吗?一旦有这种情况发生,那么只能说明之前的选择器组件通用性不够,需要我们重新设计.
Antd 的 Select 组件预留了dropdownRender
来进行自定义渲染,其依赖的 rc-select
组件中的代码如下
Antd 依赖了大量以rc-
开头的底层组件,这些组件被react-component团队(同时也就是Antd 团队)维护,其主要实现组件的底层逻辑,Antd 则是在此基础上添加Ant Design设计语言而实现的
当然类似的设计还有很多,通用性设计其实是一定意义上放弃对 DOM 的掌控,而将 DOM 结构的决定权转移给开发者,dropdownRender
其实就是放弃对 Select 下拉菜单中条目的掌控,Antd 的 Select 组件其实还有一个没有在文档中体现的方法getInputElement
应该是对 Input 组件的自定义方法,Antd整个 Select 的组件设计非常复杂,基本将所有的 DOM 结构控制权全部暴露给了开发者,其本身只负责底层逻辑和最基本的 DOM 结构.
这是 Antd 所依赖的 re-select 最终 jsx 的结构,其 DOM 结构很简单,但是暴露了大量自定义渲染的接口给开发者.
return (
<SelectTrigger
onPopupFocus={this.onPopupFocus}
onMouseEnter={this.props.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
dropdownAlign={props.dropdownAlign}
dropdownClassName={props.dropdownClassName}
dropdownMatchSelectWidth={props.dropdownMatchSelectWidth}
defaultActiveFirstOption={props.defaultActiveFirstOption}
dropdownMenuStyle={props.dropdownMenuStyle}
transitionName={props.transitionName}
animation={props.animation}
prefixCls={props.prefixCls}
dropdownStyle={props.dropdownStyle}
combobox={props.combobox}
showSearch={props.showSearch}
options={options}
multiple={multiple}
disabled={disabled}
visible={realOpen}
inputValue={state.inputValue}
value={state.value}
backfillValue={state.backfillValue}
firstActiveValue={props.firstActiveValue}
onDropdownVisibleChange={this.onDropdownVisibleChange}
getPopupContainer={props.getPopupContainer}
onMenuSelect={this.onMenuSelect}
onMenuDeselect={this.onMenuDeselect}
onPopupScroll={props.onPopupScroll}
showAction={props.showAction}
ref={this.saveSelectTriggerRef}
menuItemSelectedIcon={props.menuItemSelectedIcon}
dropdownRender={props.dropdownRender}
ariaId={this.ariaId}
>
<div
id={props.id}
style={props.style}
ref={this.saveRootRef}
onBlur={this.onOuterBlur}
onFocus={this.onOuterFocus}
className={classnames(rootCls)}
onMouseDown={this.markMouseDown}
onMouseUp={this.markMouseLeave}
onMouseOut={this.markMouseLeave}
>
<div
ref={this.saveSelectionRef}
key="selection"
className={`${prefixCls}-selection
${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`}
role="combobox"
aria-autocomplete="list"
aria-haspopup="true"
aria-controls={this.ariaId}
aria-expanded={realOpen}
{...extraSelectionProps}
>
{ctrlNode}
{this.renderClear()}
{this.renderArrow(!!multiple)}
</div>
</div>
</SelectTrigger>
);
那么这么多需要自定义的地方,这个 Select 组件岂不是很难用?因为好像所有地方都需要开发者自定义,通用性设计在将 DOM 结构决定权交给开发者的同时也保留了默认结构,在开发者没有显示自定义的时候默认使用默认渲染结构,其实 Select 的基本使用很方便,如下:
<Select defaultValue="lucy" style={{ width: 120 }} disabled>
<Option value="lucy">Lucy</Option>
</Select>
组件的形态(DOM结构)永远是千变万化的,但是其行为(逻辑)是固定的,因此通用组件的秘诀之一就是将 DOM 结构的控制权交给开发者,组件只负责行为和最基本的 DOM 结构
由于CSS 本身的众多缺陷,如书写繁琐(不支持嵌套)、样式易冲突(没有作用域概念)、缺少变量(不便于一键换主题)等不一而足。为了解决这些问题,社区里的解决方案也是出了一茬又一茬,从最早的 CSS prepocessor(SASS、LESS、Stylus)到后来的后起之秀 PostCSS,再到 CSS Modules、Styled-Components 等。
Antd 选择了 less 作为 css 的预处理方案,Bootstrap 选择了 Scss,这两种方案孰优孰劣已经争论了很多年了:
但是不管是哪种方案都有一个很烦人的点,就是需要额外引入 css,比如 Antd 需要这样显示引入:
import Button from 'antd/lib/button';
import 'antd/lib/button/style';
为了解决这种尴尬的情况,Antd 用 Babel 插件将这种情况 Hack 掉了
而material-ui
并不存在这种情况,他不需要显示引入 css,这个最流行的 React 前端组件库里面只有 js 和 ts 两种代码,并不存在 css 相关的代码,为什么呢?
他们用 jss
作为css-in-js 的解决方案,jsx 的引入已经将 js 和 html 耦合,css-in-js将 css 也耦合进去,此时组件便不需要显示引入 css,而是直接引用 js 即可.
这不是退化到史前前端那种写内联样式的时代了吗?
并不是,史前前端的内联样式是整个项目耦合的状态,当然要被抛弃到历史的垃圾堆中,后来的样式和逻辑分离,实际上是以页面为维度将 js css html 解耦的过程,如今的时代是组件化的时代了,jsx 已经将 js 和 html 框定到一个组件中,css 依然处于分离状态,这就导致了每次引用组件却还需要显示引入 css,css-in-js 正式彻底组件化的解决方案.
当然,我个人目前在用 styled-components,其优点引用如下:
选 Typescript ,因为巨硬大法好...
可以看看知乎问题下我的回答你为什么不用 Typescript
或者看此文TypeScript体系调研报告
组件的具体实现部分当然是组件库的核心,但是在现代前端库中其他部分也必不可少,我们需要一堆工具来辅助我们开发,例如编译工具、代码检测工具、打包工具等等。
市面上打包工具数不胜数,最火爆的当然是需要_配置工程师_专门配置的webpack,但是在类库开发领域它有一个强大的对手就是 rollup。
现代市面上主流的库基本都选择了 rollup 作为打包工具,包括Angular React 和 Vue, 作为基础类库的打包工具 rollup 的优势如下:
虽然上面部分功能已经被 webpack 实现了,但是 rollup 明显引入得更早,而Scope Hoisting更是杀手锏,由于 webpack 不得不在打包代码中构建模块系统来适应 app 开发(模块系统对于单一类库用处很小),Scope Hoisting将模块构建在一个函数内的做法更适合类库的打包.
由于 JavaScript 各种诡异的特性和大型前端项目的出现,代码检测工具已经是前端开发者的标配了,Douglas Crockford最早于2002创造出了 JSLint,但是其无法拓展,具有极强的Douglas Crockford个人色彩,Anton Kovalyov由于无法忍受 JSLint 无法拓展的行为在2011年发布了可拓展的JSHint,一时之间JSHint成为了前端代码检测的流行解决方案.
随后的2013年,Nicholas C. Zakas鉴于JSHint拓展的灵活度不够的问题开发了全新的基于 AST 的 Lint 工具 ESLint,并随着 ES6的流行统治了前端界,ESLint 基于Esprima进行 JavaScript 解析的特性极易拓展,JSHint 在很长一段时间无法支持 ES6语法导致被 ESLint 超越.
但是在 Typescript 领域 ESLint 却处于弱势地位,TSLint 的出现要比 ESLint 正式支持 Typescript 早很多,~目前 TSLint 似乎是 TS 的事实上的代码检测工具~.
注: 文章成文较早,我也没想到前阵子 TS 官方钦点了 ESLint,TSLint 失宠了,面向未来的官方标配的代码检测工具肯定是 ESLint 了,但是 TSLint 目前依然被大量使用,现在仍然可以放心使用
代码检测工具是一方面,代码检测风格也需要我们做选择,市面上最流行的代码检测风格应该是 Airbnb 出品的eslint-config-airbnb
,其最大的特点就是极其严格,没有给开发者任何选择的余地,当然在大型前端项目的开发中这种严格的代码风格是有利于协作的,但是作为一个类库的代码检测工具而言并不适合,所以我们选择了eslint-config-standard
这种相对更为宽松的代码检测风格.
以下两种 commit 哪个更严谨且易于维护?
最开始使用 commit 的时候我也经常犯下图的错误,直到看到很多明星类库的 commit 才意识到自己的错误,写好 commit message 不仅有助于他人 review, 还可以有效的输出 CHANGELOG, 对项目的管理实际至关重要.
目前流行的方案是 Angular 团队的规范,其关于 head 的大致规范如下:
当然规范人们不一定会遵守,我最初知道此类规范的时候也并没有严格遵循,因为人总会偷懒,直到用commitizen
将此规范集成到工具流中,每个 commit 就不得不遵循规范了.
我具体参考了这篇文章: 优雅的提交你的 Git Commit Message
业务开发中由于前端需求变动频繁的特性,导致前端对测试的要求并没有后端那么高,后端业务逻辑一旦定型变动很少,比较适合测试.
但是基础类库作为被反复依赖的模块和较为稳定的需求是必须做测试的,前端测试库也可谓是种类繁多了,经过比对之后我还是选择了目前最流行也是被三大框架同时选择了的 Jest 作为测试工具,其优点很明显:
当然以上是主要工具的选择,还有一些比如:
那么以上这么多配置难道要我们每次都自己写吗?组件的具体实现才是组件库的核心,我们为什么要花这么多时间在配置上面?
我们在建立 APP 项目时通常会用到框架官方提供的脚手架,比如 React 的 create-react-app,Angular 的 Angular-Cli 等等,那么能不能有一个专门用于组件开发的快速启动的脚手架呢?
有的,我最近开发了一款快速启动组件库开发的命令行工具--create-component
利用
create-component init <name>
来快速启动项目,我们提供了丰富的可选配置,只要你做好技术选型后,根据提示去选择配置即可,create-component 会自动根据配置生成脚手架,其灵感就来源于 vue-cli和 Angular-cli.
说了很多理论,那么实战如何呢?设计一个通用组件试试吧!
轮播图(Carousel),在 Antd 中被称为走马灯,可能是前端开发者最常见的组件之一了,不管是在 PC 端还是在移动端我们总能见到他的身影.
那么我们通常是如何使用轮播图的呢?Antd 的代码如下
<Carousel>
<div><h3>1</h3></div>
<div><h3>2</h3></div>
<div><h3>3</h3></div>
<div><h3>4</h3></div>
</Carousel>
问题是我们在Carousel
中放入了四组div
为什么一次只显示一组呢?
图中被红框圈住的为可视区域,可视区域的位置是固定的,我们只需要移动后面div
的位置就可以做到1 2 3 4四个子组件轮播的效果,那么子组件2目前在可视区域是可以被看到的,1 3 4应该被隐藏,这就需要我们设置overflow 属性为 hidden来隐藏非可视区域的子组件.
复制查看动图: https://images2015.cnblogs.com/blog/979044/201707/979044-20170710105934040-1007626405.gif
因此就比较明显了,我们设计一个可视窗口组件Frame
,然后将四个 div
共同放入幻灯片组合组件SlideList
中,并用SlideItem
分别将 div
包裹起来,实际代码应该是这样的:
<Frame>
<SlideList>
<SlideItem>
<div><h3>1</h3></div>
</SlideItem>
<SlideItem>
<div><h3>2</h3></div>
</SlideItem>
<SlideItem>
<div><h3>3</h3></div>
</SlideItem>
<SlideItem>
<div><h3>4</h3></div>
</SlideItem>
</SlideList>
</Frame>
我们不断利用translateX
来改变SlideList
的位置来达到轮播效果,如下图所示,每次轮播的触发都是通过改变transform: translateX()
来操作的
搞清楚基本原理那么实现起来相对容易了,我们以移动端的实现为例,来实现一个基础的移动端轮播图.
首先我们要确定可视窗口的宽度,因为我们需要这个宽度来计算出SlideList
的长度(SlideList
的长度通常是可视窗口的倍数,比如要放三张图片,那么SlideList
应该为可视窗口的至少3倍),不然我们无法通过translateX
来移动它.
我们通过getBoundingClientRect
来获取可视区域真实的长度,SlideList
的长度那么为:
slideListWidth = (len + 2) * width
(len 为传入子组件的数量,width 为可视区域宽度)
至于为什么要+2
后面会提到.
/**
* 设置轮播区域尺寸
* @param x
*/
private setSize(x?: number) {
const { width } = this.frameRef.current!.getBoundingClientRect()
const len = React.Children.count(this.props.children)
const total = len + 2
this.setState({
slideItemWidth: width,
slideListWidth: total * width,
total,
translateX: -width * this.state.currentIndex,
startPositionX: x !== undefined ? x : 0,
})
}
获取到了总长度之后如何实现轮播呢?我们需要根据用户反馈来触发轮播,在移动端通常是通过手指滑动来触发轮播,这就需要三个事件onTouchStart
onTouchMove
onTouchEnd
.
onTouchStart
顾名思义是在手指触摸到屏幕时触发的事件,在这个事件里我们只需要记录下手指触摸屏幕的横轴坐标 x 即可,因为我们会通过其横向滑动的距离大小来判断是否触发轮播
/**
* 处理触摸起始时的事件
*
* @private
* @param {React.TouchEvent} e
* @memberof Carousel
*/
private onTouchStart(e: React.TouchEvent) {
clearInterval(this.autoPlayTimer)
// 获取起始的横轴坐标
const { x } = getPosition(e)
this.setSize(x)
this.setState({
startPositionX: x,
})
}
onTouchMove
顾名思义是处于滑动状态下的事件,此事件在onTouchStart
触发后,onTouchEnd
触发前,在这个事件中我们主要做两件事,一件事是判断滑动方向,因为用户可能向左或者向右滑动,另一件事是让轮播图跟随手指移动,这是必要的用户反馈.
/**
* 当触摸滑动时处理事件
*
* @private
* @param {React.TouchEvent} e
* @memberof Carousel
*/
private onTouchMove(e: React.TouchEvent) {
const { slideItemWidth, currentIndex, startPositionX } = this.state
const { x } = getPosition(e)
const deltaX = x - startPositionX
// 判断滑动方向
const direction = deltaX > 0 ? 'right' : 'left'
this.setState({
direction,
moveDeltaX: deltaX,
// 改变translateX来达到轮播组件跟随手指移动的效果
translateX: -(slideItemWidth * currentIndex) + deltaX,
})
}
onTouchEnd
顾名思义是滑动完毕时触发的事件,在此事件中我们主要做一个件事情,就是判断是否触发轮播,我们会设置一个阈值threshold
,当滑动距离超过这个阈值时才会触发轮播,毕竟没有阈值的话用户稍微触碰轮播图就造成轮播,误操作会造成很差的用户体验.
/**
* 滑动结束处理的事件
*
* @private
* @memberof Carousel
*/
private onTouchEnd() {
this.autoPlay()
const { moveDeltaX, slideItemWidth, direction } = this.state
const threshold = slideItemWidth * THRESHOLD_PERCENTAGE
// 判断是否轮播
const moveToNext = Math.abs(moveDeltaX) > threshold
if (moveToNext) {
// 如果轮播触发那么进行轮播操作
this.handleSwipe(direction!)
} else {
// 轮播不触发,那么轮播图回到原位
this.handleMisoperation()
}
}
我们常见的轮播图肯定不是生硬的切换,一般在轮播中会有一个渐变或者缓动的动画,这就需要我们加入动画效果.
我们制作动画通常有两个选择,一个是用 css3自带的动画效果,另一个是用浏览器提供的requestAnimationFrame API
孰优孰劣?css3简单易用上手快,兼容性好,requestAnimationFrame
灵活性更高,能实现 css3实现不了的动画,比如众多缓动动画 css3都束手无策,因此我们毫无疑问地选择了requestAnimationFrame
.
双方对比请看张鑫旭大神的CSS3动画那么强,requestAnimationFrame还有毛线用?
想用requestAnimationFrame
实现缓动效果就需要特定的缓动函数,下面就是典型的缓动函数
type tweenFunction = (t: number, b: number, _c: number, d: number) => number
const easeInOutQuad: tweenFunction = (t, b, _c, d) => {
const c = _c - b;
if ((t /= d / 2) < 1) {
return c / 2 * t * t + b;
} else {
return -c / 2 * ((--t) * (t - 2) - 1) + b;
}
}
缓动函数接收四个参数,分别是:
通过这个函数我们能算出每一帧轮播图所在的位置, 如下:
在获取每一帧对应的位置后,我们需要用requestAnimationFrame
不断递归调用依次移动位置,我们不断调用animation
函数是其触发函数体内的 this.setState({ translateX: tweenQueue[0], })
来达到移动轮播图位置的目的,此时将这数组内的30个位置依次快速执行就是一个缓动动画效果.
/**
* 递归调用,根据轨迹运动
*
* @private
* @param {number[]} tweenQueue
* @param {number} newIndex
* @memberof Carousel
*/
private animation(tweenQueue: number[], newIndex: number) {
if (tweenQueue.length < 1) {
this.handleOperationEnd(newIndex)
return
}
this.setState({
translateX: tweenQueue[0],
})
tweenQueue.shift()
this.rafId = requestAnimationFrame(() => this.animation(tweenQueue, newIndex))
}
但是我们发现了一个问题,当我们移动轮播图到最后的时候,动画出现了问题,_当我们向左滑动最后一个轮播图div4
时,这种情况下应该是图片向左滑动,然后第一张轮播图div1
进入可视区域,但是反常的是图片快速向右滑动div1
出现在可是区域..._
因为我们此时将位置4设置为了位置1,这样才能达到不断循环的目的,但是也造成了这个副作用,图片行为与用户行为产生了相悖的情况(用户向左划动,图片向右走).
目前业界的普遍做法是将图片首尾相连,例如图片1前面连接一个图片4,图片4后跟着一个图片1,这就是为什么之前计算长度时要+2
slideListWidth = (len + 2) * width
(len 为传入子组件的数量,width 为可视区域宽度)
当我们移动图片4时就不会出现上述向左滑图片却向右滑的情况,因为真实情况是:
图片4 -- 滑动为 -> 伪图片1
也就是位置 5 变成了位置 6
当动画结束之后,我们迅速把伪图片1
的位置设置为真图片1
,这其实是个障眼法,也就是说动画执行过程中实际上是图片4
到伪图片1
的过程,当结束后我们偷偷把伪图片1
换成真图片1
,因为两个图一模一样,所以这个转换的过程用户根本看不出来...
如此一来我们就可以实现无缝切换的轮播图了
我们实现了轮播图的基本功能,但是其通用性依然存在缺陷:
以上都是可以对轮播图进行拓展的方向,相关的还有性能优化方面
我们的具体代码中有一个相关实现,我们的轮播图其实是有自动轮播功能的,但是很多时候页面并不在用户的可视页面中,我们可以根据是否页面被隐藏来取消定时器终止自动播放.
github项目地址
以上 demo 仅供参考,实际项目开发中最好还是使用成熟的开源组件,要有造轮子的能力和不造轮子的觉悟
背景:反复try catch 请求 handleError 很麻烦
方案: await-to-js
应用:简单处理await 等异步错误 ,它可以有效地帮助我们写出健壮的异步逻辑,降低异步编写成本
/**
* @param { Promise } promise
* @param { Object= } errorExt - Additional Information you can pass to the err object
* @return { Promise }
*/
export function to<T, U = Error> (
promise: Promise<T>,
errorExt?: object
): Promise<[U, undefined] | [null, T]> {
return promise
.then<[null, T]>((data: T) => [null, data])
.catch<[U, undefined]>((err: U) => {
if (errorExt) {
const parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
return [err, undefined];
});
}
export default to;
其实这种方式 多了 还是 嵌套地狱
const getDetail = async () => {
try {
let res = await getWorkOrderDetail('id')
} catch (e) {
console.log("e", e);
message.error("获取详情失败!")
}
}
同层级 处理多个,解决地狱问题
const [err, user] = await to(getWorkOrderDetail('id'));
if(err) message.error("获取详情失败!")
]]>背景:未采取技术手段对信息传输过程中的信息进行防护处理。(查看信息传输过程中是否对信息进行数字签名等防护处理。
方案:对请求参数进行HmacSHA256 获取hash 后写入 header中,方便后端进行参数校验
应用:传输加密
对requestbody 参数进行签名
const secretKey = 'secretkey';//用于加密的秘钥
setApiDefaultConfig({
//axios 用于转换 request 的方法
transformRequest: [(data, headers) => {
headers['X-Signature'] = CryptoJS.HmacSHA256(JSON.stringify(data),secretKey)
// axios默认是Request Payload格式,加了transformRequest会默认变成form Data格式,需要自己再转一下变回Request Payload,我在这里 再默认将他转为 json格式,这里是个小坑 需要注意一下
headers['Content-Type'] = 'application/json;charset=UTF-8'
return JSON.stringify(data);
}]
})
保证传参过程中不被篡改,实际使用 加密前的参数 进行同样的 加密过程 比较签名即可
//解密参数 生成签名
const calculatedSignature = crypto.HmacSHA256(JSON.stringify(data), secretKey).toString();
if (calculatedSignature === header中携带的签名) {
console.log('签名验证通过');
} else {
console.log('签名验证失败');
}
]]>背景:应用页面有些函数方法只需要调用一次
方案:闭包变量控制函数执行
应用:初始化操作、异步请求、异步请求、单例模式等
var once = function(fn) {
let used = false
return function (...args) {
if (!used) {
used = true
return fn(...args)
}
}
};
//或者
var once = function(fn) {
return function(...args){
try{
return fn(...args)
}
finally{
fn=()=>{}
}
}
};
function greet(name) {
console.log(`Hello, ${name}!`);
}
const greetOnce = once(greet);
greetOnce('Alice'); // Hello, Alice!
greetOnce('Bob'); // undefined
背景:next.js 项目中 引入@wangeditor/editor 编辑器 在build时候,该依赖报错 ReferenceError: Element is not defined 导致无法成功发布
方案:利用 dynamic 动态引入该组件取消ssr渲染,改为cli端 渲染即可
应用:第三方插件不支持ssr渲染
原来报错的代码就不写了,就是该富文本按照react的方式引入页面文件中,导致报错
很明显 这个问题就是在打包的环境的中(node)中没有Element变量支持,因为这个变量在window上。
// /components/MyEdit.tsx
import React, { useEffect, useState } from 'react'
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
import { IDomEditor, IEditorConfig, IToolbarConfig, SlateDescendant } from "@wangeditor/editor";
import '@wangeditor/editor/dist/css/style.css'
interface Props {
html: any,
setHtmlFn: any
}
export default function MyEditor(props: Props) {
const { html, setHtmlFn } = props
const [editor, setEditor] = useState<IDomEditor | null>(null); // TS 语法
useEffect(() => {
return () => {
if (editor == null) return;
editor.destroy();
setEditor(null);
};
}, [editor]);
const toolbarConfig: Partial<IToolbarConfig> = {}; // TS 语法
const editorConfig: Partial<IEditorConfig> = {
placeholder: "请输入内容...",
};
return (
<div style={{ border: "1px solid #ccc", zIndex: 100 }}>
<Toolbar
editor={editor}
defaultConfig={toolbarConfig}
mode="default"
style={{ borderBottom: "1px solid #ccc" }}
/>
<Editor
defaultConfig={editorConfig}
value={html}
onCreated={setEditor}
onChange={(editor: any) => setHtmlFn(editor.getHtml())}
mode="default"
style={{ height: "500px", overflowY: "hidden" }}
/>
</div>
)
}
// 页面代码
...
import dynamic from 'next/dynamic'
const ReactWEditor = dynamic(import("../../../components/MyEditor"), {
ssr: false,
loading: () => <p>Loading ...</p>,
});
...
const Page = () => {
const [attachments, setAttachments] = useState<any[]>([]);
const [html, setHtml] = useState("");
return (
<div>
<ReactWEditor html={html} setHtmlFn={setHtml} />
</div>
)
}
按照上述代码配置后 ,该富文本插件的代码就会在 客户端 渲染该组件就不会有打包时候的问题了。
]]>背景:最近项目中需要展示用户的设备组成图,也就是展开设备的结构。常见的方案就是echarts以及G6等图形解决方案,但是面临的问题就是他们都是画布去展示,经过调研发现,画布通常是固定的,但是用户不想拖拽画布中的内容去预览,于是就要根据画布中的内容展开和缩小去调整画布的大小,能不能实现反正不知道,我看了一天反正挺难得 没有头绪去搞,于是自己写了一个
方案:利用js 和css 手绘一下
应用:全量数据展示出来,不需要拖拽并且能够根据内容撑开页面的高度
直接放代码了,预览效果图根据实际需要自己搬吧~
]]>