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
我们知道,在 Class 组件的设计中是通过 this 将 state 与对应的处理方法关联在一起,这样主要包含两个方面:
处理用户交互的回调里可以通过 this 访问 state 与 setState;
在组件初始化或者更新的过程中,各生命周期方法可以通过 this 访问 state 与 setState;
但 Function Component 不在有生命周期的概念:Hooks 是通过闭包实现 state 与对应的处理方法关联在一起,而且每一次更新时 Function Component 的所有部分都会执行。
我们把 Function Component 每一次更新后所对应的 state 称作一次快照,React 的 Hooks 会根据执行顺序在内部维护一个递增的 index 来将闭包里的变量映射到对应的 state,并且只在第一次 render 时接受 initState, 之后每次 render 都通过 index 从闭包里获取对应的 state 值。例如以下代码:
在良好抽象的基础上,实现关注分离并合理地复用代码,这是编程的核心。
组件化开发可以帮助前端实现一定程度的关注分离,但其主要解决 UI 的复用,我们日常开发过程中还面临着 state 逻辑的关注分离与复用问题。
State 逻辑的复用
下面是两个纯组件,分别用来展示 users 和 posts 信息:
users 和 posts 数据获取的方式是一样的,我们通过 HOC 来实现请求数据的 state 逻辑复用:
最终使用如下:
上面的例子相对简单,如果遇到复杂的业务逻辑,HOC 的缺点很明显:比如属性不能完全一致导致覆盖,又或者遇到黑盒问题,必须到 BaseComponent 查看实现细节等。
另外一种实现 state 逻辑复用的方式是 Render Props:
此时最终使用如下:
使用 Render Props 可以避免 HOC 所遇到的问题,但是很容易陷入标签嵌套地狱。
除了上面提及的问题之外,日常开发我们还经常面临:
UI 与 可复用的 State 逻辑分离
Componet 在 pros 发生改变时会重新 render,这是 React 组件化设计的一个基础约定。
我们也见过其他形式,例如基于原生 JavaScript 的地图渲染引擎中常常可以看到类似这样的代码:
如果将其改写为 React 的 Componet 形式,代码会是:
过去主流的前端架构体系均通过 this 将 state 与生命周期函数绑定,将 state 的逻辑分割在组件的不同生命周期中。在这个基础上, state 的逻辑的复用只能围绕 props 来开展。
HOC 和 Render Props 实现 state 逻辑复用均是建立在 props 传递之上的,所以显得十分笨拙。那么是否有更好的方式呢?
React 团队基于 Function Component 提出了 Hooks 的概念,包含了 useState、useEffect、useContext 等几个关键 API。
使用这些 API 我们可以将可复用的 state 逻辑与 UI 分离,这样我们无需基于 props 实现逻辑复用,而是通过灵活的组合将可复用的 state 逻辑使用在不同的组件中。这种方式不仅用起来非常简单,而且让 React 更 Reactive:
最终使用如下:
Hooks 使用闭包来将 state 和处理 state 的方法关联起来,这种方式相比于使用 Class 能降低可观的代码量,且代码看起来十分清爽。
Hooks 的好处非常明显,且十分好用!
但好用并不等于上手快,这一点和 React 框架本身很像:语法和概念简单,API 少,但想很好的驾驭需要一定的内功,对于编程能力不足的人来说有一定的挑战。
Hooks 的使用
Hooks 是 Function,所以我们只要划分好职责,明确输入和输出便可以尽情享受 Hooks 带来的编程乐趣:
下面是随时获取到浏览器窗口宽度的代码:
createContainer 的使用如下:
Hooks 的设计缺陷
我们知道,在 Class 组件的设计中是通过 this 将 state 与对应的处理方法关联在一起,这样主要包含两个方面:
但 Function Component 不在有生命周期的概念:Hooks 是通过闭包实现 state 与对应的处理方法关联在一起,而且每一次更新时 Function Component 的所有部分都会执行。
我们把 Function Component 每一次更新后所对应的 state 称作一次快照,React 的 Hooks 会根据执行顺序在内部维护一个递增的 index 来将闭包里的变量映射到对应的 state,并且只在第一次 render 时接受 initState, 之后每次 render 都通过 index 从闭包里获取对应的 state 值。例如以下代码:
每次快照:
副作用 useEffect 在每一次快照中会将其 Array Dependency 中的 state 和 返回的 cleanup 方法存储在自己的 hooks[index] 中。在下一次更新时会先执行 cleanup 方法,然后对比依赖的state 与上一次相比是否发生变化,进而决定副作用回调方法是否执行。
useEffect 的代码实现大致如下:
useMemo 与 useCallback 原理与 useEffect 类似,会存储所依赖的 state 并在下一次更新时做对比,再根据依赖是否发生变化返回对应的结果。
这样 Hooks 就可以基于 Function Component 做到:
这样我们就可以让 Function Component 拥有与 Class Component 一样的能力。
但需要注意的是,这样的设计并不完美,缺陷非常明显:
这些从设计根源上所带来的问题,需要我们在利用 React Hooks 优点简化代码,提高代码可读性和复用性时,努力避免踏入其缺陷误区。
令人困惑的 Dependency Array
从上文我们可以得知,Dependency Array 在 Hooks 中的作用主要有两点:
Dependency Array 在 useEffect 中的滥用比较多。新手往往会在 useEffect 的 Dependency Array 里放入许多本不应该放入的依赖变量,从而导致许多副作用回调被过多或异常触发。
或者由于 props 的错误传入导致
问题 1 依赖变量 a,问题 2 依赖变量 b,但如果放在同一个 useEffect 中,b 的变更也会导致问题 1 逻辑的执行。
上面的例子中,每次 form 输入都会触发一次事件监听。下面这段更隐晦的代码是等效的:
比较糙的解决方法是
如果希望代码更加优雅,可以使用 useReducer,可以达到上面代码相同的效果。他们解决问题的本质是:拒绝从 useEffect 的 Array dependency 中获取副作用回调执行所需要的 state!
我们知道,在每一次 render 时取到的 setState 或 useReducer 返回的 dispatch 都是第一次 render 生成并留在内存中的对象,所以 stateState 或 dispatch 是稳定不变的,我们可以放心使用。
我们可以利用 setState 的 callback 参数获取 state,甚至你可以通过以下代码实现类似 useReducer 的效果:
我们在使用 useEffect 时应该优先思考的原则是:
只要我们按照这三个原则去使用 useEffect,就一定可以避免绝大部分误区!
The text was updated successfully, but these errors were encountered: