All Posts

初识 Deno

初识 Deno

几天前的 JavaScript Weekly 里推送了一条名为 “A Look at Deno: A New JavaScript Runtime” 的视频,视频的内容是 Deno 的作者(同时也是 Node.js 项目的最初发起人与维护者) Ryan Dahl 在今年 4 月初的 JS Fest 大会上做的关于 Deno 的演讲。 Deno, a new way to JavaScript 看完视频感觉这个项目挺有趣,于是花了一点时间做了一些了解,记录如下。

TypeScript 中的泛型(Generic)

TypeScript 中的泛型(Generic)

在函数中使用泛型 这里 T 可以代表任意类型。 function identity<T>(arg: T): T { return arg; } 使用时可以明确指定类型: let output = identity<string>("myString"); 不过更多时候是交给 TypeScript 进行类型推断: let output = identity("myString"); 在接口中使用泛型 interface GenericIdentityFn { <T>(arg: T): T; } function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn = identity; 我们还可以把泛型参数提升到整个接口的层面,像下面这样: interface GenericIdentityFn<T> { (arg: T): T; } 然后在我们使用这个接口的时候,必须明确指定这个泛型参数的类型: function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn<number> = identity; 我们得到的 myIdentity 方法只能接受数字作为参数。

TypeScript 中的函数类型与索引类型

TypeScript 中的函数类型与索引类型

函数类型 利用 interface 声明函数类型,只需要把接口定义中的属性写成函数签名即可。 像这样: interface SearchFunc { (source: string, subString: string): boolean; } 如果我们的函数本身还有一些属性,例如 Node.js 中的 require() 方法,我们既可以 require("package") 又可以 require.resolve("package")。这是需要首先为该方法定义一个接口,然后再通过 extends 它来添加属性。 interface NodeRequireFunction { (id: string): any; } interface NodeRequire extends NodeRequireFunction { resolve: RequireResolve; cache: any; extensions: NodeExtensions; main: NodeModule | undefined; } 索引类型 有时我们想让我们对象支持向数组那样按照数字下标来存取数据,有时我们会想创建一个”字典”对象来存取任意的 “key/value” 对应关系。这两种情况就需要声明索引类型。区别在于索引本身的类型是数字还是字符串。 interface StringArray { [index: number]: string; } interface NameAddressMap { [index: string]: string; } 我们可以把一个对象声明成既支持字符串索引有支持数字索引: interface ItsOK { [index: string]: string; [index: number]: string; } 如果我们的对象要同时支持两种索引类型,那么必须保证字符串索引对应值的类型是数字索引对应值的类型的超集。

Node.js 的模块系统

Node.js 的模块系统

两个核心模块 require 和 module。 require 模块对应全局的 require 方法。module 模块对应每一个模块全局空间中的 module 属性。 Node.js 加载一个模块主要经历以下几个步骤: Resolving -> Loading -> Wrapping -> Evaluating -> Caching Resolving Node.js 中的模块和文件系统中的文件是一一对应的(这一点很重要)。加载模块的过程其实就是执行文件系统中的脚本并将结果载入内存的过程。 每一个模块都有一个 id 属性,该属性的值就是这个模块对应文件的绝对路径(在 REPL 里为 ““)。 Resolving 阶段的工作就是把我们 require 的字符串解析成一个文件系统中的绝对路径。根据我们 require 包的类型,这里又分为三种情况: 核心模块。也即 Node.js 内置的模块,例如 “fs”、”path”、”http” 等,这类模块无需安装即可直接使用。 相对路径/绝对路径。Node.js 直接将相对路径转换成对应的绝对路径。 第三方依赖。如果不是前两种情况,那么 Node.js 会依次查找 module.paths 列表中的目录是否存在。 我们来看看 module.paths 中都有哪些目录: 可以看到主要是从当前目录逐级向上查找 node_modules 目录。这也就是为什么我们的依赖会被安装在 node_modules 目录下的原因。 为了向前兼容,Node.js 还会检查一些已经被废弃的目录,不推荐使用它们。 在找到这个列表中某个存在的目录之后,Node.js 会在该目录下继续查找,假设我们执行的是 require("moduleA"),那么又可以分为以下三种情况: 存在一个 moduleA.js 文件,那么该文件就是最终我们要加载的文件。 存在一个 moduleA 子目录,且该目录下存在一个名为 index.

Yarn 的 Plug'n'Play 特性

Yarn 的 Plug'n'Play 特性

前言 Yarn 团队在春节前公布了 Yarn 2.0 的规划。其中提到了一个之前没听说过的名词 “PnP”。发现 Yarn 的这个功能早在 18 年 9 月份就被提出并实现了。于是花了一些时间了解了一下它的工作原理以及解决的问题并整理除了本篇文章。 现状与痛点 Yarn 团队开发 PnP 特性最直接的原因就是现有的依赖管理方式效率太低。引用依赖时慢,安装依赖时也慢。 先说说 Node 在处理依赖引用时的逻辑,这个流程会有如下两种情况: 如果我们传给 require() 调用的参数是一个核心模块(例如 “fs”、”path”等)或者是一个本地相对路径(例如 ./module-a.js 或 /my-li/module-b.js),那么 Node 会直接使用对应的文件。 如果不是前面描述的情况,那么 Node 会开始寻找一个名为 node_modules 的目录: 首先 Node 会在当前目录寻找 node_modules,如果没有则到父目录查找,以此类推直到系统根目录。 找到 node_modules 目录之后,再在该目录中寻找名为 moduleName.js 的文件或是名为 moduleName 的子目录。 此处旨在说明问题,对 Node 内部模块解析逻辑做了简化描述 可见 Node 在解析依赖时需要进行大量的文件 I/O 操作,效率并不高。 再来看看安装依赖时发生了什么,现阶段 yarn install 操作会执行以下 4 个步骤: 将依赖包的版本区间解析为某个具体的版本号 下载对应版本依赖的 tar 包到本地离线镜像 将依赖从离线镜像解压到本地缓存 将依赖从缓存拷贝到当前目录的 node_modules 目录 其中第 4 步同样涉及大量的文件 I/O,导致安装依赖时效率不高(尤其是在 CI 环境,每次都需要安装全部依赖)。

从 Flutter 源码看 InheritedWidget 内部实现原理

从 Flutter 源码看 InheritedWidget 内部实现原理

这两天学习了一下 Flutter 中的 InheritedWidget 的使用方法,顺便查看一下相关源码了解了其底层实现机制。特地记录一下。 Prerequirements 由于本文主要是从源码的角度分析 InheritedWidget 的工作原理,所以对阅读本文的小伙伴的 Flutter 知识有一定的要求。主要有以下几点,如果其中某部分你还不太清楚,请先阅读相关链接: 了解 Flutter 的基本用法。 了解 Flutter 中的 Widget 和 Element 的基本概念。 推荐阅读:Flutter, what are Widgets, RenderObjects and Elements? 对 Flutter 中 Element 的生命周期有基本了解。推荐阅读:Element class 下面开始正文。 InheritedWidget 的使用方法 先看一个 InheritedWidget 最简单的使用示例: import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyWelcomeInfo extends InheritedWidget { MyWelcomeInfo({Key key, this.welcomeInfo, Widget child}) : super(key: key, child: child); final String welcomeInfo; @override bool updateShouldNotify(InheritedWidget oldWidget) { return oldWidget.

Flutter 中 ListView 组件的子元素曝光统计

Flutter 中 ListView 组件的子元素曝光统计

在使用 Flutter 开发应用的过程中我们经常遇到需要展示一组连续元素的情景。这时我们通常会选择使用 ListView 组件。在电商场景中,被展示的元素通常是一组商品、一组店铺又或是一组优惠券信息。把这些信息正确的展示出来仅仅是第一步,通常业务同学为了统计用户的浏览习惯、活动的展示效果还会让我们上报列表元素的曝光信息。 什么是曝光信息? 什么是曝光是信息呢?简单来说就是用户实际看到了一个列表中的哪些元素?实际展示给用户的这部分元素用户浏览了多少次? 让我们通过一个简单示例应用来说明: import 'package:flutter/material.dart'; class Card extends StatelessWidget { final String text; Card({ @required this.text, }); @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.only(bottom: 10.0), color: Colors.greenAccent, height: 300.0, child: Center( child: Text( text, style: TextStyle(fontSize: 40.0), ), ), ); } } class HelloFlutter extends StatelessWidget { final items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; @override Widget build(BuildContext context) { return ListView.

利用蓝灯为命令行配置HTTP代理

利用蓝灯为命令行配置HTTP代理

蓝灯是个好工具。可以帮忙我们访问一些由于众所周知的原因在国内无法正常访问的网站(比如 Google、Stack Overflow、Medium 等等)。 每次启动蓝灯,它会自动修改操作系统的网络代理指向它自己。不需要我们手动配置,很是方便。 需要网络代理的地方其实不止是浏览器,很多命令行工具也会访问网络。比如,我们通过 homebrew 安装 dart 的时候,brew 命令会从 Google 的服务器上下载安装文件。然后你就会看到网络连接错误的提示信息。 要解决这类问题,只需要为 Shell 设置两个环境变量 HTTP_PROXY 和 HTTPS_PROXY 即可。我们直接利用蓝灯在本地启动好的代理端口。 我们首先找到蓝灯在本地启动的具体端口号。打开蓝灯,依次选择 Settings -> ADVANCED SETTINGS 即可看到蓝灯在本地选择的端口号。 然后去 Shell 里执行以下两个命令设置环境变量: export HTTP_PROXY=http://127.0.0.1:51350 export HTTPS_PROXY=http://127.0.0.1:51350 就完成配置了。 最后为了避免每次都要敲这么长的命令,我们写一个 Shell 函数: # http proxy util hp() { if [ "$1" = "enable" ] then PORT="51350" if [ -n "$2" ] then PORT="$2" fi export HTTP_PROXY=http://127.0.0.1:$PORT export HTTPS_PROXY=http://127.0.0.1:$PORT else export HTTP_PROXY="" export HTTPS_PROXY="" fi } 把这都函数代码放到 Shell 启动脚本里。然后需要开启代理的时候执行 hp enable 即可,或是指定代理端口号 hp enable 33333。关闭代理执行 hp 即可。

Flutter 初体验

Flutter 初体验

初识 Flutter 还是在上个月的 GMTC 大会上。来自 Google 的工程师现场演示了如何使用 Flutter 构建美观、高性能的移动应用。个人对其中一些特性,比如良好的开发者体验、优秀的跨平台能力很感兴趣。于是决定在会后亲自体验一下。 最近几天尝试使用 Flutter 把京东 APP 中的排行榜频道的首屏布局实现了一下,算是对基于 Flutter 的开发有了一个最简单的了解,特地记录一下,方便其他想了解、尝试 Flutter 框架的小伙伴。 首先看看我实现了一个什么样的界面: 接下来让我们从不同方面说说 Flutter 的开发。 Flutter 开发环境搭建 安装 Flutter 不同平台的安装流程基本一致,这里就以 macOS 为例。首先下载 flutter macOS 版,并解压。 把 Flutter 的 bin 目录添加到系统 PATH 中: export PATH=~/Software/flutter/bin:$PATH 提示:记得把这行代码放到你的 shell 启动脚本中,避免每次都要手动执行。 路径添加完成后就可以执行 flutter 命令啦。 Flutter 提供了一个 flutter doctor 命令协助我们安装 Flutter 的依赖。它会检查本地是否有 iOS 和 Android 的开发环境。如果检测到依赖缺失,它还会给出对应依赖的安装方法。你只需要不断执行该方法,然后安装缺失的依赖,直到全部依赖安装完成即可。 配置代码编辑器 主要是给 IDE 安装相关插件。 VS Code 上只需要安装 flutter 扩展即可。 Android Studio 上需要安装 flutter 和 dart 两个插件。

利用 Jest 为 React 组件编写单元测试

利用 Jest 为 React 组件编写单元测试

图片来自 https://blog.algolia.com 在上一篇文章中我们介绍了什么是单元测试以及单元测试框架 Jest 的基本用法。在本文中我们会具体聊聊如何用 Jest 为 React 组件编写单元测试。 首先我们要明确的一点,那就是 React 组件的单元测试本质是也是单元测试。因此它也符合我们之前介绍过的单元测试的全部特点。唯一不同的地方在于 React 组件的单元测试中我们需要找到合适的方法对执行结果进行断言。换言之,我们要根据 React 的特点来设置代码是否正确执行的判断条件。 那么 React 组件和其它的被测试对象有何不同呢?仔细想过,我们会发现: React 组件的 render 结果是一个组件树,并且整个树最终会被解析成一个纯粹由 HTML 元素构成的树形结构 React 组件可以拥有 state,且 state 的变化会影响 render 结果 React 组件可以拥有生命周期函数,这些生命周期函数会在特定时间点执行 知道了要测试的内容,接下来的问题就是如何执行一个 React 组件并编写断言了。如何执行一个 React 组件呢?看到这个问题估计大多数儿人是蒙的。平时不就是直接 ReactDOM.render 吗?不错,ReactDOM.render 确实可以执行一个 React 组件并将它渲染到页面中,但这种方式不利于编写测试代码。 有没有更简单的方式呢?其实 React 已经帮我们提供好了工具,让我们一起来看看。 React 提供的测试工具 在 React 的官方文档中提到了两个用于测试 React 组件的库。让我们分别介绍。 react-test-renderer 在说 react-test-renderer 之前,让我们先聊聊什么是 renderer。React 最早是被用来开发网页的,所以早期的 React 库中还包含了大量和 DOM 相关的逻辑。后来 React 的设计思想慢慢被迁移到其它场景,最被人们熟知的莫过于 React Native 了。为了灵活性和扩展性,React 的代码被分拆为 React 核心代码与各种 renderer。React 自带了 3 个 renderer,前两个是大家常见的: