引入
這個問題是對自己的發問,但我相信會有很多跟我一樣的同學。 對於 babel 的使用,近半年來一直停留在與 webpack 結合使用,以及在瀏覽器開發環境下。導致很多 babel 的包,我都不清楚他們是幹嘛的。比如 babel-register,還有 babel-runtime,各種 presets 的區別,transform-runtime 和 babel-polyfill 的區別,helpers 是幹嘛的。儘管網上的 babel 的教程很多了,但是解答自己的一些疑問,還是要花費一些功夫。所以抽出時間來總結一下。如果你對於以上概念已經比較清楚了,就不需要往下看了。
本次的 example 程式碼都在 github 上,而且每個資料夾都有詳細的 README,說明我的使用方式。可以去參照一下用例的使用,並 clone 下來自己研究一下。
版本變化
說實話,從我做前端的時候,接觸 babel 的時候,就已經是 babel 6 了,但是這不妨礙瞭解一下它的重大版本變化。
上一個版本 babel 5 是全家桶,包括各種package, plugins,儘可能的想通過你的一次安裝,達到全能的效果。不過你現在安裝npm install babel
,會得到一個 warning。babel 6 是 2015年10月30號釋出,主要做了以下更新:
- 拆分成幾個核心包,
babel-core
,babel-node
,babel-cli
... - 沒有了預設的轉換,現在你需要手動的新增 plugin。也就是外掛化
- 新增了 preset,也就是預置條件。
- 增加了 .babelrc 檔案,方便自定義的配置。
差不多了,我感覺其他的也不需要了解了。
包
babel 裡面有好多的包,所以必須搞清楚他們都是幹嘛的,才能讓我們更好的使用這個工具。
babel-core
可以看做 babel 的編譯器。babel 的核心 api 都在這裡面,比如 transform,主要都是處理轉碼的。它會把我們的 js 程式碼,抽象成 ast,即 abstract syntax tree 的縮寫,是原始碼的抽象語法結構的樹狀表現形式。我們可以理解為,它定義的一種分析 js 語法的樹狀結構。也就是說 es6 的新語法,跟老語法是不一樣的,那我們怎麼去定義這個語法呢。所以必須要先轉成 ast,去發現這個語法的 kind,分別做對應的處理,才能轉化成 es5.
主要 api:
var babel = require('babel-core');
var transform = babel.transform;
複製程式碼
- babel.transform(code: string, options?: Object)
transform("code", options) // => { code, map, ast }
複製程式碼
- babel.transformFile(filename: string, options?: Object, callback: Function)
var path = require('path');
babel.transformFile(path.resolve(__dirname) + "/test.js", {
presets: ['env'],
plugins: ['transform-runtime'],
}, function(err, result) {// { code, map, ast }
console.log(result);
});
複製程式碼
- babel.transformFileSync(filename: string, options?: Object)
var result = babel.transformFileSync(path.resolve(__dirname) + "/test.js", {
presets: ['env'],
plugins: ['transform-runtime'],
});
console.log(result, 'res');
複製程式碼
- babel.transformFromAst(ast: Object, code?: string, options?: Object)
反轉,你把 ast 傳入,解析為 code 程式碼。
babel-cli
提供命令列執行 babel。也就是你可以 babel filename
去對檔案轉碼。
安裝的話
npm install --save-dev babel-cli
npm isntall babel-cli -g
複製程式碼
使用對應就是
node_module/.bin/babel script.js --out-file script-compiled.js
babel script.js --out-file script-compiled.js
複製程式碼
具體使用還是看官方文件吧,我就不搬文件了。
babel-external-helpers
babel-cli 中的一個command,用來生成一段程式碼,包含 babel 所有的 helper 函式。
首先我們需要了解什麼是 helpers。babel 有很多輔助函式,例如 toArray函式, jsx轉化函式。這些函式是 babel transform 的時候用的,都放在 babel-helpers
這個包中。如果 babe 編譯的時候檢測到某個檔案需要這些 helpers,在編譯成模組的時候,會放到模組的頂部。
像這樣
(function(module, exports, __webpack_require__) {
function _asyncToGenerator(fn) { return function () { }; } // 模組頂部定義 helper
// some code
// async 語法已經被 transform-async-to-generator 轉化,再利用 helper 函式包裝,消費 generator。
const func = (() => {
var _ref = _asyncToGenerator(function* () {
console.log('begin');
yield new Promise(function (resolve) {
setTimeout(function () {
resolve();
}, 1000);
});
console.log('done');
});
})
})
複製程式碼
但是如果多個檔案都需要提供,會重複引用這些 helpers,會導致每一個模組都定義一份,程式碼冗餘。所以 babel 提供了這個命令,用於生成一個包含了所有 helpers 的 js 檔案,用於直接引用。然後再通過一個 plugin,去檢測全域性下是否存在這個模組,存在就不需要重新定義了。
使用:
- 執行 babel-external-helpers 生成 helpers.js 檔案,
node_modules/.bin/babel-external-helpers > helpers.js
複製程式碼
注意:示例程式碼的包都是裝到專案中的,也就是本地。同樣你可以全域性安裝直接執行。 2. 安裝 plugin
npm install --save-dev babel-plugin-external-helpers
複製程式碼
- 然後在 babel 的配置檔案加入
{
"plugins": ["external-helpers"]
}
複製程式碼
- 入口檔案引入 helpers.js
require('./helpers.js');
複製程式碼
這樣就可以啦,還是可以減少很多程式碼量的。另外如果使用了 transform-runtime,就不需要生成 helpers.js 檔案了,這個在後面的 babel-runtime 再說。
babel-node
也是 babel-cli 下面的一個 command,主要是實現了 node 執行指令碼和命令列寫程式碼的能力。舉兩個栗子就清楚了。
執行指令碼
node 環境肯定是不支援 jsx 的
// test.js
const React = require('react');
const elements = [1, 2, 3].map((item) => {
return (
<div>{item}</div>
)
});
console.log(elements);
複製程式碼
執行 test.js,會報錯,不認識這個語法。
node test.js //報錯
複製程式碼
但是使用 babel-node 就可以。
node_modules/.bin/babel-node --presets react test.js
複製程式碼
--presets react 是引數,等同於
{
"presets": ["react"]
}
複製程式碼
執行正常。
node 命令列寫程式碼
注意: 本文所有程式碼示例,均在 node 版本 4.8.4 下執行。
寫個解構賦值的,直接執行 node,不支援。
執行 node_modules/.bin/babel-node --presets env
得到 a 的 value 是 1。
通過栗子基本已經介紹了 babel-node 的用法了,就是方便我們平常開發時候,寫一些指令碼的。所以它不適用於生產環境。另外,babel-node 已經內建了 polyfill,並依賴 babel-register 來編譯指令碼。好,那 babel-register 是什麼呢
babel-register
npm install babel-register --save-dev
複製程式碼
babel-node 可以通過它編譯程式碼,可以瞭解到,它其實就是一個編譯器。我們同樣可以在程式碼中引入它 require('babel-register')
,並通過 node 執行我們的程式碼。
它的原理是通過改寫 node 本身的 require,新增鉤子,然後在 require 其他模組的時候,就會觸發 babel 編譯。也就是你引入require('babel-register')
的檔案程式碼,是不會被編譯的。只有通過 require 引入的其他程式碼才會。我們是不是可以理解,babel-node 就是在記憶體中寫入一個臨時檔案,在頂部引入 babel-register,然後再引入我們的指令碼或者程式碼?
舉個例子,還是 node 中執行 jsx,要通過 babel 編譯。我們可以把 jsx 的程式碼 a.js 編譯完輸出到一個 b.js,然後 node b.js
也是可以執行的。但是太麻煩,不利於開發。讓我們看一下通過 register 怎麼用:
// register.js 引入 babel-register,並配置。然後引入要執行程式碼的入口檔案
require('babel-register')({ presets: ['react'] });
require('./test')
複製程式碼
// test.js 這個檔案是 jsx...
const React = require('react');
const elements = [1, 2, 3].map((item) => {
return (
<div>{item}</div>
)
});
console.log(elements);
複製程式碼
// 執行
$ node register.js
複製程式碼
它的特點就是實時編譯,不需要輸出檔案,執行的時候再去編譯。所以它很適用於開發。總結一下就是,多用在 node 跑程式,做實時編譯用的,通常會結合其他外掛作編譯器使用,比如 mocha 做測試的時候。
值得一提的是,babel-register 這個包之前是在 babel-core 下面的,所以也可以 require('babel-core/register')
去引入,跟require('babel-register')
是一樣的。但是,babel 的團隊把 register 獨立出來了,而且未來的某一天(升 7.0)會從 babel-core 中廢除,所以我們現在最好還是使用 babel-register 吧。babel-core/register.js
babel-runtime
npm install babel-runtime --save
複製程式碼
這個包很簡單,就是引用了 core-js 和 regenerator,然後生產環境把它們編譯到 dist 目錄下,做了對映,供使用。那麼什麼是 core-js 和 regenerator 呢。 首先我們要知道上面提到的 babel-core 是對語法進行 transform 的,但是它不支援 build-ints(Eg: promise,Set,Map),prototype function(Eg: array.reduce,string.trim),class static function (Eg:Array.form,Object.assgin),regenerator (Eg:generator,async)等等擴充的編譯。所以才要用到 core-js 和 regenerator。
core-js
core-js 是用於 JavaScript 的組合式標準化庫,它包含 es5 (e.g: object.freeze), es6的 promise,symbols, collections, iterators, typed arrays, es7+提案等等的 polyfills 實現。也就是說,它幾乎包含了所有 JavaScript 最新標準的墊片。不過為什麼它不把 generator 也實現了... ?
// 比如,只不過需要單個引用
require('core-js/array/reduce');
require('core-js/object/values');
複製程式碼
regenerator
它是來自於 facebook 的一個庫,連結。主要就是實現了 generator/yeild, async/await。
所以 babel-runtime 是單純的實現了 core-js 和 regenerator 引入和匯出,比如這裡是 filter 函式的定義,做了一箇中轉並處理了 esModule 的相容。
module.exports = { "default": require("core-js/library/fn/array/filter"), __esModule: true };
複製程式碼
helpers
還記得提 babel-external-helpers 的時候,介紹 helpers 了嗎。babel-runtime 裡面的 helpers 就相當於我們上面通過 babel-external-helpers 生成的 helpers.js。只不過它把每個 helper 都單獨放到一個資料夾裡。這樣,配合 transform-runtime 使用的時候,需要用 helper 轉化的時候,就從 babel-runtime 中直接引用了。
var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
複製程式碼
檔案結構:
使用
可以單獨引入require('babel-runtime/core-js/object/values');
不過這些模組都做了 esModule 的相容處理,也就是上面引入的模組是{ "default": require("core-js/library/fn/array/filter"), __esModule: true }
這樣的,要使用還得加上 .default
。所以我們期待的是,最好能有幫我們自動處理的外掛,babel-plugin-transform-runtime
就是用來做這個的。這個我們放到 plugin 去講。
babel-polyfill
npm install babel-polyfill --save
複製程式碼
babel-runtime 已經是一堆 polyfill 了,為什麼這裡還有一個類似的包,它同樣是引用了 core-js 和 regenerator,墊片支援是一樣的。官網是這麼說的,babel-polyfill 是為了模擬一個完整的ES2015 +環境,旨在用於應用程式而不是庫/工具。並且使用babel-node時,這個polyfill會自動載入(這個我們在介紹 babel-node 的最後已經說了)。
也就是說,它會讓我們程式的執行環境,模擬成完美支援 es6+ 的環境,畢竟無論是瀏覽器環境還是 node 環境對 es6+ 的支援都不一樣。它是以過載全域性變數 (E.g: Promise),還有原型和類上的靜態方法(E.g:Array.prototype.reduce/Array.form),從而達到對 es6+ 的支援。不同於 babel-runtime 的是,babel-polyfill 是一次性引入你的專案中的,就像是 React 包一樣,同專案程式碼一起編譯到生產環境。
使用
我們結合 babel-register 去使用一下
// index.js
require('babel-core/register')({});
require('babel-polyfill'); // 是的,你要手動引入。
require('./async');
複製程式碼
// async.js
async function a() {
console.log('begin');
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000)
})
console.log('done');
}
a();
複製程式碼
$ node index.js
複製程式碼
完美執行。 注意:babel-polyfill 只是為當前環境全域性下注入墊片,ES6 語法(E.g: arrow func,esModules)還是要加入 plugins 去 transform 的。
plugins
要說 plugins 就不得不提 babel 編譯的過程。babel 編譯分為三步:
- parser:通過 babylon 解析成 AST。
- transform[s]:All the plugins/presets ,進一步的做語法等自定義的轉譯,仍然是 AST。
- generator: 最後通過 babel-generator 生成 output string。
所以 plugins 是在第二步加強轉譯的,所以假如我們自己寫個 plugin,應該就是對 ast 結構做一個遍歷,操作。
babel-plugin-transform-runtime
上面我們知道,transform-runtime 是為了方便使用 babel-runtime 的,它會分析我們的 ast 中,是否有引用 babel-rumtime 中的墊片(通過對映關係),如果有,就會在當前模組頂部插入我們需要的墊片。試一下:
npm install babel-plugin-transform-runtime
複製程式碼
// 編譯前
console.log(Object.values({ 1: 2 }));
複製程式碼
node_modules/.bin/babel --plugins transform-runtime values.js
複製程式碼
// 編譯後
'use strict';
var _values = require('babel-runtime/core-js/object/values');
var _values2 = _interopRequireDefault(_values);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
onsole.log((0, _values2.default)({ 1: 2 }));
複製程式碼
另外,它還有幾個配置
// 預設值
{
"plugins": [
["transform-runtime", {
"helpers": true,
"polyfill": true,
"regenerator": true,
"moduleName": "babel-runtime"
}]
]
}
複製程式碼
如果你只需要用 regenerator,不需要 core-js 裡面的 polyfill 那你就可以在 options 中把 polyfill 設為 false。helpers 設為 false,就相當於沒有啟用 babel-plugin-external-helpers
的效果,比如翻譯 async 的時候,用到了 asyncToGenerator 函式,每個檔案還會重新定義一下。moduleName 的話,就是用到的庫,你可以把 babel-runtime 換成其他類似的。
transform-runtime 對比 babel-polyfill
其實通過上面的介紹我們已經瞭解他們是幹什麼的了,這裡再稍微總結區分一下吧。我在這裡把 babel-runtime 和 babel-plugin-transform-runtime 統稱為 transform-runtime,因為一起用才比較好。
- babel-polyfill 是當前環境注入這些 es6+ 標準的墊片,好處是引用一次,不再擔心相容,而且它就是全域性下的包,程式碼的任何地方都可以使用。缺點也很明顯,它可能會汙染原生的一些方法而把原生的方法重寫。如果當前專案已經有一個 polyfill 的包了,那你只能保留其一。而且一次性引入這麼一個包,會大大增加體積。如果你只是用幾個特性,就沒必要了,如果你是開發較大的應用,而且會頻繁使用新特性並考慮相容,那就直接引入吧。
- transform-runtime 是利用 plugin 自動識別並替換程式碼中的新特性,你不需要再引入,只需要裝好 babel-runtime 和 配好 plugin 就可以了。好處是按需替換,檢測到你需要哪個,就引入哪個 polyfill,如果只用了一部分,打包完的檔案體積對比 babel-polyfill 會小很多。而且 transform-runtime 不會汙染原生的物件,方法,也不會對其他 polyfill 產生影響。所以 transform-runtime 的方式更適合開發工具包,庫,一方面是體積夠小,另一方面是使用者(開發者)不會因為引用了我們的工具,包而汙染了全域性的原生方法,產生副作用,還是應該留給使用者自己去選擇。缺點是隨著應用的增大,相同的 polyfill 每個模組都要做重複的工作(檢測,替換),雖然 polyfill 只是引用,編譯效率不夠高效。
另外,關於 babel-runtime 為什麼是 dependencies 依賴。它只是一個集中了 polyfill 的 library,對應需要的 polyfill 都是要引入專案中,並跟專案程式碼一起打包的。不過它不會都引入,你用了哪個,plugin 就給你 require 哪個。所以即使你最終專案只是 require('babel-runtime/core-js/object/values')
其中的一個檔案,但是對於這包來說,也是生產依賴的。
注意:babel-polyfill 並不是一定會汙染全域性環境,在引入這個 js,並執行的時候,它會先判斷當前有沒有這個方法,在看要不要重寫。如上圖
presets
各種配置 plugin 實在是費勁,es6+ 編譯要加入好多 plugins,比如為了在 node 中使用 esmodule,要把 esmodule 轉化成 commomjs,使用 transform-es2015-modules-commonjs
,還有 asyncToGenerator,React jsx轉化等等,不僅要裝好多,還要配好多。
presets 就是 plugins 的組合,你也可以理解為是套餐... 主要有
大部分的 presets 我覺得都不需要介紹了,官網上寫的比較詳細。而且 babel-preset-lastet(包括 es2105,es2016,es2017)跟預設情況下的 env 是一樣的,也就是說包括 lastest 在內,這四個 presets 都要被 babel-preset-env 代替。即:
{ "presets": ["latest"] } === { "presets": ["env"] }
複製程式碼
babel-preset-env
這個 preset 真是神器啊,它能根據當前的執行環境,自動確定你需要的 plugins 和 polyfills。通過各個 es標準 feature 在不同瀏覽器以及 node 版本的支援情況,再去維護一個 feature 跟 plugins 之間的對映關係,最終確定需要的 plugins。
preset-env 配置
詳情:
{
"presets": [
[
"env",
{
"targets": { // 配支援的環境
"browsers": [ // 瀏覽器
"last 2 versions",
"safari >= 7"
],
"node": "current"
},
"modules": true, //設定ES6 模組轉譯的模組格式 預設是 commonjs
"debug": true, // debug,編譯的時候 console
"useBuiltIns": false, // 是否開啟自動支援 polyfill
"include": [], // 總是啟用哪些 plugins
"exclude": [] // 強制不啟用哪些 plugins,用來防止某些外掛被啟用
}
]
],
plugins: [
"transform-react-jsx" //如果是需要支援 jsx 這個東西要單獨裝一下。
]
}
複製程式碼
主要介紹 debug 和 很好用的 useBuiltIns 吧。
debug
開啟debug後,編譯結果會得到使用的 targets,plugins,polyfill 等資訊
Using targets:
{
"chrome": "59",
"android": "4.4.3",
"edge": "14",
"firefox": "54",
"ie": "10",
"ios": "10",
"safari": "7",
"node": "4.8.4"
}
Modules transform: commonjs
Using plugins:
check-es2015-constants {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
transform-es2015-arrow-functions {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
transform-es2015-block-scoped-functions {"android":"4.4.3","ie":"10","safari":"7"}
transform-es2015-block-scoping {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
...
Using polyfills:
es6.typed.array-buffer {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
es6.typed.int8-array {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
es6.typed.uint8-array {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
es6.typed.uint8-clamped-array {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
es6.typed.int16-array {"android":"4.4.3","ie":"10","safari":"7","node":"4.8.4"}
...
複製程式碼
useBuiltIns
env 會自動根據我們的執行環境,去判斷需要什麼樣的 polyfill,而且,打包後的程式碼體積也會大大減小,但是這一切都在使用 useBuiltIns,而且需要你安裝 babel-polyfill,並 import。它會啟用一個外掛,替換你的import 'babel-polyfill'
,不是整個引入了,而是根據你配置的環境和個人需要單獨的引入 polyfill。 我嘗試了一下是否真的有效,下面是我的對比實驗過程:
step1: 首先是這樣一段測試編譯的程式碼,有 jsx,Object.values,async。env 的配置除了 useBuiltIns 都跟上面的配置一樣。然後通過 webpack + babel-loader 打包,生成 build.js
require('./async');
// import 'babel-polyfill';
const React = require('react');
const elements = [1, 2, 3].map((item) => {
return (
<div>{item}</div>
)
});
console.log(elements);
async function a() {
console.log('begin');
await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 1000)
})
console.log('done');
}
a();
console.log(Object.values({ 1: 2 }));
console.log(Array.isArray([]));
複製程式碼
step2: 然後通過設定不同的引數,打包,獲取 build.js,並執行。得到下表
preset-env 條件下 | useBuiltIns: true |
不引入 polyfill | build.js 程式碼體積 158k,node build.js 執行報錯。 |
引入 polyfill | build.js 體積 369k,執行通過。包確實減小了。 |
- 用 preset-es2015,並引入 polyfill plugins 多加一個 transform-regenerator,這方面確實不如 env 方便一些。 體積 418k,執行通過。不方便在要配好多 plugins。
具體的過程、截圖猛戳 這裡
最終的結論就是,使用了 useBuiltIns 確實體積變小了,比直接 import 'babel-polyfill'
好了許多。
step3: 然後... 我又試了一下 env 下,使用 transform-runtime。在不加 useBuiltIns,不引入 babel-polyfill 的情況下。build.js 體積234k,執行通過。
咦,這樣好像體積更小啊。別忘了,我們的 babel-polyfill 是配置了執行環境的,通過環境看你需要哪些 polyfill。而 transform-runtime,是發現我們程式碼需要什麼 polyfill,當然會少很多了。所以,又回到了用哪個的問題... ? 參考上面的總結。
then
helpers 的問題。開發專案,使用 preset-env,並 import 'babel-polyfill'
,但是 helpers 好像沒有地方配置。而且我試了兩個檔案分別用 async 函式,編譯後每個模組都定義了 asyncToGenerat 函式,這種情況下我覺得最後就是自己生成一個 helpers.js 檔案了。
總結
現在看起來開發大點的專案,最好用的配置應該就是 preset-env,確定自己的執行環境,如果有需要,再加上 useBuiltIns,並生成一份 helpers.js 的檔案。不過,一切還是看你的需求,我的前提是開發大點的“專案”,不過了解了這些東西,你會做出自己的選擇。
babel 的配置
目前 babel 官方推薦是寫到 .babelrc 檔案下,你還可以在 package.json 裡面新增 babel 欄位。不用配置檔案的話,可以把配置當做引數傳給 babel-cli
- .babelrc
{
"presets": [
"env"
],
"plugins": [
["transform-runtime", {
"helpers": true,
"polyfill": true,
"regenerator": true,
"moduleName": "babel-runtime"
}]
]
}
複製程式碼
- 寫到 package.json
"babel": {
"presets": [
"env"
],
}
複製程式碼
- babel cli
babel script.js --plugins=transform-runtime --presets=env
複製程式碼
配合其他工具
webpack
比較常用,除了 babel 自己的包,多裝一個 babel-loader
配合 webpack 使用。並在 webpack.config.js 中加入 loader 的配置
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/,
}
]
}
複製程式碼
mocha
專案裡的程式碼都是用 es6+ 寫的,但是做單元測試的時候,測試框架並不認識你的什麼 esModule,es6+ 的一些語法,mocha 是 node 程式,所以你要把 esModule 轉化成 commomjs 之類的。
mocha 是支援編譯器的,通過 --compilers
指定,這裡我們用 babel,舉個例子
// 求和函式 add.js
const add = (x, y) => x + y;
export default add;
複製程式碼
// 測試指令碼 add.test.js
import { expect } from 'chai'; // chai 是斷言庫
import add from './add';
describe('es6 兩數相加', () => {
it('2 + 4 = 6', () => {
expect(add(2, 4)).equal(6);
})
});
複製程式碼
./node_modules/mocha/bin/mocha --compilers js:babel-register add.test.js
複製程式碼
因為 mocha 終究是在跑 node 程式的,適用於實時編譯,所以可以用 babel-register 做編譯器。
最後
總結這些東西花了我兩三天的時間,雖然搞清楚了這些包是幹嘛的,但是又在想到底應不應該花時間研究這些,工具始終是用來使用的,對於 babel 來說更應該研究的是它對 ast 的處理方式?不過看到自己的產出,我覺得是有必要的,另外,因為對工具更進一步的瞭解,才能更好的在專案中使用它們,而不是一頓照搬,程式碼是可以正常用了,但是可能會有很多不需要的東西,導致程式碼體積變大。“割雞焉用牛刀”,我覺得還是需要有精益求精的精神。希望對你有所幫助。
我的個人部落格地址 https://github.com/sunyongjian/blog ,歡迎訂閱,star,謝謝。