摘要:可以看到,這樣不僅沒有占用組件自己的,也不需要手寫回調函數進行處理,這些處理都壓縮成了一行。效果通過拿到周期才執行的回調函數。實現等價于的回調僅執行一次時,因此直接把回調函數拋出來即可。
1 引言
上周的 精讀《React Hooks》 已經實現了對 React Hooks 的基本認知,也許你也看了 React Hooks 基本實現剖析(就是數組),但理解實現原理就可以用好了嗎?學的是知識,而用的是技能,看別人的用法就像刷抖音一樣(哇,飯還可以這樣吃?),你總會有新的收獲。
這篇文章將這些知識實踐起來,看看廣大程序勞動人民是如何發掘 React Hooks 的潛力的(造什么輪子)。
首先,站在使用角度,要理解 React Hooks 的特點是 “非常方便的 Connect 一切”,所以無論是數據流、Network,或者是定時器都可以監聽,有一點 RXJS 的意味,也就是你可以利用 React Hooks,將 React 組件打造成:任何事物的變化都是輸入源,當這些源變化時會重新觸發 React 組件的 render,你只需要挑選組件綁定哪些數據源(use 哪些 Hooks),然后只管寫 render 函數就行了!
2 精讀參考了部分 React Hooks 組件后,筆者按照功能進行了一些分類。
由于 React Hooks 并不是非常復雜,所以就不按照技術實現方式去分類了,畢竟技術總有一天會熟練,而且按照功能分類才有持久的參考價值。DOM 副作用修改 / 監聽
做一個網頁,總有一些看上去和組件關系不大的麻煩事,比如修改頁面標題(切換頁面記得改成默認標題)、監聽頁面大小變化(組件銷毀記得取消監聽)、斷網時提示(一層層裝飾器要堆成小山了)。而 React Hooks 特別擅長做這些事,造這種輪子,大小皆宜。
由于 React Hooks 降低了高階組件使用成本,那么一套生命周期才能完成的 “雜?!?將變得非常簡單。
下面舉幾個例子:
修改頁面 title效果:在組件里調用 useDocumentTitle 函數即可設置頁面標題,且切換頁面時,頁面標題重置為默認標題 “前端精讀”。
useDocumentTitle("個人中心");
實現:直接用 document.title 賦值,不能再簡單。在銷毀時再次給一個默認標題即可,這個簡單的函數可以抽象在項目工具函數里,每個頁面組件都需要調用。
function useDocumentTitle(title) { useEffect( () => { document.title = title; return () => (document.title = "前端精讀"); }, [title] ); }
在線 Demo
監聽頁面大小變化,網絡是否斷開效果:在組件調用 useWindowSize 時,可以拿到頁面大小,并且在瀏覽器縮放時自動觸發組件更新。
const windowSize = useWindowSize(); return頁面高度:{windowSize.innerWidth};
實現:和標題思路基本一致,這次從 window.innerHeight 等 API 直接拿到頁面寬高即可,注意此時可以用 window.addEventListener("resize") 監聽頁面大小變化,此時調用 setValue 將會觸發調用自身的 UI 組件 rerender,就是這么簡單!
最后注意在銷毀時,removeEventListener 注銷監聽。
function getSize() { return { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }; } function useWindowSize() { let [windowSize, setWindowSize] = useState(getSize()); function handleResize() { setWindowSize(getSize()); } useEffect(() => { window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); return windowSize; }
在線 Demo
動態注入 css效果:在頁面注入一段 class,并且當組件銷毀時,移除這個 class。
const className = useCss({ color: "red" }); returnText.;
實現:可以看到,Hooks 方便的地方是在組件銷毀時移除副作用,所以我們可以安心的利用 Hooks 做一些副作用。注入 css 自然不必說了,而銷毀 css 只要找到注入的那段引用進行銷毀即可,具體可以看這個 代碼片段。
DOM 副作用修改 / 監聽場景有一些現成的庫了,從名字上就能看出來用法:document-visibility、network-status、online-status、window-scroll-position、window-size、document-title。組件輔助
Hooks 還可以增強組件能力,比如拿到并監聽組件運行時寬高等。
獲取組件寬高效果:通過調用 useComponentSize 拿到某個組件 ref 實例的寬高,并且在寬高變化時,rerender 并拿到最新的寬高。
const ref = useRef(null); let componentSize = useComponentSize(ref); return ( <> {componentSize.width} > );
實現:和 DOM 監聽類似,這次換成了利用 ResizeObserver 對組件 ref 進行監聽,同時在組件銷毀時,銷毀監聽。
其本質還是監聽一些副作用,但通過 ref 的傳遞,我們可以對組件粒度進行監聽和操作了。
useLayoutEffect(() => { handleResize(); let resizeObserver = new ResizeObserver(() => handleResize()); resizeObserver.observe(ref.current); return () => { resizeObserver.disconnect(ref.current); resizeObserver = null; }; }, []);
在線 Demo,對應組件 component-size。
拿到組件 onChange 拋出的值效果:通過 useInputValue() 拿到 Input 框當前用戶輸入的值,而不是手動監聽 onChange 再騰一個 otherInputValue 和一個回調函數把這一堆邏輯寫在無關的地方。
let name = useInputValue("Jamie"); // name = { value: "Jamie", onChange: [Function] } return ;
可以看到,這樣不僅沒有占用組件自己的 state,也不需要手寫 onChange 回調函數進行處理,這些處理都壓縮成了一行 use hook。
實現:讀到這里應該大致可以猜到了,利用 useState 存儲組件的值,并拋出 value 與 onChange,監聽 onChange 并通過 setValue 修改 value, 就可以在每次 onChange 時觸發調用組件的 rerender 了。
function useInputValue(initialValue) { let [value, setValue] = useState(initialValue); let onChange = useCallback(function(event) { setValue(event.currentTarget.value); }, []); return { value, onChange }; }
這里要注意的是,我們對組件增強時,組件的回調一般不需要銷毀監聽,而且僅需監聽一次,這與 DOM 監聽不同,因此大部分場景,我們需要利用 useCallback 包裹,并傳一個空數組,來保證永遠只監聽一次,而且不需要在組件銷毀時注銷這個 callback。
在線 Demo,對應組件 input-value。
做動畫利用 React Hooks 做動畫,一般是拿到一些具有彈性變化的值,我們可以將值賦給進度條之類的組件,這樣其進度變化就符合某種動畫曲線。
在某個時間段內獲取 0-1 之間的值這個是動畫最基本的概念,某個時間內拿到一個線性增長的值。
效果:通過 useRaf(t) 拿到 t 毫秒內不斷刷新的 0-1 之間的數字,期間組件會不斷刷新,但刷新頻率由 requestAnimationFrame 控制(不會卡頓 UI)。
const value = useRaf(1000);
實現:寫起來比較冗長,這里簡單描述一下。利用 requestAnimationFrame 在給定時間內給出 0-1 之間的值,那每次刷新時,只要判斷當前刷新的時間點占總時間的比例是多少,然后做分母,分子是 1 即可。
在線 Demo,對應組件 use-raf。
彈性動畫效果:通過 useSpring 拿到動畫值,組件以固定頻率刷新,而這個動畫值以彈性函數進行增減。
實際調用方式一般是,先通過 useState 拿到一個值,再通過動畫函數包住這個值,這樣組件就會從原本的刷新一次,變成刷新 N 次,拿到的值也隨著動畫函數的規則變化,最后這個值會穩定到最終的輸入值(如例子中的 50)。
const [target, setTarget] = useState(50); const value = useSpring(target); returnsetTarget(100)}>{value};
實現:為了實現動畫效果,需要依賴 rebound 庫,它可以實現將一個目標值拆解為符合彈性動畫函數過程的功能,那我們需要利用 React Hooks 做的就是在第一次接收到目標值是,調用 spring.setEndValue 來觸發動畫事件,并在 useEffect 里做一次性監聽,再值變時重新 setValue 即可。
最神奇的 setTarget 聯動 useSpring 重新計算彈性動畫部分,是通過 useEffect 第二個參數實現的:
useEffect( () => { if (spring) { spring.setEndValue(targetValue); } }, [targetValue] );
也就是當目標值變化后,才會進行新的一輪 rerender,所以 useSpring 并不需要監聽調用處的 setTarget,它只需要監聽 target 的變化即可,而巧妙利用 useEffect 的第二個參數可以事半功倍。
在線 Demo
Tween 動畫明白了彈性動畫原理,Tween 動畫就更簡單了。
效果:通過 useTween 拿到一個從 0 變化到 1 的值,這個值的動畫曲線是 tween。可以看到,由于取值范圍是固定的,所以我們不需要給初始值了。
const value = useTween();
實現:通過 useRaf 拿到一個線性增長的值(區間也是 0 ~ 1),再通過 easing 庫將其映射到 0 ~ 1 到值即可。這里用到了 hook 調用 hook 的聯動(通過 useRaf 驅動 useTween),還可以在其他地方舉一反三。
const fn: Easing = easing[easingName]; const t = useRaf(ms, delay); return fn(t);發請求
利用 Hooks,可以將任意請求 Promise 封裝為帶有標準狀態的對象:loading、error、result。
通用 Http 封裝效果:通過 useAsync 將一個 Promise 拆解為 loading、error、result 三個對象。
const { loading, error, result } = useAsync(fetchUser, [id]);
實現:在 Promise 的初期設置 loading,結束后設置 result,如果出錯則設置 error,這里可以將請求對象包裝成 useAsyncState 來處理,這里就不放出來了。
export function useAsync(asyncFunction) { const asyncState = useAsyncState(options); useEffect(() => { const promise = asyncFunction(); asyncState.setLoading(); promise.then( result => asyncState.setResult(result);, error => asyncState.setError(error); ); }, params); }
具體代碼可以參考 react-async-hook,這個功能建議僅了解原理,具體實現因為有一些邊界情況需要考慮,比如組件 isMounted 后才能相應請求結果。
Request Service業務層一般會抽象一個 request service 做統一取數的抽象(比如統一 url,或者可以統一換 socket 實現等等)。假如以前比較 low 的做法是:
async componentDidMount() { // setState: 改 isLoading state try { const data = await fetchUser() // setState: 改 isLoading、error、data } catch (error) { // setState: 改 isLoading、error } }
后來把請求放在 redux 里,通過 connect 注入的方式會稍微有些改觀:
@Connect(...) class App extends React.PureComponent { public componentDidMount() { this.props.fetchUser() } public render() { // this.props.userData.isLoading | error | data } }
最后會發現還是 Hooks 簡潔明了:
function App() { const { isLoading, error, data } = useFetchUser(); }
而 useFetchUser 利用上面封裝的 useAsync 可以很容易編寫:
const fetchUser = id => fetch(`xxx`).then(result => { if (result.status !== 200) { throw new Error("bad status = " + result.status); } return result.json(); }); function useFetchUser(id) { const asyncFetchUser = useAsync(fetchUser, id); return asyncUser; }填表單
React Hooks 特別適合做表單,尤其是 antd form 如果支持 Hooks 版,那用起來會方便許多:
function App() { const { getFieldDecorator } = useAntdForm(); return (); }
不過雖然如此,getFieldDecorator 還是基于 RenderProps 思路的,徹底的 Hooks 思路是利用之前說的 組件輔助方式,提供一個組件方法集,用解構方式傳給組件。
Hooks 思維的表單組件效果:通過 useFormState 拿到表單值,并且提供一系列 組件輔助 方法控制組件狀態。
const [formState, { text, password }] = useFormState(); return ();
上面可以通過 formState 隨時拿到表單值,和一些校驗信息,通過 password("pwd") 傳給 input 組件,讓這個組件達到受控狀態,且輸入類型是 password 類型,表單 key 是 pwd。而且可以看到使用的 form 是原生標簽,這種表單增強是相當解耦的。
實現:仔細觀察一下結構,不難發現,我們只要結合 組件輔助 小節說的 “拿到組件 onChange 拋出的值” 一節的思路,就能輕松理解 text、password 是如何作用于 input 組件,并拿到其輸入狀態。
往簡單的來說,只要把這些狀態 Merge 起來,通過 useReducer 聚合到 formState 就可以實現了。
為了簡化,我們只考慮對 input 的增強,源碼僅需 30 幾行:
export function useFormState(initialState) { const [state, setState] = useReducer(stateReducer, initialState || {}); const createPropsGetter = type => (name, ownValue) => { const hasOwnValue = !!ownValue; const hasValueInState = state[name] !== undefined; function setInitialValue() { let value = ""; setState({ [name]: value }); } const inputProps = { name, // 給 input 添加 type: text or password get value() { if (!hasValueInState) { setInitialValue(); // 給初始化值 } return hasValueInState ? state[name] : ""; // 賦值 }, onChange(e) { let { value } = e.target; setState({ [name]: value }); // 修改對應 Key 的值 } }; return inputProps; }; const inputPropsCreators = ["text", "password"].reduce( (methods, type) => ({ ...methods, [type]: createPropsGetter(type) }), {} ); return [ { values: state }, // formState inputPropsCreators ]; }
上面 30 行代碼實現了對 input 標簽類型的設置,監聽 value onChange,最終聚合到大的 values 作為 formState 返回。讀到這里應該發現對 React Hooks 的應用都是萬變不離其宗的,特別是對組件信息的獲取,通過解構方式來做,Hooks 內部再做一下聚合,就完成表單組件基本功能了。
實際上一個完整的輪子還需要考慮 checkbox radio 的兼容,以及校驗問題,這些思路大同小異,具體源碼可以看 react-use-form-state。
模擬生命周期有的時候 React15 的 API 還是挺有用的,利用 React Hooks 幾乎可以模擬出全套。
componentDidMount效果:通過 useMount 拿到 mount 周期才執行的回調函數。
useMount(() => { // quite similar to `componentDidMount` });
實現:componentDidMount 等價于 useEffect 的回調(僅執行一次時),因此直接把回調函數拋出來即可。
useEffect(() => void fn(), []);componentWillUnmount
效果:通過 useUnmount 拿到 unmount 周期才執行的回調函數。
useUnmount(() => { // quite similar to `componentWillUnmount` });
實現:componentWillUnmount 等價于 useEffect 的回調函數返回值(僅執行一次時),因此直接把回調函數返回值拋出來即可。
useEffect(() => fn, []);componentDidUpdate
效果:通過 useUpdate 拿到 didUpdate 周期才執行的回調函數。
useUpdate(() => { // quite similar to `componentDidUpdate` });
實現:componentDidUpdate 等價于 useMount 的邏輯每次執行,除了初始化第一次。因此采用 mouting flag(判斷初始狀態)+ 不加限制參數確保每次 rerender 都會執行即可。
const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } });Force Update
效果:這個最有意思了,我希望拿到一個函數 update,每次調用就強制刷新當前組件。
const update = useUpdate();
實現:我們知道 useState 下標為 1 的項是用來更新數據的,而且就算數據沒有變化,調用了也會刷新組件,所以我們可以把返回一個沒有修改數值的 setValue,這樣它的功能就僅剩下刷新組件了。
const useUpdate = () => useState(0)[1];
對于 getSnapshotBeforeUpdate, getDerivedStateFromError, componentDidCatch 目前 Hooks 是無法模擬的。isMounted
很久以前 React 是提供過這個 API 的,后來移除了,原因是可以通過 componentWillMount 和 componentWillUnmount 推導。自從有了 React Hooks,支持 isMount 簡直是分分鐘的事。
效果:通過 useIsMounted 拿到 isMounted 狀態。
const isMounted = useIsMounted();
實現:看到這里的話,應該已經很熟悉這個套路了,useEffect 第一次調用時賦值為 true,組件銷毀時返回 false,注意這里可以加第二個參數為空數組來優化性能。
const [isMount, setIsMount] = useState(false); useEffect(() => { if (!isMount) { setIsMount(true); } return () => setIsMount(false); }, []); return isMount;
在線 Demo
存數據上一篇提到過 React Hooks 內置的 useReducer 可以模擬 Redux 的 reducer 行為,那唯一需要補充的就是將數據持久化。我們考慮最小實現,也就是全局 Store + Provider 部分。
全局 Store效果:通過 createStore 創建一個全局 Store,再通過 StoreProvider 將 store 注入到子組件的 context 中,最終通過兩個 Hooks 進行獲取與操作:useStore 與 useAction:
const store = createStore({ user: { name: "小明", setName: (state, payload) => { state.name = payload; } } }); const App = () => (); function YourApp() { const userName = useStore(state => state.user.name); const setName = userAction(dispatch => dispatch.user.setName); }
實現:這個例子的實現可以多帶帶拎出一篇文章了,所以筆者從存數據的角度剖析一下 StoreProvider 的實現。
對,Hooks 并不解決 Provider 的問題,所以全局狀態必須有 Provider,但這個 Provider 可以利用 React 內置的 createContext 簡單搞定:
const StoreContext = createContext(); const StoreProvider = ({ children, store }) => ({children} );
剩下就是 useStore 怎么取到持久化 Store 的問題了,這里利用 useContext 和剛才創建的 Context 對象:
const store = useContext(StoreContext); return store;
更多源碼可以參考 easy-peasy,這個庫基于 redux 編寫,提供了一套 Hooks API。
封裝原有庫是不是 React Hooks 出現后,所有的庫都要重寫一次?當然不是,我們看看其他庫如何做改造。
RenderProps to Hooks這里拿 react-powerplug 舉例。
比如有一個 renderProps 庫,希望改造成 Hooks 的用法:
import { Toggle } from "react-powerplug" function App() { return ({({ on, toggle }) => ( ) } ↓ ↓ ↓ ↓ ↓ ↓ import { useToggle } from "react-powerhooks" function App() { const [on, toggle] = useToggle() return)} }
效果:假如我是 react-powerplug 的維護者,怎么樣最小成本支持 React Hook? 說實話這個沒辦法一步做到,但可以通過兩步實現。
export function Toggle() { // 這是 Toggle 的源碼 // balabalabala.. } const App = wrap(() => { // 第一步:包 wrap const [on, toggle] = useRenderProps(Toggle); // 第二步:包 useRenderProps });
實現:首先解釋一下為什么要包兩層,首先 Hooks 必須遵循 React 的規范,我們必須寫一個 useRenderProps 函數以符合 Hooks 的格式,那問題是如何拿到 Toggle 給 render 的 on 與 toggle?正常方式應該拿不到,所以退而求其次,將 useRenderProps 拿到的 Toggle 傳給 wrap,讓 wrap 構造 RenderProps 執行環境拿到 on 與 toggle 后,調用 useRenderProps 內部的 setArgs 函數,讓 const [on, toggle] = useRenderProps(Toggle) 實現曲線救國。
const wrappers = []; // 全局存儲 wrappers export const useRenderProps = (WrapperComponent, wrapperProps) => { const [args, setArgs] = useState([]); const ref = useRef({}); if (!ref.current.initialized) { wrappers.push({ WrapperComponent, wrapperProps, setArgs }); } useEffect(() => { ref.current.initialized = true; }, []); return args; // 通過下面 wrap 調用 setArgs 獲取值。 };
由于 useRenderProps 會先于 wrap 執行,所以 wrappers 會先拿到 Toggle,wrap 執行時直接調用 wrappers.pop() 即可拿到 Toggle 對象。然后構造出 RenderProps 的執行環境即可:
export const wrap = FunctionComponent => props => { const element = FunctionComponent(props); const ref = useRef({ wrapper: wrappers.pop() }); // 拿到 useRenderProps 提供的 Toggle const { WrapperComponent, wrapperProps } = ref.current.wrapper; return createElement(WrapperComponent, wrapperProps, (...args) => { // WrapperComponent => Toggle,這一步是在構造 RenderProps 執行環境 if (!ref.current.processed) { ref.current.wrapper.setArgs(args); // 拿到 on、toggle 后,通過 setArgs 傳給上面的 args。 ref.current.processed = true; } else { ref.current.processed = false; } return element; }); };
以上實現方案參考 react-hooks-render-props,有需求要可以拿過來直接用,不過實現思路可以參考,作者的腦洞挺大。
Hooks to RenderProps好吧,如果希望 Hooks 支持 RenderProps,那一定是希望同時支持這兩套語法。
效果:一套代碼同時支持 Hooks 和 RenderProps。
實現:其實 Hooks 封裝為 RenderProps 最方便,因此我們使用 Hooks 寫核心的代碼,假設我們寫一個最簡單的 Toggle:
const useToggle = initialValue => { const [on, setOn] = useState(initialValue); return { on, toggle: () => setOn(!on) }; };
在線 Demo
然后通過 render-props 這個庫可以輕松封裝出 RenderProps 組件:
const Toggle = ({ initialValue, children, render = children }) => renderProps(render, useToggle(initialValue));
在線 Demo
其實 renderProps 這個組件的第二個參數,在 Class 形式 React 組件時,接收的是 this.state,現在我們改成 useToggle 返回的對象,也可以理解為 state,利用 Hooks 機制驅動 Toggle 組件 rerender,從而讓子組件 rerender。
封裝原本對 setState 增強的庫Hooks 也特別適合封裝原本就作用于 setState 的庫,比如 immer。
useState 雖然不是 setState,但卻可以理解為控制高階組件的 setState,我們完全可以封裝一個自定義的 useState,然后內置對 setState 的優化。
比如 immer 的語法是通過 produce 包裝,將 mutable 代碼通過 Proxy 代理為 immutable:
const nextState = produce(baseState, draftState => { draftState.push({ todo: "Tweet about it" }); draftState[1].done = true; });
那這個 produce 就可以通過封裝一個 useImmer 來隱藏掉:
function useImmer(initialValue) { const [val, updateValue] = React.useState(initialValue); return [ val, updater => { updateValue(produce(updater)); } ]; }
使用方式:
const [value, setValue] = useImmer({ a: 1 }); value(obj => (obj.a = 2)); // immutable3 總結
本文列出了 React Hooks 的以下幾種使用方式以及實現思路:
DOM 副作用修改 / 監聽。
組件輔助。
做動畫。
發請求。
填表單。
模擬生命周期。
存數據。
封裝原有庫。
歡迎大家的持續補充。
4 更多討論討論地址是:精讀《怎么用 React Hooks 造輪子》 · Issue #112 · dt-fe/weekly
如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。前端精讀 - 幫你篩選靠譜的內容。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108696.html
摘要:未來可能成為官方之一。討論地址是精讀組件如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。前端精讀幫你篩選靠譜的內容。 1. 引言 為什么要了解 Function 寫法的組件呢?因為它正在變得越來越重要。 那么 React 中 Function Component 與 Class Component 有何不同? how-are-function-components-di...
摘要:拿到的都是而不是原始值,且這個值會動態變化。精讀對于的與,筆者做一些對比。因此采取了作為優化方案只有當第二個依賴參數變化時才返回新引用。不需要使用等進行性能優化,所有性能優化都是自動的。前端精讀幫你篩選靠譜的內容。 1. 引言 Vue 3.0 的發布引起了軒然大波,讓我們解讀下它的 function api RFC 詳細了解一下 Vue 團隊是怎么想的吧! 首先官方回答了幾個最受關注的...
摘要:今天我們就來解讀一下的源碼。比較有意思,將定時器以方式提供出來,并且提供了方法。實現方式是,在組件內部維護一個定時器,實現了組件更新銷毀時的計時器更新銷毀操作,可以認為這種定時器的生命周期綁定了組件的生命周期,不用擔心銷毀和更新的問題。 1. 引言 React PowerPlug 是利用 render props 進行更好狀態管理的工具庫。 React 項目中,一般一個文件就是一個類,...
摘要:引言于發布版本,時至今日已更新到,且引入了大量的令人振奮的新特性,本文章將帶領大家根據更新的時間脈絡了解的新特性。其作用是根據傳遞的來更新。新增等指針事件。 1 引言 于 2017.09.26 Facebook 發布 React v16.0 版本,時至今日已更新到 React v16.6,且引入了大量的令人振奮的新特性,本文章將帶領大家根據 React 更新的時間脈絡了解 React1...
摘要:需要說明是的,這里說的專家不再關心細節,不代表成為專家后學不會細節,也不代表專家不了解細節。本文將從三個點去解釋,為什么專家看上去越來越原理技術細節。試想一個不能理解業務要做什么的人,即便懂得再多技術細節,對業務也是沒有價值的。1. 引言 本周的精讀是有感而發。 筆者接觸前端已有八年,觀察了不少前端大牛的發展路徑,發現成功的人都具有相似的經歷: 初期技術熱情極大 -> 大量標志性技術項目 -...
閱讀 923·2023-04-26 01:34
閱讀 3356·2023-04-25 20:58
閱讀 3260·2021-11-08 13:22
閱讀 2108·2019-08-30 14:17
閱讀 2522·2019-08-29 15:27
閱讀 2673·2019-08-29 12:45
閱讀 2996·2019-08-29 12:26
閱讀 2811·2019-08-28 17:51