胖蔡叨叨叨
你听我说

前端Latex 表达式不能正常显示

胖蔡阅读(25)

e681337914c5397-1

 

<form name="checkListForm">
    <input type="text" name="checkListItem"/>
</form>
<div id="button">Add!</div>
<br/>
<div class="list"></div>
    <script>
        $(document).ready(function(){
           $('#button').click(function(){
           var toAdd = $('input[name=checkListItem]').val();
           $('.list').append('<img src="http://latex.codecogs.com/gif.latex?\frac{x}  {y}"/>');

        }); 
         $(document).on('click', '.item', function(){
            $(this).remove();
         });
      });
    </script>

 

如上的代码,我在表格中通过按钮点击使用latex转化成图片的api, 但是我得到了这个我正在使用这个lates 表达式文件形式的codecogs文件:codecogs.com/latex/htmlequations.php

解决方法

在api请求参数上添加行转义符 ‘\’

$('.list').append('<img src="http://latex.codecogs.com/gif.latex?\frac{x}{y}"/>');

所以,我们需要把代码改成:

$('.list').append('<img src="http://latex.codecogs.com/gif.latex?\\frac{x}{y}"/>');

 

 

 

 

 

 

如何发布Jquery插件

胖蔡阅读(16)

这里记录下jquery插件发布的流程一般的Jquery发布插件分为四个步骤(这里有关jquery插件的书写就不做赘述,之后会另做介绍):

560cf6adb5312b9

 

在GitHub上床架jquery插件仓库

首先,jquery插件的发布依托于github仓库,因此,我们需要提前创建一个仓库用于存储我们自定义的jquery插件。

关联添加服务挂钩

首先,您需要在 GitHub 上启用 jQuery Plugins 服务挂钩。在存储库的设置页面上,单击 Webhooks 和服务链接,然后单击配置服务按钮。向下滚动以找到 jQuery Plugins 服务并启用它(没有配置,只需选中 Active 复选框并单击 Update settings 按钮)。

关联将清单添加到您的存储库

jQuery 插件注册表将在存储库的根级别中查找任何名为*.jquery.json. 您将希望*yourplugin*.jquery.json根据包清单规范进行创建 。使用在线 JSON 验证程序(例如 JSONlint)确保文件有效。您现在可以发布您的插件了!

关联发布版本

设置服务挂钩并添加清单后,发布插件就像在 git 中标记版本并将标记推送到 GitHub 一样简单。服务钩子会通知插件站点有一个新标签可用,插件站点会处理剩下的事情!

1
2
$ git tag 0.1.0
$ git push origin --tags

标签的名称必须是有效的semver值,但可以包含可选的v前缀。标记名称还必须与清单文件中列出的版本相匹配。因此,如果清单中的版本字段为“0.1.1”,则标签应为“0.1.1”或“v0.1.1”。如果清单文件有效,版本将自动添加到插件站点。

注册表不支持重新处理它已经看到的标签。 因此,我们强烈建议您不要覆盖旧标签。相反,更新清单中的版本号标记、提交并创建新标记以修复您遇到的任何错误。

例如,您已推送v1.7.0插件版本,但 在清单中检测到错误。如果您修复错误、删除、重新创建并推送另一个v1.7.0标签,注册表将不会检测到它。您必须创建并推送v1.7.1.

关联该过程需要多长时间

当一切正常时,这个过程几乎是即时的。有缓存等,但一般来说,如果您没有看到您的插件在 5 分钟内在网站上更新,则很有可能出现问题。如果您最近推送了一个新标签,进入您的 Web Hooks 设置并点击“Test Hook”按钮(一次)可能会有所帮助。

momentjs常规使用说明

胖蔡阅读(17)

moment.js是一个前端比较方便的时间操作库,该库大小不到20k。操作起来却非常简单,且支持多语言功能(多语言库的大小要偏大些,大概70kb)左右,我们可以根据需求进行选择。

a99d26134f5fd83

 

moment下载与安装

首先这里给贴出momentjs的中文官网,官网的说明相对而说应该是比较全面的。然后也会贴出写常用的moment cdn、以及npm等下载使用方式。

常见CDN

1. Staticfile CDN

2.BootCDN

 

依赖安装(node方式)

npm install moment --save # npm 
yarn add moment # Yarn Install-Package Moment.js # NuGet 
spm install moment --save # spm 
meteor add momentjs:moment # 
meteor bower install moment --save # bower (废弃)

 

momentjs基础使用

本文的所有使用方式都是基于npm依赖引用方式。

1、日期格式化

import moment from 'moment';

moment().format('MMMM Do YYYY, h:mm:ss a'); // 十二月 1日 2021, 11:02:13 上午 
moment().format('dddd');  // 星期三 moment().format("MMM Do YY");  // 12月 1日 21 
moment().format('YYYY [escaped] YYYY');  // 2021 escaped 2021 
moment().format();  // 2021-12-01T11:02:13+08:00

2、相对时间

import moment from 'moment';

moment("20111031", "YYYYMMDD").fromNow(); // 10 年前
moment("20120620", "YYYYMMDD").fromNow(); // 9 年前
moment().startOf('day').fromNow();        // 11 小时前
moment().endOf('day').fromNow();          // 13 小时内
moment().startOf('hour').fromNow();       // 2 分钟前

3、多语言支持

import moment from 'moment';

moment.locale();  // zh-cn 
moment().format('LT');  // 11:02 
moment().format('LTS');  // 11:02:13 
moment().format('L');  // 2021/12/01 
moment().format('l');  // 2021/12/1 
moment().format('LL');  // 2021年12月1日 
moment().format('ll');  // 2021年12月1日 
moment().format('LLL');  // 2021年12月1日上午11点02分 
moment().format('lll');  // 2021年12月1日 11:02 
moment().format('LLLL'); // 2021年12月1日星期三上午11点02分 
moment().format('llll'); // 2021年12月1日星期三 11:02

4、时间比较

import moment from 'moment';


moment(curTime).diff(moment(createTime), 'days') // 如果curTime不传,表示当前时间
const startTime= moment('16:24:33 04/28/20',format);
const endTime = moment('20:24:33 04/28/20',format);
const diff1 = moment(endTime).diff(moment(startTime), 'years')
const diff2 = moment(endTime).diff(moment(startTime), 'months')
const diff3 = moment(endTime).diff(moment(startTime), 'days')
const diff4 = moment(endTime).diff(moment(startTime), 'minutes' )
const diff5 = moment(endTime).diff(moment(startTime), 'seconds')
// 得到的数字 判断正负即可 (存在 diff = 0 的情况 即为第一条时间相等的情况)


Ant design UI库使用官方建议配置local出现中英文混杂的问题

胖蔡阅读(14)

因为Ant Design 库默认使用的多语言配置环境为英文,所以展示的日期控件DatePicker为英文。而我们开发需要在中文环境展示,这时就需要将DatePicker修改为中文环境了,但根据官方提供的方案修改,发现控件内出现了中英文混杂的情况。

场景复现

根据官网提供的配置方案我们进行配置复现:

默认配置为 en-US,如果你需要设置其他语言,推荐在入口处使用我们提供的国际化组件,详见:ConfigProvider 国际化

如有特殊需求(仅修改单一组件的语言),请使用 locale 参数,参考:默认配置

 

import 'moment/locale/zh-cn';
import locale from 'antd/es/date-picker/locale/zh_CN';

<DatePicker locale={locale} />;

// 默认语言为 en-US,如果你需要设置其他语言,推荐在入口文件全局设置 locale
import moment from 'moment';
import 'moment/locale/zh-cn';
import locale from 'antd/lib/locale/zh_CN';

<ConfigProvider locale={locale}> 
   <DatePicker defaultValue={moment('2015-01-01', 'YYYY-MM-DD')} /> 
</ConfigProvider>;

通过如上配置,发现的确出现了中文,但是大部分还是英文:

42f8b9c7b32dbc7

 

复现场景相关版本

  • Ant Design : 4.17.2
  • moment:2.26.0

 

解决方法

修改moment版本重装依赖即可,升级moment版本到2.29.1以上,重新运行。

1、通过命令升级

$ npm update moment@2.29.1

 

2、通过修改package.json文件配置,然后重新install依赖

bf94a32f07010ac

$ npm i

 

 

结果显示

8708e0bf1fc778d

Vue 中使用 html2Canvas 实现 html 生成 PDF 文件

胖蔡阅读(24)

前端开发过程中,可能需要将我们预览的数据通过 pdf 的方式下载保存。这时,我们就可以通过 html2canvas+jspdf 结合的方式来实现。通常情况下,jspdf 已经可以完成由 html 转 pdf 的功能了,但 jspdf 并不支持中文环境(开发人员是国外友人),这时候我们就需要先通过 html2Canvas 将 html 内容绘制到 canvas 中,然后通过 canvas 保存为图片,最后通过 jspdf 将图片生成 pdf 文件。

e301e5fe9ee1f08

通过上述的了解,我们可以将整个的封装过程分为如下几个部分:

  1. 通过 html2Canvas,将目标 dom 渲染到 canvas 中

import html2Canvas from "html2canvas";

/**
 * 通过dom的id将其加载到canvas中,并返回canvas的data数据
 * @params id dom的id
 *
 * */
function generateCanvas(id) {
  const elements = document.getElementById(id);
  const canvas = html2Canvas(element, {
    dpi: 172,
    useCORS: true,
    allowTaint: false,
    backgroundColor: "#fff",
    logging: false,
    pagesplit: true,
    scale: 1.5,
  });

  const contentWidth = canvas.width;
  const contentHeight = canvas.height;
  // 页面偏移
  const position = 0;
  // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
  const imgWidth = 595.28;
  const imgHeight = (595.28 / contentWidth) * contentHeight;
  const pageData = canvas.toDataURL("image/jpeg", 1.0);
  return {
    data: pageData,
    height: contentHeight,
    width: contentWidth,
  };
}
  1. 将 html 生成 canvas 后,我们需要将 canvas 通过 jsPdf 加载并以 pdf 的方式导出(下载)

import JsPDF from "jspdf";

function saveAsPdf(filename) {
  const pdf = new JsPDF("", "pt", "a4", true);
  const target = generateCanvas("template");
  const { data, height, width } = target;
  // 一页pdf显示html页面生成的canvas高度;
  const pageHeight = (width / 592.28) * 841.89;
  // 未生成pdf的html页面高度
  let leftHeight = height;

  if (leftHeight < pageHeight) {
    pdf.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
  } else {
    while (leftHeight > 0) {
      pdf.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
      leftHeight -= pageHeight;
      // 避免添加空白页
      position -= 841.89;
      if (leftHeight > 0) {
        pdf.addPage();
      }
    }
  }
  pdf.save(`${filename}.pdf`);
}
  1. 功能完成后就需要将其和 vue 集成了


// 将导出功能封装成一个vue指令并在全局加载  /src/directive/jsPrintPDF
import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';


export default {
    inserted:function(el,binding){
        const { value } = binding;
        el.addEventListener('click',()=>{

             const canvas = html2Canvas(element,{
                  dpi: 172,
                  useCORS: true,
                  allowTaint: false,
                  backgroundColor: '#fff',
                  logging: false,
                  pagesplit: true,
                  scale: 1.5,
            });
            const contentWidth = canvas.width;
            const contentHeight = canvas.height;
            // 页面偏移
            const position = 0;
            // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
           const imgWidth = 595.28;
           const imgHeight = 595.28 / contentWidth * contentHeight;
           const pageData = canvas.toDataURL('image/jpeg', 1.0);

           const pdf = new JsPDF("", "pt", "a4", true);
           // 一页pdf显示html页面生成的canvas高度;
           const pageHeight = (contentWidth / 592.28) * 841.89;
           // 未生成pdf的html页面高度
           let leftHeight = contentHeight;

          if (leftHeight < pageHeight) {
            pdf.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
          } else {
               while (leftHeight > 0) {
                   pdf.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
                   leftHeight -= pageHeight;
                    // 避免添加空白页
                   position -= 841.89;
                   if (leftHeight > 0) {
                      pdf.addPage();
                    }
                }
           }
          pdf.save(`${value||'未命名'}.pdf`);
        })
    }
}



// main.js中加载该指令

import jsPdfDt from '@/directive/jsPrintPDF';
Vue.use({
    install:{
        Vue.directive('loadpdf',jsPdfDt);
    }
})

通过上面的封装后,我们就可以在全局使用v-loadpdf指令了,使用如下:

<div v-loadpdf="测试下载文件">这里是下载区域 </div>

几种视频流介绍 RTP/RTCP/RTSP/WebRTC/RTMP/HLS

胖蔡阅读(17)

869892fe1f4a132

目前几种视频流的简单对比:

协议 httpflv rtmp hls dash
传输方式 http流 tcp流 http http
视频封装格式 flv flv tag Ts文件 Mp4 3gp webm
延时
数据分段 连续流 连续流 切片文件 切片文件
Html5播放 可通过html5解封包播放(flv.js) 不支持 可通过html5解封包播放(hls.js) 如果dash文件列表是mp4webm文件,可直接播放

 

题外话:

HTTP渐进下载流媒体播放:  基于TCP。

yy、乐视、爱奇艺、优酷土豆、搜狐视频、花椒直播,主要还是通过rtmp&hls来实现的,

但他们也意识到rtmp的天生缺陷,所以不管是技术预研也好,还是测试版也好,都已经或多或少在弄WebRTC了。

 

流媒体概述:

所谓流媒体是指采用流式传输的方式在 Internet 播放的媒体格式。
流媒体又叫流式媒体,它是指商家用一个视频传送服务器把节目当成数据包发出,传送到网络上。
用户通过解压设备对这些数据进行解压后,节目就会像发送前那样显示出来。
流媒体以流的方式在网络中传输音频、视频和多媒体文件的形式。
流媒体文件格式是支持采用流式传输及播放的媒体格式。
流式传输方式是将视频和音频等多媒体文件经过特殊的压缩方式分成一个个压缩包,
由服务器向用户计算机连续、实时传送。在采用流式传输方式的系统中,用户不必像非流式播放那样等到整个文件
全部下载完毕后才能看到当中的内容,而是只需要经过几秒钟或几十秒的启动延时即可在用户计算机上利用
相应的播放器对压缩的视频或音频等流式媒体文件进行播放,剩余的部分将继续进行下载,直至播放完毕。

 

RTP :(Real-time Transport Protocol)

是用于Internet上针对多媒体数据流的一种传输层协议.RTP 协议和 RTP 控制协议 RTCP 一起使用,
而且它是建立在 UDP 协议上的.
RTP 不像http和ftp可完整的下载整个影视文件,它是以固定的数据率在网络上发送数据,客户端也是按照这种速度观看影视文件,当
影视画面播放过后,就不可以再重复播放,除非重新向服务器端要求数据。

 

RTCP:Real-time Transport Control Protocol 或 RTP Control Protocol或简写 RTCP)

实时传输控制协议,是实时传输协议(RTP)的一个姐妹协议.

注:–:RTP 协议和 RTP控制协议(RTCP) 一起使用,而且它是建立在UDP协议上的(一般用于视频会议)

RTSP:(Real Time Streaming Protocol)

实时流媒体会话协议,SDP(会话描述协议),RTP(实时传输协议)。

是用来控制声音或影像的多媒体串流协议,RTSP 提供了一个可扩展框架,使实时数据,如音频与视频的受控、点播成为可能。
媒体数据使用rtp,rtcp协议。
一般使用udp 作为传输层。适合IPTV场景。
数据源包括现场数据与存储在剪辑中的数据。该协议目的在于控制多个数据发送连接,为选择发送通道,如UDP、多播UDP与TCP提供途
径,并为选择基于RTP上发送机制提供方法
传输时所用的网络通讯协定并不在其定义的范围内,服务器端可以自行选择使用TCP或UDP来传送串流内容,比较能容忍网络延迟.

—>:RTSP 与 RTP 最大的区别在于:RTSP 是一种双向实时数据传输协议,它允许客户端向服务器端发送请求,如回放、快进、倒退等操作。当
然,RTSP 可基于 RTP 来传送数据,还可以选择 TCP、UDP、组播 UDP 等通道来发送数据,具有很好的扩展性。它时一种类似与http协议
的网络应用层协议.

WebRTC:

web端实现流媒体的协议。google刚推出WebRTC的时候巨头们要么冷眼旁观,要么抵触情绪很大。使用RTP协议传输。

 

RTMP(Real Time Messaging Protocol)

Macromedia 开发的一套视频直播协议,现在属于 Adobe。和 HLS 一样都可以应用于视频直播,基于TCP不会丢失。
// 区别是 RTMP 基于 flash 无法在 iOS 的浏览器里播放,但是实时性比 HLS 要好。
实时消息传送协议是 Adobe Systems 公司为 Flash 播放器和服务器之间音频、视频和数据传输开发的开放协议.
// iOS 代码里面一般常用的是使用 RTMP 推流,可以使用第三方库 librtmp-iOS 进行推流,librtmp 封装了一些核心的 API 供使用者调用
RTMP 协议也要客户端和服务器通过”握手”来建立 RTMP Connection,然后在Connection上传输控制信息。RTMP 协议传输时会对数据格式化,而实际传输的时候为了更好地实现多路复用、分包和信息的公平性,发送端会把Message划分为带有 Message ID的Chunk,每个Chunk可能是一个单独的Message,
也可能是Message的一部分,在接受端会根据Chunk中包含的data的长度,message id和message的长度把chunk还原成完整的Message,从而实现信息的收发。

HLS:HTTP Live Streaming(HLS)

是苹果公司(Apple Inc.)实现的基于HTTP的流媒体传输协议,

可实现流媒体的直播和点播 ,主要应用在iOS系
统,为iOS设备(如iPhone、iPad)提供音视频直播和点播方案。
HLS 点播,基本上就是常见的分段HTTP点播,不同在于,它的分段非常小。
相对于常见的流媒体直播协议,例如RTMP协议、RTSP 协议、MMS 协议等,HLS 直播最大的不同在于,直播客户端获取到的,并不是一个完
整的数据流。
HLS 协议在服务器端将直播数据流存储为连续的、很短时长的媒体文件(MPEG-TS格式),而客户端则不断的下载并播放这些小文件,
因为服务器端总是会将最新的直播数据生成新的小文件,这样客户端只要不停的按顺序播放从服务器获取到的文件,就实现了直播。
由此可见,基本上可以认为,HLS 是以>>点播的技术方式来实现直播<<。由于数据通过 HTTP 协议传输,所以完全不用考虑防火墙或者代理的问
题,而且分段文件的时长很短,客户端可以很快的选择和切换码率,以适应不同带宽条件下的播放。不过HLS的这种技术特点,决定了它的
延迟一般总是会高于普通的流媒体直播协议。
// iOS和 Android 都天然支持这种协议,配置简单,直接使用 video 标签即可

***VLS :是一种流服务器,专门用来解决流的各种问题,它也具有一些 VLC 的特征。 videolan 作为服务器可以输出http,rtp,rtsp的流。

原则上,RTSP,RTMP,HTTP 都可以做直播和点播,但一般做直播用 RTSP和RTMP,做点播用 HTTP。我们选用的是RTMP协议。

 

各种协议延时及其原因

rtmp和httpflv:这两种协议大致数据一致,所以延时原因都是差不多的。按理说tcp流式传输直播因该都是延时极低的,为什么rtmp和httpflv还有延时呢?原因在h264上,rtmp和httpflv都是传输的flv tag,视频tag的数据平常就是h264数据,h264解码有个IBP,I是关键帧,是一帧完整的图像,必须要先有个I才能解码后面的BP,BP帧可以随便少,但是I帧不能少,所以I帧必须是在flv tag传输中第二个传输的(第一个是h264spspps),但是I帧在h264流里不是常有的,是隔一段才有个I帧,这个一段的间隔,俗称GOP,当编码时候GOP设置很短,当客户端连接上来,服务器会以最快速度找到流中最近I帧,从I帧开始发送直播数据,然而当GOP很长,I帧间隔很长,或者等待下一个I帧开始向新连接发送数据,或者在缓存里找最近的上一个I帧开始发送,这里就是rtmp和hls协议延时的关键了,在各大cdn平台,叫”rtmp秒开技术”,原理就是将推流数据二次解编码,设置很小的gop。总的来说,gop设置1s,在不考虑网络传输链路延时情况,数据延时最大就为1s,运气好刚好就是I帧就是0延时!

使用docsify生成简易的文档网站

胖蔡阅读(31)

1. docsify介绍

docsify 是一个动态生成文档网站的工具。不同于 GitBook、Hexo 的地方是它不会生成将 .md 转成 .html 文件,所有转换工作都是在运行时进行。

这将非常实用,如果只是需要快速的搭建一个小型的文档网站,或者不想因为生成的一堆 .html 文件“污染” commit 记录,只需要创建一个 index.html 就可以开始写文档而且直接部署在 GitHub Pages。

2. 引入docsify方式

2.1 手动创建index.html并引入docsify文件

docsify使用方式很简单,只需要在项目中创建一个index.html文件,内容能够如下:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta charset="UTF-8">
  <link rel="stylesheet" href="//unpkg.com/docsify/themes/vue.css">
</head>
<body>
  <div id="app"></div>
  <script>
    window.$docsify = {
      //...
    }
  </script>
  <script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
</body>
</html>

然后在项目中创建一个README.md文件:

## 我是首页

这是我的首页介绍

如果你的系统安装了Python 的话,可以使用Python来启动一个服务:

python -m SimpleHTTPServer 3000

Serving HTTP on 0.0.0.0 port 3000 ...
127.0.0.1 - - [03/Jan/2019 13:45:27] "GET / HTTP/1.1" 200 -

 

 

然后在浏览器中输入http://localhost:3000/查看浏览效果。

如果没有Python,还是可以使用http-server启动服务,
可在终端输入npm install http-server -g来安装http-server

http-server 

Starting up http-server, serving ./
Available on:
  http://127.0.0.1:8080
  http://172.24.70.142:8080
Hit CTRL-C to stop the server

然后在浏览器中输入http://127.0.0.1:8080查看浏览效果。

3f77307323923a7-1

 

注意:
1.在此模式下编辑文件保存后,需要手动刷新浏览器才能看见修改的效果,下面介绍的docsify-cli可实现自动查看效果。
2.强烈建议把index.html文件中的docsify.min.jsvue.css文件复制到本地项目,然后使用如下方式引入:

<link rel="stylesheet" href="./vue.css">
<script src="./docsify.min.js"></script>

这样做的好处是不在依赖网络环境了。

2.2 使用docsify-cli来开发

docsify-cli 工具,可以方便创建及本地预览文档网站。

2.2.1 安装

docsify需要本地先安装node, 如果没有安装node,可在node官网选择对应操作系统下载安装:https://nodejs.org/zh-cn/

终端输入npm i docsify-cli -g进行全局安装:

npm i docsify-cli -g

/usr/local/bin/docsify -> /usr/local/lib/node_modules/docsify-cli/bin/docsify
> fsevents@1.2.4 install /usr/local/lib/node_modules/docsify-cli/node_modules/fsevents
> node install
[fsevents] Success: "/usr/local/lib/node_modules/docsify-cli/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" already installed
Pass --update-binary to reinstall or --build-from-source to recompile
> docsify@4.8.6 postinstall /usr/local/lib/node_modules/docsify-cli/node_modules/docsify
> opencollective postinstall

                         Thanks for installing docsify 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.

          👉  Donate: https://opencollective.com/docsify/donate

+ docsify-cli@4.3.0
added 456 packages from 206 contributors in 32.827s

 

8660f0db15842ef

安装结束后使用docsify -v查看是否安装成功:

docsify -v

docsify-cli version:
  4.3.0

2.2.2 初始化一个项目

首先需要创建一个项目目录:

mkdir docsify

进入项目目录后,使用docsify init ./来初始化一个项目:

cd docsify

docsify init ./

Initialization succeeded! Please run docsify serve ./
tree -a

.
├── .nojekyll
├── README.md
└── index.html

初始化成功后,docsify目录会生成如下几个文件:

  1. index.html入口文件
  2. README.md会做为主页内容渲染
  3. .nojekyll用于阻止 GitHub Pages 会忽略掉下划线开头的文件、

.nojekyll文件很重要,如果网站部署到GitHub Pages时,一定要注意这个文件。

直接编辑 ./README.md 就能更新网站内容,当然也可以添加其他.md文件。

2.2.3 启动本地服务

终端输入docsify serve ./来启动服务:

docsify serve ./

Serving /Users/dragon/tmp/docsify now.
Listening at http://localhost:3000

然后浏览器打开http://localhost:3000就能看见效果。

 

7a0aa8f3e94623c

当修改文件保存后, docsify serve ./服务会自动实时更新。

3. 关于每个页面和URL路径说明

如果需要创建多个页面,或者需要多级路由的网站,在docsify里也能很容易的实现。例如创建一个guide.md文件,那么对应的路由就是/#/guide
如果你的目录结构如下:

-| ./
  -| README.md
  -| guide.md
  -| zh-cn/
    -| README.md
    -| guide.md

那么对应的访问页面将是:

./README.md        => http://domain.com
./guide.md         => http://domain.com/guide
./zh-cn/README.md  => http://domain.com/zh-cn/
./zh-cn/guide.md   => http://domain.com/zh-cn/guide

4. 侧边栏设置

默认情况下,侧边栏会根据当前文档的标题生成目录。

 

7650429c70b8fb5

4.1 定制侧边栏

首先需要在index.html文件中的window.$docsify添加loadSidebar: true,选项:

<script>
  window.$docsify = {
    loadSidebar: true
  }
</script>
<script src="//unpkg.com/docsify"></script>

接着在项目根目录创建_sidebar.md文件,内容格式如下:

* [home1](home1)
* [home2](home2)
* [bar](bar/)
* [bar/a](bar/a)

 

8561438a574faaa

注:配置了loadSidebar后就不会生成默认的侧边栏了。

4.2 关于侧边栏_sidebar.md文件的说明

  • 如果只在根目录有一个_sidebar.md文件,那么所有页面都将使用这个一个配置,也就是所有页面的侧边栏都一样。
  • 如果一个子目录中有_sidebar.md文件,那么这个子目录下的所有页面将使用这个文件的侧边栏。
  • _sidebar.md的加载逻辑是从每层目录下获取文件,如果当前目录不存在该文件则回退到上一级目录。例如当前路径为/zh-cn/more-pages则从/zh-cn/_sidebar.md获取文件,如果不存在则从/_sidebar.md获取。

如果子目录有_sidebar.md,但你就想使用根目录的_sidebar.md
可在index.html文件中的window.$docsify添加alias字段:

<script>
  window.$docsify = {
    loadSidebar: true,
    alias: {
      '/.*/_sidebar.md': '/_sidebar.md'
    }
  }
</script>

配置alias字段后,所有页面都会显示项目根目录_sidebar.md文件的配置作为侧边栏,子目录的_sidebar.md文件会失效。

4.3 显示页面目录(当前页面的标题)

定制的侧边栏仅显示了页面的链接。
还可以设置在侧边栏显示当前页面的目录(标题)。
需要在index.html文件中的window.$docsify添加subMaxLevel字段来设置:

<script>
  window.$docsify = {
    loadSidebar: true,
    subMaxLevel: 3
  }
</script>
<script src="//unpkg.com/docsify"></script>

 

23afaeb6e4f977b

subMaxLevel说明:

subMaxLevel类型是number(数字),表示显示的目录层级
注意:如果md文件中的第一个标题是一级标题,那么不会显示在侧边栏,如上图所示

说明
0 默认值,表示不显示目录
1 显示一级标题(h1)
2 显示一、二级标题(h1 ~ h2)
3 显示一、二、三级标题(h1 ~ h3)
n n是数字,显示一、二、….n 级标题(h1 ~ hn)

在md文件中标题的写法:

# 这是一级标题,对应HTML中<h1>标签

## 这是二级标题,对应HTML中<h2>标签

### 这是三级标题,对应HTML中<h3>标签

#### 这是四级标题,对应HTML中<h4>标签

4.3.1 页面的标题不在侧边栏目录显示

注意: 如果md文件的第一个标题是一级标题,那么默认已经忽略了。

当设置了 subMaxLevel 时,默认情况下每个标题都会自动添加到目录中。如果你想忽略特定的标题,可以给它添加 {docsify-ignore} :

# Getting Started

## Header {docsify-ignore}

该标题不会出现在侧边栏的目录中。

要忽略特定页面上的所有标题,你可以在页面的第一个标题上使用 {docsify-ignore-all} :

# Getting Started {docsify-ignore-all}

## Header

该页面所有标题都不会出现在侧边栏的目录中。

在使用时, {docsify-ignore} 和 {docsify-ignore-all} 都不会在页面上显示。

5. 导航栏配置

docsify默认是没有导航栏的,可以通过配置来显示导航栏。

5.1 在index.html中定义导航栏

如果导航的链接少,则可以直接在index.html文件直接定义导航栏,要注意链接要以#/开头:

<body>
  <nav>
    <a href="#/">项目</a>
    <a href="#/home1">home1</a>
    <a href="#/bar/a">bar/a</a>
  </nav>
</body>

 

5688d590e39580b

5.2 通过配置文件定义导航栏

首先需要在index.html文件中的window.$docsify添加loadNavbar: true,选项:

<script>
  window.$docsify = {
    loadNavbar: true
  }
</script>
<script src="//unpkg.com/docsify"></script>

接着在项目根目录创建_navbar.md文件,内容格式如下:

* [home1](home1)
* [home2](home2)
* [bar](bar/)
* [bar/a](bar/a)

 

be9727bd3295bf3

注意:
1.如果使用配置文件来设置导航栏,那么在index.html中定义的导航栏只有在定制的首页才会生效,其他页面会被覆盖。
2.如果只在根目录有一个_navbar.md文件,那么所有页面都将使用这个一个配置,也就是所有页面的导航栏都一样。
3.如果一个子目录中有_navbar.md文件,那么这个子目录下的所有页面将使用这个文件的导航栏。
4._navbar.md的加载逻辑是从每层目录下获取文件,如果当前目录不存在该文件则回退到上一级目录。例如当前路径为/zh-cn/more-pages则从/zh-cn/_navbar.md获取文件,如果不存在则从/_navbar.md获取。

5.3 导航栏嵌套

如果导航内容过多,可以写成嵌套的列表,会被渲染成下拉列表的形式:

* 根目录
  * [home1](home1)
  * [home2](home2)
  * [guide](guide)

* bar目录
  * [bar](bar/)
  * [a文件](bar/a)
  * [b文件](bar/b)

* foo目录
  * [one](foo/one)

https://image-static.segmentfault.com/140/870/1408700234-5c2b9d613eb39_fix732

6. 设置封面

docsify默认是没有封面的,默认有个首页./README.md
通过设置coverpage参数,可以开启渲染封面的功能。

首先需要在index.html文件中的window.$docsify添加coverpage: true选项:

<script>
  window.$docsify = {
    coverpage: true
  }
</script>
<script src="//unpkg.com/docsify"></script>

接着在项目根目录创建_coverpage.md文件,内容格式如下:

![logo](_media/icon.svg)
# 我的文档网站
## 个人文档网站
> 一个神奇的文档网站生成巩固

* Simple and lightweight (~12kb gzipped)
* Multiple themes
* Not build static html files

[GitHub](https://github.com/docsifyjs/docsify/)
[Get Started](#quick-start)
[Get Started](#quick-start)

894110722013d3e

注:一份文档只会在根目录下加载封面,其他页面或者二级目录下都不会加载。

6.1 自定义封面背景

目前的背景是随机生成的渐变色,每次刷新都会显示不同的颜色。
docsify封面支持自定义背景色或者背景图,在_coverpage.md文档末尾添加:

<!-- 背景图片 -->
![](_media/bg.png)

<!-- 背景色 -->
![color](#2f4253)

 

38b650c1fc1da03

注意:
1.自定义背景配置一定要在_coverpage.md文档末尾。
2.背景图片和背景色只能有一个生效.
3.背景色一定要是#2f4253这种格式的。

6.2 把封面作为首页

配置了封面后,封面和首页是同时出现的,封面在上面,首页在下面。
通过设置onlyCover参数,可以让docsify网站首页只显示封面,
原来的首页通过http://localhost:3000/#/README访问。

index.html文件中的window.$docsify添加onlyCover: true,选项:

<script>
  window.$docsify = {
    coverpage: true,
    onlyCover: true,
  }
</script>
<script src="./docsify.min.js"></script>

通过此配置可以把./README.md文件独立出来,当成项目真正的README介绍文件。

6.3 多个封面

如果你的文档网站是多语言的,或许你需要设置多个封面。
例如你的文档目录结构如下

.
└── docs
    ├── README.md
    ├── guide.md
    ├── _coverpage.md
    └── zh-cn
        ├── README.md
        └── guide.md
        └── _coverpage.md

那么你可以在index.html文件中的window.$docsify这么配置:

window.$docsify = {
  coverpage: ['/', '/zh-cn/']
};

或者具体指名文件名:

window.$docsify = {
  coverpage: {
    '/': 'cover.md',
    '/zh-cn/': 'cover.md'
  }
};

7. 网站部署到GitHub Pages

GitHub Pages 支持从三个地方读取文件:

1、master分支
2、master分支下的docs目录
3、gh-pages分支

1、如果你的文档直接是在项目根目录写的,那么可直接把代码推送到master分支上, GitHub Pages里选择master branch.
2.如果你的文档是在master分支下的docs/目录下编写的,那么可直接把代码推送到master分支上,GitHub Pages里选择master branch/docs folder.

本例子项目是直接在根目录中编写的,所以GitHub Pages里选择master branch的方式部署。

首先在github网站上创建好仓库,然后终端打开项目目录:

git init
git add .
git commit -m 'docsify项目初始化'
git remote add origin https://github.com/username/docsify.git
git push --set-upstream origin master

代码推送到github上后,打开github的仓库,选择Settings -> GitHub Pages -> master branch -> save

https://image-static.segmentfault.com/226/945/2269451939-5c2cf1d93d4b5_fix732

 

 

7343e016944c5ae

7.1 使用docsify的例子

https://spiritree.github.io/n…

https://ripperhe.com/awesome-…

8. 一些插件

8.1 搜索插件

全文搜索插件会根据当前页面上的超链接获取文档内容,在 localStorage 内建立文档索引。默认过期时间为一天,当然我们可以自己指定需要缓存的文件列表或者配置过期时间。

<script>
    window.$docsify = {
      // 完整配置参数
      search: {
        maxAge: 86400000,               // 过期时间,单位毫秒,默认一天
        paths: [],                      // or 'auto',匹配文件路径
        placeholder: 'Type to search',  // 搜索提示框文字, 支持本地化,例子在下面
        // placeholder: {
        //   '/zh-cn/': '搜索',
        //   '/': 'Type to search'
        // },
        noData: 'No Results!',          // 找不到结果文字提示,支持本地化,例子在下面
        // noData: {
        //   '/zh-cn/': '找不到结果',
        //   '/': 'No Results'
        // },
        depth: 2,                       // 搜索标题的最大程级, 1 - 6
      }
    }
  </script>
  <!-- 引入搜索模块 -->
  <script src="//unpkg.com/docsify/lib/plugins/search.js"></script>

8.2 评论插件Gitalk

Gitalk:一个现代化的,基于Preact和Github Issue的评论系统。
使用例子:

<link rel="stylesheet" href="//unpkg.com/gitalk/dist/gitalk.css">
<script src="//unpkg.com/docsify/lib/plugins/gitalk.min.js"></script>
<script src="//unpkg.com/gitalk/dist/gitalk.min.js"></script>
<script>
  const gitalk = new Gitalk({
    clientID: 'Github Application Client ID',
    clientSecret: 'Github Application Client Secret',
    repo: 'Github repo',
    owner: 'Github repo owner',
    admin: ['Github repo collaborators, only these guys can initialize github issues'],
    // facebook-like distraction free mode
    distractionFreeMode: false
  })
</script>

Gitalk具体使用教程:https://segmentfault.com/a/11…

docsify其他插件:https://docsify.js.org/#/zh-c…

8.3 样式插件

在网上找到一个样式:https://jhildenbiddle.github….

使用方法,在HTML文件中引入:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsify-themeable@0/dist/css/theme-simple.css">

参考资料

docsify中文官网:https://docsify.js.org/#/zh-cn/

React Hooks 简单使用方法

胖蔡阅读(18)

1. 前言

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性React Hooks 要解决的问题是状态共享,是继 render-props 和 higher-order components 之后的第三种状态逻辑复用方案,不会产生 JSX 嵌套地狱问题。

2. 状态逻辑复用

一般来说,组件是 UI 和逻辑,但是逻辑这一层面却很难复用。对用户而言,组件就像一个黑盒,我们应该拿来即用。但当组件的样式或者结构不满足需求的时候,我们只能去重新实现这个组件。

a65a1fc775f4726

在我们开发 React 应用的时候,经常会遇到类似下面这种场景,你可能会有两个疑问:

  1. Loading 是否可以复用?
  2. Loading 该怎么复用?

    82ff67c05ff55ec

这几个例子都指向了同一个问题,那就是如何实现组件的逻辑复用?

2.1 render props

将函数作为 props 传给父组件,父组件中的状态共享,通过参数传给函数,实现渲染,这就是 render props。使用 render prop 的库有 React Router、Downshift 以及 Formik。以下面这个 Toggle 组件为例子,我们一般可以这样用:

fe20808c741d446

可以看到,控制 Modal 组件是否展示的状态被提取到了 Toggle 组件中,这个 Toggle 组件还可以拿来多次复用到其他组件里面。那么这个 Toggle 是怎么实现的呢?看到实现后你就会理解 render props 的原理

cdb58e2a9a5aa1d

关于 render props 的更多内容可以参考 React 中文网的相关章节:Render Props

2.2 higher-order components

higher-order components 一般简称 hoc,中文翻译为高阶组件。从名字上就可以看出来,高阶组件肯定和高阶函数有什么千丝万缕的关系。高阶组件的本质是一个高阶函数,它接收一个组件,返回一个新的组件。在这个新的组件中的状态共享,通过 props 传给原来的组件。以刚刚那个 Toggle 组件为例子,高阶组件同样可以被多次复用,常常可以配合装饰器一起使用。

36bf389c2e3a7ed

高阶组件的实现和 render props 也不太一样,主要是一个高阶函数。

031210719189608

2.3 render props 和高阶组件的弊端

不管是 render props 还是高阶组件,他们要做的都是实现状态逻辑的复用,可这俩是完美的解决方案吗?考虑一下,如果我们依赖了多个需要复用的状态逻辑的时候,该怎么写呢?以 render props 为例:

451d03e12b78753

看看这个代码,你有没有一种似曾相识的感觉?这一天,我们终于想起被“回调地狱”支配的恐惧。不得不再次祭出这张图了。

ec7062162ac2910

同样地,高阶组件也会有这个问题,但由于装饰器的简洁性,没有 render props 看起来那么可怕。除此之外,他们俩还有另一个问题,那就是组件嵌套过深之后,会给调试带来很大的麻烦。这个是 render props 中组件嵌套在 React 开发者工具中的表现。

6049febc889ed9f

对于高阶组件来说,如果你没有对组件手动设置 name/displayName,就会遇到更严重的问题,那就是一个个匿名组件嵌套。毕竟上面 render props 的嵌套至少能知道组件名。

7aa01a1620a57df

社区里面也已经有很多解决 render props 嵌套的方案,其中 Epitath 提供了一种以 generator 的方法来解决嵌套问题,利用 generator 实现了伪同步代码。

81483e2127b2940

更多细节可以参考黄子毅的这篇文章:精读《Epitath 源码 – renderProps 新用法》

2.4 React Hooks

React Hooks 则可以完美解决上面的嵌套问题,它拥有下面这几个特性。

  1. 多个状态不会产生嵌套,写法还是平铺的
  2. 允许函数组件使用 state 和部分生命周期
  3. 更容易将组件的 UI 与状态分离

b3591e31fb896fe

上面是一个结合了 useState 和 useEffect 两个 hook 方法的例子,主要是在 resize 事件触发时获取到当前的 window.innerWidth。这个 useWindowWidth 方法可以拿来在多个地方使用。常用的 Hook 方法如下:

d6c7e3b03fc93f4

3. useState & useRef

useState 是 React Hooks 中很基本的一个 API,它的用法主要有这几种:

  1. useState 接收一个初始值,返回一个数组,数组里面分别是当前值和修改这个值的方法(类似 state 和 setState)。
  2. useState 接收一个函数,返回一个数组。
  3. setCount 可以接收新值,也可以接收一个返回新值的函数。
  1. const [ count1, setCount1 ] = useState(0);
  2. const [ count2, setCount2 ] = useState(() => 0);
  3. setCount1(1); // 修改 state

3.1 和 class state 的区别

虽然函数组件也有了 state,但是 function state 和 class state 还是有一些差异:

  1. function state 的粒度更细,class state 过于无脑。
  2. function state 保存的是快照,class state 保存的是最新值。
  3. 引用类型的情况下,class state 不需要传入新的引用,而 function state 必须保证是个新的引用。

3.2 快照(闭包) vs 最新值(引用)

在开始前,先抛出这么一个问题。在 1s 内频繁点击10次按钮,下面代码的执行表现是什么?

1ce896aa99238c9

如果是这段代码呢?它又会是什么表现?

28ed963643f82e0

如果你能成功答对,那么恭喜你,你已经掌握了 useState 的用法。在第一个例子中,连续点击十次,页面上的数字会从0增长到10。而第二个例子中,连续点击十次,页面上的数字只会从0增长到1。

这个是为什么呢?其实这主要是引用和闭包的区别。

class 组件里面可以通过 this.state 引用到 count,所以每次 setTimeout 的时候都能通过引用拿到上一次的最新 count,所以点击多少次最后就加了多少。

在 function component 里面每次更新都是重新执行当前函数,也就是说 setTimeout 里面读取到的 count 是通过闭包获取的,而这个 count 实际上只是初始值,并不是上次执行完成后的最新值,所以最后只加了1次。

3.3 快照和引用的转换

如果我想让函数组件也是从0加到10,那么该怎么来解决呢?聪明的你一定会想到,如果模仿类组件里面的 this.state,我们用一个引用来保存 count 不就好了吗?没错,这样是可以解决,只是这个引用该怎么写呢?我在 state 里面设置一个对象好不好?就像下面这样:

const [state, setState] = useState({ count: 0 })

答案是不行,因为即使 state 是个对象,但每次更新的时候,要传一个新的引用进去,这样的引用依然是没有意义。

  1. setState({
  2. count: state.count + 1
  3. })

3.3 useRef

想要解决这个问题,那就涉及到另一个新的 Hook 方法 —— useRef。useRef 是一个对象,它拥有一个 current 属性,并且不管函数组件执行多少次,而 useRef 返回的对象永远都是原来那一个。

0395a4081889411

useRef 有下面这几个特点:

  1. useRef 是一个只能用于函数组件的方法。
  2. useRef 是除字符串 ref、函数 refcreateRef 之外的第四种获取 ref 的方法。
  3. useRef 在渲染周期内永远不会变,因此可以用来引用某些数据。
  4. 修改 ref.current 不会引发组件重新渲染。

useRef vs createRef:

  1. 两者都是获取 ref 的方式,都有一个 current 属性。
  2. useRef 只能用于函数组件,createRef 可以用在类组件中。
  3. useRef 在每次重新渲染后都保持不变,而 createRef 每次都会发生变化。

3.4 写需求遇到的坑

35883a5b3b54f24

之前在写需求的时候遇到过这样的一个坑。bankId 和 ref 都是从接口获取到的,这里很自然就想到在 useCallback 里面指定依赖。

036737cc68c4176

但是呢,这个 handlerReappear 方法需要在第一次进入页面的时候,向 JS Bridge 注册的事件,这就导致了一个问题,不管后来 handlerReappear 如何变化,registerHandler 里面依赖的 callback 都是第一次的,这也是闭包导致的问题。当然,你可能会说,我在 useEffect 里面也指定了依赖不好吗?但要注意这是个注册事件,意味着每次我都要清除上一次的事件,需要调用到 JS Bridge,在性能上肯定不是个好办法。

最终,我选择使用 useRef 来保存 bankId 和 ref,这样就可以通过引用来获取到最新的值。

9226ab0315def18

3.5 Vue3 Composition API

e736a6b589316e5

在 vue3 里面提供了新的 Composition API,之前知乎有个问题是 React Hooks 是否可以改为用类似 Vue 3 Composition API 的方式实现?

然后我写了一篇文章,利用 Object.defineProperty 简单实现了 Composition API,可以参考:用 React Hooks 简单实现 Vue3 Composition API

当然这个实现还有很多问题,也比较简单,可以参考工业聚写的完整实现:react-use-setup

4. useEffect

useEffect 是一个 Effect Hook,常用于一些副作用的操作,在一定程度上可以充当 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个生命周期。useEffect 是非常重要的一个方法,可以说是 React Hooks 的灵魂,它用法主要有这么几种:

  1. useEffect 接收两个参数,分别是要执行的回调函数、依赖数组。
  2. 如果依赖数组为空数组,那么回调函数会在第一次渲染结束后(componentDidMount)执行,返回的函数会在组件卸载时(componentWillUnmount)执行。
  3. 如果不传依赖数组,那么回调函数会在每一次渲染结束后(componentDidMount 和 componentDidUpdate)执行。
  4. 如果依赖数组不为空数组,那么回调函数会在依赖值每次更新渲染结束后(componentDidUpdate)执行,这个依赖值一般是 state 或者 props。

    0fe8c792271a1a4

useEffect 比较重要,它主要有这几个作用:

  1. 代替部分生命周期,如 componentDidMount、componentDidUpdate、componentWillUnmount。
  2. 更加 reactive,类似 mobx 的 reaction 和 vue 的 watch。
  3. 从命令式变成声明式,不需要再关注应该在哪一步做某些操作,只需要关注依赖数据。
  4. 通过 useEffect 和 useState 可以编写一系列自定义的 Hook。

4.1 useEffect vs useLayoutEffect

useLayoutEffect 也是一个 Hook 方法,从名字上看和 useEffect 差不多,他俩用法也比较像。在90%的场景下我们都会用 useEffect,然而在某些场景下却不得不用 useLayoutEffect。useEffect 和 useLayoutEffect 的区别是:

  1. useEffect 不会 block 浏览器渲染,而 useLayoutEffect 会。
  2. useEffect 会在浏览器渲染结束后执行,useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制之前执行。

这两句话该怎么来理解呢?我们以一个移动的方块为例子:

caefbe26c45e229

0c6fcff074ac3cf

在 useEffect 里面会让这个方块往后移动 600px 距离,可以看到这个方块在移动过程中会闪一下。但如果换成了 useLayoutEffect 呢?会发现方块不会再闪动,而是直接出现在了 600px 的位置。

af10915d312e5eb

944a002f7040684

原因是 useEffect 是在浏览器绘制之后执行的,所以方块一开始就在最左边,于是我们看到了方块移动的动画。然而 useLayoutEffect 是在绘制之前执行的,会阻塞页面的绘制,所以页面会在 useLayoutEffect 里面的代码执行结束后才去继续绘制,于是方块就直接出现在了右边。那么这里的代码是怎么实现的呢?以 preact 为例,useEffect 在 options.commit 阶段执行,而 useLayoutEffect 在 options.diffed 阶段执行。然而在实现 useEffect 的时候使用了 requestAnimationFramerequestAnimationFrame 可以控制 useEffect 里面的函数在浏览器重绘结束,下次绘制之前执行。

e7873b809a71223

5. useMemo

useMemo 的用法类似 useEffect,常常用于缓存一些复杂计算的结果。useMemo 接收一个函数和依赖数组,当数组中依赖项变化的时候,这个函数就会执行,返回新的值。

  1. const sum = useMemo(() => {
  2. // 一系列计算
  3. }, [count])

举个例子会更加清楚 useMemo 的使用场景,我们就以下面这个 DatePicker 组件的计算为例:

9e1a1fb022473c0

DatePicker 组件每次打开或者切换月份的时候,都需要大量的计算来算出当前需要展示哪些日期。然后再将计算后的结果渲染到单元格里面,这里可以使用 useMemo 来缓存,只有当传入的日期变化时才去计算。

6. useCallback

和 useMemo 类似,只不过 useCallback 是用来缓存函数。

6.1 匿名函数导致不必要的渲染

在我们编写 React 组件的时候,经常会用到事件处理函数,很多人都会简单粗暴的传一个箭头函数。

  1. class App extends Component {
  2. render() {
  3. return <h1 onClick={() => {}}></h1>
  4. }
  5. }

这种箭头函数有个问题,那就是在每一次组件重新渲染的时候都会生成一个重复的匿名箭头函数,导致传给组件的参数发生了变化,对性能造成一定的损耗。

在函数组件里面,同样会有这个传递新的匿名函数的问题。从下面这个例子来看,每次点击 div,就会引起 Counter 组件重新渲染。这次更新明显和 Input 组件无关,但每次重新渲染之后,都会创建新的 onChange 方法。这样相当于传给 Input 的 onChange 参数变化,即使 Input 内部做过 shadowEqual 也没有意义了,都会跟着重新渲染。原本只想更新 count 值的,可 Input 组件 却做了不必要的渲染。

65aac7322b357c0

这就是体现 useCallback 价值的地方了,我们可以用 useCallback 指定依赖项。在无关更新之后,通过 useCallback 取的还是上一次缓存起来的函数。因此,useCallback 常常配合 React.memo 来一起使用,用于进行性能优化。

9817376fa759b10

7. useReducer && useContext

7.1 useReducer

useReducer 和 useState 的用法很相似,甚至在 preact 中,两者实现都是一样的。useReducer 接收一个 reducer 函数和初始 state,返回了 state 和 dispatch 函数,常常用于管理一些复杂的状态,适合 action 比较多的场景。

353adbd8b34bd2c

7.2 useContext

在上一节讲解 React16 新特性的时候,我们讲过新版 Context API 的用法。

新版 Context 常常有一个提供数据的生产者(Provider),和一个消费数据的消费者(Consumer),我们需要通过 Consumer 来以 render props 的形式获取到数据。如果从祖先组件传来了多个 Provider,那最终就又陷入了 render props 嵌套地狱。

9a12737942acb7b

useContext 允许我们以扁平化的形式获取到 Context 数据。即使有多个祖先组件使用多个 Context.Provider 传值,我们也可以扁平化获取到每一个 Context 数据。

41d6e940e42ad08

7.3 实现一个简单的 Redux

通过 useReducer 和 useContext,我们完全可以实现一个小型的 Redux。

reducer.js

49821ee3dbae935

Context.js

export const Context = createContext(null);

App.js

0258cdb7109d586

8. Custom Hooks

对于 react 来说,在函数组件中使用 state 固然有一些价值,但最有价值的还是可以编写通用 custom hooks 的能力。想像一下,一个单纯不依赖 UI 的业务逻辑 hook,我们开箱即用。不仅可以在不同的项目中复用,甚至还可以跨平台使用,react、react native、react vr 等等。编写自定义 hook 也需要以 use 开头,这样保证可以配合 eslint 插件使用。在 custom hooks 中也可以调用其他 hook,当前的 hook 也可以被其他 hook 或者组件调用。以官网上这个获取好友状态的自定义 Hook 为例:

1a71ccb2f2377ab

这个自定义 Hook 里面对好友的状态进行了监听,每次状态更新的时候都会去更新 isOnline,当组件卸载的时候会清除掉这个监听。这就是 React Hooks 最有用的地方,它允许我们编写自定义 Hook,然后这个自定义 Hook 可以复用给多个组件,并且不会和 UI 耦合到一起。

9. React Hooks 原理

由于 preact hooks 的代码和原有的逻辑耦合度很小,这里为了更加浅显易懂,我选用了 preact hooks 的源码来解读。

9.1 Hooks 执行流程

在 React 中,组件返回的 JSX 元素也会被转换为虚拟 DOM,就是下方的 vnode,每个 vnode 上面挂载了一个 _component 属性,这个属性指向了组件实例。而在组件实例上面又挂载了一个 _hooks 属性,这个 _hooks 属性里面保存了我们执行一个组件的时候,里面所有 Hook 方法相关的信息。

a43e038b3a1db37

首先,我们有一个全局的 currentIndex 变量,当组件第一次渲染或者更新的时候,它会在每次进入一个函数组件的时候都重置为0,每次遇到一个 Hook 方法就会增加1,同时将这个 Hook 方法的信息放到 _list 里面。

当我们下次进来或者进入下一个组件的时候, currentIndex 又会被置为0。

★组件渲染 => currentIndex 重置 0 => 遇到 Hooks 方法,放进 _list => currentIndex++ => 渲染结束

★组件更新 => currentIndex 重置 0 => 遇到 Hooks 方法,获取 _list[currentIndex]=> currentIndex++ => 重复上面步骤 => 更新结束

这个时候就会从刚才的 _list 里面根据 currentIndex 来取出对应项,所以我们每次进来执行 useState,它依然能拿到上一次更新后的值,因为这里是缓存了起来。

f858f9ba2e4c11d

通过上面的分析,你就不难发现,为什么 hooks 方法不能放在条件语句里面了。因为每次进入这个函数的时候,都是要和 currentIndex 一一匹配的,如果更新前后少了一个 Hook 方法,那么就完全对不上了,导致出现大问题。

9.2 useState 和 useReducer

这样你再来看下面 useState 和 useReducer 的源码就会更容易理解一些。

c393ff117977aac

很明显,getHookState 是根据 currentIndex 来从 _list 里面取和当前 Hook 相关的一些信息。如果是初始化状态(即没有 hookState._component)这个属性的时候,就会去初始化 useState 的两个返回值,否则就会直接返回上一次缓存的结果。

9.3 useEffect

useEffect 和 useState 差不多,区别就在 useEffect 接收的函数会放到一个 _pendingEffects 里面,而非 _list 里面。

7cdfdea7a4adcd3

在 diff 结束之后会从 _pendingEffects 里面取出来函数一个个执行。afterPaint 里面使用了 requestAnimateFrame 这个方法,所以传给 useEffect 里面的方法是在浏览器绘制结束之后才会执行的。

e681a378b461b8e

9.4 总结

最后,这里对 React Hooks 的整个运行流程来进行一下总结和梳理。

  1. 每个组件实例上挂载一个 _hooks 属性,保证了组件之间不会影响。
  2. 每当遇到一个 hooks 方法,就将其 push 到 currentComponent._hooks._list 中,且 currentIndex 加一。
  3. 每次渲染进入一个组件的时候,都会从将 currentIndex 重置为 0 。遇到 hooks 方法时,currentIndex 重复第二步。这样可以把 currentIndex 和 currentComponent._hooks._list 中的对应项匹配起来,直接取上次缓存的值。
  4. 函数组件每次重新执行后,useState 中还能保持上一次的值,就是来自于步骤3中的缓存。
  5. 由于依赖了 currentComponent 实例,所以 hooks 不能用于普通函数中。

10. React Hooks 实践

得益于 react hooks 将业务逻辑从 ui 中抽离出来,目前社区里面关于 react hooks 的实践,大都是从功能点出发。

从最简单的 api 封装,例如 useDebounce、useThrottle、useImmerState 等等,再到业务层面功能封装,比较出名的库有 react-use、umijs/hooks 等等。

举个栗子:umijs/hooks 的表格:

067b6f743cb2d9c

在后台管理系统开发中,表格是非常常见的场景,将分页、查询、loading、排序等等功能打包封装成通用 Hook,就能发挥很大的潜力。

Webpack 项目添加 eslint 实现代码检测功能

胖蔡阅读(93)

项目环境及相关资料

依赖库

1、eslint-loader
想要 webpack 实现 eslint 的检测,首先需要安装 loader eslint-loader ,并在配置文件中添加加载规则:


// 使用npm
$ npm install --save-dev eslint-loader

// 使用yarn
$yarn add -D eslint-loader
// webpack.config.js

{
...
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: "eslint-loader",
         exclude: /node_modules/,
        enforce: "pre",
        include: [path.resolve(__dirname, "src")], // 指定检查的目录
        options: {
          // 这里的配置项参数将会被传递到 eslint 的 CLIEngine
          formatter: require("eslint-friendly-formatter"), // 指定错误报告的格式规范
      //  fix: true, // 自动修正,会改变文件内容,根据需要配置
        },
      },
    ];
  }
  ...
}

2、 eslint

为了能使项目具有eslint检测功能,我们需要安装eslint依赖库,并生成配置eslint配置文件.

// npm 
$ npm install --save-dev eslint

// yarn
$ yarn add -D eslint

eslint的配置文件有很多类型,可以是 .eslintrc,也可以是 .eslint.js又或者是 .eslint.json,这里以.eslintrc.js为例,给出一个示例配置:

这里需要注意的是eslint的配置,需要配置一个错误级别,通常错误级别有三个类别:

  • “off” or 0 – 关闭规则
  • “warn” or 1 – 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  • “error” or 2 – 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
/* eslint-disable no-undef */
module.exports = {
    'env': {
        'browser': true,
        'es2021': true,
    },
    'extends': 'eslint:recommended',
    'parserOptions': {
        'ecmaVersion': 13,
        'sourceType': 'module',
    },
    'rules': {
        'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', // allow console during development
        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // allow debugger during development
        // 以下为该项目自定义部分
        'indent': ['error', 4], //缩进风格 - 开启缩进4格
        'max-len': ['error', {
            'code': 120,         // 强制单行的最大长度为120
            'comments': 120,    // 强制单行注释的最大长度为120    
        }],
        'space-in-parens': ['error', 'always'], // 强制圆括号内有一个空格
        'curly': ['error', 'multi', 'consistent'], //   if、else if 和 else 所有的代码块使用或者不使用大括号。
        'keyword-spacing':['warn',{         // 关键字前后需要保持至少有一个空格
            'before': true,
            'after': true,
        }],
        'lines-around-comment':['warn',{        
            'beforeBlockComment': true,
            'afterBlockComment': true,
        }],
        'one-var-declaration-per-line': ['error', 'initializations'], // 强制每个变量初始化语句换行
        'comma-dangle': ['error', 'always-multiline'], //  当最后一个元素或属性与闭括号 ] 或 } 在 不同的行时,要求使用拖尾逗号;当在 同一行时,禁止使用拖尾逗号。
        'quotes': ['error', 'single'], // 要求尽可能地使用单引号
        'no-nested-ternary': 'error', // 禁止使用嵌套的三元表达式
        'multiline-ternary': ['error', 'always-multiline'], // 如果表达式跨越多个行,则在三元表达式的操作数之间强制换行
        'wrap-iife': ['error', 'any'], // 强制总是包裹,但允许其它风格。
        'no-new-wrappers': 'error', // 禁止对 String,Number 和 Boolean 使用 new 操作符
        'no-var': 'error', // 要求使用 let 或 const 而不是 var
        'no-duplicate-imports': 'error', // 禁止重复模块导入
        'eol-last': ['error', 'always'], //文件强制使用换行 (LF)结束最后一行
        'camelcase': ['error', {'properties': 'always'}], //强制驼峰法命名 
    },
};


上面配置中的 ‘extends’: ‘eslint:recommended’,为继承eslint默认的rule配置,所以一些基础的配置就不需要我们再重复配置了。上面所包含的配置属性以及相关描述,在下方给一一列出来了:

规则名 规则描述
for-direction 强制 “for” 循环中更新子句的计数器朝着正确的方向移动
getter-return 强制 getter 函数中出现 return 语句
no-async-promise-executor 禁止使用异步函数作为 Promise executor
no-compare-neg-zero 禁止与 -0 进行比较
no-cond-assign 禁止条件表达式中出现赋值操作符
no-constant-condition 禁止在条件中使用常量表达式
no-control-regex 禁止在正则表达式中使用控制字符
no-debugger 禁用 debugger
no-dupe-args 禁止 function 定义中出现重名参数
no-dupe-keys 禁止对象字面量中出现重复的 key
no-duplicate-case 禁止出现重复的 case 标签
no-empty 禁止出现空语句块
no-empty-character-class 禁止在正则表达式中使用空字符集
no-ex-assign 禁止对 catch 子句的参数重新赋值
no-extra-boolean-cast(#fix) 禁止不必要的布尔转换
no-extra-semi(#fix) 禁止不必要的分号
no-func-assign 禁止对 function 声明重新赋值
no-inner-declarations 禁止在嵌套的块中出现变量声明或 function 声明
no-invalid-regexp 禁止 RegExp 构造函数中存在无效的正则表达式字符串
no-irregular-whitespace 禁止不规则的空白
no-misleading-character-class 不允许在字符类语法中出现由多个代码点组成的字符
no-obj-calls 禁止把全局对象作为函数调用
no-prototype-builtins 禁止直接调用 Object.prototypes 的内置属性
no-regex-spaces(#fix) 禁止正则表达式字面量中出现多个空格
no-sparse-arrays 禁用稀疏数组
no-unexpected-multiline 禁止出现令人困惑的多行表达式
no-unreachable 禁止在 return、throw、continue 和 break 语句之后出现不可达代码
no-unsafe-finally 禁止在 finally 语句块中出现控制流语句
no-unsafe-negation(#fix) 禁止对关系运算符的左操作数使用否定操作符
require-atomic-updates 禁止由于 await 或 yield的使用而可能导致出现竞态条件的赋值
use-isnan 要求使用 isNaN() 检查 NaN
valid-typeof 强制 typeof 表达式与有效的字符串进行比较
no-case-declarations 不允许在 case 子句中使用词法声明
no-empty-pattern 禁止使用空解构模式
no-fallthrough 禁止 case 语句落空
no-global-assign 禁止对原生对象或只读的全局对象进行赋值
no-octal 禁用八进制字面量
no-redeclare 禁止多次声明同一变量
no-self-assign 禁止自我赋值
no-unused-labels 禁用出现未使用过的标
no-useless-catch 禁止不必要的 catch 子句
no-useless-escape 禁用不必要的转义字符
no-with 禁用 with 语句
no-delete-var 禁止删除变量
no-shadow-restricted-names 禁止将标识符定义为受限的名字
no-undef 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
no-unused-vars 禁止出现未使用过的变量
no-mixed-spaces-and-tabs 禁止空格和 tab 的混合缩进
constructor-super 要求在构造函数中有 super() 的调用
no-class-assign 禁止修改类声明的变量
no-const-assign 禁止修改 const 声明的变量
no-dupe-class-members 禁止类成员中出现重复的名称
no-new-symbol 禁止 Symbolnew 操作符和 new 一起使用
no-this-before-super 禁止在构造函数中,在调用 super() 之前使用 this 或 super
require-yield 要求 generator 函数内有 yield
no-console 禁用 console
indent 强制使用一致的缩进
max-len 强制一行的最大长度
space-in-parens 强制在圆括号内使用一致的空格
curly 强制所有控制语句使用一致的括号风格
keyword-spacing 强制在关键字前后使用一致的空格
lines-around-comment 要求在注释周围有空行
one-var-declaration-per-line 要求或禁止在变量声明周围换行
comma-dangle 要求或禁止末尾逗号
quotes 强制使用一致的反勾号、双引号或单引号
no-nested-ternary 禁用嵌套的三元表达式
wrap-iife 要求 IIFE 使用括号括起来
no-new-wrappers 禁止对 String,Number 和 Boolean 使用 new 操作符
no-var 要求使用 let 或 const 而不是 var
no-duplicate-imports 禁止重复模块导入
eol-last 要求或禁止文件末尾存在空行
camelcase 强制使用骆驼拼写法命名约定

Где Находится Дубай – Всё Просто

antoinette81v阅读(156)

Это возможно, если вы проводите отпуск в пятизвездочной гостинице и оплатили проживание на два месяца вперед. Это скорее реклама, за которую платить пришлось нам. В Дубае алкогольные магазины тоже есть, но купить там что-либо можно только при наличии специальной карты, которую в народе называют «харам-карта». Женщинам посетить мечеть можно только в специальной одежде – абайях. В назначенное время к отелю подъехал большой туристических автобус. Вежливые водители по просьбе пассажиров останавливают автобус в нужном месте, если нет необходимости ехать до аэропорта или на один из двух автовокзалов этого маршрута. Обилечивание происходит прямо при посадке в автобус. Исключение составляют, пожалуй, только летние месяцы, потому что в это время года становится довольно жарко. В настоящее время это самое высокое здание в мире – 828 метров. Благодаря наступившему и длящемуся по настоящее время экономическому кризису, возникла необходимость сохранения ранее нажитых денежных средств и разработка выгодных вложений. Все это время охотники преследовали газель по пятам. Все пляжи расположены внутри острова.

Кстати,в Бурдж-Халифе расположены не только офисы крупныхкомпаний, но также и жилые квартиры, отель «Армани»,а сотый этаж полностью принадлежит одному миллиардеру. Я ничего не имею против, ведь это физиология и все такое, но это явно давало о себе знать. Но надо бы знать английский. Но Абу-Даби и Дубай – это два разных места, со своими прекрасными достопримечательностями и удивительными видами. Живописные места, горячие источники, так же, как и везде, есть магазины и восточные лавки. Внутри, опять же, роскошь. Внутрь, нас, конечно же, никто не пустил. 80% времени она рассказывала не о том, что вокруг нас, а о еде и свадьбах в ОАЭ. Что купить в ОАЭ. Что лучше из перечисленных видов транспорта – решать вам. Обратите внимание, что во время праздничных дней посольство не работает. За относительно короткое время можно попасть дубай на новый год роскошный пляж, ведь перелет в ОАЭ из Москвы занимает всего 5 часов времени. Маршрутные минивэны – это один из видов городского общественного транспорта в ОАЭ. В отличие от других стран, в ОАЭ визу не дадут ребенку, едущему без хотя бы одного из родителей, даже если оба родителя напишут доверенность на сопровождающего. ОАЭ. К таким районам относятся Dubai Marina, Emirates Hills, Jumeirah Islands, Palm Islands, Dubai Waterfront, Jumeirah Beach Residence, Meadows, Springs, Arabian Ranches и др.

Dubai Wonder Land с дельфинарием и ознакомительными выставками, посвященные морским обитателям Индийского океана и Персидского залива. Столица ОАЭ Абу-Даби располагается на одном из островов Персидского залива в его юго-восточной части. Он расположился к северо-востоку от столицы государства на берегу Персидского залива. Он находится на пересечении Hazza bin Zayed (11th) St и East (4th) Rd. Метро и такси до автовокзала – это дополнительные затраты, хотя и сравнительно небольшие. Стойки регистрации в аэропорту, открывают за 3 часа до вылета рейса и они прекращают работать за один час до времени отправления. Для гостей отеля они предоставляются бесплатно, если посетитель не проживает в гостинице, за использование зонтиков и шезлонгов могут взять до 40 дирхам. Для них важно, чтобы было где припарковаться. Для экономии времени и денег рекомендуем использовать сервис Kiwitaxi . Даже если такси будет ехать по противоположной стороне от вас, завидев потенциального клиента, водитель немедленно начнет разворачиваться, чтобы подъехать к вам. В такси работают счетчики. Пассажиры авиакомпании Flydubai ещё не знают насколько в онлайн режиме регистрация на рейс удобна и полезна.

На борту самолетов flydubai вас ожидают русскоговорящие бортпроводники, магазин duty-free, более 200 фильмов и видеоигр. Найти самые дешевые авиабилеты авиакомпании Flydubai проще всего онлайн в поиске Авианити. На экскурсии и на прогулку по городу желательно одеваться скромно. На потолке красивая роспись и несколько люстр обвешанных камнями Сваровски, по несколько тонн каждая. На деле оно оказывается гораздо больше и растягивается из-за автомобильных пробок. На сегодняшний день компания Флай Дубаи осуществляет собственные перелёты по нескольким главным направлениям. Властями Эмирата проводится активная компания по озеленению окружающей среды. На полу ковер огромной площади сшитый вручную умельцами, если не подводит память, из Ирана. На побережье удобно загорать и купаться в Персидском заливе, из городских отелей отправляются автобусы на экскурсии к достопримечательностям. Если вы не брезгуете общественным транспортом и готовы мириться с соседством представителей другой культуры в маршрутке, то поездка окажется вполне комфортной. Если вы предпочитаете умеренный отдых вдали от городской суеты при этом решите посетить столицу Эмиратов, Абу Даби – самое идеальное место.