React Hook
是React16.8.0
版本以后提出的新增特性,因为以前的项目都不怎么用到React
,所以也就匆匆了解一下,最近由于换工做,主要技术栈变为React
了,因此须要着重研究一下React
的一些特性以更好地应用到项目开发中和更好地进行知识沉淀。javascript
在解释这个问题以前,能够先看一段代码:css
import React, { useState } from 'react' function Example() { // 声明一个叫 "count" 的 state 变量 const [count, setCount] = useState(0) // 与 componentDidMount and componentDidUpdate效果相似 useEffect(() => { // Update the document title using the browser API document.title = `You clicked ${count} times` }) return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
Hook
是一个特殊的函数,它可让你“钩入”React
的特性。例如,useState
是容许你在React
函数组件中添加state
的Hook
。若是你在编写函数组件并意识到须要向其添加一些state
,之前的作法是必须将其它转化为class
。如今你能够在现有的函数组件中使用Hook
;又例如useEffect Hook
能够告诉React
组件须要在渲染后执行某些操做,React
会保存你传递的函数(咱们将它称之为 “effect”),而且在执行 DOM 更新以后调用它,能够把它看作componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合。html
官方解释:Hook 是 React 16.8 的新增特性。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。
React
没有提供将可复用性行为“附加”到组件的途径(例如,把组件链接到store
)。若是你使用过React
一段时间,你也许会熟悉一些解决此类问题的方案,好比render props
和高阶组件
。可是这类方案须要从新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。若是你在React DevTools
中观察过React
应用,你会发现由providers,consumers,高阶组件,render props
等其余抽象层组成的组件会造成“嵌套地狱”。尽管咱们能够在DevTools
过滤掉它们,但这说明了一个更深层次的问题:React
须要为共享状态逻辑提供更好的原生途径。
Hook
能够在无需修改组件结构的状况下复用状态逻辑,这使得在组件间或社区内共享Hook
变得更便捷java
咱们常常维护一些组件,组件起初很简单,但随着业务复杂度的提高,组件逐渐会变得比较复杂,使得每一个生命周期经常包含一些不相关的逻辑。例如,组件经常在componentDidMount
和componentDidUpdate
中获取数据。可是,同一个componentDidMount
中可能也包含不少其它的逻辑,如设置事件监听,而以后需在componentWillUnmount
中清除。相互关联且须要对照修改的代码被进行了拆分,而彻底不相关的代码却在同一个方法中组合在一块儿,如此很容易产生bug
,而且致使逻辑不一致,维护起来也会显得比较吃力。react
为了解决这个问题,Hook
将组件中相互关联的部分拆分红更小的函数(好比设置订阅或请求数据),而并不是强制按照生命周期划分。你还可使用reducer
来管理组件的内部状态,使其更加可预测。npm
引用官方的话:编程
除了代码复用和代码管理会遇到困难外,咱们还发现class
是学习React
的一大屏障。你必须去理解JavaScript
中this
的工做方式,这与其余语言存在巨大差别。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码很是冗余。你们能够很好地理解props
,state
和自顶向下的数据流,但对class
却束手无策。即使在有经验的React
开发者之间,对于函数组件与class
组件的差别也存在分歧,甚至还要区分两种组件的使用场景。另外,
React
已经发布五年了,咱们但愿它能在下一个五年也与时俱进。就像Svelte
,Angular
,Glimmer
等其它的库展现的那样,组件预编译会带来巨大的潜力。尤为是在它不局限于模板的时候。最近,咱们一直在使用Prepack
来试验component folding
,也取得了初步成效。可是咱们发现使用class
组件会无心中鼓励开发者使用一些让优化措施无效的方案。class
也给目前的工具带来了一些问题。例如,class
不能很好的压缩,而且会使热重载出现不稳定的状况。所以,咱们想提供一个使代码更易于优化的API
。segmentfault
Hook
可以在非class
的状况下使用更多的React
特性。 其实, React
组件一直更像是函数。而Hook
则拥抱了函数,同时也没有牺牲React
的精神原则。Hook
提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。数组
最重要的是,Hook
是向下兼容的,它和现有代码能够同时工做,你能够渐进式地使用他们,不用急着迁移到Hook
。浏览器
HOOK
概览基础Hook
额外的Hook
能够看下面的代码:
import React, { useState } from "react"; import "./styles.css"; export default function App() { const [count, setCount] = useState(0); return ( <div className="App"> <h1>这是一个示例</h1> <div>点击了{count}次</div> <button onClick={() => { setCount(count + 1); }} > 点击 </button> <button onClick={() => { setCount(0); }} > 清除 </button> </div> ); }
上述代码中,useState
就是一个Hook
。经过在函数组件里调用它来给组件添加一些内部state
。React
会在重复渲染时保留这个state
。useState
会返回一对值:当前状态和一个让你更新它的函数,你能够在事件处理函数中或其余一些地方调用这个函数。它相似class
组件的this.setState
,可是它不会把新的state
和旧的state
进行合并。
useState
惟一的参数就是初始state
。在上面的例子中,咱们的计数器是从零开始的,因此初始state
就是0
。值得注意的是,不一样于this.state
,这里的state
不必定要是一个对象,但若是你有须要,它也能够是。这个初始state
参数只有在第一次渲染时会被用到。
你也能够在函数组件中屡次使用state Hook
。
它定义一个 “state 变量”。在上面的示例中该变量叫count
, 但它能够是任意的变量名,好比banana
。这是一种在函数调用时保存变量的方式,useState
是一种新方法,它与class
里面的this.state
提供的功能彻底相同。通常来讲,在函数退出后变量就会”消失”,而state
中的变量会被React
保留。
useState()
方法里面惟一的参数就是初始state
。不一样于class
的是,咱们能够按照须要使用数字或字符串对其进行赋值,而不必定是对象。在示例中,只需使用数字来记录用户点击次数,因此咱们传了0
做为变量的初始state
。(若是咱们想要在state
中存储两个不一样的变量,只需调用useState()
两次便可。)
返回值为:当前state
以及更新state
的函数。这就是咱们写 const [count, setCount] = useState()
的缘由。这与class
里面this.state.count
和this.setState
相似,惟一区别就是你须要成对的获取它们。
咱们直接看代码来方便理解:
function Counter() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
<p>You clicked {count} times</p>
看到这段代码,有必定经验的人可能会想,这其中的原理是否是经过watcher
,或者是data binding
或者是proxy
来实现的呢?都不是,count仅仅只是一个数字类型的变量而已,不是上述中的任何一个,就像下面的普通的变量赋值同样:
const count = 42; // ... <p>You clicked {count} times</p>
组件在第一次渲染的时候,从useState()
拿到count
的初始值0
。当咱们调用setCount(1)
,React
会再次渲染组件,这一次count
是1
。就如同下面示例的同样:
// During first render function Counter() { const count = 0; // Returned by useState() // ... <p>You clicked {count} times</p> // ... } // After a click, our function is called again function Counter() { const count = 1; // Returned by useState() // ... <p>You clicked {count} times</p> // ... } // After another click, our function is called again function Counter() { const count = 2; // Returned by useState() // ... <p>You clicked {count} times</p> // ... }
当咱们更新状态的时候,React
会从新渲染组件。每一次渲染都能拿到独立的count
状态,这个状态值是函数中的一个常量
因此下面的这行代码没有作任何特殊的数据绑定:
<p>You clicked {count} times</p>
它仅仅只是在渲染输出中插入了count
这个数字。这个数字由React
提供。当setCount
的时候,React
会带着一个不一样的count
值再次调用组件。而后,React
会更新DOM
以保持和渲染输出一致。
这里关键的点在于任意一次渲染中的count
常量都不会随着时间改变。渲染输出会变是由于咱们的组件被一次次调用,而每一次调用引发的渲染中,它包含的count
值独立于其余渲染。
什么是反作用?React
官网是这么定义的:
你以前可能已经在React
组件中执行过数据获取、订阅或者手动修改过DOM
。咱们统一把这些操做称为“反作用”,或者简称为“做用”。
useEffect
就是一个Effect Hook
,给函数组件增长了操做反作用的能力。它跟class
组件中的 componentDidMount
、componentDidUpdate
和componentWillUnmount
具备相同的用途,只不过被合并成了一个API
。
例如,下面这个组件在React
更新DOM
后会设置一个页面标题:
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // 至关于 componentDidMount 和 componentDidUpdate: useEffect(() => { // 使用浏览器的 API 更新页面标题 document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
当你调用useEffect
时,就是在告诉React
在完成对DOM
的更改后运行你的“反作用”函数。因为反作用函数是在组件内声明的,因此它们能够访问到组件的props
和state
。默认状况下,React
会在每次渲染后调用反作用函数,包括第一次渲染的时候。
反作用函数还能够经过返回一个函数来指定如何“清除”反作用。例如,在下面的组件中使用反作用函数来订阅好友的在线状态,并经过取消订阅来进行清除操做:
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
在这个示例中,React
会在组件销毁时取消对ChatAPI
的订阅,而后在后续渲染时从新执行反作用函数。
跟useState
同样,你能够在组件中屡次使用useEffect
。经过使用Hook
,你能够把组件内相关的反作用组织在一块儿(例如建立订阅及取消订阅),而不要把它们拆分到不一样的生命周期函数里。这样就有利于你对代码的维护。也再一次说明了React
官方为何会使用Hook
。
再次看到官网文档中的例子:
function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
那么:effect是如何读取到最新的count
状态值的呢?
也许,是某种data binding
或watching
机制使得count
可以在effect
函数内更新?也或许count
是一个可变的值,React
会在咱们组件内部修改它以使咱们的effect
函数总能拿到最新的值?
都不是。
咱们已经知道count是某个特定渲染中的常量。事件处理函数“看到”的是属于它那次特定渲染中的count
状态值。对于effects
也一样如此:
并非count的值在“不变”的effect
中发生了改变,而是effect
函数自己在每一次渲染中都不相同。
每个effect
版本“看到”的count
值都来自于它属于的那次渲染:
// During first render function Counter() { // ... useEffect( // Effect function from first render () => { document.title = `You clicked ${0} times`; } ); // ... } // After a click, our function is called again function Counter() { // ... useEffect( // Effect function from second render () => { document.title = `You clicked ${1} times`; } ); // ... } // After another click, our function is called again function Counter() { // ... useEffect( // Effect function from third render () => { document.title = `You clicked ${2} times`; } ); // .. }
React
会记住你提供的effect
函数,而且会在每次更改做用于DOM
并让浏览器绘制屏幕后去调用它。
因此虽然咱们说的是一个effect
(这里指更新document
的title
),但其实每次渲染都是一个不一样的函数 — 而且每一个effect
函数“看到”的props
和state
都来自于它属于的那次特定渲染。
Hook 就是 JavaScript 函数,可是使用它们会有两个额外的规则:
Hook
。不要在循环、条件判断或者子函数中调用。只能在React
的函数中调用调用Hook
。不要在普通的JavaScript
函数中调用Hook
,你能够:
React
的函数组件中调用Hook
Hook
中调用其余Hook
为了更好地执行这个规则,react提供了eslint插件帮助你去检测和强制执行上述规则:eslint-plugin-react-hooks。
这要从React
内部执行Hook
的机制提及:
React
函数组件中,可使用多个useState
或者useEffect
,那么React
怎么知道哪一个state
对应哪一个useState
?答案是React
靠的是Hook
调用的顺序。只要Hook
的调用顺序在屡次渲染之间保持一致,React
就能正确地将内部state
和对应的Hook
进行关联。若是咱们将一个Hook
调用放在了条件语句中,就有可能会扰乱Hook
的调用的顺序,致使内部错误的对应state和useState,进而致使bug的产生。
自定义Hook
是一个函数,其名称以 “use” 开头,函数内部能够调用其余的Hook
。
当咱们想在两个函数之间共享逻辑时,咱们会把它提取到第三个函数中。而组件和Hook
都是函数,因此也一样适用这种方式。
能够直接看下面的例子:
import { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
与React
组件不一样的是,自定义Hook
不须要具备特殊的标识。咱们能够自由的决定它的参数是什么,以及它应该返回什么。换句话说,它就像一个正常的函数,可是它的名字应该始终以use
开头,这样能够一眼看出其符合Hook
的规则。
此处useFriendStatus
的Hook
目的是订阅某个好友的在线状态。这就是咱们须要将friendID
做为参数,并返回这位好友的在线状态的缘由。
function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; } function FriendListItem(props) { const isOnline = useFriendStatus(props.friend.id); return ( <li style={{ color: isOnline ? 'green' : 'black' }}> {props.friend.name} </li> ); }
自定义Hook
是一种天然遵循Hook
设计的约定,而并非React
的特性。
自定义Hook
必须以 “use” 开头吗?必须如此。这个约定很是重要。不遵循的话,因为没法判断某个函数是否包含对其内部Hook
的调用,React
将没法自动检查你的Hook
是否违反了Hook
的规则。
在两个组件中使用相同的Hook
会共享state
吗?不会。自定义Hook
是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),因此每次使用自定义Hook
时,其中的全部state
和反作用都是彻底隔离的。
自定义Hook
如何获取独立的state
?每次调用Hook
,它都会获取独立的state
。因为咱们直接调用了useFriendStatus
,从React
的角度来看,咱们的组件只是调用了useState
和useEffect
。正如咱们在以前章节中了解到的同样,咱们能够在一个组件中屡次调用useState
和useEffect
,它们是彻底独立的。
零零碎碎写了这么多,做为一个入门参考,看了这篇文章,应该会对React Hook有了大体的了解,文章中也有深刻其内部机制剖析的地方,可是仅仅对state和effect部分作了简要的深刻,而实际上React Hook中间还有不少的点值得去深刻推敲,因为实际项目工做中用到的很少,所以也无法抓住某个坑作深刻的研究,准备后续认真研读一下react的源码,对其内部机制作深刻的研究。好好静下心来沉淀。