做者: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