Making Sense of React Hooks
(原文发布于 2018 年 10 月 31 日)
这周, Sophie Alpert 和我在 React Conf 上介绍了 "Hooks" 提案,以后 Ryan Florence 又进行了深刻的介绍:
视频地址html
我强烈建议你们去看看这个开场主题演讲,了解咱们试图用 Hooks 提案解决的问题。不过一个小时也是巨大的时间投入,因此我决定在下面分享一些关于 Hooks 的想法。react
注意:Hooks 是 React 的实验性提案。你不须要如今了解他们。另请注意,这篇文章包含了我本身的观点,并不必定反映 React 团队的立场。
咱们知道组件和自上而下的数据流帮助咱们将大型的 UI 分解成小型,独立,可重用的部分。可是咱们常常没法进一步分解复杂的组件,由于逻辑是有状态的,没法提取到函数或者其余组件。这就是人们说 React 不让他们 “分开关注” 的意思。
这种状况很是常见,包括动画,表单处理,链接到外部数据源以及咱们但愿的许多在组件中执行的其余操做。当咱们尝试只使用组件解决这鞋问题时,咱们一般会这么解决:git
咱们认为 Hooks 是解决全部这些问题的最好机会。Hooks 让咱们将组件内部的逻辑组织成可重用的隔绝单元:github
在一个组件中 Hooks 符合 React 的理念(明确的数据流和组成),而不只仅是组件之间。这就是为何我以为 Hooks 很适合 React 的组件模型。spring
和 render props 或者高阶组件的模式不一样,Hooks 不会在组件树中引入没必要要的嵌套。它们也没有 mixins 的弊端。编程
即便你的第一反应是发自肺腑的(像我第一次那样),我也鼓励你不带偏见地去尝试这个提案,我想你会喜欢它的。ide
在咱们仔细了解 Hooks 以前,你可能会担忧咱们只是用 Hooks 在 React 中添加了更多的概念。这是一种正常的顾虑。我认为学习它们确定会有短时间的认知成本,可是最终的结果将是相反的。
若是 React 社区拥抱了 Hooks 提案,它将减小编写 React 应用程序时须要处理的概念数量。 Hooks 让你老是使用函数而没必要在函数(function),类(classes),高阶组件(higher-order components)和 render props 之间切换。函数式编程
就实现大小而言,Hooks 支持只增长了 React ~1.5kB(min + gzip)。虽然这很少,可是使用 Hooks 可能会减小你的包的大小,由于使用 Hooks 的代码比使用类(classes)的代码更容易缩小。下面这个例子比较极端可是它有效地演示了为何(点击查看整个例子):
(原文这里挂掉了)函数
Hooks 提案不包含任何破坏性升级。当你在新写的组件中使用 Hooks 的时候你现有的代码将继续工做。实际上这正是咱们所推崇的——不作任何重大的改写!在任何关键代码中采用 Hooks 都是一个好主意。尽管如此,若是你尝试使用 16.7 alpha 版本以后向咱们提供关于 Hooks 的反馈并报告一些错误,咱们会很是感激。工具
要理解 Hooks,咱们须要退一步而后思考代码重用。
今天,有不少方法能够在 React 应用中重用逻辑。咱们能够写简单的方法并调用它们来计算某些东西。咱们也能够编写组件(它们自己能够是函数或是类)。组件功能更强大,可是他们须要渲染一些 UI。这使得它们不便于共享非可视逻辑。这就是为何最终咱们会获得像渲染 props 和高阶组件的复杂的模式。若是有一种经常使用的方法替代这些实现代码重用,React 会不会更简单?
函数看上去是代码重用的完美机制。函数之间逻辑转移花费最少的消耗。可是函数不能在它里面包含本地 React 状态(local React state)。若是你不重构代码或者引入像 Observables 这样的抽象,你没法从类组件中抽取出“监视窗口大小且更新状态”或“随时间变化值”这样的事件。这两种事件会伤害咱们所喜欢的 React 的简洁性。
Hooks 正好解决了这个问题。 Hooks 容许你在函数中使用 React 的功能(像 state)——经过执行单个函数调用。React 提供了一些内置的 Hooks,它们暴露了 React 的“构建快”:状态,生命周期和上下文。
因为 Hooks 是常规的 Javascript 函数,所以你能够将 React 提供的内置 Hooks 组合到你本身的“自定义 Hooks”中。这使你能够将复杂的问题转换成单行,并在整个应用程序或者 React 社区中分享它们:
请注意,自定义 Hooks 在技术上不是 React 的功能。你本身的 Hooks 的实现得益于 Hooks 设计的方式。
假设咱们想给组件订阅当前窗口的宽度(例如在窄视窗上显示不一样的内容)。
如今你有好几种方法去编写这种代码。它们包括编写类,设置一些生命周期方法,若是要在组件之间重用它,甚至能够提取出 render props 或者高阶组件。但我认为没有什么比这更好:
// MyResponsiveComponent.js function MyResponsiveComponent() { const width = useWindowWidth(); // Our custom Hook return ( <p>Window width is {width}</p> ); } // useWindowWidth.js function useWindowWidth() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; }); return width; }
Examples from "Making Sense of React Hooks"
若是你读了这段代码,它彻底照它说的执行。咱们在组件中使用窗口高度,当它变化时 React 从新渲染组件。这就是 Hooks 的目标——使组件真正声明,即便它们包含状态和反作用。
让咱们看看如何实现这个自定义 Hook。咱们使用 React 的本地状态 来保存当前窗口宽度,窗口大小改变时用一个反作用去设置这个状态。
如你所见, 像 useState 和 useEffet 这些内置的 React Hooks 充当基本构建块。咱们能够直接在咱们的组件中使用它们,或者咱们能够将它们组合成像useWindowWidth那样的自定义 Hooks。使用自定义 Hooks 感受就像使用 React 内置 API 同样顺手。
你能够经过这个介绍了解更多的内置 Hooks 相关内容。
Hooks 是彻底封装的——每一次你调用 Hook,它在当前正在执行的组件中得到隔离的本地状态。这对于这个特定的例子可有可无(窗口宽度在全部组件中都相同),但这是 Hooks 如此强大的缘由。它们不是一种分享状态的方式 — 而是一种分享状态逻辑的方式。咱们不想破坏自上而下的数据流!
每一个 Hook 可能包含一些本地状态和反作用。你能够像平时在函数之间那样在多个 Hooks 之间传递数据。它们能够接受参数并返回值,由于它们是 Javascript 函数。
这是一个实验 Hooks 的 React 动画库的例子:
https://codesandbox.io/embed/...
请注意,演示代码中经过一个渲染函数中使用多个自定义 Hooks 来传递值实现交错动画。
https://codesandbox.io/s/ppxnl191zx
(若是你想了解更多关于这个例子,看看这个教程。)
虽然这不是 Hooks 的主要目标,它们还为强大的交互调试工具打开了大门:
Hooks 之间传递数据的能力使它们很是适合展现动画,描述数据,表单管理,以及其余有状态的抽象。不像 render props 或者 高阶组件, Hooks 不会在你的渲染树上建立一个“错误的层级”。它们更像是一个链接到组件的“存储单元”的平面列表。没有额外的层。
在咱们看来,自定义 Hooks 是 Hooks 提案中最吸引人的部分。可是为了使自定义 Hooks 工做,React 得提供函数去声明状态和反作用。这就是 useState 和 useEffect 这样的内置 Hooks 让咱们作的。你能够在文档中了解它们。
事实证实,这些内置 Hooks 不只在建立自定义 Hooks 的时候有用。它们也足以定义通常的组件,由于它们为咱们提供了像 state 这样的全部必要的功能。这就是为何咱们但愿 Hooks 在将来能够成为定义 React 组件的主要方式。
咱们没有打算弃用类。在 Facebook,咱们有成千上万的类组件,和你同样,咱们无心重写它们。可是若是 React 社区拥抱 Hooks,用两种不一样的推荐方法来编写组件是没有意义的。Hooks 能够覆盖类的全部用例,同时在提取,测试和重用代码方面提供更大的灵活性。这就是为何 Hooks 表明了咱们对 React 将来的愿景。
你可能会对 Hooks 的规则 感到惊讶。
虽然必须在顶层调用 Hooks 是不常规的,不过即便能够你估计也不但愿在某个条件下定义状态。举个例子,你不能在类中有条件地定义状态,在与 React 用户交流的四年中我历来没有听到过他们对此有任何怨言。
这种设计对于启用自定义 Hooks 而不引入额外的语法杂质或其余陷阱相当重要。咱们意识到最初的陌生感,可是咱们认为这种代价是值得的。若是你不一样意,我建议你在练习中使用它,看看它是否会改变你的感觉。
为了了解开发者是否会对这些规则困惑,咱们已经在生产中使用了一个月 Hooks。咱们发如今实践中人们会在几个小时内习惯它们。就我我的而言,我认可起初我对这些规则也“感受不舒服”,可是我很快就克服了它。此次经历很像了我对 React 的第一印象。(你立刻就爱上 React 了吗?第二次使用时我才开始喜欢它。)
请注意,Hooks 的实现中也没有“魔法”,如 Jamie 指出 的那样,它看起来与下面很是类似:
咱们保留了每一个组件的 Hooks 列表,并在每次使用 Hooks 的时候移动到列表中的下一个。因为 Hooks 的规则,在每一个 render 中它们的顺序都是相同的,所以咱们能够为组件的每一个调用提供正确的状态。不要忘记 React 不须要作任何特殊的事情来知道哪一个组件正在渲染 — React 只调用你的组件。
(Rudi Yardley 的这篇文章 包含了一个很好的可视化解释!)
或许你还想知道 React 把 Hooks 的状态放在哪里。答案是它们保存在 React 为类保存状态的地方。不论你怎么定义你的组件,React 有一个内部更新队列,这是全部状态的来源。
Hooks 不依赖于现代 Javascript 库中常见的代理(Proxies)或者 getters。因此讲道理 Hooks 没有那些流行的解决相似问题的途径神奇。我会说 Hooks 和调用 array.push
, array.pop
同样神奇(调用顺序也很重要!)
Hooks 的设计与 React 无关。事实上,在提案发布后的前几天,针对 Vue,Web 组件甚至纯 Javascript 函数不一样的人都想出了相同的 Hooks API 的实验性实现方式。
最后,若是你是一个纯粹的函数式编程主义者而且对 React 的依赖可变状态做为实现细节感到不自在,你可能会对使用代数效果纯粹地处理 Hooks 的方式感到满意(若是 Javascript 支持它们)。固然 React 一直在内部依赖可变状态 — 目的就是你没必要这么作。
不管你是从务实的角度仍是教条的角度来考虑(若是你有的话),我但愿这些理由中至少有一个能说服你。若是你很好奇,Sebastian(Hooks 的做者)在这篇关于 RFC 的 回复中 也回应了这些和其余问题。最重要的是,我认为 Hooks 帮助咱们用更少的精力构建组件,创造了更好的用户体验。这就是我我的对 Hooks 感到兴奋的缘由。
若是你还没被 Hooks 吸引,我彻底能够理解。但我仍是但愿你能尝试一个小的项目,看看是否会改变你的观点。不管你是遇到了 Hooks 解决不了的问题,或是你有其余的解决方案,请经过 RFC 告诉咱们!
若是个人介绍让你兴奋了,或者至少有一点好奇,那就太好了!我只有一个请求。如今有不少人学习 React,若是咱们忙于编写教程并为几乎刚出来没几天的功能发布最佳实践,他们会感到困惑。Hooks 中有一些东西甚至对 React 团队中的咱们来讲都不是很清楚。
若是你在 Hooks 不稳定时建立任何有关 Hooks 的内容,请特别说起它们是实验性提案,并带上包含 官方文档 的连接。咱们会及时更新全部提案的更改。咱们也花了很多精力使它更全面,在那里不少问题都已经获得解答了。
当你和其余不像你那么兴奋的人交流时,请保持理性。若是你发现别人误解了它,能够在对方开放的时候分享一些额外的信息。不过任何的改变都是可怕的,做为一个社区,咱们应该尽力帮助人们,而不是疏远他们。若是我(或者是 React 团队中的任何其余人)未能遵循这个建议,请联系咱们!
查看 Hooks 提案的文档以了解更多信息:
Hooks 仍然处于早期阶段,可是咱们很高兴听到你么全部人的反馈,你能够前往 RFC ,但咱们也会尽力跟上 Twitter 上的对话。
若是有不清楚的地方,请告诉我,我很乐意与你聊聊你的疑虑。谢谢你的阅读!