element 源碼學(xué)習(xí)一 —— 認(rèn)識(shí)框架

由于面試需要粟耻,先來幾發(fā) element 源碼學(xué)習(xí)博客。Vue 源碼還將繼續(xù)更新眉踱。

好挤忙,現(xiàn)在我們開始學(xué)習(xí) element —— 最受歡迎的 Vue UI 框架。

package.json

我覺得要看一個(gè)前端項(xiàng)目谈喳,首先必須得看看 package.json 這個(gè)文件册烈。

編譯入口

來看看編譯的入口

  "scripts": {
    # 安裝依賴
    "bootstrap": "yarn || npm i",
    # 構(gòu)建文件
    "build:file": "node build/bin/iconInit.js & node build/bin/build-entry.js & node build/bin/i18n.js & node build/bin/version.js",
    # 構(gòu)建樣式
    "build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
    # 構(gòu)建工具
    "build:utils": "cross-env BABEL_ENV=utils babel src --out-dir lib --ignore src/index.js",
    # 構(gòu)建umd
    "build:umd": "node build/bin/build-locale.js"
  }

這里我們以看源碼的角度,先了解構(gòu)建文件命令婿禽。其實(shí)就是 node 執(zhí)行了幾個(gè) js 腳本赏僧。我們深入看下 iconInitbuild-entry扭倾、 i18n淀零、 version 這些腳本文件。

// build/bin/build-all.js
'use strict';

const components = require('../../components.json');
const execSync = require('child_process').execSync;
const existsSync = require('fs').existsSync;
const path = require('path');

let componentPaths = [];

delete components.index;
delete components.font;

// 遍歷 components 的 key膛壹,找到相應(yīng) component 的路徑驾中,將路徑保存到 componentPaths 數(shù)組
Object.keys(components).forEach(key => {
  const filePath = path.join(__dirname, `../../packages/${key}/cooking.conf.js`);

  if (existsSync(filePath)) {
    componentPaths.push(`packages/${key}/cooking.conf.js`);
  }
});

// pathA,pathB,pathC
const paths = componentPaths.join(',');
// 拼接為 shell 命令琼牧,并調(diào)用 execSync 方法執(zhí)行。
const cli = path.join('node_modules', '.bin', 'cooking') + ` build -c ${paths} -p`;

execSync(cli, {
  stdio: 'inherit'
});

以上方法主要是獲取所有組件名哀卫,然后拼接為 shell 命令,執(zhí)行 shell 命令進(jìn)行 build撬槽。

// build/bin/iconInit.js
'use strict';

var postcss = require('postcss');
var fs = require('fs');
var path = require('path');
var fontFile = fs.readFileSync(path.resolve(__dirname, '../../packages/theme-chalk/src/icon.scss'), 'utf8');
var nodes = postcss.parse(fontFile).nodes;
var classList = [];
// 遍歷匹配正則此改,符合則傳入到數(shù)組中
nodes.forEach((node) => {
  var selector = node.selector || '';
  var reg = new RegExp(/\.el-icon-([^:]+):before/); // 正則: .el-icon-(多個(gè)非:字符):before
  var arr = selector.match(reg);

  if (arr && arr[1]) {
    classList.push(arr[1]);
  }
});
// 導(dǎo)出 icon.json 文件
fs.writeFile(path.resolve(__dirname, '../../examples/icon.json'), JSON.stringify(classList));

以上方法通過解析 icon.scss 最終導(dǎo)出 icon.json 文件,該文件保存了各種圖標(biāo)侄柔。

// build/bin/i18n.js
'use strict';

var fs = require('fs');
var path = require('path');
// 獲取 page.json
var langConfig = require('../../examples/i18n/page.json');

langConfig.forEach(lang => {
  try {
    // 獲取文件信息
    fs.statSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));
  } catch (e) {
    // 創(chuàng)建文件夾
    fs.mkdirSync(path.resolve(__dirname, `../../examples/pages/${ lang.lang }`));
  }

  // 遍歷寫入文件
  Object.keys(lang.pages).forEach(page => {
    var templatePath = path.resolve(__dirname, `../../examples/pages/template/${ page }.tpl`);
    var outputPath = path.resolve(__dirname, `../../examples/pages/${ lang.lang }/${ page }.vue`);
    var content = fs.readFileSync(templatePath, 'utf8');
    var pairs = lang.pages[page];

    Object.keys(pairs).forEach(key => {
      content = content.replace(new RegExp(`<%=\\s*${ key }\\s*>`, 'g'), pairs[key]);
    });

    fs.writeFileSync(outputPath, content);
  });
});

以上代碼是國際化的過程共啃,最終將會(huì)在 examples/pages/ 目錄中生成不同語言的內(nèi)容。國際化具體內(nèi)容請參照 國際化暂题。

生成結(jié)果
// build/bin/version.js
var fs = require('fs');
var path = require('path');
var version = process.env.VERSION || require('../../package.json').version;
var content = { '1.4.13': '1.4', '2.0.11': '2.0', '2.1.0': '2.1' };
if (!content[version]) content[version] = '2.2';
fs.writeFileSync(path.resolve(__dirname, '../../examples/versions.json'), JSON.stringify(content));

獲取 version移剪,定義了一個(gè) content,如果當(dāng)前版本不在 content 中薪者,那么再添加一個(gè)版本數(shù)據(jù)纵苛。由于我學(xué)習(xí)的版本是 2.2.1,最終生成的結(jié)果是:

{"1.4.13":"1.4","2.0.11":"2.0","2.1.0":"2.1","2.2.1":"2.2"}

出現(xiàn)這幾個(gè)版本號(hào)的原因么言津,看下官網(wǎng)就能發(fā)現(xiàn)端倪友鼻,應(yīng)該是幾個(gè)重要的穩(wěn)定版本窒篱。

版本號(hào)

來看下 build-entry.js 文件

// build/bin/build-entry.js
var Components = require('../../components.json'); // 組件數(shù)據(jù)
var fs = require('fs'); // node文件系統(tǒng)
var render = require('json-templater/string');
var uppercamelcase = require('uppercamelcase'); // 駝峰大小寫寫法
var path = require('path'); // node路徑系統(tǒng)
var endOfLine = require('os').EOL;

// 導(dǎo)出路徑
var OUTPUT_PATH = path.join(__dirname, '../../src/index.js');

// 導(dǎo)入template、安裝組件template、主要template
var IMPORT_TEMPLATE = 'import {{name}} from \'../packages/{{package}}/index.js\';';
var INSTALL_COMPONENT_TEMPLATE = '  {{name}}';
var MAIN_TEMPLATE = `/* Automatically generated by './build/bin/build-entry.js' */

{{include}}
import locale from 'element-ui/src/locale';
import CollapseTransition from 'element-ui/src/transitions/collapse-transition';

const components = [
{{install}},
  CollapseTransition
];

const install = function(Vue, opts = {}) {
  locale.use(opts.locale);
  locale.i18n(opts.i18n);

  components.map(component => {
    Vue.component(component.name, component);
  });

  Vue.use(Loading.directive);

  const ELEMENT = {};
  ELEMENT.size = opts.size || '';

  Vue.prototype.$loading = Loading.service;
  Vue.prototype.$msgbox = MessageBox;
  Vue.prototype.$alert = MessageBox.alert;
  Vue.prototype.$confirm = MessageBox.confirm;
  Vue.prototype.$prompt = MessageBox.prompt;
  Vue.prototype.$notify = Notification;
  Vue.prototype.$message = Message;

  Vue.prototype.$ELEMENT = ELEMENT;
};

/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}

module.exports = {
  version: '{{version}}',
  locale: locale.use,
  i18n: locale.i18n,
  install,
  CollapseTransition,
  Loading,
{{list}}
};

module.exports.default = module.exports;
`;

delete Components.font;
// 組件名
var ComponentNames = Object.keys(Components);

var includeComponentTemplate = [];
var installTemplate = [];
var listTemplate = [];

// 遍歷組件名解析template
ComponentNames.forEach(name => {
  var componentName = uppercamelcase(name); // 駝峰命名

  includeComponentTemplate.push(render(IMPORT_TEMPLATE, {
    name: componentName,
    package: name
  }));

  if (['Loading', 'MessageBox', 'Notification', 'Message'].indexOf(componentName) === -1) {
    installTemplate.push(render(INSTALL_COMPONENT_TEMPLATE, {
      name: componentName,
      component: name
    }));
  }

  if (componentName !== 'Loading') listTemplate.push(`  ${componentName}`);
});
// 主要template
var template = render(MAIN_TEMPLATE, {
  include: includeComponentTemplate.join(endOfLine),
  install: installTemplate.join(',' + endOfLine),
  version: process.env.VERSION || require('../../package.json').version,
  list: listTemplate.join(',' + endOfLine)
});
// 導(dǎo)出文件
fs.writeFileSync(OUTPUT_PATH, template);
console.log('[build entry] DONE:', OUTPUT_PATH);

以上代碼中须板,先是定義了三個(gè) template,然后使用 render 方法來渲染這些 template砾跃。最后生成一個(gè)主要 template 導(dǎo)出為文件矫户。render 函數(shù)中的第二個(gè)參數(shù)為 template 中 {{name}} 的數(shù)據(jù)。
這個(gè) render 方法來自 json-templater 庫磅叛,這個(gè)庫可以將字符串編譯為 js 代碼屑咳。

依賴關(guān)系

看看 element 都 depend 了些什么?下面對 element 的依賴作了注釋弊琴。

  "dependencies": {
    // 異步驗(yàn)證器
    "async-validator": "~1.8.1",
    // vue和jsx合并參數(shù)的語法轉(zhuǎn)譯器乔宿?
    "babel-helper-vue-jsx-merge-props": "^2.0.0",
    // 深入合并
    "deepmerge": "^1.2.0",
    // 鼠標(biāo)滾輪在多個(gè)瀏覽器之間的標(biāo)準(zhǔn)化。
    "normalize-wheel": "^1.0.1",
    // 方法的 Throttle/debounce访雪? https://www.npmjs.com/package/throttle-debounce
    "throttle-debounce": "^1.0.1"
  },
  "peerDependencies": {
    // vue核心源碼
    "vue": "^2.5.2"
  },
  "devDependencies": {
    // 一個(gè)托管的全文详瑞、數(shù)字和分面搜索引擎,能夠在第一次按鍵時(shí)提供實(shí)時(shí)結(jié)果臣缀。
    "algoliasearch": "^3.24.5",
    // babel
    "babel-cli": "^6.14.0",
    "babel-core": "^6.14.0",
    "babel-loader": "^6.2.5",
    "babel-plugin-add-module-exports": "^0.2.1",
    "babel-plugin-module-resolver": "^2.2.0",
    "babel-plugin-syntax-jsx": "^6.8.0",
    "babel-plugin-transform-vue-jsx": "^3.3.0",
    "babel-preset-es2015": "^6.14.0",
    // chai斷言庫
    "chai": "^3.5.0",
    // 為服務(wù)器專門設(shè)計(jì)的核心jQuery的快速坝橡、靈活、精益的實(shí)現(xiàn)精置。
    "cheerio": "^0.18.0",
    // node fs工具
    "chokidar": "^1.7.0",
    // cooking前端構(gòu)建工具
    "cooking": "^1.5.4",
    "cooking-lint": "0.1.3",
    // 繼承vue2配置項(xiàng)的cooking插件
    "cooking-vue2": "^0.3.3",
    // 復(fù)制webpack插件
    "copy-webpack-plugin": "^4.1.1",
    // 代碼測試覆蓋率
    "coveralls": "^2.11.14",
    // 跨平臺(tái)支持UNIX命令
    "cp-cli": "^1.0.2",
    // 運(yùn)行在平臺(tái)上設(shè)置和使用環(huán)境變量的腳本计寇。
    "cross-env": "^3.1.3",
    // css 加載器
    "css-loader": "^0.28.7",
    // es6 promise支持
    "es6-promise": "^4.0.5",
    // eslint語法檢測
    "eslint": "4.14.0",
    "eslint-config-elemefe": "0.1.1",
    "eslint-loader": "^1.9.0",
    "eslint-plugin-html": "^4.0.1",
    "eslint-plugin-json": "^1.2.0",
    "extract-text-webpack-plugin": "^3.0.1",
    // 文件加載和保存
    "file-loader": "^1.1.5",
    "file-save": "^0.2.0",
    // 將文件發(fā)布到github的 gh-pages 分支
    "gh-pages": "^0.11.0",
    // gulp打包
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^4.0.0",
    "gulp-cssmin": "^0.1.7",
    "gulp-postcss": "^6.1.1",
    "gulp-sass": "^3.1.0",
    // js 高亮
    "highlight.js": "^9.3.0",
    // html加載器
    "html-loader": "^0.5.1",
    // html webpack插件
    "html-webpack-plugin": "^2.30.1",
    // A Webpack loader for injecting code into modules via their dependencies
    "inject-loader": "^3.0.1",
    // isparta instrumenter loader for webpack,用于測試
    "isparta-loader": "^2.0.0",
    // json加載器
    "json-loader": "^0.5.7",
    // json和js的模板生成工具
    "json-templater": "^1.0.4",
    // karma測試庫
    "karma": "^1.3.0",
    "karma-chrome-launcher": "^2.2.0",
    "karma-coverage": "^1.1.1",
    "karma-mocha": "^1.2.0",
    "karma-sinon-chai": "^1.2.4",
    "karma-sourcemap-loader": "^0.3.7",
    "karma-spec-reporter": "0.0.26",
    "karma-webpack": "^1.8.0",
    // 用于管理具有多個(gè)包的JavaScript項(xiàng)目的工具。
    "lerna": "^2.0.0-beta.32",
    // 模擬時(shí)間工具
    "lolex": "^1.5.1",
    // markdown解析器
    "markdown-it": "^6.1.1",
    "markdown-it-anchor": "^2.5.0",
    "markdown-it-container": "^2.0.0",
    // mocha測試庫
    "mocha": "^3.1.1",
    // node.js 的 sass
    "node-sass": "^4.5.3",
    // 視差滾動(dòng) https://perspective.js.org/#/zh-cn/
    "perspective.js": "^1.0.0",
    // postcss
    "postcss": "^5.1.2",
    "postcss-loader": "0.11.1",
    "postcss-salad": "^1.0.8",
    // node深度刪除模塊
    "rimraf": "^2.5.4",
    // sass加載器
    "sass-loader": "^6.0.6",
    // sinon測試框架
    "sinon": "^1.17.6",
    "sinon-chai": "^2.8.0",
    // 樣式加載器
    "style-loader": "^0.19.0",
    // utf-8 字符轉(zhuǎn)換
    "transliteration": "^1.1.11",
    // 駝峰寫法
    "uppercamelcase": "^1.1.0",
    "url-loader": "^0.6.2",
    // vue
    "vue": "^2.5.2",
    "vue-loader": "^13.3.0",
    "vue-markdown-loader": "1",
    "vue-router": "2.7.0",
    "vue-template-compiler": "^2.5.2",
    "vue-template-es2015-compiler": "^1.6.0",
    // webpack
    "webpack": "^3.7.1",
    "webpack-dev-server": "^2.9.1",
    "webpack-node-externals": "^1.6.0"
  }

阿西吧番宁,這依賴庫真心多~~不知道他們?nèi)绾握业竭@么多庫的元莫。

src目錄

再來看看項(xiàng)目結(jié)構(gòu)部分。按常理源碼肯定是放在 src 目錄中的蝶押,我們找到 src/index.js踱蠢。代碼有點(diǎn)長,只貼出 install 方法部分了棋电。說下都干了什么:導(dǎo)入所有組件茎截,定義安裝方法,判斷環(huán)境執(zhí)行 install 方法赶盔,最后整體導(dǎo)出企锌。

const install = function(Vue, opts = {}) {
  locale.use(opts.locale);
  locale.i18n(opts.i18n);

  components.map(component => {
    // 遍歷將組件加入到Vue中
    Vue.component(component.name, component);
  });

  // 加載中
  Vue.use(Loading.directive);

  const ELEMENT = {};
  ELEMENT.size = opts.size || '';

  // 定義Vue的原型 prototype
  Vue.prototype.$loading = Loading.service;
  Vue.prototype.$msgbox = MessageBox;
  Vue.prototype.$alert = MessageBox.alert;
  Vue.prototype.$confirm = MessageBox.confirm;
  Vue.prototype.$prompt = MessageBox.prompt;
  Vue.prototype.$notify = Notification;
  Vue.prototype.$message = Message;

  Vue.prototype.$ELEMENT = ELEMENT;
};

/* istanbul ignore if */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}

從組件的導(dǎo)入 import Button from '../packages/button/index.js'; 可以看到所有組件都是在 packages 目錄下的。這部分我們會(huì)在之后重點(diǎn)學(xué)習(xí)于未。
那么問題來了撕攒,既然組件都在 packages 中了,那么 src 目錄下都干了些什么呢烘浦?來看看各個(gè)目錄的功能:

  • directive 實(shí)現(xiàn)滾輪優(yōu)化和避免重復(fù)點(diǎn)擊打却。
  • locale 用于 i18n 國際化功能。
  • mixins 看樣子應(yīng)該是用于混合到 Vue 實(shí)例的 options 中的谎倔。
  • transition 在渲染是操作style做過渡效果處理柳击。
  • utils 工具文件夾。

主要目的是項(xiàng)目結(jié)構(gòu)片习,就不深入展開了捌肴。如有需要后面再講。

其他目錄

上面說過藕咏,package 目錄中存放了所有 component 組件的代碼状知。另外也存放了組件的樣式 .scss 文件。
而對于type目錄中孽查,存放的 .ts 文件饥悴。都是 TypeScript 文件。但是有個(gè)問題盲再,我不太清楚這些 .ts 都用在何處西设。而且在 package.json 中也未導(dǎo)入 TypeScript 的庫,只是在更新日志中有 新增 TypeScript 類型聲明 這么一句話答朋。這點(diǎn)有所疑惑贷揽。
test 目錄下是各個(gè)組件的單元測試用例,這部分是學(xué)習(xí)單元測試寫法的很好的參考代碼(我學(xué)習(xí)測試框架就是在這里學(xué)的)梦碗。需要學(xué)習(xí)單元測試的可以深入看看禽绪。
example 目錄下是 element 的示例項(xiàng)目蓖救。我們的目的是學(xué)習(xí)源碼,所以這部分先忽略~

最后

簡單了解了下項(xiàng)目的編譯印屁、項(xiàng)目的依賴庫情況循捺、項(xiàng)目的機(jī)構(gòu)。下一篇開始學(xué)習(xí)一些組件的實(shí)現(xiàn)雄人。逐步深入扒開element的神秘面紗从橘。

打個(gè)廣告

上海鏈家-鏈家上海研發(fā)中心需求大量前端、后端柠衍、測試,需要內(nèi)推請將簡歷發(fā)送至 dingxiaojie001@ke.com晶乔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末珍坊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子正罢,更是在濱河造成了極大的恐慌阵漏,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翻具,死亡現(xiàn)場離奇詭異履怯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)裆泳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門叹洲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人工禾,你說我怎么就攤上這事运提。” “怎么了闻葵?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵民泵,是天一觀的道長。 經(jīng)常有香客問我槽畔,道長栈妆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任厢钧,我火速辦了婚禮鳞尔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘早直。我一直安慰自己铅檩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布莽鸿。 她就那樣靜靜地躺著昧旨,像睡著了一般拾给。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兔沃,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天蒋得,我揣著相機(jī)與錄音,去河邊找鬼乒疏。 笑死额衙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的怕吴。 我是一名探鬼主播窍侧,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼转绷!你這毒婦竟也來了伟件?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤议经,失蹤者是張志新(化名)和其女友劉穎斧账,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體煞肾,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咧织,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了籍救。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片习绢。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝙昙,靈堂內(nèi)的尸體忽然破棺而出毯炮,到底是詐尸還是另有隱情,我是刑警寧澤耸黑,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布桃煎,位于F島的核電站,受9級(jí)特大地震影響大刊,放射性物質(zhì)發(fā)生泄漏为迈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一缺菌、第九天 我趴在偏房一處隱蔽的房頂上張望葫辐。 院中可真熱鬧,春花似錦伴郁、人聲如沸耿战。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽剂陡。三九已至狈涮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸭栖,已是汗流浹背歌馍。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晕鹊,地道東北人松却。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像溅话,于是被迫代替她去往敵國和親晓锻。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,285評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理飞几,服務(wù)發(fā)現(xiàn)砚哆,斷路器,智...
    卡卡羅2017閱讀 134,701評(píng)論 18 139
  • 稀里糊涂地考了教師資格證循狰,然后當(dāng)了老師窟社。 跟大學(xué)專業(yè)沒有絲毫關(guān)系券勺。 一年級(jí)的孩子绪钥,讓人每天頭都要炸了。 完全沒有教...
    Pakaki閱讀 328評(píng)論 0 1
  • 摘自《創(chuàng)業(yè)維艱:如何完成比難更難的事》,只做細(xì)微修改儒拂。 好的產(chǎn)品經(jīng)理極其了解市場寸潦、產(chǎn)品、生產(chǎn)線和競爭情況社痛,憑借自己...
    蕭子一閱讀 430評(píng)論 0 0
  • 01 近期迷上了一個(gè)節(jié)目蒜哀,早就聽說過斩箫,但一直沒去看。 它就是《非凡匠心》撵儿,是一檔全新兩代匠心文化體驗(yàn)類節(jié)目乘客。 ...
    諳忞閱讀 1,629評(píng)論 0 3