前言

大家好,我是虚竹。

2022年是我们的一次重大技术栈转型(尝试、试错、挑战、精进),发动组员学习React新技术,前端小组采用主流的前端框架基于React、React Native系列生态支撑公司各种项目或产品的开发,其中应用包括 WEB 端、APP 端、H5 移动端、桌面端。

在团队协作中,为避免低级 Bug、以及团队协作时不同代码风格对彼此造成的困扰与影响。如果项目中没有统一的规范就会导致代码风格的五花八门,不利于代码的阅读和维护。所以会预先制定编码规范,使用 Lint 工具或代码风格检测工具,则可以辅助编码规范执行,有效控制代码质量。EsLint 则是其中一个很好的工具。

对于 EsLint 你们应该比较了解,是用来校验代码规范的。Prettier 是用来统一代码风格,格式化代码的,支持 js、ts、css、less、scss、json、jsx。而且集成了 vscode、vim、webstorm、sublime text 插件。

如果你用的技术栈是 Vue.js,可以移步到我之前发的文章:https://blog.csdn.net/qq_15041931/article/details/124517616

Eslint & Prettier 最佳 CP 组合

EsLint 安装与配置

安装 Eslint

npm install -D eslint

VSCode 插件市场搜索 Eslint,下载并安装

在项目根目录新增.eslintrc.js文件,加入如下配置:

module.exports = {root: true, // 当前配置为根配置,将不再从上级文件夹查找配置parser: "@babel/eslint-parser", // 采用 @babel/eslint-parser 作为语法解析器parserOptions: {requireConfigFile: false, // 禁用未检测到babel配置文件ecmaFeatures: {// 指定要使用其他那些语言对象jsx: true // 启用jsx语法},ecmaVersion: 9, // 指定使用的ECMAScript版本(2015-6,、2016-7、2017-8、2018-9、2019-10)sourceType: "module", // 指定来源的类型,有两种script或modulebabelOptions: {presets: ["@babel/preset-react"] // 对react语法的转换}},env: {browser: true, // 设置为所需检查的代码是在浏览器环境运行的es6: true, // 设置为所需检查的代码是nodejs环境运行的node: true // 设置所需检查代码为es6语法书写},extends: ["plugin:prettier/recommended", "plugin:react/recommended", "prettier"],plugins: ["react", "prettier"],// ESLint 附带有大量的规则。你可以使用注释或配置文件修改你项目中要使用的规则。要改变一个规则设置,你必须将规则 ID 设置为下列值之一:// "off" 或 0 - 关闭规则// "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)// "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)rules: {"prettier/prettier": 2,"no-console": process.env.NODE_ENV === "production" ? "error" : "off", // 只有开发环境可以使用console"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", // 只有开发环境可以使用debugger"accessor-pairs": 2, // 应同时设置setter和getter"arrow-spacing": [2, { before: true, after: true }], // 箭头间距"block-spacing": [2, "always"], // 块间距"brace-style": [2, "1tbs", { allowSingleLine: true }], // 大括号样式允许单行camelcase: [1, { properties: "always" }], // 为属性强制执行驼峰命名"comma-dangle": [2, "never"], // 逗号不使用悬挂"comma-spacing": [2, { before: false, after: true }], // 逗号间距"comma-style": [2, "last"], // (默认)与数组元素,对象属性或变量声明在同一行之后和同一行需要逗号"constructor-super": 2,"consistent-this": [2, "that"], // 强制this别名为thatcurly: [2, "multi-line"], // 当一个块只包含一条语句时,不允许省略花括号。"dot-location": [2, "property"], // 成员表达式中的点应与属性部分位于同一行"eol-last": 2, // 强制文件以换行符结束(LF)eqeqeq: ["error", "always", { null: "ignore" }], // 强制使用全等"generator-star-spacing": [2, { before: true, after: true }], // 生成器中'*'两侧都要有间距"global-require": 1, // 所有调用require()都位于模块的顶层indent: ["off", 2, { SwitchCase: 2 }], // 缩进风格"key-spacing": [2, { beforeColon: false, afterColon: true }], // 强制在对象字面量属性中的键和值之间保持一致的间距"keyword-spacing": [2, { before: true, after: true }], // 关键字如if/function等的间距"new-cap": [2, { newIsCap: true, capIsNew: false }], // 允许在没有new操作符的情况下调用大写启动的函数;(默认)要求new使用大写启动函数调用所有操作符"new-parens": 2, // JavaScript通过new关键字调用函数时允许省略括号"no-array-constructor": 1, // 不允许使用Array构造函数。除非要指定生成数组的长度"no-class-assign": 2, // 不允许修改类声明的变量"no-const-assign": 2, // 不能修改使用const关键字声明的变量"no-control-regex": 0, // 不允许正则表达式中的控制字符"no-delete-var": 2, // 不允许在变量上使用delete操作符"no-dupe-args": 2, // 不允许在函数声明或表达式中使用重复的参数名称"no-dupe-class-members": 2, // 不允许在类成员中使用重复的参数名称"no-dupe-keys": 2, // 不允许在对象文字中使用重复键"no-duplicate-case": 2, // 不允许在switch语句的case子句中使用重复的测试表达式"no-empty-character-class": 2, // 不允许在正则表达式中使用空字符类"no-empty-pattern": 2, // 不允许空块语句"no-eval": 2, // 不允许使用eval"no-ex-assign": 2, // 不允许在catch子句中重新分配例外"no-extend-native": 2, // 不允许直接修改内建对象的原型"no-extra-boolean-cast": 2, // 禁止不必要的布尔转换"no-extra-parens": [2, "functions"], // 仅在函数表达式附近禁止不必要的括号"no-fallthrough": 2, // 消除一个案件无意中掉到另一个案件"no-floating-decimal": 2, // 消除浮点小数点,并在数值有小数点但在其之前或之后缺少数字时发出警告"no-func-assign": 2, // 允许重新分配function声明"no-implied-eval": 2, // 消除隐含eval()"no-inner-declarations": [2, "functions"], // 不允许function嵌套块中的声明"no-invalid-regexp": 2, // 不允许RegExp构造函数中的无效正则表达式字符串"no-irregular-whitespace": 2, // 捕获无效的空格"no-iterator": 2, // 消除阴影变量声明"no-label-var": 2, // 禁止创建与范围内的变量共享名称的标签"no-labels": [2, { allowLoop: false, allowSwitch: false }], // 消除 JavaScript 中使用带标签的语句"no-lone-blocks": 2, // 消除脚本顶层或其他块中不必要的和可能混淆的块"no-mixed-spaces-and-tabs": 2, // 不允许使用混合空格和制表符进行缩进"no-multi-spaces": 2, // 禁止在逻辑表达式,条件表达式,声明,数组元素,对象属性,序列和函数参数周围使用多个空格"no-multi-str": 2, // 防止使用多行字符串"no-multiple-empty-lines": [2, { max: 1 }], // 最多一个空行"no-native-reassign": 2, // 不允许修改只读全局变量"no-new-object": 2, // 不允许使用Object构造函数"no-new-require": 2, // 消除new require表达的使用"no-new-symbol": 2, // 防止Symbol与new 同时使用的意外错误"no-new-wrappers": 2, // 杜绝使用String,Number以及Boolean与new操作"no-obj-calls": 2, // 不允许调用Math,JSON和Reflect对象作为功能"no-octal": 2, // 不允许使用八进制文字"no-octal-escape": 2, // 不允许字符串文字中的八进制转义序列"no-path-concat": 2, // 防止 Node.js 中的目录路径字符串连接无效"no-redeclare": 2, // 消除在同一范围内具有多个声明的变量"no-regex-spaces": 2, // 在正则表达式文字中不允许有多个空格"no-return-assign": [2, "except-parens"], // 消除return陈述中的任务,除非用括号括起来"no-self-assign": 2, // 消除自我分配"no-self-compare": 2, // 禁止比较变量与自身"no-sequences": 2, // 禁止使用逗号运算符"no-sparse-arrays": 2, // 不允许稀疏数组文字"no-this-before-super": 2, // 在呼call前标记this/super关键字super()"no-throw-literal": 2, // 不允许抛出不可能是Error对象的文字和其他表达式"no-trailing-spaces": 2, // 不允许在行尾添加尾随空白"no-undef": 2, // 任何对未声明的变量的引用都会导致错误"no-undef-init": 2, // 消除初始化为undefined的变量声明"no-underscore-dangle": 2, // 标识符不能以_开头或结尾"no-unexpected-multiline": 2, // 不允许混淆多行表达式"no-unmodified-loop-condition": 2, // 查找循环条件内的引用,然后检查这些引用的变量是否在循环中被修改"no-unneeded-ternary": [2, { defaultAssignment: false }], // 不允许将条件表达式作为默认的分配模式"no-unreachable": 2, // 不允许可达代码return,throw,continue,和break语句后面还有语句。"no-unsafe-finally": 2, // 不允许return,throw,break,和continue里面的语句finally块"no-unused-vars": [1, { vars: "all", args: "after-used" }],// 消除未使用的变量,函数和函数的参数// vars: 'all' 检查所有变量的使用情况,包括全局范围内的变量。这是默认设置。 args: 'after-used' 只有最后一个参数必须使用。例如,这允许您为函数使用两个命名参数,并且只要您使用第二个参数,ESLint 就不会警告您第一个参数。这是默认设置。"no-useless-call": 2, // 标记使用情况,Function.prototype.call()并且Function.prototype.apply()可以用正常的函数调用来替代"no-useless-computed-key": 2, // 禁止不必要地使用计算属性键"no-useless-constructor": 2, // 在不改变类的工作方式的情况下安全地移除的类构造函数"no-useless-escape": 0, // 禁用不必要的转义字符"no-whitespace-before-property": 2, // 如果对象的属性位于同一行上,不允许围绕点或在开头括号之前留出空白"no-with": 2, // 禁用with"no-var": 2, // 禁用var"one-var": [2, { initialized: "never" }], // 强制将变量声明为每个函数(对于var)或块(对于let和const)范围一起声明或单独声明。 initialized: 'never' 每个作用域要求多个变量声明用于初始化变量"operator-linebreak": [2, "after", { overrides: { "?": "before", ":": "before" } }], // 实施一致的换行"padded-blocks": [2, "never"], // 在块内强制执行一致的空行填充"prefer-destructuring": ["error", { object: false, array: false }], // 此规则强制使用解构而不是通过成员表达式访问属性。quotes: [2, "double", { avoidEscape: true, allowTemplateLiterals: true }], // avoidEscape: true 允许字符串使用单引号或双引号,只要字符串包含必须以其他方式转义的引号 ;"allowTemplateLiterals": true 允许字符串使用反引号radix: 2, // parseInt必须指定第二个参数semi: [0, "never"], // 不使用分号"semi-spacing": [2, { before: false, after: true }], // 强制分号间隔"space-before-blocks": [2, "always"], // 块必须至少有一个先前的空间"space-before-function-paren": [2,{anonymous: "always", // 用于匿名函数表达式(例如function () {})named: "never", // 用于命名函数表达式(例如function foo () {})asyncArrow: "always" // 用于异步箭头函数表达式(例如async () => {})}],"space-in-parens": [2, "never"], // 禁止或要求(或)左边的一个或多个空格"space-infix-ops": 2, // 强制二元运算符左右各有一个空格"space-unary-ops": [2, { words: true, nonwords: false }], // words: true 如:new,delete,typeof,void,yield 左右必须有空格 // nonwords: false 一元运算符,如:-,+,--,++,!,!!左右不能有空格"spaced-comment": [2, "always", { markers: ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], // 注释开始后,此规则将强制间距的一致性//或/*"template-curly-spacing": [2, "never"], // 不允许大括号内的空格"use-isnan": 2, // 禁止比较时使用NaN,只能用isNaN()"valid-typeof": 2, // 必须使用合法的typeof的值"wrap-iife": [2, "any"], // 立即执行函数表达式的小括号风格"yield-star-spacing": [2, "both"], // 强制执行*周围 yield*表达式的间距,两侧都必须有空格yoda: [2, "never"],"prefer-const": 2, // 使用let关键字声明的变量,但在初始分配后从未重新分配变量,应改为const声明"object-curly-spacing": [2, "always", { objectsInObjects: true }], // 不允许以对象元素开始和/或以对象元素结尾的对象的大括号内的间距"array-bracket-spacing": [2, "never"], // 不允许数组括号内的空格"react/jsx-uses-react": 1, // 防止React被错误地标记为未使用"react/jsx-uses-vars": 2, // 防止在JSX中使用的变量被错误地标记为未使用"react/react-in-jsx-scope": 0, // 关闭使用JSX时防止丢失React"react/prop-types": 0 // 关闭React组件中的props验证}
};

Prettier 安装与配置

安装 Prettier

npm install -D prettier

VSCode 插件市场搜索 Prettier,下载并安装

在项目根目录新增.prettierrc.js文件,加入如下配置:

module.exports = {printWidth: 175, // 每行代码长度(默认175)trailingComma: "none", // 在对象或数组最后一个元素后面是否加逗号tabWidth: 2, // 每个tab相当于多少个空格(默认2)useTabs: true, // 使用tab(制表符)缩进而非空格semi: true, // 是否在行尾加分号singleQuote: false, // 使用单引号代替双引号arrowParens: "avoid", // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号bracketSpacing: true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }"proseWrap: "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行htmlWhitespaceSensitivity: "ignore", // HTML 文件空格敏感度jsxSingleQuote: false, // jsx中是否使用单引号endOfLine: "auto", // 结尾是 \n \r \n\r autojsxBracketSameLine: true // 将>多行JSX元素放在最后一行的末尾,而不是单独放在下一行
};

忽略校验的文件

在项目根目录新建.eslintignore & .prettierignore两个文件,加入如下配置:

node_modules
build
src/statics
public
README.md
package.json
yarn.lock

VSCode 编辑器配置

方法一

直接打开vscode编辑器,选择顶部菜单栏:文件->首选项->设置,如下图所示:

方法二

在项目根目录新建 .vscode 文件夹,再新增 settings.json 文件,加入如下配置:

{"editor.tabSize": 2,"editor.formatOnSave": true, // 保存时自动格式化"editor.wordWrap": "on", // 是否自动换行/* eslint的配置 */"eslint.enable": true,"eslint.run": "onSave", // 保存后"eslint.validate": ["javascript", "javascriptreact", "vue", "html"],"editor.codeActionsOnSave": {"source.fixAll": true},"eslint.alwaysShowStatus": true,"javascript.validate.enable": false,"html.validate.scripts": false,"[javascript]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[vue]": {"editor.defaultFormatter": "octref.vetur"},"[less]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[scss]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[html]": {"editor.defaultFormatter": "vscode.html-language-features"},// 文件头部注释"fileheader.customMade": {"description": "描述信息","author": "Jack Chen @懒人码农","Date": "Do not edit","LastEditors": "Jack Chen","LastEditTime": "Do not Edit"},// 函数注释"fileheader.cursorMode": {"desc": "描述信息","param": "","return": ""},// 配置md和json文件不自动添加头部注释"fileheader.configObj": {"prohibitAutoAdd": ["json", "md"]},"[jsonc]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"editor.suggestSelection": "first","vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue","files.exclude": {"**/.classpath": true,"**/.project": true,"**/.settings": true,"**/.factorypath": true},"emmet.includeLanguages": {"javascript": "javascriptreact"},"emmet.triggerExpansionOnTab": true

在项目开发中,加入 @babel/eslint-parser @babel/preset-react eslint eslint-config-babel eslint-config-prettier eslint-plugin-import eslint-plugin-prettier eslint-plugin-react prettier 这些依赖库

npm install -D @babel/eslint-parser @babel/preset-react eslint eslint-config-babel eslint-config-prettier eslint-plugin-import eslint-plugin-prettier eslint-plugin-react prettier

开发时,保存文件,即可按 prettier 规则格式化文件,并自动修复可修复的 issue,不能自动修复的,请根据提示,手动修复。

提示:vscode 已设置保存时格式化,但有时并不会格式化文件。已保存的文件还存在报错的,请手动格式化,并修改相应问题后,再次保存。

如果出现报错如下图所示:

处理方式:

// 先卸载
npm uninstall -D @baebel/eslint-parser
// 再安装
npm install -D @baebel/eslint-parser

再重启 VSCode 编辑器,运行,报错消失

提交时校验(git commit)

安装配置 husky v7.0.4

npm install -D husky

package.json文件中添加如下代码:

{ "scripts": { "prepare": "husky install" }
}

执行命令生成.husky目录,代码如下:

npm run prepare

添加另一个钩子commit-msg可以用来规范化标准格式,并且可以按需指定是否要拒绝本次提交,命令如下:

npx husky add .husky/commit-msg

.husky目录下,会生成commit-msg hook文件,打开后添加如下代码:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh" npx --no-install commitlint --edit $1

安装配置 commitlint

自定义 GIT 提交规范,命令如下:

npm install @commitlint/cli @commitlint/config-conventional -D

在项目根目录创建commitlint.config.js文件,添加代码如下:

module.exports = {// 继承的规则extends: ['@commitlint/config-conventional'],// 定义规则类型rules: {// type 类型定义,表示 git 提交的 type 必须在以下类型范围内'type-enum': [2,'always',['feat', // 新功能 feature'fix', // 修复 bug'docs', // 文档注释'style', // 代码格式(不影响代码运行的变动)'refactor', // 重构(既不增加新功能,也不是修复bug)'perf', // 性能优化'test', // 增加测试'chore', // 构建过程或辅助工具的变动'revert', // 回退'build' // 打包]],// subject 大小写不做校验'subject-case': [0]}
}

添加另一个钩子pre-commit hook会在提交前被调用,并且可以按需指定是否要拒绝本次提交,命令如下:

npx husky add .husky/pre-commit "npx --no-install lint-staged --allow-empty"

.husky目录下,会生成pre-commit文件,打开后添加如下代码:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh" # 这里就是唤醒lint-staged
npx --no-install lint-staged --allow-empty

安装配置 lint-staged

npm install -D lint-staged

package.json文件中,添加如下代码:

{"lint-staged": { "*.{js,jsx,ts,tsx}": [ "eslint --fix", "prettier --write"]}
}

常用 GIT HOOKS

提交规范

git commit -m <type>[optional scope]: <description> (注意冒号后面有空格)

  • type:用于表明我们这次提交的改动类型,是新增了功能?还是修改了测试代码?又或者是更新了文档?
  • optional scope:一个可选的修改范围。用于标识此次提交主要涉及到代码中哪个模块。
  • description:一句话描述此次提交的主要内容,做到言简意赅。

示例代码如下:

git add .
git commit -m "feat: 增加 xxx 功能"
# 或者
git commit -m "bug: 修复 xxx 功能"

到这里已大功告成,大家可以自行尝试,如有问题,请留言讨论。

create-react-app 兼容 IE11 配置

安装react-app-polyfill插件,代码如下:

npm install -S react-app-polyfill

安装成功后,在src文件夹下的index.js文件首行加入代码如下图所示:

还没结束,需要在package.json文件中加入browserslist,如下图所示:

最后重新运行项目,如果发现还是空白但是浏览器没有报错,把node_moudles删掉重新安装运行即可。

结语

好了,就写到这,简单几步搞定,不需要花太长时间。如果你对自己的代码有严格的代码洁癖或者你对自己的前端团队有必要的代码书写规范,建议赶快用起来。

文中如有错误,欢迎在评论区留言指正,如果这篇文章有帮到你了,还望点个赞以表鼓励。

基于 React 项目前端团队推行代码规范及代码质量,真香(姐妹篇)相关推荐

  1. 【Vue3+Vite+TS项目集成ESlint +Prettier实现代码规范检查和代码格式化】

    目录 前言 创建项目 安装初始化ESlint 安装ESlint: 初始化ESlint: 安装配置Prettier 安装prettier: 配置prettier: 配置VScode保存时自动格式化代码 ...

  2. 现代软件工程讲义 3 代码规范与代码复审

    请参考原址:http://www.cnblogs.com/xinz/archive/2011/11/20/2255971.html 第10章 代码规范与代码复审 在第9章中,同学们完成了WC程序,经过 ...

  3. 数据开发 的 代码规范 以及 代码评审脚本

    文章目录 1.概述 2.代码规范 2.1.通用代码规范 2.2.配置文件和传参规范 2.3.Python代码规范 2.4.SQL代码规范 2.5.其它 3.代码评审 自动化脚本 4.数据逻辑校验机制 ...

  4. java sleep方法_6种快速统计代码执行时间的方法,真香!(史上最全)

    我们在日常开发中经常需要测试一些代码的执行时间,但又不想使用向 JMH(Java Microbenchmark Harness,Java 微基准测试套件)这么重的测试框架,所以本文就汇总了一些 Jav ...

  5. 前端代码规范之代码格式化配置

    前言 由于每个前端人员的格式化配置或安装的格式化插件不一,导致在对项目开发的过程中代码风格不一,影响团队开发效率: 为了方便维护及统一代码风格制定团队的一套格式化配置,来对项目代码进行约束以及对部分问 ...

  6. 前端代码规范——CSS代码规范

    文章目录 CSS代码规范 1.命名规范 1.1 block 1.2 Element 1.3 Modifier 2 代码风格 3 通用 3.1 选择器 3.2 属性缩写 3.3 清除浮动 3.4 !im ...

  7. python 检查代码规范_Python代码规范检测

    一定要注重代码规范,按照平时的代码管理,可以将Python代码规范检测分为两种: 静态本地检测:可以借助静态检查工具,比如:Flake8,Pylint等,调研了一下,用Flake8的相对较多,功能满足 ...

  8. Python代码规范:代码规范整改和编码技巧-flake8扫描问题整改

    为了修正flake8扫描出的问题,需要按照以下步骤进行: 1. 分类问题:首先需要对扫描得到的问题进行分类,以便更好地理解和解决它们.例如: - 代码格式问题(如缩进不正确.行长度超过限制等) - 变 ...

  9. React项目前端开发总结

    此项目为公司的公众号管理系统,承载了公司每个部门的业务,需求多,开发周期长,技术可圈可点之处较多,特此记录与大家分享! 1. 使用技术 react+react-router + react-redux ...

最新文章

  1. Python发行版本Anaconda的安装说明:基于Anaconda2-4.3.1-Windows-x86_64
  2. vue分页tbale小荔枝
  3. python入门教程2word-入门干货:Python操作Word文件经验分享
  4. leetcode 搜索插入位置
  5. Android Studio——字体大小的修改
  6. Spark 1.3 新特性 :176个贡献者,1000+ patches
  7. qsettings删除注册表_Qt QSettings读取注册表和ini | 学步园
  8. Visual Studio 2008单元测试实践一
  9. 正则表达式如何匹配正反斜杠
  10. 数学知识点大集合!有了它学数学真的很简单!
  11. 【JavaScript】网站源码防止被人另存为
  12. 用Vue.js开发一个电影App的前端界面
  13. [ZJOI2008]骑士
  14. 深度剖析Java数据结构之迭代器(Iterator)
  15. C#隐式类型和显示类型
  16. 外键查询_详解MySQL数据库删除所有表的外键约束、禁用外键约束相关脚本
  17. batch script learn
  18. 混凝土弹性波速计算公式_混凝土强度与应力波速相关性研究
  19. Java个人博客项目
  20. 用ROS来做无人测试平台系列之国外的一些RACECAR

热门文章

  1. 新开发的手机网址导航--酷网导航
  2. 在 4G 内存的机器上,申请 8G 内存会怎么样?
  3. 储存区域网路(SAN)-使用Linux(Centos5)来做iSCSI Target 篇
  4. PostgreSQL修改数据库timezone
  5. CreateCompatibleDC 与 CreateCompatibleBitmap 小小结
  6. Transformer详解(三):Transformer 结构
  7. 计算机系统结构:Pipelining 基本流水线技术
  8. 使用tooltip弹出图片
  9. macOS 常用键盘快捷键大全 - 最值得你记住的 Mac 常用快捷键组合
  10. 【博客话题】其实我只是玩玩Linux而已的啦