react --(4)hook:useState、useEffect、规则、自定义hook

2019-11-15:

学习内容:


 

  Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

  Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

 

一、简介:

请记住 Hook 是:

  • 完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。但是如果你不想,你不必现在就去学习或使用 Hook。
  • 100% 向后兼容的。 Hook 不包含任何破坏性改动。
  • 现在可用。 Hook 已发布于 v16.8.0。

没有计划从 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 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。

 


 

 

二、使用 State Hook:

 

(1)state Hook: { useState }

  例子:计数器,记录按键次数 

  在这里,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

 

(2)等价的class 示例:

 

  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);)方括号是数组解构

 

 

🌟(3)关于useState使用:

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 类似,唯一区别就是你需要成对的获取它们。

 

(4)为什么叫useState:

  “Create” 可能不是很准确,因为 state 只在组件首次渲染的时候被创建。在下一次重新渲染时,useState 返回给我们当前的 state。否则它就不是 “state”了!这也是 Hook 的名字总是以 use 开头的一个原因。

 

🌟(5)如何读取state?

i、class中: { this.state.count }

ii、hook中: { count }

 

🌟(6)更新 state?

i、class中,用 this.setState( { count : this.state.count + 1 } )

ii、hook中,已经有setCount 和count 变量: setCount( count + 1 )

 

(7)为什么react知道这个hook对应哪个组件?

  React 保持对当先渲染中的组件的追踪。

  当你用 useState() 调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化),然后把指针移动到下一个。这就是多个 useState() 调用会得到各自独立的本地 state 的原因。

 

(8)用函数组件和hook 取代clss:

  函数组件,曾经称为“无状态组件”,但现在为他们引入使用React state(hook造成的)。

 

 

 


 

三、使用 Effect Hook:(两种常见副作用操作:需要清除的和不需要清除的)

(1)Effect Hook: { useEffect }

  可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。

  useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。

举例1: 上面例子添加功能--更新DOM后设置一个页面标题:(运行“副作用”)

  当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。

 

 

(2)不需清除的effect:

  在 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 不需要同步地执行。

 

(3)需要清楚的 effect:

  例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露

举例2: (清除副作用)订阅好友的在线状态,并通过取消订阅来清除操作:

 

  React 会在组件销毁时取消对 ChatAPI 的订阅,然后在后续渲染时重新执行副作用函数。

  通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。

 

在class中会这样做:

 

  componentDidMount 和 componentWillUnmount 之间相互对应。使用生命周期函数迫使我们拆分这些逻辑代码,即使这两部分代码都作用于相同的副作用。

 

对比hook和class 的副作用写法:

为什么要在 effect 中返回一个函数?-- 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

React 何时清除 effect?-- React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 在执行当前 effect 之前对上一个 effect 进行清除。

 

(4)使用Effect 的提示:

i、使用多个 Effect 实现关注点分离:

  使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题

  上面hook,effect事例就是使用多个effect的例子。

  Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。

ii、为什么每次更新的时候都要运行 Effect:

  并不需要特定的代码来处理更新逻辑(class中的 componentDidUpdate),因为 useEffect 默认就会处理。它会在调用一个新的 effect 之前对前一个 effect 进行清理。

🌟iii、通过跳过 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 使用规则:

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中,我们稍后会学习到。)

同时,我们提供了 linter 插件来自动执行这些规则。这些规则乍看起来会有一些限制和令人困惑,但是要让 Hook 正常工作,它们至关重要。

 

 


 

 

五、自定义 Hook:

  上面订阅好友在线状态功能,可以提取出来成为一个自定义Hook,提供给不同的组件使用。由于Hook 是一种复用状态逻辑的方式,它不复用 state 本身。事实上 Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个自定义 Hook。

 

 

 

  约束:自定义 Hook 更像是一种约定而不是功能。如果函数的名字以 “use” 开头并调用其他 Hook,我们就说这是一个自定义 Hook。 useSomething 的命名约定可以让我们的 linter 插件在使用 Hook 的代码中找到 bug。

 


 

 

六、Hook API 索引

https://zh-hans.reactjs.org/docs/hooks-reference.html

 

七、Hooks FAQ

https://zh-hans.reactjs.org/docs/hooks-faq.html

 

 

 

posted @ 2019-11-15 16:42  Marvin_Tang  阅读(1536)  评论(0编辑  收藏  举报