林小志

林小志 查看完整档案

杭州编辑  |  填写毕业院校  |  填写所在公司/组织 lab.tianyizone.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

林小志 收藏了文章 · 3月24日

从零搭建webpack4+react+typescript+eslint脚手架(一)

更多内容,请收藏我的博客:http://blog.tianzhen.tech

引言

项目github仓库地址: https://github.com/mecoepcoo/ts-react-boilerplate

这个系列的文章主要讲述如何从一个空目录建立webpack+react+typescript+eslint脚手架,书写此文时各主要工具的版本为:

  • webpack v4
  • react v16.9
  • typescript v3.5
  • babel v7
  • eslint v6.2

本文涉及的内容大致包含:

  • webpack的配置
  • 对静态资源(图片,模板等)的处理
  • 使react项目支持typescript,eslint,prettier等工具
  • 优化webpack配置,减小代码的体积
  • 支持不同的css预处理器(less,sass等)
  • 一套好用的样式方案
  • 使项目支持多个环境切换(开发,测试,预发布,生产等)
  • 使用规则来自动约束代码规范
  • 优化开发体验
  • 一些优化项目性能的建议

阅读这个系列的文章需要具备的条件:

  • 你使用过vuereactangular等任意一种前端框架
  • 你了解过vue-clicreate-react-appangular-cli等任意一种脚手架生成工具
  • 你了解webpack的基本原理或用法
  • 你有生产环境代码的开发经验,了解生产环境中的代码与自娱自乐代码的区别
Why not create-react-app?

笔者使用CRA新建项目时,觉得自定义程度不够。尝试过 react-app-rewired + customize-cra 的方案,还是觉得异常繁琐,而且会消耗额外的维护精力,鲁迅说:青年应当有朝气,敢作为,遂自行搭建一个 boilerplate 。

初始化目录

我们要从一个空目录开始,先新建这个目录,做一些必要的初始化工作:

$ mkdir my-react
$ cd my-react

$ git init
$ npm init

新建如下目录结构:

react-project

  • config 打包配置
  • public 静态文件夹

    • index.html
    • favicon.ico
  • src 源码目录

规范git提交

协作开发时,git提交的内容如果没有规范,就不好管理项目,我们用 husky + commitlint 来规范git提交。

我们先在根目录下建立 .gitignore 文件,忽略不需要要的文件。

然后安装工具:

$ npm i -D husky
$ npm i -D @commitlint/cli
husky 会为 git 增加钩子,在 commit 时执行一系列操作,commitlint 可以检查 git message 是否符合规则。

package.json 中增加配置如下:

"husky": {
  "hooks": {
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
  }
},

在根目录新建文件 .commitlintrc.js,根据具体情况配置:

module.exports = {
  parserPreset: {
    parserOpts: {
      headerPattern: /^(\w*)(?:\((.*)\))?:\s(.*)$/,
      headerCorrespondence: ['type', 'scope', 'subject']
    }
  },
  rules: {
    'type-empty': [2, 'never'],
    'type-case': [2, 'always', 'lower-case'],
    'subject-empty': [2, 'never'],
    'type-enum': [
      2,
      'always',
      ['feat', 'fix', 'update', 'docs', 'style', 'refactor', 'test', 'chore', 'release', 'revert']
    ]
  }
}

这样即可完成配置,具体的使用方法参考 commitlint文档

React hello, world

安装react,写一个react hello, world

现在让主角 React 登场:

$ npm i react react-dom

新建一个 hello, world 结构,这里直接用ts书写:

// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './style.css';

ReactDOM.render(<App />, document.getElementById('root'));
// src/App.tsx
import React from 'react';
import './app.css';

const App: React.FC = () => {
  return (<div>hello, world</div>);
};

export default App;

我们还需要一个html模板:

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <title>react-app</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

完整的结构参考 代码示例

webpack的基本配置

安装webpack相关工具:

$ npm i -D webpack webpack-cli webpack-dev-server webpack-merge

在 config 目录下新建几个文件:config.js, webpack.base.js, webpack.prod.js, webpack.dev.js, build.js

先抽取一些通用的配置:

// config/config.js
const path = require('path');

module.exports = {
  assetsRoot: path.resolve(__dirname, '../dist'),
  assetsDirectory: 'static',
  publicPath: '/',
  indexPath: path.resolve(__dirname, '../public/index.html'),
};
// config/webpack.base.js
const path = require('path');
const webpack = require('webpack');
const config = require('./config');

module.exports = {
  entry: {
    app: './src/index.tsx',
  },
  output: {
    filename: 'js/[name].bundle.js',
    path: config.assetsRoot,
    publicPath: config.publicPath
  },
  module: {
    rules: [
      {
        oneOf: []
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'] // 自动判断后缀名,引入时可以不带后缀
  },
  plugins: []
};

babel和typescript,路径别名

接下来我们需要让webpack支持typescript,并且将代码转换为es5,这样才能在低版本的浏览器上运行。

依然是先安装工具:

$ npm i -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/polyfill
$ npm i core-js@2 # babel的按需引入依赖
$ npm i -D @babel/plugin-proposal-class-properties # 能够在class中自动绑定this的指向
$ npm i -D typescript awesome-typescript-loader # 处理ts,主要就靠它
$ npm i -D html-loader html-webpack-plugin # 顺便把html的支持做好

用了ts,就要有一个tsconfig配置,在根目录新建 tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "typeRoots": [
      "src/types" // 指定 d.ts 文件的位置,根据具体情况修改
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react",
    "baseUrl": ".",
  },
  "include": [
    "src"
  ],
  "exclude": [
    "./node_modules"
  ]
}

来配一下webpack:

// webpack.base.js
const APP_PATH = path.resolve(__dirname, '../src');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module: {
  rules: [
    {
      oneOf: [
        {
          test: /\.(html)$/,
          loader: 'html-loader'
        },
        {
          test: /\.(j|t)sx?$/,
          include: APP_PATH,
          use: [
            {
              loader: 'babel-loader',
              options: {
                presets: [
                  '@babel/preset-react',  // jsx支持
                  ['@babel/preset-env', { useBuiltIns: 'usage', corejs: 2 }] // 按需使用polyfill
                ],
                plugins: [
                  ['@babel/plugin-proposal-class-properties', { 'loose': true }] // class中的箭头函数中的this指向组件
                ],
                cacheDirectory: true // 加快编译速度
              }
            },
            {
              loader: 'awesome-typescript-loader'
            }
          ]
        },
      ]
    }
  ]
},
plugins: [
  new HtmlWebpackPlugin({
    inject: true,
    template: config.indexPath,
    showErrors: true
  }),
],
optimization: {}

为了以后开发时引入路径方便,我们加个路径别名的配置,需要改webpack配置和tsconfig两处:

// webpack.base.js
resolve: {
  extensions: ['.js', '.json', '.jsx', '.ts', '.tsx'],
  alias: {
   '@': path.resolve(__dirname, '../src/') // 以 @ 表示src目录
  }
 },
{
  "compilerOptions": {
    // ...
    "paths": {
      "@/*": ["src/*"]
    }
    // ...
  }
}

至此,我们完成了最最基本的webpack配置,但暂时还不能打包。


查看原文

林小志 收藏了文章 · 2月5日

JS 中使用扩展运算符的10种方法,好家伙,点个赞呗!

作者:Chris Bongers
译者:前端小智
来源:ishadeed
点赞再看,微信搜索大迁世界,B站关注前端小智这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

复制数组

我们可以使用展开操作符复制数组,不过要注意的是这是一个浅拷贝

const arr1 = [1,2,3];
const arr2 = [...arr1];
console.log(arr2);
// [ 1, 2, 3 ]

这样我们就可以复制一个基本的数组,注意,它不适用于多级数组或带有日期或函数的数组。

合并数组

假设我们有两个数组想合并为一个,早期间我们可以使用concat方法,但现在可以使用展开操作符:

const arr1 = [1,2,3];
const arr2 = [4,5,6];
const arr3 = [...arr1, ...arr2];
console.log(arr3);
// [ 1, 2, 3, 4, 5, 6 ]

我们还可以通过不同的排列方式来说明哪个应该先出现。

const arr3 = [...arr2, ...arr1];
console.log(arr3);
[4, 5, 6, 1, 2, 3];

此外,展开运算符号还适用多个数组的合并:

const output = [...arr1, ...arr2, ...arr3, ...arr4];

向数组中添加元素

let arr1 = ['this', 'is', 'an'];
arr1 = [...arr1, 'array'];
console.log(arr1);
// [ 'this', 'is', 'an', 'array' ]

向对象添加属性

假设你有一个user 的对象,但它缺少一个age属性。

const user = {
  firstname: 'Chris',
  lastname: 'Bongers'
};

要向这个user对象添加age,我们可以再次利用展开操作符。

const output = {...user, age: 31};

使用 Math() 函数

假设我们有一个数字数组,我们想要获得这些数字中的最大值、最小值或者总和。

const arr1 = [1, -1, 0, 5, 3];

为了获得最小值,我们可以使用展开操作符和 Math.min 方法。

const arr1 = [1, -1, 0, 5, 3];
const min = Math.min(...arr1);
console.log(min);
// -1

同样,要获得最大值,可以这么做:

const arr1 = [1, -1, 0, 5, 3];
const max = Math.max(...arr1);
console.log(max);
// 5

如大家所见,最大值5,如果我们删除5,它将返回3

你可能会好奇,如果我们不使用展开操作符会发生什么?

const arr1 = [1, -1, 0, 5, 3];
const max = Math.max(arr1);
console.log(max);
// NaN

这会返回NaN,因为JavaScript不知道数组的最大值是什么。

rest 参数

假设我们有一个函数,它有三个参数。

const myFunc(x1, x2, x3) => {
    console.log(x1);
    console.log(x2);
    console.log(x3);
}

我们可以按以下方式调用这个函数:

myFunc(1, 2, 3);

但是,如果我们要传递一个数组会发生什么。

const arr1 = [1, 2, 3];

我们可以使用展开操作符将这个数组扩展到我们的函数中。

myFunc(...arr1);
// 1
// 2
// 3

这里,我们将数组分为三个单独的参数,然后传递给函数。

const myFunc = (x1, x2, x3) => {
  console.log(x1);
  console.log(x2);
  console.log(x3);
};
const arr1 = [1, 2, 3];
myFunc(...arr1);
// 1
// 2
// 3

向函数传递无限参数

假设我们有一个函数,它接受无限个参数,如下所示:

const myFunc = (...args) => {
  console.log(args);
};

如果我们现在调用这个带有多个参数的函数,会看到下面的情况:

myFunc(1, 'a', new Date());

返回:

[
  1,
  'a',
  Date {
    __proto__: Date {}
  }
]

然后,我们就可以动态地循环遍历参数。

将 nodeList 转换为数组

假设我们使用了展开运算符来获取页面上的所有div

const el = [...document.querySelectorAll('div')];
console.log(el);
// (3) [div, div, div]

在这里可以看到我们从dom中获得了3个div

现在,我们可以轻松地遍历这些元素,因为它们是数组了。

const el = [...document.querySelectorAll('div')];
el.forEach(item => {
  console.log(item);
});
// <div></div>
// <div></div>
// <div></div>

解构对象

假设我们有一个对象user:

const user = {
  firstname: 'Chris',
  lastname: 'Bongers',
  age: 31
};

现在,我们可以使用展开运算符将其分解为单个变量。

const {firstname, ...rest} = user;
console.log(firstname);
console.log(rest);
// 'Chris'
// { lastname: 'Bongers', age: 31 }

这里,我们解构了user对象,并将firstname解构为firstname变量,将对象的其余部分解构为rest变量。

展开字符串

展开运算符的最后一个用例是将一个字符串分解成单个单词。

假设我们有以下字符串:

const str = 'Hello';

然后,如果我们对这个字符串使用展开操作符,我们将得到一个字母数组。

const str = 'Hello';
const arr = [...str];
console.log(arr);
// [ 'H', 'e', 'l', 'l', 'o' ]

~ 完,我是小智,我要刷碗去了,我们下期再见!


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://dev.to/dailydevtips1/...

交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

林小志 收藏了文章 · 2月4日

前端进阶(13) - 搭建自己的前端脚手架

搭建自己的前端脚手架

一般新开发一个项目时,我们会首先搭建好一个脚手架,然后才会开始写代码。搭建脚手架可以用 create-react-appvue-cliyeoman 等命令行工具,也可以直接用 html5-boilerplatereact-boilerplatehackathon-starter 等模板,如果这些都不能满足你的个性化需求,可以尝试搭建自己的前端脚手架。

一般来说,脚手架包括目录结构定义、必要的项目配置文件与工具配置文件、工具与命令。

一个基本的脚手架:

|-- /                              项目根目录
    |-- src/                       源代码目录
    |-- package.json               npm 项目文件
    |-- README.md                  项目说明文件
    |-- CHANGELOG.md               版本更新记录
    |-- .gitignore                 git 忽略配置文件
    |-- .editorconfig              编辑器配置文件
    |-- .npmrc                     npm 配置文件
    |-- .npmignore                 npm 忽略配置文件
    |-- .eslintrc                  eslint 配置文件
    |-- .eslintignore              eslint 忽略配置文件
    |-- .stylelintrc               stylelint 配置文件
    |-- .stylelintignore           stylelint 忽略配置文件
    |-- .prettierrc                prettier 配置文件
    |-- .prettierignore            prettier 忽略配置文件
    
    |-- .babelrc                   babel 配置文件
    |-- webpack.config.js          webpack 配置文件
    |-- rollup.config.js           rollup 配置文件
    |-- gulpfile.js                gulp 配置文件

一些扩展的脚手架:

|-- /                              项目根目录
    |-- bin/                       bin 目录
    |-- test/                      测试目录
    |-- docs/                      文档目录
    |-- jest.config.js             jest 配置文件
    |-- .gitattributes             git 属性配置
    |-- .travis.yml                travis 配置文件
    |-- appveyor.yml               appveyor 配置文件

1. package.json: npm 项目文件

{
  "name": "name",                  项目名字
  "version": "0.0.1",              版本
  "main": "index.js",              入口文件
  "bin": "bin/bin.js",             bin 文件
  "description": "description",    描述
  "repository": {                  
    "type": "git",
    "url": "url"
  },
  "keywords": [],
  "homepage": "homepage",          主页
  "readmeFilename": "README.md",
  "devDependencies": {             工具依赖
    "babel-eslint": "^8.2.6",
    "eslint": "^4.19.1",
    "husky": "^0.14.3",
    "lint-staged": "^7.2.0",
    "prettier": "^1.14.0",
    "stylelint": "^9.3.0",
    "eslint-config-airbnb": "^17.0.0",
    "eslint-config-prettier": "^2.9.0",
    "eslint-plugin-babel": "^5.1.0",
    "eslint-plugin-import": "^2.13.0",
    "eslint-plugin-jsx-a11y": "^6.1.0",
    "eslint-plugin-prettier": "^2.6.2",
    "eslint-plugin-react": "^7.10.0",
    "stylelint-config-prettier": "^3.3.0",
    "stylelint-config-standard": "^18.2.0"
  },
  "scripts": {                     可以添加更多命令
    "precommit": "npm run lint-staged",
    "prettier": "prettier --write \"./**/*.{js,jsx,css,less,md,json}\"",
    "eslint": "eslint .",
    "eslint:fix": "eslint . --fix",
    "stylelint": "stylelint \"./**/*.{css,less}\"",
    "stylelint:fix": "stylelint \"./**/*.{css,less}\" --fix",
    "lint-staged": "lint-staged"
  },
  "lint-staged": {                 对提交的代码进行检查与矫正
    "**/*.{js,jsx}": [
      "eslint --fix",
      "prettier --write",
      "git add"
    ],
    "**/*.{css,less}": [
      "stylelint --fix",
      "prettier --write",
      "git add"
    ],
    "**/*.{md,json}": [
      "prettier --write",
      "git add"
    ]
  },
  "engines": {                     运行时对 node 版本的要求
    "node": ">=8.0.0"
  },
  "dependencies": {}               开发依赖
}

1.1 main: 项目入口文件

如果你将当前的项目发布为一个 npm 包,而其他的包在引用你的包时,构建工具就会去找 main 字段定义的入口文件,详细参考 package.json#main

还有其他的特殊的入口文件,参考 package.json 非官方字段集合

1.2 bin: 配置命令行可执行文件

如果你需要将当前的项目安装成一个全局的命令,那么就需要指定这个字段。

详细信息参考 package.json#bin

1.3 scripts: 配置项目命令

这里定义的命令可以用 npm run 来调用。比如上面定义的几个命令:

npm run prettier
npm run eslint
npm run eslint:fix
npm run stylelint
npm run stylelint:fix

一般来说,还可能定义如下的一些命令:

{
  "test": "",                      测试  
  "build": "",                     构建
  "dev": "",                       开发
  ...
}

2. README.md: 项目说明文件

项目说明的入口文件,包括文档。一般 git 项目 web 端首页显示的就是这个文件的内容,包括 githubbitbucketgitlab

文件格式是 markdown,具体介绍与语法可以参考 https://www.markdownguide.org/

3. CHANGELOG.md: 版本更新记录

一般项目都会有这个文件,用于记录版本更新及相应的功能变化,比如 react 的 CHANGELOG

文件格式也是 markdown

4. .gitignore: git 忽略配置文件

用于指定哪些文件或目录不需要进行 git 版本控制。

比如:

.DS_STORE
node_modules
build/
*.log*
.idea
.vscode

详细信息参考 https://git-scm.com/docs/gitignore

5. .editorconfig: 编辑器配置文件

用于指定编辑器特定的配置。比如,不同的编辑器对 tab 的定义不一样,可能是 2 个空格,也可能是 4 个或者 8 个,所以就需要用这个文件来统一配置编辑器。

比如:

# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

详细信息参考 http://editorconfig.org

6. .npmrc: npm 配置文件

比如:

package-lock=false

详细信息参考 https://www.npmjs.com.cn/files/npmrc/

7. .npmignore: npm 忽略配置文件

详细信息参考 keeping-files-out-of-your-package

8. .eslintrc, .eslintignore: eslint 相关配置文件

用于 js, jsx 代码检查与矫正,让你编写的代码符合特定的规范与风格。

详细信息参考 https://eslint.org/

9. .stylelintrc, .stylelintignore: stylelint 相关配置文件

用于 css, less, scss 代码检查与矫正,让你编写的代码符合特定的规范与风格。

详细信息参考 https://stylelint.io/

10. .prettierrc, .prettierignore: prettier 相关配置文件

优化 js, jsx, css, less, scss, md, json 等文件的格式。

详细信息参考 https://prettier.io/

11. .babelrc: babel 配置文件

es6 -> es5 转码。

详细信息参考 https://babeljs.io/

12. webpack.config.js: webpack 配置文件

前端打包工具。

详细信息参考 https://webpack.js.org/

13. rollup.config.js: rollup 配置文件

另一个前端打包工具。

详细信息参考 https://rollupjs.org/

14. gulpfile.js: gulp 配置文件

前端文件流操作构建工具。

详细信息参考 https://www.gulpjs.com/

15. jest.config.js: jest 配置文件

前端测试组件。

详细信息参考 https://jestjs.io/

16. .gitattributes: git 属性配置

详细信息参考 https://git-scm.com/docs/gitattributes

17. .travis.yml: travis 配置文件

一个持续集成服务。

详细信息参考 https://www.travis-ci.org/

18. appveyor.yml: appveyor 配置文件

又一个持续集成服务。

详细信息参考 https://www.appveyor.com/

19. 后续

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

查看原文

林小志 赞了文章 · 2月4日

前端进阶(13) - 搭建自己的前端脚手架

搭建自己的前端脚手架

一般新开发一个项目时,我们会首先搭建好一个脚手架,然后才会开始写代码。搭建脚手架可以用 create-react-appvue-cliyeoman 等命令行工具,也可以直接用 html5-boilerplatereact-boilerplatehackathon-starter 等模板,如果这些都不能满足你的个性化需求,可以尝试搭建自己的前端脚手架。

一般来说,脚手架包括目录结构定义、必要的项目配置文件与工具配置文件、工具与命令。

一个基本的脚手架:

|-- /                              项目根目录
    |-- src/                       源代码目录
    |-- package.json               npm 项目文件
    |-- README.md                  项目说明文件
    |-- CHANGELOG.md               版本更新记录
    |-- .gitignore                 git 忽略配置文件
    |-- .editorconfig              编辑器配置文件
    |-- .npmrc                     npm 配置文件
    |-- .npmignore                 npm 忽略配置文件
    |-- .eslintrc                  eslint 配置文件
    |-- .eslintignore              eslint 忽略配置文件
    |-- .stylelintrc               stylelint 配置文件
    |-- .stylelintignore           stylelint 忽略配置文件
    |-- .prettierrc                prettier 配置文件
    |-- .prettierignore            prettier 忽略配置文件
    
    |-- .babelrc                   babel 配置文件
    |-- webpack.config.js          webpack 配置文件
    |-- rollup.config.js           rollup 配置文件
    |-- gulpfile.js                gulp 配置文件

一些扩展的脚手架:

|-- /                              项目根目录
    |-- bin/                       bin 目录
    |-- test/                      测试目录
    |-- docs/                      文档目录
    |-- jest.config.js             jest 配置文件
    |-- .gitattributes             git 属性配置
    |-- .travis.yml                travis 配置文件
    |-- appveyor.yml               appveyor 配置文件

1. package.json: npm 项目文件

{
  "name": "name",                  项目名字
  "version": "0.0.1",              版本
  "main": "index.js",              入口文件
  "bin": "bin/bin.js",             bin 文件
  "description": "description",    描述
  "repository": {                  
    "type": "git",
    "url": "url"
  },
  "keywords": [],
  "homepage": "homepage",          主页
  "readmeFilename": "README.md",
  "devDependencies": {             工具依赖
    "babel-eslint": "^8.2.6",
    "eslint": "^4.19.1",
    "husky": "^0.14.3",
    "lint-staged": "^7.2.0",
    "prettier": "^1.14.0",
    "stylelint": "^9.3.0",
    "eslint-config-airbnb": "^17.0.0",
    "eslint-config-prettier": "^2.9.0",
    "eslint-plugin-babel": "^5.1.0",
    "eslint-plugin-import": "^2.13.0",
    "eslint-plugin-jsx-a11y": "^6.1.0",
    "eslint-plugin-prettier": "^2.6.2",
    "eslint-plugin-react": "^7.10.0",
    "stylelint-config-prettier": "^3.3.0",
    "stylelint-config-standard": "^18.2.0"
  },
  "scripts": {                     可以添加更多命令
    "precommit": "npm run lint-staged",
    "prettier": "prettier --write \"./**/*.{js,jsx,css,less,md,json}\"",
    "eslint": "eslint .",
    "eslint:fix": "eslint . --fix",
    "stylelint": "stylelint \"./**/*.{css,less}\"",
    "stylelint:fix": "stylelint \"./**/*.{css,less}\" --fix",
    "lint-staged": "lint-staged"
  },
  "lint-staged": {                 对提交的代码进行检查与矫正
    "**/*.{js,jsx}": [
      "eslint --fix",
      "prettier --write",
      "git add"
    ],
    "**/*.{css,less}": [
      "stylelint --fix",
      "prettier --write",
      "git add"
    ],
    "**/*.{md,json}": [
      "prettier --write",
      "git add"
    ]
  },
  "engines": {                     运行时对 node 版本的要求
    "node": ">=8.0.0"
  },
  "dependencies": {}               开发依赖
}

1.1 main: 项目入口文件

如果你将当前的项目发布为一个 npm 包,而其他的包在引用你的包时,构建工具就会去找 main 字段定义的入口文件,详细参考 package.json#main

还有其他的特殊的入口文件,参考 package.json 非官方字段集合

1.2 bin: 配置命令行可执行文件

如果你需要将当前的项目安装成一个全局的命令,那么就需要指定这个字段。

详细信息参考 package.json#bin

1.3 scripts: 配置项目命令

这里定义的命令可以用 npm run 来调用。比如上面定义的几个命令:

npm run prettier
npm run eslint
npm run eslint:fix
npm run stylelint
npm run stylelint:fix

一般来说,还可能定义如下的一些命令:

{
  "test": "",                      测试  
  "build": "",                     构建
  "dev": "",                       开发
  ...
}

2. README.md: 项目说明文件

项目说明的入口文件,包括文档。一般 git 项目 web 端首页显示的就是这个文件的内容,包括 githubbitbucketgitlab

文件格式是 markdown,具体介绍与语法可以参考 https://www.markdownguide.org/

3. CHANGELOG.md: 版本更新记录

一般项目都会有这个文件,用于记录版本更新及相应的功能变化,比如 react 的 CHANGELOG

文件格式也是 markdown

4. .gitignore: git 忽略配置文件

用于指定哪些文件或目录不需要进行 git 版本控制。

比如:

.DS_STORE
node_modules
build/
*.log*
.idea
.vscode

详细信息参考 https://git-scm.com/docs/gitignore

5. .editorconfig: 编辑器配置文件

用于指定编辑器特定的配置。比如,不同的编辑器对 tab 的定义不一样,可能是 2 个空格,也可能是 4 个或者 8 个,所以就需要用这个文件来统一配置编辑器。

比如:

# http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

详细信息参考 http://editorconfig.org

6. .npmrc: npm 配置文件

比如:

package-lock=false

详细信息参考 https://www.npmjs.com.cn/files/npmrc/

7. .npmignore: npm 忽略配置文件

详细信息参考 keeping-files-out-of-your-package

8. .eslintrc, .eslintignore: eslint 相关配置文件

用于 js, jsx 代码检查与矫正,让你编写的代码符合特定的规范与风格。

详细信息参考 https://eslint.org/

9. .stylelintrc, .stylelintignore: stylelint 相关配置文件

用于 css, less, scss 代码检查与矫正,让你编写的代码符合特定的规范与风格。

详细信息参考 https://stylelint.io/

10. .prettierrc, .prettierignore: prettier 相关配置文件

优化 js, jsx, css, less, scss, md, json 等文件的格式。

详细信息参考 https://prettier.io/

11. .babelrc: babel 配置文件

es6 -> es5 转码。

详细信息参考 https://babeljs.io/

12. webpack.config.js: webpack 配置文件

前端打包工具。

详细信息参考 https://webpack.js.org/

13. rollup.config.js: rollup 配置文件

另一个前端打包工具。

详细信息参考 https://rollupjs.org/

14. gulpfile.js: gulp 配置文件

前端文件流操作构建工具。

详细信息参考 https://www.gulpjs.com/

15. jest.config.js: jest 配置文件

前端测试组件。

详细信息参考 https://jestjs.io/

16. .gitattributes: git 属性配置

详细信息参考 https://git-scm.com/docs/gitattributes

17. .travis.yml: travis 配置文件

一个持续集成服务。

详细信息参考 https://www.travis-ci.org/

18. appveyor.yml: appveyor 配置文件

又一个持续集成服务。

详细信息参考 https://www.appveyor.com/

19. 后续

更多博客,查看 https://github.com/senntyou/blogs

作者:深予之 (@senntyou)

版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

查看原文

赞 274 收藏 221 评论 10

林小志 发布了文章 · 1月27日

终于我又可以使用 Quick Look 来快速预览 md 文件了……

在 macOS 系统中,一般都会喜欢按空格键去快速预览一个文件,这个操作能够很方便地查看文件内容。不过默认的能够快速打开的文件格式并不多,十分有限,为了满足自己的需求,当初各种搜索后,能够通过 Quick Look 的方式查看:

  • Markdown
  • acss
  • axml
  • json
  • ……

等一系列常用文件的查看方式。

image.png

image.png

image.png

上次在办公电脑上,更新系统到 big sur 后,也不知道自己做了什么操作,无法通过 Quick Look 查看了。突然间感觉自己好像丢失了什么。

各种排查,通过各种方式不停地尝试,最终还是没有结果。这两行命令反反复复使用,就是没效果。

qlmanage -r && qlmanage -r cache
killall Finder

在一次无意间发现系统设置的扩展面板中“Quick Look”里有一个叫 apparency 的应用,没见过这个东西。怀疑是因为这个导致的。

image.png

找到了这个 app 网站,一堆英文,看着真是头疼。回想一下,好像也没从 App Store 中下载。难道是某次通过 brew 安装的时候带上了?

image.png

好吧,看来的确是某次想要安装更多 Quick Look 插件的时候把这个带上了。

brew list

证实了一下,的确有这个 app 的存在。好吧,加上双引号问一下谷歌,看看这行命令可能出现的站点吧。

image.png

这结果,没错了,肯定是自己手欠,偷懒,没去筛选,直接 copy 了 github 上这条 brew 的安装命令。把这玩意给带上了,但自己却又不知道这个是什么东西。

image.png

不用多想,既然不知道是什么东西,又是自己安装上去的,直接干掉就是了。

brew uninstall apparency

卸载了,这个时候再把之前使用了很多次的那两行命令再运行一次。

qlmanage -r && qlmanage -r cache
killall Finder

嗯。开心,finder 中的 MarkDown 文件图标变了,不再是
image.png,而是image.png,开心的按下空格键,不用再双击打开文件了,而且也可以通过 alfred 查找文件后按一下 shift 键来快速预览了。

image.png

喜大普奔……以后装东西不能太随便,尤其是直接 copy 的方式……好奇害死猫啊。🤣

Quick Look 这个真的是一个好东西,添加扩展名,添加代码高亮,在不编译文件的情况下快速预览,心情十分愉悦。下面这四个小小推荐一下,具体介绍可以上 github 上找。

查看原文

赞 0 收藏 0 评论 0

林小志 关注了用户 · 1月27日

chokcoco @chokcoco

坎坷切图仔

关注 281

林小志 赞了文章 · 1月27日

CSS奇思妙想 -- 使用 CSS 创造艺术

本文属于 CSS 绘图技巧其中一篇。之前有过一篇:在 CSS 中使用三角函数绘制曲线图形及展示动画

想写一篇关于 CSS 创造艺术的文章已久,本文主要介绍如何借助 CSS-doodle ,利用 CSS 快速创造美妙的 CSS 图形。

中心布局

本文的所有技巧都会围绕这个布局展开,属于一类技巧。

首先,我们需要这样一种中心布局。简单的 HTML 结构如下:

<div class="g-container">
    <div class="g-box"></div>
    <div class="g-box"></div>
    <div class="g-box"></div>
    <div class="g-box"></div>
    <div class="g-box"></div>
</div>
.g-container {
    position: relative;
    width: 300px;
    height: 300px;
}
.g-box {
    position: absolute;
    top:50%;
    left: 50%;
    margin-left: -150px;
    margin-top: -150px;
    width: 100%;
    height: 100%;
    border: 1px solid #000;
}

利用绝对定位和 margin,将元素全部水平垂直居中叠在一起(因为后面会用到 transform,所以选取了这种水平垂直居中的方式),结果如下:

好吧,看着平平无奇,但是基于这种布局,我们可以衍生出非常多有意思的图案。

改变元素大小

最简单的,就是我们可以改变元素的大小。

CSS 代码写着太累,所以我们简单的借助 pug HTML 模板引擎和 SASS。

div.g-container
    -for(var i=0; i<10; i++)
        div.g-box  
$count: 10;
@for $i from 1 to $count + 1 {
    .g-box:nth-child(#{$i}) {
        --width: #{$i * 30}px;
        width: var(--width);
        height: var(--width);
        margin-left: calc(var(--width) / -2);
        margin-top: calc(var(--width) / -2);
    }
}

容器下包含 10 个子元素,每个子元素大小逐渐递增,很容易得到如下结果:

改变元素颜色

接着,我们继续改变元素的颜色,让它呈现渐变颜色逐级递进,可以是边框颜色

@for $i from 1 to $count + 1 {
    .g-box:nth-child(#{$i}) {
         ...
         border-color: hsla(
            calc(#{$i * 25}),
            50%,
            65%,
            1
        );
    }
}

得到这样的效果:

也可以是改变背景 background 的颜色:

@for $i from 1 to $count + 1{
    .g-box:nth-child(#{$i}) {
        ...
        background: hsla(
            calc(#{$i * 25}),
            50%,
            65%,
            1
        );
        z-index: #{$count - $i};
    }
}

改变元素角度

好,接下来,就可以开始变换角度了,我们利用 transform,将元素旋转不同的角度:

@for $i from 1 to $count + 1{
    .g-box:nth-child(#{$i}) {
        ....
        transform: rotate(#{$i * 7}deg);
    }
}

效果如下:

CodePen Demo -- CSS Pattern

OK,到这里,基本的一些概念就引入的差不多了,总而言之,利用多元素居中布局,改变元素的大小、颜色、透明度、角度、阴影、滤镜、混合模式等等等等,只要你能想到的,都可以。

接下来,我们再引入本文的另外一个主角 -- CSS-doodle 。

CSS-doodle 是一个基于 Web-Component 的库。允许我们快速的创建基于 CSS Grid 布局的页面,以实现各种 CSS 效果(或许可以称之为 CSS 艺术)。

其最终效果的代码,本质是都还是 CSS。具体的一些概念可以点击主页看看,一看就懂。

使用 CSS-doole 实现多元素水平垂直居中布局

我们将上面的布局利用 CSS-doodle 再实现一次,要实现 50个元素的居中对齐,只需要如下简单的声明即可:

<css-doodle>
    :doodle {
        @grid: 1x50 / 100vmin;
    }
    @place-cell: center;
</css-doodle>

上面的意思大概是,在 100vmin x 100vmin 大小的容器下,声明一个 1x50 的 grid 网格布局,利用 @place-cell: center 将它们全部水平垂直居中,也就是会叠加在一起。

这样可能看不出效果,我们再给每个元素设置不同的大小,给它们都加上一个简单的 border

<css-doodle>
    :doodle {
        @grid: 1x50 / 100vmin;
    }
    @place-cell: center;
    @size: calc(100% - @calc(@index() - 1) * 2%);
    border: 1px solid #000;
</css-doodle>
  • @size: calc(100% - @calc(@index() - 1) * 2%) 表示每个子元素宽高的大小(也可以单独设置高宽),@index 是个变量,表示当前元素的序号,从 1 - 50,表示没个元素分别为容器的 2% 高宽、4% 高宽一直到 100% 高宽。
  • border: 1px solid #000 就是正常的 CSS 代码,里面没有变量,作用于每一个元素

效果如下:

Oh No,眼睛开始花了。这样,我们就快速的实现了前面铺垫时候利用 HTML 代码和繁琐的 CSS 生成的图形效果。

CSS 艺术

接下来,就开始美妙的 CSS 艺术。

改变元素的旋转角度及边框颜色

我们利用上述代码继续往下,为了更好的展示效果,首先整体容器的底色改为黑色,接着改变元素的旋转角度。每个元素旋转 30deg x @index

代码非常的短,大概是这样:

<css-doodle>
    :doodle {
        @grid: 1x100 / 100vmin;
    }
    @place-cell: center;
    @size: calc(100% - @calc(@index() - 1) * 1%);
    transform: rotate(calc(@index() * 30deg));
    border: 1px solid #fff;
</css-doodle>

不太好看,接着,我们试着给每个元素,渐进的设置不同的 border 颜色,并且透明度 opacity 逐渐降低,,这里我们会用到 hsla 颜色表示法:

<css-doodle>
    :doodle {
        @grid: 1x100 / 100vmin;
    }
    @place-cell: center;
    @size: calc(100% - @calc(@index() - 1) * 1%);
    transform: rotate(calc(@index() * 30deg));
    border: 1px solid hsla(
        calc(calc(100 - @index()) * 2.55), 
        calc(@index() * 1%), 
        50%,
        calc(@index() / 100)
    );
</css-doodle>

再看看效果:

所有贴图都存在一定色差,可以点进 Demo 里看看~

Wow,第一幅看上去还不错的作品出现了。

当然,每一个不同的角度,都能产生不一样的效果,通过 CSS-doodle,可以快速生成不同随机值,随机产生不同的效果。我们稍微改变一下上述代码,将 transform 那一行改一下,引入了一个随机值:

<css-doodle>
    :doodle {
        --rotate: @r(0, 360)deg;
    }
    transform: rotate(calc(@index() * var(--rotate)));
</css-doodle>
  • 利用 @r(0, 360)deg,能随机生成一个介于 0 到 360 之间的随机数,后面可以直接跟上单位,也就变成了一个随机角度值
  • transform: rotate(calc(@index() * var(--rotate))),利用 calc 规则引入随机生成的 CSS 变量,当然,再不刷新页面的前提下,每一次这个值都是固定的

这样,我们每次刷新页面,就可以得到不同的效果了(当然,CSS-doodle 做了优化,添加短短几行代码就可以通过点击页面刷新效果),改造后的效果,我们每次点击都可以得到一个新的效果:

CodePen Demo -- CSS Doodle - CSS Magic Pattern

强烈建议你点进 Demo,自己点点鼠标感受一下 :)

background 颜色奇偶不同

好,我们再换个思路,这次不改变 border 的颜色,而是通过选择器控制奇数序号的元素和偶数序号的元素,分别给予它们不一样的背景色:

<css-doodle>
    :doodle {
        @grid: 1x100 / 100vmin;
    }
    @place-cell: center;
    @size: calc(100% - @calc(@index() - 1) * 1%);
    transform: rotate(calc(@index() * 60deg));

    background: rgba(0, 0, 0, calc((@index * 0.01)));
    @even {
        background: rgba(255, 255, 255, calc((@index * 0.01)));
    }
</css-doodle>

利用 @even {} 可以快速选中偶数序号的元素,然后给他赋予白色底色,而奇数元素则赋予黑色底色,看看效果:

还是一样的思路,我们可以将随机值赋予 transform 的旋转角度,利用黑白叠加,看看再不同角度下,都会有什么效果:

CodePen Demo -- CSS Doodle - CSS Magic Pattern

当然,在随机的过程中,你也可以选取自己喜欢的,将它们保留下来。

CSS-doodle 支持多种方式的引入,在一页中展示多个图形,不在话下,像是这样:

CodePen Demo -- CSS-doodle Pure CSS Pattern

规律总结

小小总结一下,想要生成不同的图案,其实只需要找到能够生成不同线条,或者造型图案图形,将它们按照不同的大小,不同的旋转角度,不同颜色及透明度叠加在一起即可

这样的话,一些可能的 idea:

  • 只利用单向的 border 会是怎么样的呢?
  • 出现的 border 都是 solid,如果换成是虚线 dashed 呢?或许可以再加上 border-radius
  • text-decoration 也支持一些各式的下划线,我们也可以利用它们试试

OK,将上述想法付诸实践,我们就可以得到利用各式线条绘制出来的各式图形。它们可能是这样:

当然,每次的效果都可以做到随机,只要我们合理利用好随机的参数即可,你可以戳进下面的 Demo 感受一下:

CodePen Demo -- CSS-doodle Pure CSS Pattern

Clip-pathdrop-shadow

嘿,说到创造不同的线条与图案,就不得不提 CSS 里另外两个有意思是属性。Clip-pathfitler: drop-shadow()

嗯哼?什么意思呢。我们来个简单的 Demo,利用 Clip-path ,我们可以裁剪出不同的元素造型。譬如实现一个简单的多边形:

div {
    width: 300px;
    height: 300px;
    clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 0% 60%, 10% 20%);
    background: #333;
}

效果如下:

那么利用这个思路,我们就可以尝试利用 clip-path 裁剪出各种不同的造型进行叠加。

CSS-doodle Shapes 中,内置了非常多的 clip-path 图形供我们选择:

我们随机选取一个:

套用上述的规则,尝试着实现一个图形:

<css-doodle>
    :doodle {
        @grid: 1x100 / 100vmin;
    }
    @place-cell: center;
    @size: calc(100% - @calc(@index() - 1) * 1%);
    background: hsla(
        calc(calc(100 - @index()) * 2.55), 
        calc(@index() * 1%), 
        65%,
        calc(@index() / 100)
    );
    clip-path: @shape(
        fill-rule: evenodd;
        split: 200;
        scale: .45;
        x: cos(2t) + cos(π - 5t);
        y: sin(2t) + sin(π - 5t);
    );
</css-doodle>

这次没有旋转不同的角度,只是给每一层赋予不同的背景底色,能够得到这样的效果:

CodePen Demo -- CSS Doodle - CSS Magic Pattern

Clip-pathdrop-shadow 创造不同线条

OK,上述是利用 Clip-path 创造了不同的图案,那不同的线条怎么得来呢?

别急。这就需要请出我们另外一个属性 drop-shadow,利用 drop-shadow,可以给 Clip-path 裁剪出来的图形创造不同的阴影,当然有一些结构上的限制,大概的伪代码如下:

div {
    position: relative;
    width: 300px;
    height: 300px;
    filter: drop-shadow(0px 0px 1px black);

    &::after {
        content: "";
        position: absolute;
        width: 100%;
        height: 100%;
        left: 0;
        right: 0;
        background: #fff;
        clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 0% 60%, 10% 20%);
    }
}

我们需要将 filter: drop-shadow(0px 0px 2px black) 作用在利用了 clip-path 的元素的父元素之上,并且,利用了 clip-path: 的元素必须带有 background,才能给裁剪元素附上阴影效果。

上述的代码如下:

OK,完美,这样一来,我们就极大极大的丰富了我们的线条库,再运用会上述的线条规则,一大波新的图案应运而生。

CodePen Demo -- CSS-doodle Pure CSS Pattern - clip-path - drop-shadow

OK,限于篇幅,就不一一展开了,感兴趣可以点进上述 Demo Fork 一份自己尝试。还有非常多有意思的图案等待挖掘生成。

最后,再来欣赏一下 CSS-doodle 作者,袁川袁老师利用上述技巧的作品:

CodePen Demo -- css doodle art

最后

本文到此结束,希望对你有帮助 :),想 Get 到最有意思的 CSS 资讯,千万不要错过我的公众号 -- iCSS前端趣闻 😄

更多精彩 CSS 技术文章汇总在我的 Github -- iCSS ,持续更新,欢迎点个 star 订阅收藏。

如果还有什么疑问或者建议,可以多多交流,原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知。

查看原文

赞 13 收藏 7 评论 1

林小志 收藏了文章 · 1月12日

JavaScript Promise 完整指南

作者:Adrian Mejia
译者:前端小智
来源:adrianmjia
点赞再看,微信搜索大迁世界,B站关注【前端小智】这个没有大厂背景,但有着一股向上积极心态人。本文 GitHubhttps://github.com/qq44924588... 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

github 地址:https://github.com/qq44924588...

这篇文章算是 JavaScript Promises 比较全面的教程,该文介绍了必要的方法,例如 thencatchfinally。 此外,还包括处理更复杂的情况,例如与Promise.all并行执行Promise,通过Promise.race 来处理请求超时的情况,Promise 链以及一些最佳实践和常见的陷阱。

1.JavaScript Promises

Promise 是一个允许我们处理异步操作的对象,它是 es5 早期回调的替代方法。

与回调相比,Promise 具有许多优点,例如:

  • 让异步代码更易于阅读。
  • 提供组合错误处理。

* 更好的流程控制,可以让异步并行或串行执行。

回调更容易形成深度嵌套的结构(也称为回调地狱)。 如下所示:

a(() => {
  b(() => {
    c(() => {
      d(() => {
        // and so on ...
      });
    });
  });
});

如果将这些函数转换为 Promise,则可以将它们链接起来以生成更可维护的代码。 像这样:

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error);

在上面的示例中,Promise 对象公开了.then.catch方法,我们稍后将探讨这些方法。

1.1 如何将现有的回调 API 转换为 Promise?

我们可以使用 Promise 构造函数将回调转换为 Promise。

Promise 构造函数接受一个回调,带有两个参数resolvereject

  • Resolve:是在异步操作完成时应调用的回调。
  • Reject:是发生错误时要调用的回调函数。

构造函数立即返回一个对象,即 Promise 实例。 当在 promise 实例中使用.then方法时,可以在Promise “完成” 时得到通知。 让我们来看一个例子。

Promise 仅仅只是回调?

并不是。承诺不仅仅是回调,但它们确实对.then.catch方法使用了异步回调。 Promise 是回调之上的抽象,我们可以链接多个异步操作并更优雅地处理错误。来看看它的实际效果。

Promise 反面模式(Promises 地狱)

a(() => {
  b(() => {
    c(() => {
      d(() => {
        // and so on ...
      });
    });
  });
});

不要将上面的回调转成下面的 Promise 形式:

a().then(() => {
  return b().then(() => {
    return c().then(() => {
      return d().then(() =>{
        // ⚠️ Please never ever do to this! ⚠️
      });
    });
  });
});

