2019-11-15:
学习内容:
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。
请记住 Hook 是:
没有计划从 React 中移除 class。
Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。
动机:
i、组件之间复用状态逻辑很难:
React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)。React 需要为共享状态逻辑提供更好的原生途径。
你可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。
ii、复杂组件变得难以理解:
组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。
相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。
在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。
为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
iii、class 是一个障碍:
理解 JavaScript 中 this
的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。
class 不能很好的压缩,并且会使热重载出现不稳定的情况。
为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。
例子:计数器,记录按键次数
在这里,useState
就是一个 Hook 。通过在函数组件里调用它来给组件添加一些内部 state。React 会在重复渲染时保留这个 state。useState
会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState
,但是它不会把新的 state 和旧的 state 进行合并。
useState
唯一的参数就是初始 state。在上面的例子中,我们的计数器是从零开始的,所以初始 state 就是 0
。值得注意的是,不同于 this.state
,这里的 state 不一定要是一个对象 —— 如果你有需要,它也可以是。这个初始 state 参数只有在第一次渲染时会被用到。
我们声明了一个叫 count
的 state 变量,然后把它设为 0
。React 会在重复渲染时记住它当前的值,并且提供最新的值给我们的函数。我们可以通过调用 setCount
来更新当前的 count
。
state 初始值为 { count: 0 }
,当用户点击按钮后,我们通过调用 this.setState()
来增加 state.count
。
对比class和hook两个例子:
i、在 class 中,我们通过在构造函数中设置 this.state
为 { count: 0 }
来初始化 count
state 为 0;
ii、在函数组件中,我们没有
this
,所以我们不能分配或读取 this.state
。我们直接在组件中调用 useState
Hook。
(const [count, setCount] = useState(0);)方括号是数组解构。
i、调用 useState
方法的时候做了什么? -- 它定义一个 “state 变量”。我们的变量叫 count
, 但是我们可以叫他任何名字,比如 banana
。这是一种在函数调用时保存变量的方式 —— useState
是一种新方法,它与 class 里面的 this.state
提供的功能完全相同。一般来说,在函数退出后变量就就会”消失”,而 state 中的变量会被 React 保留。
ii、useState
需要哪些参数? useState()
方法里面唯一的参数就是初始 state。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。在示例中,只需使用数字来记录用户点击次数,所以我们传了 0
作为变量的初始 state。(如果我们想要在 state 中存储两个不同的变量,只需调用 useState()
两次即可。)
iii、useState
方法的返回值是什么? 返回值为:当前 state 以及更新 state 的函数。这就是我们写 const [count, setCount] = useState()
的原因。这与 class 里面 this.state.count
和 this.setState
类似,唯一区别就是你需要成对的获取它们。
“Create” 可能不是很准确,因为 state 只在组件首次渲染的时候被创建。在下一次重新渲染时,useState
返回给我们当前的 state。否则它就不是 “state”了!这也是 Hook 的名字总是以 use
开头的一个原因。
i、class中: { this.state.count }
ii、hook中: { count }
i、class中,用 this.setState( { count : this.state.count + 1 } )
ii、hook中,已经有setCount 和count 变量: setCount( count + 1 )
React 保持对当先渲染中的组件的追踪。
当你用 useState()
调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化),然后把指针移动到下一个。这就是多个 useState()
调用会得到各自独立的本地 state 的原因。
函数组件,曾经称为“无状态组件”,但现在为他们引入使用React state(hook造成的)。
可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。
useEffect
就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不过被合并成了一个 API。
举例1: 上面例子添加功能--更新DOM后设置一个页面标题:(运行“副作用”)
当你调用 useEffect
时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。因为我们在执行完这些操作之后,就可以忽略他们了。
class中的副作用实现:
render
函数是不应该有任何副作用的。我们基本上都希望在 React 更新 DOM 之后才执行我们的操作。这就是为什么在 React class 中,我们把副作用操作放到 componentDidMount
和 componentDidUpdate
函数中。
对比hook和class 在副作用中的区别:
i、useEffect
做了什么?-- 通过使用这个 Hook,你可以告诉 React 组件需要在渲染后执行某些操作。React 会保存你传递的函数(我们将它称之为 “effect”),并且在执行 DOM 更新之后调用它。在这个 effect 中,我们设置了 document 的 title 属性,不过我们也可以执行数据获取或调用其他命令式的 API。
ii、为什么在组件内部调用 useEffect
?-- 将 useEffect
放在组件内部让我们可以在 effect 中直接访问 count
state 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。
iii、useEffect
会在每次渲染后都执行吗?-- 是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
更快:
与 componentDidMount
或 componentDidUpdate
不同,使用 useEffect
调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。
例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!
举例2: (清除副作用)订阅好友的在线状态,并通过取消订阅来清除操作:
React 会在组件销毁时取消对 ChatAPI
的订阅,然后在后续渲染时重新执行副作用函数。
通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。
在class中会这样做:
componentDidMount
和 componentWillUnmount
之间相互对应。使用生命周期函数迫使我们拆分这些逻辑代码,即使这两部分代码都作用于相同的副作用。
对比hook和class 的副作用写法:
为什么要在 effect 中返回一个函数?-- 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
React 何时清除 effect?-- React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。
使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。
上面hook,effect事例就是使用多个effect的例子。
Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。
并不需要特定的代码来处理更新逻辑(class中的 componentDidUpdate
),因为 useEffect
默认就会处理。它会在调用一个新的 effect 之前对前一个 effect 进行清理。
这是很常见的需求,所以它被内置到了 useEffect
的 Hook API 中。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect
的第二个可选参数即可:
如果 count
的值是 5
,而且我们的组件重渲染的时候 count
还是等于 5
,React 将对前一次渲染的 [5]
和后一次渲染的 [5]
进行比较。因为数组中的所有元素都是相等的(5 === 5
),React 会跳过这个 effect,这就实现了性能的优化。
对于清除操作的effect(return 即清除)同样:
如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([]
)作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect
,因此会使得额外操作很方便。
Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
同时,我们提供了 linter 插件来自动执行这些规则。这些规则乍看起来会有一些限制和令人困惑,但是要让 Hook 正常工作,它们至关重要。
上面订阅好友在线状态功能,可以提取出来成为一个自定义Hook,提供给不同的组件使用。由于Hook 是一种复用状态逻辑的方式,它不复用 state 本身。事实上 Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个自定义 Hook。
约束:自定义 Hook 更像是一种约定而不是功能。如果函数的名字以 “use
” 开头并调用其他 Hook,我们就说这是一个自定义 Hook。 useSomething
的命名约定可以让我们的 linter 插件在使用 Hook 的代码中找到 bug。
https://zh-hans.reactjs.org/docs/hooks-reference.html
https://zh-hans.reactjs.org/docs/hooks-faq.html
react --(4)hook:useState、useEffect、规则、自定义hook
原文:https://www.cnblogs.com/marvintang1001/p/11867719.html