摘要:單元測試針對(duì)程序模塊進(jìn)行測試。是開源的單元測試工具。一個(gè)好的單元測試應(yīng)該具備的條件安全重構(gòu)已有代碼單元測試一個(gè)很重要的價(jià)值是為重構(gòu)保駕護(hù)航。斷言外部依賴單元測試的一個(gè)重要原則就是無依賴和隔離。
前端測試金字塔
對(duì)于一個(gè) Web 應(yīng)用來說,理想的測試組合應(yīng)該包含大量單元測試(unit tests),部分快照測試(snapshot tests),以及少量端到端測試(e2e tests)。參考測試金字塔,我們構(gòu)建了前端應(yīng)用的測試金字塔。
單元測試
針對(duì)程序模塊進(jìn)行測試。模塊是軟件設(shè)計(jì)中的最小單位,一個(gè)函數(shù)或者一個(gè) React 組件都可以稱之為一個(gè)模塊。單元測試運(yùn)行快,反饋周期短,在短時(shí)間內(nèi)就能夠知道是否破壞了代碼,因此在測試組合中占據(jù)了絕大部分。
快照測試
對(duì)組件的 UI 進(jìn)行測試。傳統(tǒng)的快照測試會(huì)拍攝組件的圖片,并且將它和之前的圖片進(jìn)行對(duì)比,如果兩張圖片不匹配則測試失敗。Jest 的快照測試不會(huì)拍攝圖片,而是將 React 樹序列化成字符串,通過比較兩個(gè)字符串來判斷 UI 是否改變。因?yàn)槭羌兾谋镜膶?duì)比,所以不需要構(gòu)建整個(gè)應(yīng)用,運(yùn)行速度自然比傳統(tǒng)快照測試更快。
E2E 測試
相當(dāng)于黑盒測試。測試者不需要知道程序內(nèi)部是如何實(shí)現(xiàn)的,只需要根據(jù)業(yè)務(wù)需求,模擬用戶的真實(shí)使用場景進(jìn)行測試。
測試種類 | 技術(shù)選型 |
---|---|
單元測試 | Jest + Enzyme |
快照測試 | Jest |
E2E 測試 | jest-puppeteer |
Jest?是 Facebook 開源的測試框架。它的功能很強(qiáng)大,包含了測試執(zhí)行器、斷言庫、spy、mock、snapshot 和測試覆蓋率報(bào)告等。
Enzyme?是 Airbnb 開源的 React 單元測試工具。它擴(kuò)展了 React 官方的 TestUtils,通過類 jQuery 風(fēng)格的 API?對(duì) DOM 進(jìn)行處理,減少了很多重復(fù)代碼,可以很方便的對(duì)渲染出來的結(jié)果進(jìn)行斷言。
jest-p[uppeteer]()?是一個(gè)同時(shí)包含 Jest 和 Puppeteer 的工具。Puppeteer 是谷歌官方提供的 Headless?Chrome Node API,它提供了基于?DevTools Protocol?的上層 API 接口,用來控制?Chrome 或者 Chromium。有了?Puppeteer,我們可以很方便的進(jìn)行端到端測試。
測試本質(zhì)上是對(duì)代碼的保護(hù),保證項(xiàng)目在迭代的過程中正常運(yùn)行。當(dāng)然,寫測試也是有成本的,特別是復(fù)雜邏輯,寫測試花的時(shí)間,可能不比寫代碼少。所以我們要制定合理的測試策略,有針對(duì)性的去寫測試。至于哪些代碼要測,哪些代碼不測,總的來說遵循一個(gè)原則:投入低,收益高?!竿度氲汀故侵笢y試容易寫,「收益高」是測試的價(jià)值高。換句話說,就是指測試應(yīng)該優(yōu)先保證核心代碼邏輯,比如核心業(yè)務(wù)、基礎(chǔ)模塊、基礎(chǔ)組件等,同時(shí),編寫測試和維護(hù)測試的成本也不宜過高。當(dāng)然,這是理想情況,在實(shí)際的開發(fā)過程中還是要進(jìn)行權(quán)衡。
單元測試基于 React 和 Redux 項(xiàng)目的特點(diǎn),我們制定了下面的測試策略:
分類 | 哪些要測? | 哪些不測? |
---|---|---|
組件 |
有條件渲染的組件(如 if-else 分支,聯(lián)動(dòng)組件,權(quán)限控制組件等) 有用戶交互的組件(如 Click、提交表單等) * 邏輯組件(如高階組件和 Children Render 組件) |
connect 生成的容器組件 純組合子組件的 Page 組件 純展示的組件 組件樣式 |
Reducer | 有邏輯的 Reducer。如合并、刪除? state。 | 純?nèi)≈档?reducer 不測。比如 (_, action) => action.payload.data? |
Middleware | 全測 | 無 |
Action Creator | 無 | 全不測 |
方法 |
validators formatters * 其他公有方法 |
私有方法 |
公用模塊 | 全測。比如處理 API 請(qǐng)求的模塊。 | 無 |
Note: 如果使用了 TypeScript,類型約束可以替代部分函數(shù)入?yún)⒑头祷刂殿愋偷臋z查。快照測試
Jest 的 snapshot 測試雖然運(yùn)行起來很快,也能夠起到一定保護(hù) UI 的作用。但是它維護(hù)起來很困難(大量依賴人工對(duì)比),并且有時(shí)候不穩(wěn)定(UI 無變化但 className 變化仍然會(huì)導(dǎo)致測試失敗)。因此,個(gè)人不推薦在項(xiàng)目中使用。但是為了應(yīng)付測試覆蓋率,以及「給自己信心」,也可以給以下部分添加?snapshot 測試:
Page 組件:一個(gè) page 對(duì)應(yīng)一個(gè) snapshot。
純展示的公用 UI 組件。
快照測試可以等整個(gè) Page 或者 UI 組件構(gòu)建完成之后再添加,以保證穩(wěn)定。
E2E 測試覆蓋核心的業(yè)務(wù) flow。
一個(gè)好的單元測試應(yīng)該具備的條件? 安全重構(gòu)已有代碼單元測試一個(gè)很重要的價(jià)值是為重構(gòu)保駕護(hù)航。當(dāng)輸入不變時(shí),當(dāng)且僅當(dāng)「被測業(yè)務(wù)代碼功能被改動(dòng)了」時(shí),測試才應(yīng)該掛掉。也就是說,無論怎么重構(gòu),測試都不應(yīng)該掛掉。
在寫組件測試時(shí),我們常常遇到這樣的情況:用 css class 選擇器選中一個(gè)節(jié)點(diǎn),然后對(duì)它進(jìn)行斷言,那么即使業(yè)務(wù)邏輯沒有發(fā)生變化,重命名這個(gè) class 時(shí)也會(huì)使測試掛掉。理論上來說,這樣的測試并不算一個(gè)「好的測試」,但是考慮到它的業(yè)務(wù)價(jià)值,我們還是會(huì)寫一些這樣的測試,只不過寫測試的時(shí)候需要注意:使用一些不容易發(fā)生變化的選擇器,比如 component name、arial-label 等。
保存業(yè)務(wù)上下文我們經(jīng)常說測試即文檔,沒錯(cuò),一個(gè)好的測試往往能夠非常清晰的表單業(yè)務(wù)或代碼的含義。
快速回歸快速回歸是指測試運(yùn)行速度快,且穩(wěn)定。要想運(yùn)行速度快,很重要的一點(diǎn)是 mock 好外部依賴。至于怎么具體怎么 mock 外部依賴,后面會(huì)詳細(xì)說明。
單元測試怎么寫? 定義測試名稱建議采用?BDD?的方式,即測試要接近自然語言,方便團(tuán)隊(duì)中的各個(gè)成員進(jìn)行閱讀。編寫測試用例的時(shí)候,可以參考 AC,試著將 AC 的 Give-When-Then 轉(zhuǎn)化成測試用例。
GIVEN: 準(zhǔn)備測試條件,比如渲染組件。
WHEN:在某個(gè)具體的場景下,比如點(diǎn)擊 button。
THEN:斷言
describe("add user", () => { it("when I tap add user button, expected dialog opened with 3 form fields", () => { // Given: in profile page. // Prepare test env, like render component etc. // When: button click. // Simulate button click // Then: display `add user` form, which contains username, age and phone number. // Assert form fields length to equal 3 }); });Mock 外部依賴
單元測試的一個(gè)重要原則就是無依賴和隔離。也就是說,在測試某部分代碼時(shí),我們不期望它受到其他代碼的影響。如果受到外部因素影響,測試就會(huì)變得非常復(fù)雜且不穩(wěn)定。
我們寫單元測試時(shí),遇到的最大問題就是:代碼過于復(fù)雜。比如當(dāng)頁面有 API 請(qǐng)求、日期、定時(shí)器或 redux conent 時(shí),寫測試就變得異常困難,因?yàn)槲覀冃枰ù罅繒r(shí)間去隔離這些外部依賴。
隔離外部依賴需要用到測試替代方法,常見的有 spies、stubs 和 mocks。很多測試框架都實(shí)現(xiàn)了這三種方法,比如著名的 Jest 和 Sinon。這些方法可以幫助我們?cè)跍y試中替換代碼,減少測試編寫的復(fù)雜度。
spiesspies 本質(zhì)上是一個(gè)函數(shù),它可以記錄目標(biāo)函數(shù)的調(diào)用信息,如調(diào)用次數(shù)、傳參、返回值等等,但不會(huì)改變?cè)己瘮?shù)的行為。Jest 中的?mock function?就是 spies,比如我們常用的?jest.fn()?。
// Example: onSubmit() { // some other logic here this.props.dispatch("xxx_action"); } // Example Test: it("when form submit, expected dispatch function to be called", () => { const mockDispatch = jest.fn(); mount(); // simlate submit event here expect(mockDispatch).toBeCalledWith("xxx_action"); expect(mockDispatch).toBeCalledTimes(1); });
spies 還可以用于替換屬性方法、靜態(tài)方法和原型鏈方法。由于這種修改會(huì)改變?cè)紝?duì)象,使用之后必須調(diào)用 restore 方法予以還原,因此使用的時(shí)候要特別小心。
// Example: const video = { play() { return true; }, }; // Example Test: test("plays video", () => { const spy = jest.spyOn(video, "play"); const isPlaying = video.play(); expect(spy).toHaveBeenCalled(); expect(isPlaying).toBe(true); spy.mockRestore(); });stubs
stubs 跟 spies 類似,但與 spies 不同的是,stubs 會(huì)替換目標(biāo)函數(shù)。也就是說,如果使用 spies,原始的函數(shù)依然會(huì)被調(diào)用,但使用 stubs,原始的函數(shù)就不會(huì)被執(zhí)行了。stubs 能夠保證明確的測試邊界。它可以用于以下場景:
替換讓測試變得復(fù)雜或慢的外部函數(shù),如 ajax。
測試異常條件,如拋出異常。
Jest 中也提供了類似的 API [](https://jestjs.io/docs/en/jes...[]()jest.spyOn().mockImplementation(),如下:
const spy = jest.fn(); const payload = [1, 2, 3]; jest .spyOn(jQuery, "ajax") .mockImplementation(({ success }) => success(payload)); jQuery.ajax({ url: "https://example.api", success: data => spy(data) }); expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledWith(payload);mocks
mocks 是指用自定義對(duì)象代替目標(biāo)對(duì)象。我們不僅可以 mock API 返回值和自定義類,還可以 mock npm 模塊等等。
// mock middleware api const mockMiddlewareAPI = { dispatch: jest.fn(), getState: jest.fn(), }; // mock npm module `config` jest.mock("config", () => { return { API_BASE_URL: "http://base_url", }; });
使用 mocks 時(shí),需要注意:
如果 mock 了某個(gè)模塊的依賴,需要等 mock 完成了之后再 require 這個(gè)模塊。
有如下代碼:
// counter.ts let count = 0; export const get = () => count; export const inc = () => count++; export const dec = () => count--;
錯(cuò)誤做法:
// counter.test.ts import * as counter from "../counter"; describe("counter", () => { it("get", () => { jest.mock("../counter", () => ({ get: () => "mock count", })); expect(counter.get()).toEqual("mock count"); // 測試失敗,此時(shí)的 counter 模塊并非 mock 之后的模塊。 }); });
正確做法:
describe("counter", () => { it("get", () => { jest.mock("../counter", () => ({ get: () => "mock count", })); const counter = require("../counter"); // 這里的 counter 是 mock 之后的 counter expect(counter.get()).toEqual("mock count"); // 測試成功 }); });
多個(gè)測試有共享狀態(tài)時(shí),每次測試完成之后需要重置模塊 jest.resetModules()?。它會(huì)清空所有 required 模塊的緩存,保證模塊之間的隔離。
錯(cuò)誤的做法:
describe("counter", () => { it("inc", () => { const counter = require("../counter"); counter.inc(); expect(counter.get()).toEqual(1); }); it("get", () => { const counter = require("../counter"); // 這里的 counter 和上一個(gè)測試中的 counter 是同一份拷貝 expect(counter.get()).toEqual(0); // 測試失敗 console.log(counter.get()); // ? 輸出: 1 }); });
正確的做法:
describe("counter", () => { afterEach(() => { jest.resetModules(); // 清空 required modules 的緩存 }); it("inc", () => { const counter = require("../counter"); counter.inc(); expect(counter.get()).toEqual(1); }); it("get", () => { const counter = require("../counter"); // 這里的 counter 和上一個(gè)測試中的 counter 是不同的拷貝 expect(counter.get()).toEqual(0); // 測試成功 console.log(counter.get()); // ? 輸出: 0 }); });
修改代碼,從一個(gè)外部模塊 defaultCount 中獲取?count 的默認(rèn)值。
// defaultCount.ts export const defaultCount = 0; // counter.ts import {defaultCount} from "./defaultCount"; let count = defaultCount; export const inc = () => count++; export const dec = () => count--; export const get = () => count;
測試代碼:
import * as counter from "../counter"; // 首次導(dǎo)入 counter 模塊 console.log(counter); describe("counter", () => { it("inc", () => { jest.mock("../defaultCount", () => ({ defaultCount: 10, })); const counter1 = require("../counter"); // 再次導(dǎo)入 counter 模塊 counter1.inc(); expect(counter1.get()).toEqual(11); // 測試失敗 console.log(counter1.get()); // 輸出: 1 }); });
再次 require counter 時(shí),發(fā)現(xiàn)模塊已經(jīng)被 require 過了,就直接從緩存中獲取,所以 counter1 使用的還是counter 的上下文,也就是 defaultCount = 0。而調(diào)用?resetModules() 會(huì)清空 cache,重新調(diào)用模塊函數(shù)。
在上面的代碼中,注釋掉 1,2 行,測試也會(huì)成功。大家可以想想為什么?編寫測試 組件測試
要對(duì)組件進(jìn)行測試,首先要將組件渲染出來。Enzyme 提供了三種渲染方式: 淺渲染、全渲染以及靜態(tài)渲染。
shallow 方法會(huì)把組件渲染成 Virtual DOM 對(duì)象,只會(huì)渲染組件中的第一層,不會(huì)渲染它的子組件,因此不需要關(guān)心 DOM 和執(zhí)行環(huán)境,測試的運(yùn)行速度很快。
淺渲染對(duì)上層組件非常有用。上層組件往往包含很多子組件(比如 App 或 Page 組件),如果將它的子組件全部渲染出來,就意味著上層組件的測試要依賴于子組件的行為,這樣不僅使測試變得更加困難,也大大降低了效率,不符合單元測試的原則。
淺渲染也有天生的缺點(diǎn),因?yàn)樗荒茕秩疽患?jí)節(jié)點(diǎn)。如果要測試子節(jié)點(diǎn),又不想全渲染怎么辦呢?shallow?還提供了一個(gè)很好用的接口 .dive,通過它可以獲取 wrapper 子節(jié)點(diǎn)的 React DOM 結(jié)構(gòu)。
示例代碼:
export const Demo = () => ();
使用 shallow?后得到如下結(jié)構(gòu):
使用 .dive()?后得到如下結(jié)構(gòu):
mount 方法會(huì)把組件渲染成真實(shí)的 DOM 節(jié)點(diǎn)。如果你的測試依賴于真實(shí)的 DOM 節(jié)點(diǎn)或者子組件,那就必須使用 mount 方法。特別是大量使用 Child Render 的組件,很多時(shí)候測試會(huì)依賴 Child Render 里面的內(nèi)容,因此需要需要用全渲染,將子組件也渲染出來。
全渲染方式需要瀏覽器環(huán)境,不過 Jest 已經(jīng)提供了,它的默認(rèn)的運(yùn)行環(huán)境 jsdom?,就是一個(gè) JavaScript 瀏覽器環(huán)境。需要注意的是,如果多個(gè)測試依賴了同一個(gè) DOM,它們可能會(huì)相互影響,因此在每個(gè)測試結(jié)束之后,最好使用 .unmount()?進(jìn)行清理。
將組件渲染成靜態(tài)的 HTML 字符串,然后使用 Cheerio?對(duì)其進(jìn)行解析,返回一個(gè) Cheerio 實(shí)例對(duì)象,可以用來分析組件的 HTML 結(jié)構(gòu)。
我們常常會(huì)用到條件渲染,也就是在滿足不同條件時(shí),渲染不同組件。比如:
?
import React, { ReactNode } from "react"; const Container = ({ children }: { children: ReactNode }) =>{children}; const CompA = ({ children }: { children: ReactNode }) =>{children}; const List = () =>List Component; interface IDemoListProps { list: string[]; } export const DemoList = ({ list }: IDemoListProps) => (); {list.length > 0 ? : null}
對(duì)于條件渲染,這里提供了兩種思路:
測試是否渲染了正確節(jié)點(diǎn)
一般的做法是將?DemoList 組件渲染出來,再根據(jù)不同的條件,去檢查是否渲染出了正確的節(jié)點(diǎn)。
describe("DemoList", () => { it("when list length is more than 0, expected to render List component", () => { const wrapper = shallow(); expect( wrapper .dive() .find("List") .exists(), ).toBe(true); }); it("when list length is more than 0, expected to render null", () => { const wrapper = shallow( ); expect( wrapper .dive() .find("[aria-label="container"]") .children().length, ).toBe(0); }); });
公用組件 + 只測判斷條件
我們可以抽象一個(gè)公用組件
我們可以為這個(gè)組件添加測試,確保在不同的條件下顯示正確的節(jié)點(diǎn)。既然這個(gè)邏輯得已經(jīng)得到了保證,使用
export const shouldShowBtn = (a: string, b: string, c: string) => a === b || b === c;
describe("should show button or not", () => { it("should show button", () => { expect(shouldShowBtn("x", "x", "x")).toBe(true); }); it("should hide button", () => { expect(shouldShowBtn("x", "y", "z")).toBe(false); }); });
對(duì)于有權(quán)限控制的組件,一個(gè)小的配置改變也會(huì)導(dǎo)致整個(gè)渲染的不同,而且人工測試很難發(fā)現(xiàn),這種配置多一個(gè) prop 檢查會(huì)讓代碼更加安全。
常見的有點(diǎn)擊事件、表單提交、validate 等。
點(diǎn)擊事件 click。
onSubmit?。主要是測試 onSubmit?方法被調(diào)用之后是否發(fā)生了正確的行為,如 dispatch action 。
validate?。 主要是測試 error message 是否按正確的順序顯示。
Action Creator 測試action creator 的實(shí)現(xiàn)和測試都非常簡單,這里就不舉例了。但要注意的是,不要將計(jì)算邏輯放到 aciton creator 中。
錯(cuò)誤的方式:
// action.ts export const getList = createAction("@@list/getList", (reqParams: any) => { const params = formatReqParams({ ...reqParams, page: reqParams.page + 1, startDate: formatStartDate(reqParams.startDate) endDate: formatStartDate(reqParams.endDate) }); return { url: "/api/list", method: "GET", params, }; });
正確的方式:
// action.ts export const getList = createAction("@@list/getList", (params: any) => { return { url: "/api/list", method: "GET", params, }; }); // 調(diào)用 action creator 時(shí),先把值計(jì)算好,再傳給 action creator。 // utils.ts const formatReqParams = (reqParams: any) => { return formatReqParams({ ...reqParams, page: reqParams.page + 1, startDate: formatStartDate(reqParams.startDate) endDate: formatStartDate(reqParams.endDate) }); }; // page.ts getFeedbackList(formatReqParams({}));Reducer 測試
Reducer 測試主要是測試「根據(jù) Action 和 State 是否生成了正確的 State」。因?yàn)?reducer 是純函數(shù),所以測試非常好寫,這里就不細(xì)講了。?
Middleware 測試測試 middleware 最重要的就是 mock 外部依賴,其中包括 middlewareAPI?和 next?。
Test Helper:
class MiddlewareTestHelper { static of(middleware: any) { return new MiddlewareTestHelper(middleware); } constructor(private middleware: Middleware) {} create() { const middlewareAPI = { dispatch: jest.fn(), getState: jest.fn(), }; const next = jest.fn(); const invoke$ = (action: any) => this.middleware(middlewareAPI)(next)(action); return { middlewareAPI, next, invoke$, }; } }
Example Test:
it("should handle the action", () => { const { next, invoke$ } = MiddlewareTestHelper.of(testMiddleware()).create(); invoke$({ type: "SOME_ACTION", payload: {}, }); expect(next).toBeCalled(); });測試異步代碼
默認(rèn)情況下,一旦到達(dá)運(yùn)行上下文底部,jest測試立即結(jié)束。為了解決這個(gè)問題,我們可以使用:
done() 回調(diào)函數(shù)
return promise
async/await
錯(cuò)誤的方式:
test("the data is peanut butter", () => { function callback(data) { expect(data).toBe("peanut butter"); } fetchData(callback); });
正確的方式:
test("the data is peanut butter", done => { function callback(data) { expect(data).toBe("peanut butter"); done(); } fetchData(callback); });
test("the data is peanut butter", () => { expect.assertions(1); return fetchData().then(data => { expect(data).toBe("peanut butter"); }); });
test("the data is peanut butter", async () => { const data = await fetchData(); expect(data).toBe("peanut butter"); });執(zhí)行測試
采用「紅 - 綠」的方式,即先讓測試失敗,再修改代碼讓測試通過,以確保斷言被執(zhí)行。
快照測試怎么寫?通過 redux-mock-store,將組件需要的全部數(shù)據(jù)準(zhǔn)備好(給 mock store 準(zhǔn)備 state),再進(jìn)行測試。
從測試的角度反思應(yīng)用設(shè)計(jì)「好測試」的前提是要有「好代碼」。因此我們可以從測試的角度去反思整個(gè)應(yīng)用的設(shè)計(jì),讓組件的「可測試性」更高。
單一職責(zé)。 一個(gè)組件只干一類事情,降低復(fù)雜度。只要每個(gè)小的部分能夠被正確驗(yàn)證,組合起來能夠完成整體功能,那么測試的時(shí)候,只需要專注于各個(gè)小的部分即可。
良好的復(fù)用。 即復(fù)用邏輯的同時(shí),也復(fù)用了測試。
保證最小可用,再逐漸增加功能。 也就是我們平時(shí)所說的 TDD。
...
Debugconsole.log(wrapper.debug());參考文章?
譯-Sinon入門:利用Mocks,Spies和Stubs完成javascript測試
使用Jest進(jìn)行React單元測試
對(duì) React 組件進(jìn)行單元測試
How to Rethink Your Testing
使用Enzyme測試React(Native)組件
Node.js模塊化機(jī)制原理探究
單元測試的意義、做法、經(jīng)驗(yàn)
React 單元測試策略及落地?
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/8902.html
摘要:的組件開發(fā)一直處在一個(gè)比較尷尬的處境。目錄包含了當(dāng)前組件的源碼,是組件開發(fā)最主要的目錄。許多的開發(fā)者對(duì)于依然持懷疑態(tài)度。 React Native的組件開發(fā)一直處在一個(gè)比較尷尬的處境。在官方未給予相關(guān)示例與腳手架的情況下,社區(qū)中依然誕生了許許多多的React Native組件。因?yàn)槿鄙偈纠c規(guī)范,很多組件庫僅含有一個(gè)index.js文件。這種基礎(chǔ)的目錄結(jié)構(gòu)也導(dǎo)致了一些顯而易見的問題,例...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:以下內(nèi)容來自我特別喜歡的一個(gè)頻道這是一個(gè)年你成為前端,后端或全棧開發(fā)者的進(jìn)階指南你不需要學(xué)習(xí)所有的技術(shù)成為一個(gè)開發(fā)者這個(gè)指南只是通過簡單分類列出了技術(shù)選項(xiàng)我將從我的經(jīng)驗(yàn)和參考中給出建議首選我們會(huì)介紹通用的知識(shí)最后介紹年的的一些趨勢(shì)基礎(chǔ)前端開 以下內(nèi)容來自我特別喜歡的一個(gè)Youtube頻道: Traversy Media 這是一個(gè)2019年你成為前端,后端或全棧開發(fā)者的進(jìn)階指南: 你...
摘要:前端每周清單年度總結(jié)與盤點(diǎn)在過去的八個(gè)月中,我?guī)缀踔蛔隽藘杉?,工作與整理前端每周清單。本文末尾我會(huì)附上清單線索來源與目前共期清單的地址,感謝每一位閱讀鼓勵(lì)過的朋友,希望你們能夠繼續(xù)支持未來的每周清單。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清單年度總結(jié)與盤點(diǎn) 在過去的八個(gè)月中,我?guī)缀踔蛔隽?..
摘要:感謝王下邀月熊分享的前端每周清單,為方便大家閱讀,特整理一份索引。王下邀月熊大大也于年月日整理了自己的前端每周清單系列,并以年月為單位進(jìn)行分類,具體內(nèi)容看這里前端每周清單年度總結(jié)與盤點(diǎn)。 感謝 王下邀月熊_Chevalier 分享的前端每周清單,為方便大家閱讀,特整理一份索引。 王下邀月熊大大也于 2018 年 3 月 31 日整理了自己的前端每周清單系列,并以年/月為單位進(jìn)行分類,具...
閱讀 3667·2023-04-25 20:09
閱讀 3828·2022-06-28 19:00
閱讀 3190·2022-06-28 19:00
閱讀 3225·2022-06-28 19:00
閱讀 3337·2022-06-28 19:00
閱讀 2996·2022-06-28 19:00
閱讀 3233·2022-06-28 19:00
閱讀 2764·2022-06-28 19:00