You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
classPeopleextendsComponent{state={name: 'dm',age: 18,}handleClick(e){// 报错!console.log(this.state);}render(){const{ name, age }=this.state;return(<divonClick={this.handleClick}>My name is {name}, i am {age} years old.</div>);}}
functionthrowInvalidHookError(){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.');}varContextOnlyDispatcher={
...
useEffect: throwInvalidHookError,useState: throwInvalidHookError,
...
};
functionApp(){useEffect(()=>{consttimer=setTimeout(()=>{console.log('print log after 1s!');},1000);window.addEventListener('load',loadHandle);return()=>window.removeEventListener('load',loadHandle);// 执行清理},[]);}// 同等实现classAppextendsComponent{componentDidMount(){consttimer=setTimeout(()=>{console.log('print log after 1s!');},1000);window.addEventListener('load',loadHandle);}componentDidUnmount(){window.removeEventListener('load',loadHandle);}}
// 使用React.memo包裹组件constMyComponent=React.memo(()=>{return<Childprop={prop}/>},[prop]);// orfunctionA({ a, b }){constB=useMemo(()=><B1a={a}/>,[a]);constC=useMemo(()=><C1b={b}/>,[b]);return(<>{B}{C}</>);}
1 Why Hooks?
1.1 从React组件设计理论说起
React以一种全新的编程范式定义了前端开发约束,它为视图开发带来了一种全新的心智模型:
UI = F(DATA)
,这里的F
需要负责对输入数据进行加工、并对数据的变更做出响应F
在React里抽象成组件,React是以组件(Component-Based)为粒度编排应用的,组件是代码复用的最小单元props
属性来接收外部的数据,使用state
属性来管理组件自身产生的数据(状态),而为了实现(运行时)对数据变更做出响应需要,React采用基于类(Class)的组件设计!这就是React组件设计的理论基础,我们最熟悉的React组件一般长这样:
1.2 Class Component的问题
1.2.1 组件复用困局
组件并不是单纯的信息孤岛,组件之间是可能会产生联系的,一方面是数据的共享,另一个是功能的复用:
Render Props
和Higher Order Component
,直到再后来的Function Component+ Hooks设计,React团队对于组件复用的探索一直没有停止HOC使用(老生常谈)的问题:
Render Props:
this.props
属性,不能像HOC那样访问this.props.children
1.2.2 Javascript
Class
的缺陷1、
this
的指向(语言缺陷)createClass不需要处理this的指向,到了Class Component稍微不慎就会出现因
this
的指向报错。2、编译size(还有性能)问题:
Class Component编译结果(Webpack):
Function Component编译结果(Webpack):
Function类
来处理的1.3 Function Component缺失的功能
不是所有组件都需要处理生命周期,在React发布之初Function Component被设计了出来,用于简化只有render时Class Component的写法。
所以,Function Comonent是否能脱离Class Component独立存在,关键在于让Function Comonent自身具备状态处理能力,即在组件首次render之后,“组件自身能够通过某种机制再触发状态的变更并且引起re-render”,而这种“机制”就是Hooks!
Hooks的出现弥补了Function Component相对于Class Component的不足,让Function Component取代Class Component成为可能。
1.4 Function Component + Hooks组合
1、功能相对独立、和render无关的部分,可以直接抽离到hook实现,比如请求库、登录态、用户核身、埋点等等,理论上装饰器都可以改用hook实现(如react-use,提供了大量从UI、动画、事件等常用功能的hook实现)。
case:Popup组件依赖视窗宽度适配自身显示宽度、相册组件依赖视窗宽度做单/多栏布局适配
2、有render相关的也可以对UI和功能(状态)做分离,将功能放到hook实现,将状态和UI分离
case:表单验证
2 Hooks的实现与使用
2.1 useState
作用:返回一个状态以及能修改这个状态的setter,在其他语言称为元组(tuple),一旦mount之后只能通过这个setter修改这个状态。
2.1.1 为什么只能在Function Component里调用Hooks API?
Hooks API的默认实现:
当在Function Component调用Hook:
2.1.2 为什么必须在函数组件顶部作用域调用Hook API?
在类组件中,state就是一个对象,对应FiberNode的
memoizedState
属性,在类组件中当调用setState()
时更新memoizedState
即可。但是在函数组件中,memoizedState
被设计成一个链表(Hook对象):示例:
Hook存储(链表)结构:
useState(5)
分支,相反useState(4)则不会执行到,导致useState(5)
返回的值其实是4,因为首次render之后,只能通过useState返回的dispatch修改对应Hook的memoizedState,因此必须要保证Hooks的顺序不变,所以不能在分支调用Hooks,只有在顶层调用才能保证各个Hooks的执行顺序!2.1.3 Hooks如何更新数据?
useState() mount阶段(部分)源码实现:
dispatchAction
函数是更新state的关键,它会生成一个update
挂载到Hooks队列上面,并提交一个React更新调度,后续的工作和类组件一致。useState
更新数据和setState
不同的是,前者会与old state做merge,我们只需把更改的部分传进去,但是useState
则是直接覆盖!update阶段(state改变、父组件re-render等都会引起组件状态更新)useState()更新状态:
2.1.4 Hooks更新过程
图解更新过程:

2.2 useEffect
作用:处理函数组件中的副作用,如异步操作、延迟操作等,可以替代Class Component的
componentDidMount
、componentDidUpdate
、componentWillUnmount
等生命周期。2.2.1 useEffect实现剖析
effect
对象,effect
对象最终会被挂载到Fiber节点的updateQueue
队列(当Fiber节点都渲染到页面上后,就会开始执行Fiber节点中的updateQueue
中所保存的函数)2.2.2 deps参数很重要
下面一段很常见的代码,🤔有什么问题?运行demo
deps
,用于在re-render时判断是否重新执行callback,所以deps必须要按照实际依赖传入,不能少传也不要多传!2.2.3 清理副作用
Hook接受useEffect传入的callback返回一个函数,在Fiber的清理阶段将会执行这个函数,从而达到清理effect的效果:
2.3 useContext
对于组件之间的状态共享,在类组件里边官方提供了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的实现比较简单,只是读取挂载在context对象上的_currentValue值并返回:
useContext极大地简化了消费Context的过程,为组件之间状态共享提供了一种可能,事实上,社区目前存在大量的基于Hooks的状态管理方案很大一部分是基于useContext API来实现的(另一种是useState),关于状态管理方案的探索我们放在后面的文章介绍。
2.4 useReducer
作用:用于管理复杂的数据结构(
useState
一般用于管理扁平结构的状态),基本实现了redux的核心功能,事实上,基于Hooks Api可以很容易地实现一个useReducer Hook:reducer提供了一种可以在组件外重新编排state的能力,而useReducer返回的
dispatch
对象又是“性能安全的”,可以直接放心地传递给子组件而不会引起子组件re-render。2.5 性能优化(Memoization)相关Hooks API
2.5.1 useCallback
由于javascript函数的特殊性,当函数签名被作为deps传入useEffect时,还是会引起re-render(即使函数体没有改变),这种现象在类组件里边也存在:
Function Component(查看demo):
解决方案:
useEffect
内部useEffect
内部(如需要传递给子组件),可以使用useCallback
API包裹函数,useCallback
的本质是对函数进行依赖分析,依赖变更时才重新执行2.5.2 useMemo & memo
useMemo用于缓存一些耗时的计算结果,只有当依赖参数改变时才重新执行计算:
在函数组件中,React提供了一个和类组件中和
PureComponent
相同功能的APIReact.memo
,会在自身re-render时,对每一个props
项进行浅对比,如果引用没有变化,就不会触发重渲染。相比
React.memo
,useMemo
在组件内部调用,可以访问组件的props和state,所以它拥有更细粒度的依赖控制。2.6 useRef
关于useRef其实官方文档已经说得很详细了,useRef Hook返回一个ref对象的可变引用,但useRef的用途比ref更广泛,它可以存储任意javascript值而不仅仅是DOM引用。
useRef的实现比较简单:
useRef是比较特殊:
Capture Values
特性2.7 其他Hooks API
2.8 Capture Values特性
1、
useState
具有capture values,查看demo2、
useEffect
具有capture values3、event handle具有capture values,查看demo
4、。。。所有的Hooks API都具有capture values特性,除了
useRef
,查看demo(setTimeout始终能拿到state最新值),state是Immutable的,ref是mutable的。非useRef相关的Hook API,本质上都形成了闭包,闭包有自己独立的状态,这就是Capture Values的本质。
2.9 自定义组件:模拟一些常用的生命周期
3 Hooks的问题
1、Hooks能解决组件功能复用,但没有很好地解决JSX的复用问题,比如(1.4)表单验证的case:
虽能够将用户的输入、校验等逻辑封装到useName hook,但DOM部分还是有耦合,这不利于组件的复用,期待React团队拿出有效的解决方案来。
2、React Hooks模糊了(或者说是抛弃了)生命周期的概念,但也带来了更高门槛的学习心智(如Hooks生命周期的理解、Hooks Rules的理解、useEffect依赖项的判断等),相比Vue3.0即将推出的Hooks有较高的使用门槛。
3、类拥有比函数更丰富的表达能力(OOP),React采用Hooks+Function Component(函数式)的方式其实是一种无奈的选择,试想一个挂载了十几个方法或属性的Class Component,用Function Component来写如何组织代码使得逻辑清晰?这背后其实是函数式编程与面向对象编程两种设计模式的权衡。
4 Ref
The text was updated successfully, but these errors were encountered: