做者:RichLab 衍良javascript
編者按:這是一篇關於 React Hooks 的深度好文,閱讀 & 理解須要必定時間,你們能夠先收藏,再慢慢看。更多精彩互動歡迎移步 知乎。
React以一種全新的編程範式定義了前端開發約束,它爲視圖開發帶來了一種全新的心智模型:html
UI = F(DATA)
,這裏的F
須要負責對輸入數據進行加工、並對數據的變動作出響應F
在React裏抽象成組件,React是以組件(Component-Based)爲粒度編排應用的,組件是代碼複用的最小單元props
屬性來接收外部的數據,使用state
屬性來管理組件自身產生的數據(狀態),而爲了實現(運行時)對數據變動作出響應須要,React採用基於類(Class)的組件設計!這就是React組件設計的理論基礎,咱們最熟悉的React組件通常長這樣:前端
// React基於Class設計組件
class MyConponent extends React.Component {
// 組件自身產生的數據
state = {
counts: 0
}
// 響應數據變動
clickHandle = () => {
this.setState({ counts: this.state.counts++ });
if (this.props.onClick) this.props.onClick();
}
// lifecycle API
componentWillUnmount() {
console.log('Will mouned!');
}
// lifecycle API
componentDidMount() {
console.log('Did mouned!');
}
// 接收外來數據(或加工處理),並編排數據在視覺上的呈現
render(props) {
return (
<>
<div>Input content: {props.content}, btn click counts: {this.state.counts}</div>
<button onClick={this.clickHandle}>Add</button>
</>
);
}
}
複製代碼
組件並非單純的信息孤島,組件之間是可能會產生聯繫的,一方面是數據的共享,另外一個是功能的複用:java
Render Props
和Higher Order Component
,直到再後來的Function Component+ Hooks設計,React團隊對於組件複用的探索一直沒有中止HOC使用(老生常談)的問題:react
Render Props:git
this.props
屬性,不能像HOC那樣訪問this.props.children
一、this
的指向(語言缺陷)github
class People extends Component {
state = {
name: 'dm',
age: 18,
}
handleClick(e) {
// 報錯!
console.log(this.state);
}
render() {
const { name, age } = this.state;
return (<div onClick={this.handleClick}>My name is {name}, i am {age} years old.</div>);
}
}
複製代碼
createClass不須要處理this的指向,到了Class Component稍微不慎就會出現因this
的指向報錯。算法
二、編譯size(還有性能)問題:編程
// Class Component
class App extends Component {
state = {
count: 0
}
componentDidMount() {
console.log('Did mount!');
}
increaseCount = () => {
this.setState({ count: this.state.count + 1 });
}
decreaseCount = () => {
this.setState({ count: this.state.count - 1 });
}
render() {
return (
<>
<h1>Counter</h1>
<div>Current count: {this.state.count}</div>
<p>
<button onClick={this.increaseCount}>Increase</button>
<button onClick={this.decreaseCount}>Decrease</button>
</p>
</>
);
}
}
// Function Component
function App() {
const [ count, setCount ] = useState(0);
const increaseCount = () => setCount(count + 1);
const decreaseCount = () => setCount(count - 1);
useEffect(() => {
console.log('Did mount!');
}, []);
return (
<>
<h1>Counter</h1>
<div>Current count: {count}</div>
<p>
<button onClick={increaseCount}>Increase</button>
<button onClick={decreaseCount}>Decrease</button>
</p>
</>
);
}
複製代碼
Class Component編譯結果(Webpack):redux
var App_App = function (_Component) {
Object(inherits["a"])(App, _Component);
function App() {
var _getPrototypeOf2;
var _this;
Object(classCallCheck["a"])(this, App);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = Object(possibleConstructorReturn["a"])(this, (_getPrototypeOf2 = Object(getPrototypeOf["a"])(App)).call.apply(_getPrototypeOf2, [this].concat(args)));
_this.state = {
count: 0
};
_this.increaseCount = function () {
_this.setState({
count: _this.state.count + 1
});
};
_this.decreaseCount = function () {
_this.setState({
count: _this.state.count - 1
});
};
return _this;
}
Object(createClass["a"])(App, [{
key: "componentDidMount",
value: function componentDidMount() {
console.log('Did mount!');
}
}, {
key: "render",
value: function render() {
return react_default.a.createElement(/*...*/);
}
}]);
return App;
}(react["Component"]);
複製代碼
Function Component編譯結果(Webpack):
function App() {
var _useState = Object(react["useState"])(0),
_useState2 = Object(slicedToArray["a" /* default */ ])(_useState, 2),
count = _useState2[0],
setCount = _useState2[1];
var increaseCount = function increaseCount() {
return setCount(count + 1);
};
var decreaseCount = function decreaseCount() {
return setCount(count - 1);
};
Object(react["useEffect"])(function () {
console.log('Did mount!');
}, []);
return react_default.a.createElement();
}
複製代碼
Function類
來處理的🤔問題:React是如何識別純函數組件和類組件的?
不是全部組件都須要處理生命週期,在React發佈之初Function Component被設計了出來,用於簡化只有render時Class Component的寫法。
function Child(props) {
const handleClick = () => {
this.props.setCounts(this.props.counts);
};
// UI的變動只能經過Parent Component更新props來作到!!
return (
<>
<div>{this.props.counts}</div>
<button onClick={handleClick}>increase counts</button>
</>
);
}
class Parent extends Component() {
// 狀態管理仍是得依賴Class Component
counts = 0
render () {
const counts = this.state.counts;
return (
<>
<div>sth...</div>
<Child counts={counts} setCounts={(x) => this.setState({counts: counts++})} />
</>
);
}
}
複製代碼
因此,Function Comonent是否能脫離Class Component獨立存在,關鍵在於讓Function Comonent自身具有狀態處理能力,即在組件首次render以後,「組件自身可以經過某種機制再觸發狀態的變動而且引發re-render」,而這種「機制」就是Hooks!
Hooks的出現彌補了Function Component相對於Class Component的不足,讓Function Component取代Class Component成爲可能。
一、功能相對獨立、和render無關的部分,能夠直接抽離到hook實現,好比請求庫、登陸態、用戶核身、埋點等等,理論上裝飾器均可以改用hook實現(如react-use,提供了大量從UI、動畫、事件等經常使用功能的hook實現)。
case:Popup組件依賴視窗寬度適配自身顯示寬度、相冊組件依賴視窗寬度作單/多欄佈局適配
🤔:請自行腦補使用Class Component來如何實現
function useWinSize() {
const html = document.documentElement;
const [ size, setSize ] = useState({ width: html.clientWidth, height: html.clientHeight });
useEffect(() => {
const onSize = e => {
setSize({ width: html.clientWidth, height: html.clientHeight });
};
window.addEventListener('resize', onSize);
return () => {
window.removeEventListener('resize', onSize);
};
}, [ html ]);
return size;
}
// 依賴win寬度,適配圖片佈局
function Article(props) {
const { width } = useWinSize();
const cls = `layout-${width >= 540 ? 'muti' : 'single'}`;
return (
<>
<article>{props.content}<article>
<div className={cls}>recommended thumb list</div>
</>
);
}
// 彈層寬度根據win寬高作適配
function Popup(props) {
const { width, height } = useWinSize();
const style = {
width: width - 200,
height: height - 300,
};
return (<div style={style}>{props.content}</div>);
}
複製代碼
二、有render相關的也能夠對UI和功能(狀態)作分離,將功能放到hook實現,將狀態和UI分離
case:表單驗證
function App() {
const { waiting, errText, name, onChange } = useName();
const handleSubmit = e => {
console.log(`current name: ${name}`);
};
return (
<form onSubmit={handleSubmit}>
<>
Name: <input onChange={onChange} />
<span>{waiting ? "waiting..." : errText || ""}</span>
</>
<p>
<button>submit</button>
</p>
</form>
);
}
複製代碼
useState<S>(initialState: (() => S) | S): [S, Dispatch<BasicStateAction<S>>]
複製代碼
做用:返回一個狀態以及能修改這個狀態的setter,在其餘語言稱爲元組(tuple),一旦mount以後只能經過這個setter修改這個狀態。
思考🤔:useState爲啥不返回object而是返回tuple?
Hooks API的默認實現:
function throwInvalidHookError() {
invariant(false, 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.');
}
var ContextOnlyDispatcher = {
...
useEffect: throwInvalidHookError,
useState: throwInvalidHookError,
...
};
複製代碼
當在Function Component調用Hook:
function renderWithHooks(current, workInProgress, Component, props, refOrContext, nextRenderExpirationTime) {
currentlyRenderingFiber$1 = workInProgress; // 指針指向當前正在render的fiber節點
....
if (nextCurrentHook !== null) {
// 數據更新
ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
} else {
// 首次render
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
}
}
/// hook api的實現
HooksDispatcherOnMountInDEV = {
...
useState: function (initialState) {
currentHookNameInDev = 'useState';
...
return mountState(initialState);
},
};
複製代碼
在類組件中,state就是一個對象,對應FiberNode的memoizedState
屬性,在類組件中當調用setState()
時更新memoizedState
便可。可是在函數組件中,memoizedState
被設計成一個鏈表(Hook對象):
// Hook類型定義
type Hook = {
memoizedState: any, // 存儲最新的state
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null, // 更新隊列
next: Hook | null, // 下一個hook
}
// 定義一次更新
type Update<S, A> = {
...
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null, // 待更新狀態值
next: Update<S, A> | null,
...
};
// 待更新隊列定義
type UpdateQueue<S, A> = {
last: Update<S, A> | null, // 最後一次更新操做
dispatch: (A => mixed) | null,
lastRenderedReducer: ((S, A) => S) | null, // 最新處理處理state的reducer
lastRenderedState: S | null, // 最新渲染後狀態
};
複製代碼
示例:
function App() {
const [ n1, setN1 ] = useState(1);
const [ n2, setN2 ] = useState(2);
// if (sth) {
// const [ n4, setN4 ] = useState(4);
// } else {
// const [ n5, setN5 ] = useState(5);
// }
const [ n3, setN3 ] = useState(3);
}
複製代碼
Hook存儲(鏈表)結構:
useState(5)
分支,相反useState(4)則不會執行到,致使useState(5)
返回的值實際上是4,由於首次render以後,只能經過useState返回的dispatch修改對應Hook的memoizedState,所以必需要保證Hooks的順序不變,因此不能在分支調用Hooks,只有在頂層調用才能保證各個Hooks的執行順序!useState() mount階段(部分)源碼實現:
// useState() 首次render時執行mountState
function mountState(initialState) {
// 從當前Fiber生成一個新的hook對象,將此hook掛載到Fiber的hook鏈尾,並返回這個hook
var hook = mountWorkInProgressHook();
hook.memoizedState = hook.baseState = initialState;
var queue = hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: (state, action) => isFn(state) ? action(state) : action,
lastRenderedState: initialState
};
// currentlyRenderingFiber$1保存當前正在渲染的Fiber節點
// 將返回的dispatch和調用hook的節點創建起了鏈接,同時在dispatch裏邊能夠訪問queue對象
var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
}
//// 功能至關於setState!
function dispatchAction(fiber, queue, action) {
...
var update = {
action, // 接受普通值,也能夠是函數
next: null,
};
var last = queue.last;
if (last === null) {
update.next = update;
} else {
last.next = update;
}
// 略去計算update的state過程
queue.last = update;
...
// 觸發React的更新調度,scheduleWork是schedule階段的起點
scheduleWork(fiber, expirationTime);
}
複製代碼
dispatchAction
函數是更新state的關鍵,它會生成一個update
掛載到Hooks隊列上面,並提交一個React更新調度,後續的工做和類組件一致。useState
更新數據和setState
不一樣的是,前者會與old state作merge,咱們只需把更改的部分傳進去,可是useState
則是直接覆蓋!schedule階段介於reconcile和commit階段之間,schedule的起點方法是scheduleWork。 ReactDOM.render, setState,forceUpdate, React Hooks的dispatchAction都要通過scheduleWork。 Ref: zhuanlan.zhihu.com/p/54042084
update階段(state改變、父組件re-render等都會引發組件狀態更新)useState()更新狀態:
function updateState(initialState) {
var hook = updateWorkInProgressHook();
var queue = hook.queue;
var newState;
var update;
if (numberOfReRenders > 0) {
// 組件本身re-render
newState = hook.memoizedState;
// renderPhaseUpdates是一個全局變量,是一個的HashMap結構:HashMap<(Queue: Update)>
update = renderPhaseUpdates.get(queue);
} else {
// update
newState = hook.baseState;
update = hook.baseUpdate || queue.last;
}
do {
newState = update.action; // action多是函數,這裏略去了細節
update = update.next;
} while(update !== null)
hook.memoizedState = newState;
return [hook.memoizedState, queue.dispatch];
}
複製代碼
function App() {
const [n1, setN1] = useState(1);
const [n2, setN2] = useState(2);
const [n3, setN3] = useState(3);
useEffect(() => {
setN1(10);
setN1(100);
}, []);
return (<button onClick={() => setN2(20)}>click</button>);
}
複製代碼
圖解更新過程:
useEffect(effect: React.EffectCallback, deps?: ReadonlyArray<any> | undefined)
複製代碼
做用:處理函數組件中的反作用,如異步操做、延遲操做等,能夠替代Class Component的componentDidMount
、componentDidUpdate
、componentWillUnmount
等生命週期。
HooksDispatcherOnMountInDEV = {
useEffect: function() {
currentHookNameInDev = 'useEffect';
...
return mountEffectImpl(Update | Passive, UnmountPassive | MountPassive, create, deps);
},
};
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
var hook = mountWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
return hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps);
}
function pushEffect(tag, create, destroy, deps) {
var effect = {
tag: tag,
create: create, // 存儲useEffect傳入的callback
destroy: destroy, // 存儲useEffect傳入的callback的返回函數,用於effect清理
deps: deps,
next: null
};
.....
componentUpdateQueue = createFunctionComponentUpdateQueue();
componentUpdateQueue.lastEffect = effect.next = effect;
....
return effect;
}
function renderWithHooks() {
....
currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
....
}
複製代碼
effect
對象,effect
對象最終會被掛載到Fiber節點的updateQueue
隊列(當Fiber節點都渲染到頁面上後,就會開始執行Fiber節點中的updateQueue
中所保存的函數)下面一段很常見的代碼,🤔有什麼問題?運行demo
// 用Hook寫
function App() {
const [data, setData] = useState('');
useEffect(() => {
setTimeout(() => {
setData(`current data: ${Date.now()}`);
}, 3000);
});
return <div>{data}</div>;
}
// 等價代碼
class App extends Component {
state = {data = ''}
componentDidMount() {
setTimeout(() => {
this.setState({ data: `current data: ${Date.now()}` });
}, 3000);
}
render() {
return <div>{this.state.data}</div>;
}
}
複製代碼
deps
,用於在re-render時判斷是否從新執行callback,因此deps必需要按照實際依賴傳入,不能少傳也不要多傳!Hook接受useEffect傳入的callback返回一個函數,在Fiber的清理階段將會執行這個函數,從而達到清理effect的效果:
function App() {
useEffect(() => {
const timer = setTimeout(() => {
console.log('print log after 1s!');
}, 1000);
window.addEventListener('load', loadHandle);
return () => window.removeEventListener('load', loadHandle); // 執行清理
}, []);
}
// 同等實現
class App extends Component {
componentDidMount() {
const timer = setTimeout(() => {
console.log('print log after 1s!');
}, 1000);
window.addEventListener('load', loadHandle);
}
componentDidUnmount() {
window.removeEventListener('load', loadHandle);
}
}
複製代碼
對於組件之間的狀態共享,在類組件裏邊官方提供了Context相關的API:
React.createContext
API建立Context,因爲支持在組件外部調用,所以能夠實現狀態共享Context.Provider
API在上層組件掛載狀態Context.Consumer
API爲具體的組件提供狀態或者經過contextType
屬性指定組件對Context的引用在消費context提供的狀態時必需要使用contextType
屬性指定Context引用或者用<Context.Consumer>
包裹組件,在使用起來很不方便(參見React Context官方示例)。
React團隊爲函數組件提供了useContext
Hook API,用於在函數組件內部獲取Context存儲的狀態:
useContext<T>(Context: ReactContext<T>, unstable_observedBits: void | number | boolean): T
複製代碼
useContext的實現比較簡單,只是讀取掛載在context對象上的_currentValue值並返回:
function useContext(content, observedBits) {
// 處理observedBits,暫時
// 只有在React Native裏邊isPrimaryRenderer纔會是false
return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}
複製代碼
理解useContext的實現,首先要對Context源碼實現有所瞭解,推薦《 React 源碼系列 | React Context 詳解》
useContext極大地簡化了消費Context的過程,爲組件之間狀態共享提供了一種可能,事實上,社區目前基於Hooks的狀態管理方案很大一部分是基於useContext來實現的(另外一種是useState),關於狀態管理方案的探索咱們放在後面的文章介紹。
useReducer<S, I, A>(reducer: (S, A) => S, initialArg: I, init?: I => S, ): [S, Dispatch<A>]
複製代碼
做用:用於管理複雜的數據結構(useState
通常用於管理扁平結構的狀態),基本實現了redux的核心功能,事實上,基於Hooks Api能夠很容易地實現一個useReducer Hook:
const useReducer = (reducer, initialArg, init) => {
const [state, setState] = useState(
init ? () => init(initialArg) : initialArg,
);
const dispatch = useCallback(
action => setState(prev => reducer(prev, action)),
[reducer],
);
return useMemo(() => [state, dispatch], [state, dispatch]);
};
複製代碼
reducer提供了一種能夠在組件外從新編排state的能力,而useReducer返回的dispatch
對象又是「性能安全的」,能夠直接放心地傳遞給子組件而不會引發子組件re-render。
function reducer(state, action) {
// 這裏可以拿到組件的所有state!!
switch (action.type) {
case "increment":
return {
...state,
count: state.count + state.step,
};
...
}
}
function App() {
const [state, dispatch] = useReducer(reducer, {count: initialCount, step: 10});
return (
<>
<div>{state.count}</div>
// redux like diaptch
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<ChildComponent dispatch={dispatch} />
</>
);
}
複製代碼
useCallback<T>(callback: T, deps: Array<mixed> | void | null): T
複製代碼
因爲javascript函數的特殊性,當函數簽名被做爲deps傳入useEffect時,仍是會引發re-render(即便函數體沒有改變),這種現象在類組件裏邊也存在:
// 當Parent組件re-render時,Child組件也會re-render
class Parent extends Component {
render() {
const someFn = () => {}; // re-render時,someFn函數會從新實例化
return (
<>
<Child someFn={someFn} />
<Other />
</>
);
}
}
class Child extends Component {
componentShouldUpdate(prevProps, nextProps) {
return prevProps.someFn !== nextProps.someFn; // 函數比較將永遠返回false
}
}
複製代碼
Function Component(查看demo):
function App() {
const [count, setCount] = useState(0);
const [list, setList] = useState([]);
const fetchData = async () => {
setTimeout(() => {
setList(initList);
}, 3000);
};
useEffect(() => {
fetchData();
}, [fetchData]);
return (
<>
<div>click {count} times</div>
<button onClick={() => setCount(count + 1)}>Add count</button>
<List list={list} />
</>
);
}
複製代碼
解決方案:
useEffect
內部useEffect
內部(如須要傳遞給子組件),可使用useCallback
API包裹函數,useCallback
的本質是對函數進行依賴分析,依賴變動時才從新執行useMemo<T>(create: () => T, deps: Array<mixed> | void | null): T
複製代碼
useMemo用於緩存一些耗時的計算結果,只有當依賴參數改變時才從新執行計算:
function App(props) {
const start = props.start;
const list = props.list;
const fibValue = useMemo(() => fibonacci(start), [start]); // 緩存耗時操做
const MemoList = useMemo(() => <List list={list} />, [list]);
return (
<>
<div>Do some expensive calculation: {fibValue}</div>
{MemoList}
<Other />
</>
);
}
複製代碼
簡單理解:
useCallback(fn, deps) === useMemo(() => fn, deps)
在函數組件中,React提供了一個和類組件中和PureComponent
相同功能的API React.memo
,會在自身re-render時,對每個 props
項進行淺對比,若是引用沒有變化,就不會觸發重渲染。
// 只有列表項改變時組件纔會re-render
const MemoList = React.memo(({ list }) => {
return (
<ul>
{list.map(item => (
<li key={item.id}>{item.content}</li>
))}
</ul>
);
});
複製代碼
相比React.memo
,useMemo
在組件內部調用,能夠訪問組件的props和state,因此它擁有更細粒度的依賴控制。
關於useRef其實官方文檔已經說得很詳細了,useRef Hook返回一個ref對象的可變引用,但useRef的用途比ref更普遍,它能夠存儲任意javascript值而不只僅是DOM引用。
useRef的實現比較簡單:
// mount階段
function mountRef(initialValue) {
var hook = mountWorkInProgressHook();
var ref = { current: initialValue };
{
Object.seal(ref);
}
hook.memoizedState = ref;
return ref;
}
// update階段
function updateRef(initialValue) {
var hook = updateWorkInProgressHook();
return hook.memoizedState;
}
複製代碼
useRef是比較特殊:
Capture Values
特性一、useState
具備capture values,查看demo
二、useEffect
具備capture values
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
// 連續點擊三次button,頁面的title將依次改成一、二、3,而不是三、三、3
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
複製代碼
三、event handle具備capture values,查看demo
四、。。。全部的Hooks API都具備capture values特性,除了useRef
,查看demo(setTimeout始終能拿到state最新值),state是Immutable的,ref是mutable的。
function mountRef(initialValue) {
var hook = mountWorkInProgressHook();
var ref = { current: initialValue }; // ref就是一個普通object的引用,沒有閉包
{
Object.seal(ref);
}
hook.memoizedState = ref;
return ref;
}
複製代碼
非useRef相關的Hooks API,本質上都造成了閉包,閉包有本身獨立的狀態,這就是Capture Values的本質。
// mount結束,已經更新到DOM
const onMount = function useDidMount(effect) => {
useEffect(effect, []);
};
複製代碼
// layout結束,render DOM以前(會block rendering)
const onUpdate = function useUpdate(effect) => {
useLayoutEffect(effect, []);
};
複製代碼
const unMount = function useWillUnMount(effect, deps = []) => {
useEffect(() => effect, deps);
};
複製代碼
// 使用React.memo包裹組件
const MyComponent = React.memo(() => {
return <Child prop={prop} />
}, [prop]);
// or
function A({ a, b }) {
const B = useMemo(() => <B1 a={a} />, [a]);
const C = useMemo(() => <C1 b={b} />, [b]);
return (
<>
{B}
{C}
</>
);
}
複製代碼
一、Hooks能解決組件功能複用,但沒有很好地解決JSX的複用問題,好比(1.4)表單驗證的case:
function App() {
const { waiting, errText, name, onChange } = useName();
// ...
return (
<form>
<div>{name}</div>
<input onChange={onChange} />
{waiting && <div>waiting<div>}
{errText && <div>{errText}<div>}
</form>
);
}
複製代碼
雖可以將用戶的輸入、校驗等邏輯封裝到useName hook,但DOM部分仍是有耦合,這不利於組件的複用,期待React團隊拿出有效的解決方案來。
二、React Hooks模糊了(或者說是拋棄了)生命週期的概念,但也帶來了更高門檻的學習心智(如Hooks生命週期的理解、Hooks Rules的理解、useEffect依賴項的判斷等),相比Vue3.0即將推出的Hooks有較高的使用門檻。
三、類擁有比函數更豐富的表達能力(OOP),React採用Hooks+Function Component(函數式)的方式實際上是一種無奈的選擇,試想一個掛載了十幾個方法或屬性的Class Component,用Function Component來寫如何組織代碼使得邏輯清晰?這背後實際上是函數式編程與面向對象編程兩種編程範式的權衡。
最後,感謝你認真閱讀這麼長的一篇文章~
「螞蟻 RichLab 前端團隊」致力於與你共享高質量的技術文章
咱們團隊正在急招:互動圖形技術、前端/全棧開發、前端架構、算法、大數據開發等方向任選,指望層級 P6+~P7,團隊技術氛圍好,上升空間大,簡歷能夠直接砸給我哈 shudai.lyy@alibaba-inc.com