上面的转成,也形成了 Promise 地狱,千万不要这么转。相反,下面这样做会好点:

a()
  .then(b)
  .then(c)
  .then(d)

超时

你认为以下程序的输出的是什么?

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('time is up ⏰');
  }, 1e3);

  setTimeout(() => {
    reject('Oops 🔥');
  }, 2e3);
});

promise
  .then(console.log)
  .catch(console.error);

是输出:

time is up ⏰
Oops! 🔥

还是输出:

time is up ⏰

是后者,因为当一个Promise resolved 后,它就不能再被rejected

一旦你调用一种方法(resolvereject),另一种方法就会失效,因为 promise 处于稳定状态。 让我们探索一个 promise 的所有不同状态。

1.2 Promise 状态

Promise 可以分为四个状态:

  • ⏳ Pending:初始状态,异步操作仍在进行中。
  • ✅ Fulfilled:操作成功,它调用.then回调,例如.then(onSuccess)
  • ⛔️ Rejected: 操作失败,它调用.catch.then的第二个参数(如果有)。 例如.catch(onError).then(..., onError)
  • 😵 Settled:这是 promise 的最终状态。promise 已经死亡了,没有别的办法可以解决或拒绝了。 .finally方法被调用。

clipboard.png

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

1.3 Promise 实例方法

Promise API 公开了三个主要方法:thencatchfinally。 我们逐一配合事例探讨一下。

Promise then

then方法可以让异步操作成功或失败时得到通知。 它包含两个参数,一个用于成功执行,另一个则在发生错误时使用。

promise.then(onSuccess, onError);

你还可以使用catch来处理错误:

promise.then(onSuccess).catch(onError);

Promise 链

then 返回一个新的 Promise ,这样就可以将多个Promise 链接在一起。就像下面的例子一样:

Promise.resolve()
  .then(() => console.log('then#1'))
  .then(() => console.log('then#2'))
  .then(() => console.log('then#3'));

Promise.resolve立即将Promise 视为成功。 因此,以下所有内容都将被调用。 输出将是

then#1
then#2
then#3

Promise catch

Promise .catch方法将函数作为参数处理错误。 如果没有出错,则永远不会调用catch方法。

假设我们有以下承诺:1秒后解析或拒绝并打印出它们的字母。

const a = () => new Promise((resolve) => setTimeout(() => { console.log('a'), resolve() }, 1e3));
const b = () => new Promise((resolve) => setTimeout(() => { console.log('b'), resolve() }, 1e3));
const c = () => new Promise((resolve, reject) => setTimeout(() => { console.log('c'), reject('Oops!') }, 1e3));
const d = () => new Promise((resolve) => setTimeout(() => { console.log('d'), resolve() }, 1e3));

请注意,c使用reject('Oops!')模拟了拒绝。

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)

输出如下:

图片描述

在这种情况下,可以看到abc上的错误消息。

我们可以使用then函数的第二个参数来处理错误。 但是,请注意,catch将不再执行。

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d, () => console.log('c errored out but no big deal'))
  .catch(console.error)

图片描述

由于我们正在处理 .then(..., onError)部分的错误,因此未调用catchd不会被调用。 如果要忽略错误并继续执行Promise链,可以在c上添加一个catch。 像这样:

Promise.resolve()
  .then(a)
  .then(b)
  .then(() => c().catch(() => console.log('error ignored')))
  .then(d)
  .catch(console.error)

图片描述

当然,这种过早的捕获错误是不太好的,因为容易在调试过程中忽略一些潜在的问题。

Promise finally

finally方法只在 Promise 状态是 settled 时才会调用。

如果你希望一段代码即使出现错误始终都需要执行,那么可以在.catch之后使用.then

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)
  .then(() => console.log('always called'));

或者可以使用.finally关键字:

Promise.resolve()
  .then(a)
  .then(b)
  .then(c)
  .then(d)
  .catch(console.error)
  .finally(() => console.log('always called'));

1.4 Promise 类方法

我们可以直接使用 Promise 对象中四种静态方法。

  • Promise.all
  • Promise.reject
  • Promise.resolve
  • Promise.race

Promise.resolve 和 Promise.reject

这两个是帮助函数,可以让 Promise 立即解决或拒绝。可以传递一个参数,作为下次 .then 的接收:

Promise.resolve('Yay!!!')
  .then(console.log)
  .catch(console.error)

上面会输出 Yay!!!

Promise.reject('Oops 🔥')
  .then(console.log)
  .catch(console.error)

使用 Promise.all 并行执行多个 Promise

通常,Promise 是一个接一个地依次执行的,但是你也可以并行使用它们。

假设是从两个不同的api中轮询数据。如果它们不相关,我们可以使用Promise.all()同时触发这两个请求。

在此示例中,主要功能是将美元转换为欧元,我们有两个独立的 API 调用。 一种用于BTC/USD,另一种用于获得EUR/USD。 如你所料,两个 API 调用都可以并行调用。 但是,我们需要一种方法来知道何时同时完成最终价格的计算。 我们可以使用Promise.all,它通常在启动多个异步任务并发运行并为其结果创建承诺之后使用,以便人们可以等待所有任务完成。

const axios = require('axios');

const bitcoinPromise = axios.get('https://api.coinpaprika.com/v1/coins/btc-bitcoin/markets');
const dollarPromise = axios.get('https://api.exchangeratesapi.io/latest?base=USD');
const currency = 'EUR';

// Get the price of bitcoins on
Promise.all([bitcoinPromise, dollarPromise])
  .then(([bitcoinMarkets, dollarExchanges]) => {
    const byCoinbaseBtc = d => d.exchange_id === 'coinbase-pro' && d.pair === 'BTC/USD';
    const coinbaseBtc = bitcoinMarkets.data.find(byCoinbaseBtc)
    const coinbaseBtcInUsd = coinbaseBtc.quotes.USD.price;
    const rate = dollarExchanges.data.rates[currency];
    return rate * coinbaseBtcInUsd;
  })
  .then(price => console.log(`The Bitcoin in ${currency} is ${price.toLocaleString()}`))
  .catch(console.log);

如你所见,Promise.all接受了一系列的 Promises。 当两个请求的请求都完成后,我们就可以计算价格了。

我们再举一个例子:

const a = () => new Promise((resolve) => setTimeout(() => resolve('a'), 2000));
const b = () => new Promise((resolve) => setTimeout(() => resolve('b'), 1000));
const c = () => new Promise((resolve) => setTimeout(() => resolve('c'), 1000));
const d = () => new Promise((resolve) => setTimeout(() => resolve('d'), 1000));

console.time('promise.all');
Promise.all([a(), b(), c(), d()])
  .then(results => console.log(`Done! ${results}`))
  .catch(console.error)
  .finally(() => console.timeEnd('promise.all'));

解决这些 Promise 要花多长时间? 5秒? 1秒? 还是2秒?

这个留给你们自己验证咯。

Promise race

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

const a = () => new Promise((resolve) => setTimeout(() => resolve('a'), 2000));
const b = () => new Promise((resolve) => setTimeout(() => resolve('b'), 1000));
const c = () => new Promise((resolve) => setTimeout(() => resolve('c'), 1000));
const d = () => new Promise((resolve) => setTimeout(() => resolve('d'), 1000));

console.time('promise.race');
Promise.race([a(), b(), c(), d()])
  .then(results => console.log(`Done! ${results}`))
  .catch(console.error)
  .finally(() => console.timeEnd('promise.race'));

输出是什么?

输出 b。使用 Promise.race,最先执行完成就会结果最后的返回结果。

你可能会问:Promise.race的用途是什么?

我没胡经常使用它。但是,在某些情况下,它可以派上用场,比如计时请求或批量处理请求数组。

Promise.race([
  fetch('http://slowwly.robertomurray.co.uk/delay/3000/url/https://api.jsonbin.io/b/5d1fb4dd138da811182c69af'),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error('request timeout')), 1000))
])
.then(console.log)
.catch(console.error);

图片描述

如果请求足够快,那么就会得到请求的结果。

图片描述

大家都说简历没项目写,我就帮大家找了一个项目,还附赠【搭建教程】

1.5 Promise 常见问题

串行执行 promise 并传递参数

这次,我们将对Node的fs使用promises API,并将两个文件连接起来:

const fs = require('fs').promises; // requires node v8+

fs.readFile('file.txt', 'utf8')
  .then(content1 => fs.writeFile('output.txt', content1))
  .then(() => fs.readFile('file2.txt', 'utf8'))
  .then(content2 => fs.writeFile('output.txt', content2, { flag: 'a+' }))
  .catch(error => console.log(error));

在此示例中,我们读取文件1并将其写入output 文件。 稍后,我们读取文件2并将其再次附加到output文件。 如你所见,writeFile promise返回文件的内容,你可以在下一个then子句中使用它。

如何链接多个条件承诺?

你可能想要跳过 Promise 链上的特定步骤。有两种方法可以做到这一点。

const a = () => new Promise((resolve) => setTimeout(() => { console.log('a'), resolve() }, 1e3));
const b = () => new Promise((resolve) => setTimeout(() => { console.log('b'), resolve() }, 2e3));
const c = () => new Promise((resolve) => setTimeout(() => { console.log('c'), resolve() }, 3e3));
const d = () => new Promise((resolve) => setTimeout(() => { console.log('d'), resolve() }, 4e3));

const shouldExecA = true;
const shouldExecB = false;
const shouldExecC = false;
const shouldExecD = true;

Promise.resolve()
  .then(() => shouldExecA && a())
  .then(() => shouldExecB && b())
  .then(() => shouldExecC && c())
  .then(() => shouldExecD && d())
  .then(() => console.log('done'))

如果你运行该代码示例,你会注意到只有ad被按预期执行。

另一种方法是创建一个链,然后仅在以下情况下添加它们:

const chain = Promise.resolve();

if (shouldExecA) chain = chain.then(a);
if (shouldExecB) chain = chain.then(b);
if (shouldExecC) chain = chain.then(c);
if (shouldExecD) chain = chain.then(d);

chain
  .then(() => console.log('done'));

如何限制并行 Promise?

要做到这一点,我们需要以某种方式限制Promise.all

假设你有许多并发请求要执行。 如果使用 Promise.all 是不好的(特别是在API受到速率限制时)。 因此,我们需要一个方法来限制 Promise 个数, 我们称其为promiseAllThrottled

// simulate 10 async tasks that takes 5 seconds to complete.
const requests = Array(10)
  .fill()
  .map((_, i) => () => new Promise((resolve => setTimeout(() => { console.log(`exec'ing task #${i}`), resolve(`task #${i}`); }, 5000))));

promiseAllThrottled(requests, { concurrency: 3 })
  .then(console.log)
  .catch(error => console.error('Oops something went wrong', error));

输出应该是这样的:

图片描述

以上代码将并发限制为并行执行的3个任务。

实现promiseAllThrottled 一种方法是使用Promise.race来限制给定时间的活动任务数量。

/**
 * Similar to Promise.all but a concurrency limit
 *
 * @param {Array} iterable Array of functions that returns a promise
 * @param {Object} concurrency max number of parallel promises running
 */
function promiseAllThrottled(iterable, { concurrency = 3 } = {}) {
  const promises = [];

  function enqueue(current = 0, queue = []) {
    // return if done
    if (current === iterable.length) { return Promise.resolve(); }
    // take one promise from collection
    const promise = iterable[current];
    const activatedPromise = promise();
    // add promise to the final result array
    promises.push(activatedPromise);
    // add current activated promise to queue and remove it when done
    const autoRemovePromise = activatedPromise.then(() => {
      // remove promise from the queue when done
      return queue.splice(queue.indexOf(autoRemovePromise), 1);
    });
    // add promise to the queue
    queue.push(autoRemovePromise);

    // if queue length >= concurrency, wait for one promise to finish before adding more.
    const readyForMore = queue.length < concurrency ? Promise.resolve() : Promise.race(queue);
    return readyForMore.then(() => enqueue(current + 1, queue));
  }

  return enqueue()
    .then(() => Promise.all(promises));
}

promiseAllThrottled一对一地处理 Promises 。 它执行Promises并将其添加到队列中。 如果队列小于并发限制,它将继续添加到队列中。 达到限制后,我们使用Promise.race等待一个承诺完成,因此可以将其替换为新的承诺。 这里的技巧是,promise 自动完成后会自动从队列中删除。 另外,我们使用 race 来检测promise 何时完成,并添加新的 promise 。

人才们的 【三连】 就是小智不断分享的最大动力,如果本篇博客有任何错误和建议,欢迎人才们留言,最后,谢谢大家的观看。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:https://adrianmejia.com/promi...

交流

文章每周持续更新,可以微信搜索 【大迁世界 】 第一时间阅读,回复 【福利】 有多份前端视频等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,欢迎Star。

查看原文

林小志 发布了文章 · 2020-11-24

从点到面学习 flex 弹性布局.pdf

就在不久前的几天,继《个税 app 中莫名的稿费让我开始怀疑“信任”这词是否有必要存在》这篇推文之后,公众号终于更新了一篇《终于完成了这个 PDF 念头了……》,主要是跟大家分享了我拖了很久的有关 flex 的一个 PDF 文档。

这两篇文章其实并没有太多东西,《个税 app 中莫名的稿费让我开始怀疑“信任”这词是否有必要存在》是在当初被那位朱xx和智博尚书公司折腾了之后吐槽的,一个专职写书的,一个专职出版书的,🤣……

哦了,多余的话也就不说了,保护好个人信息很关键,不要随便相信任何一个人,尤其是商人。回正题,这本有关 flex 布局相关的 PDF 是免费的,好与不好,看了就知道,页数不多,截图+代码,也就 100 多页。

不骗流量,不骗公众号关注数,需要的直接下吧。

链接: https://pan.baidu.com/s/1ifBvUm22X7OCAq5jwE1hSA
提取码: qne5

来张截图:
image.png

查看原文

赞 5 收藏 3 评论 0

林小志 回答了问题 · 2020-10-14

解决关于box-sizing的小疑问?

按照这个写法,看到的宽度一样的话,没毛病啊。
image.png

  • .div1 并没有具体的宽度,也就是会撑满到父元素的宽度(300px);
  • padding-left: 50px; 的作用是内间距有 50px 的值;

结合上述的情况,这个时候无论是哪种 box-sizing 属性值的情况, .div1 是可以理解为:

.div1 {
    padding-left: 50px;
    width: 250px;
    box-sizing: content-box;
}
/* 上下这两个的 .div1 的样式结果【看到】的都是相同的 */
.div1 {
    padding-left: 50px;
    width: 300px;
    box-sizing: border-box;
}

这两个情况的结果都是相同的,因为 width 没有定义宽度,默认是 auto,那么浏览器会根据你的 box-sizing 来计算最终的结果。而如果你是给了具体的 width 值,那么情况就不一样了。

简单举例就是这样:

.div1 {
    padding-left: 50px;
    width: auto;
}

然后可以看到解析后的结果是:
image.png

而如果你给 width 有一个具体的值,那么情况就不同了,具体的可以了解一下 box-sizing 的盒模型计算方式。

关注 3 回答 2

认证与成就

  • 认证信息 《CSS那些事儿》作者、前端开发工程师
  • 获得 345 次点赞
  • 获得 9 枚徽章 获得 1 枚金徽章, 获得 1 枚银徽章, 获得 7 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2013-11-22
个人主页被 4.1k 人浏览