React Hooks
Hooks 是 React 16.8 的新特性,它是一些方法,用来增强函数组件的功能,让它有自己的状态,生命周期特性等。之前需要使用 state 或者其他的一些功能只能通过类组件。因为函数组件没有实例,生命周期函数等。
使用它需要注意的地方:
只能在 React 的函数组件中调用
只能在函数内部的最外层调用 Hook,不要在循环、条件判断、或者子函数中使用。
为什么要使用它
一、状态逻辑难复用
React 将页面拆分成多个独立、可复用的组件,并且采取单向数据流的方式传递数据。有些类组件有包含了自己的 state,所以更难复用。之前可以通过 Render Props
和 Higher-Order Components
解决,但是这两种方式都要求你重新组织你的组件结构。
Render Props(渲染属性):一个组件包含一个 render 输入属性,它是一个方法,并且返回 React element。
1 | <DataProvider render={data => ( |
示例:
1 | class Cat extends React.Component { |
Higher-Order Components(高阶组件):高阶组件是一个方法,接受一个组件,并且返回一个新的组件
1 | const EnhancedComponent = higherOrderComponent(WrappedComponent); |
示例:
1 | // CommentList 组件与 BlogPost 组件没写 |
上面的这两种方式,解决了组件状态和逻辑复用的问题。但是把原先的组件,包裹了好几层的感觉,层级冗余。
补充一下关于 mixins,它可以用于抽出通用部分,React
现在已经不再推荐使用Mixin
来解决代码复用问题,因为Mixin
带来的危害比他产生的价值还要巨大。
它的危害:
Mixin
可能会相互依赖,相互耦合,不利于代码维护- 不同的
Mixin
中的方法可能会相互冲突 Mixin
非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
二、项目复杂难以维护
在生命周期函数中混杂不相干的逻辑(如:在
componentDidMount
中注册事件以及其他的逻辑,在componentWillUnmount
中卸载事件,这样分散不集中的写法,很容易写出 bug )类组件中到处都是对状态的访问和处理,导致组件难以拆分成更小的组件
三、this 指向
用class来创建react组件时,你必须了解 this 的指向问题。在组件中使用,需要通过各种方式绑定 this 来避免问题。
Hooks
一、useReducer
它也是 useState 的替代方案,在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等
1 | const [state, dispatch] = useReducer(reducer, initialArg, init); |
1)它有两种方式初始化 state
- 作为 initialArg 传入
1 | const initialState = {count: 0}; |
- init 传入一个函数,延迟创建初始值,可以用来进行复杂的计算,返回最终的结果
init(initialArg)
1 | function init(initialCount) { |
2)使用它需要注意的地方:
在 dispatch 触发之后,它是使用 Object.is 来判断新旧值是否相等,如果被判断为相等的话就不会进入调度,所以也就不会更新视图。
1 | // reactFiberHooks.js |
煮个栗子
1 | import React, { useReducer } from "react"; |
当点击触发 dispatch 的时候,initialState.obj.count 的值被更新为 2,但是因为 Object.is 比较两个值还是相等的,所以直接 return,没有进入任务调度阶段,也就导致视图没更新。
二、useState
通过在函数组件内部调用 useState 来添加变量。它返回一个数组,分别是当前状态的值和一个更新值的方法。这个方法类似于 this.setState
。(只是这个方法不会像 this.setState
一样,合并新旧值。对比 useState 与 this.setState。)并且你可以在组件内多次使用 useState。通过数组解构可以给返回的值添加不同的变量名。
1 | import React, { useState } from 'react'; |
简单了解一下 this.setState
与 useState 返回的 dispatch 更新值的方法
的区别
- useState:它是直接做的值的替换,同时在调用 dispatch 更新方法的时候有对新值和旧值做浅比较,判断是否相等。如果相等就不会进行更新视图。
1 | import React, { useState } from "react"; |
- this.setState:它是一个合并的过程
1 | import React from "react"; |
合并过程:
在多次调用 setState 的时候,会生成一条链表,之后通过这条链表来合成出最后的结果。
1 | ... |
三、useEffect
可以将 useEffect 理解为
componentDidMount
,componentDidUpdate
, 加componentWillUnmount
的结合。它可以在组件渲染之后做某些事情,React 会记住你传入的方法,在每次渲染的时候都会调用到它,不光光只是第一次渲染时候被调用。在更新 DOM 的时候需要触发一些代码,比如:网络请求、DOM 操作、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等,这些被称为副作用。副作用也分为需要清除与不需要清除的。
useEffect 接收一个函数,这个函数会在组件渲染到屏幕后执行。如果需要清除副作用,那么这个函数应该返回一个新的函数,如果不需要清除,则不用返回任何内容。
1)每次渲染都会调用到 useEffect 传入的副作用函数,而不是只调用一次
1 | import React, { useState, useEffect } from 'react'; |
2)useEffect 与 useState 一样,都可以支持多次使用,可以拆分多个出来处理不同的业务。
1 | function FriendStatusWithCounter(props) { |
3)为什么每次渲染都会调用副作用函数
假设有这么个类组件,在 componentDidMount
的时候订阅了事件,使用的 props.friend.id 是 1。如果 props.friend.id 变为了 2,那么在组件调用 componentWillUnmount
钩子的时候,会传入错误的 props.friend.id ,也就会导致 bug 的出现。所以就需要添加 componentDidUpdate
钩子,在组件更新的时候,解除之前的事件订阅、并且重新添加订阅,从而避免 bug 出现。
1 | componentDidMount() { |
如果在函数组件内使用 useEffect,就不需要考虑上面的问题
1 | // ... |
4)性能优化,如何跳过一些副作用函数
可以通过在 useEffect
的第二个参数添加一个数组,当数组里的值产生变化的时候,才会调用副作用函数。
1 | useEffect(() => { |
如果你希望副作用函数和清除副作用,只被调用一次。可以在第二个参数传入一个空数组。它的意思就是告诉 React 它不依赖于任何的 props 或者 state,它也不需要被再次执行。所以 useEffect 就变成了类似于使用 componentDidMount
与 componentWillUnmount
的方式。
四、useCallback & useMemo
这两个 hook 分别用于缓存函数 和 缓存函数的返回值
useCallback
接收一个内联的回调函数和数组作为入参,返回该回调函数的 memoized 版本,只有依赖项被改变的时候,才会重新计算 memoized 的值。
1 | const memoizedCallback = useCallback( |
煮个栗子
1 | import React, { useState, useCallback, useEffect } from "react"; |
在第一次渲染之后,点击 setAge 的时候,不会调用到 getFetchUrl 方法和 useEffect 传入的的函数。只有当 count 和 getFetchUrl 发生变化的时候,才会调用它们。
在每次的视图更新过程中,都有调用这个方法组件,并且都调用了 useCallback 方法。它会比较新的依赖项与旧的依赖项是否相等,也是浅比较。如果依赖项相等,就会返回缓存的方法。
在 areHookInputsEqual 方法里遍历新旧依赖项,通过 objectIs 比较是否相等
1 | function areHookInputsEqual(nextDeps, prevDeps) { |
useMemo
接收一个创建函数与数组作为入参,返回 memoized 值。 只有当数组中的值发生变化的时候,才会重新计算。
1 | const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); |
煮个栗子
1 | import React, { useState, useMemo, useEffect } from "react"; |
useMemo 和 useCallback 差不多。不过它缓存的一个是值,而不是一个是函数,useMemo 在判断如果不相等的时候,会调用 useMemo 传入的回调,来生成新的值。
使用useMemo
的时候需要注意它本身也有开销。useMemo
会「记住」一些值,同时在后续 render 时,将依赖数组中的值取出来和上一次记录的值进行比较,如果不相等才会重新执行回调函数,否则直接返回「记住」的值。这个过程本身就会消耗一定的内存和计算资源。因此,过度使用 useMemo
可能会影响程序的性能。
自定义钩子
自定义钩子就是一个包含 use 开头的方法,你可以自由决定需要传入哪些参数,返回哪些值。
1 | import { useState, useEffect } from 'react'; |
如何使用自定义的 Hook
1 | function FriendListItem(props) { |
资料
https://reactjs.org/docs/hooks-intro.html
https://juejin.im/post/5be3ea136fb9a049f9121014