ReactReact全家桶React Hooks
Posted 前端More
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReactReact全家桶React Hooks相关的知识,希望对你有一定的参考价值。
文章目录
1 Hooks简介
1.1 什么是Hooks?
-
Hooks
直译是 “钩子”,它并不仅是react
,甚至不仅是前端界的专用术语,而是整个行业所熟知的用语。 一般指:系统运行到某一时期时,会调用被注册到该时机的回调函数。比如:windows
系统的钩子能监听到系统的各种事件,浏览器提供的onload
或addEventListener
能注册在浏览器各种时机被调用的方法。 -
在React中,
Hooks
是 React 16.8 新增的特性,是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的特殊JS函数
,它可以让你在不编写 class 的情况
下使用 state 以及其他的 React 特性。 简言之:Hooks是一系列方法,提供了在函数式组件
中完成开发工作的能力。 -
之前使用 state 或者其他一些功能时,只能使用类组件,因为函数组件没有实例,没有生命周期函数,只有类组件才有 , Hooks 的出现可以让你在函数组件直接使用state等功能
1.2 Hooks的优势
- hooks 之间的状态是独立的,有自己独立的上下文,不会出现混淆状态的情况
- 让函数组件有了状态管理
- 解决了组件树不直观、类组件难维护、逻辑不易复用的问题
- 避免函数重复执行的副作用
1.3 Hooks使用场景
- 利用 hooks 取代生命周期函数
- 让函数组件有了状态
- 组件辅助函数
- 处理发送请求
- 存取数据做好性能优化
1.4 Hooks使用注意事项
-
假设任何以
use
开头并紧跟着一个大写字母的函数就是一个Hook
-
只能在函数内部的最外层调用 Hook,不要在循环、条件判断或者子函数中调用
-
只能在 React 的函数组件中调用 Hook,不要在普通JS函数中调用
2 Hooks API
2.1 数据驱动更新型Hooks
useState
useState(数据驱动更新)
:给函数组件添加state状态,并进行状态的读写操作 ,函数组件通过 useState 可以让组件重新渲染,更新视图。
useState语法:
import React, useState from 'react'
const [xxx, setXxx] = useState(initState)
参数initState
: 第一种情况是非函数
,将作为 xxx 初始化的值。 第二种情况是函数
,函数的返回值作为 useState 初始化的值。
返回值[xxx, setXxx]
: 包含2个元素的数组 ,xxx
是当前状态值,setXxx
是修改状态值的函数
xxx
: 提供给 UI ,作为渲染视图的数据源setXxx
: 改变xxx
的异步函数 ,推动函数组件渲染的渲染函数, 要在下次重新渲染才能获取新值 ,下面是setXxx的两种写法:- setXxx(newValue): 参数为非函数值, 直接指定新的状态值来覆盖原来的状态值
- setXxx((value) => newValue): 参数为函数, 接收原来的状态值, 返回新的状态值进行覆盖,若newValue没有使用value可以不写
useState基础用法:
import React, useState from 'react'
const Demo = () =>
let [number, setNumber] = useState(0)
console.log(number, 'number')/* 这里的number能够即时更新的,这里是在重新渲染后调用的 */
return (
<div>
<span>number</span>
<button
type="primary"
onClick=() =>
setNumber(number + 1)
console.log(number,'number-=') /* 这里number不能够即时更新的,setNumber是异步更新 */
>
+1按钮
</button>
</div>
)
注:
- 在函数组件一次执行上下文中,state 的值是固定不变的, 当触发setXxx在当前执行上下文中获取不到最新的 state,,只有在下一次组件渲染中才能获取到 (setXxx是异步函数)
- 如果两次setXxx 传入相同的 state 值,那么组件就不会更新
补充:方括号的作用
[ ]语法叫数组解构
,它意味着我们同时创建了 fruit 和 setFruit 两个变量,fruit 的值为 useState 返回的第一个值,setFruit是返回的第二个值
const [fruit, setFruit] = useState('banana');
//等价于
var fruitStateVariable = useState('banana'); // 返回一个有两个元素的数组
var fruit = fruitStateVariable[0]; // 数组里的第一个值
var setFruit = fruitStateVariable[1]; // 数组里的第二个值
useReducer
useReducer(订阅状态,更新视图)
是useState
的替代方案,对于复杂的state操作逻辑,嵌套的state的对象, 或者下一个 state 依赖于之前的 state 等 ,推荐使用useReducer。
useReducer语法:
const [xxx, setXxx] = useReducer(reducer, initState);
参数reducer
:类似于 redux 中的 reducer 函数,(state, action) => newState
,接收当前应用的state和触发的动作action,计算并返回最新的state ,如果返回的 state 和之前的 state ,内存指向相同,那么组件将不会更新。
参数initState
: 第一种情况是非函数
,将作为state初始化的值。 第二种情况是函数
,函数的返回值作为 useReducer 初始化的值。
返回值[xxx, setXxx]
: 包含2个元素的数组 ,xxx
是当前状态值,setXxx
是修改状态值的函数
xxx
: 提供给 UI ,作为渲染视图的数据源setXxx
: 改变xxx
的异步函数 ,用来触发reducer函数,计算对应的state,其余和useState
类似
useReducer基础用法:
//demo.js
import React, useReducer from 'react'
import MyChildren from './children'
const Demo = () =>
/* number为更新后的state值, dispatchNumbner 为当前的派发函数 */
const [number, dispatchNumber] = useReducer((state, action) =>
const payload, name = action
/* return的值为新的state */
switch (name)
case 'add':
return state + 1
case 'sub':
return state - 1
case 'reset':
return payload
return state
, 0)
return (
<div>
当前值:number
/* 派发更新 */
<Button onClick=() => dispatchNumber( name: 'add' )>增加</Button>
<Button onClick=() => dispatchNumber( name: 'sub' )>减少</Button>
<Button onClick=() => dispatchNumber( name: 'reset', payload: 666 )>
赋值
</Button>
/* 把dispatch 和 state 传递给子组件 */
<MyChildren dispatch=dispatchNumber state=number />
</div>
)
//children.js
const MyChildren = props =>
const dispatch, state = props
return <div>MyChildren中值:state</div>
注:
- 如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。
2.2 状态派生与保存型Hooks
任何刚开始写 react 组件的工程师应该都能发现,组件会一直重新渲染
。比如我们在使用React开发过程中经常会遇到父组件引入子组件的情况,往往会造成子组件不必要的重复渲染,造成主线程阻塞,页面渲染卡顿,带来性能问题。 缓存组件
可以优化性能 ,能够很好解决这一问题。
memoization 含义、Hooks 逻辑、React浅比较
什么是 memoization?
- memoization 是一种用空间换时间的优化方法。
- 把计算结果缓存起来,取用前需通过校验,取出后实现复用。
- 保持返回值的引用相等。
Hooks逻辑
- 几乎所有有依赖数组的 hooks 都共享同一套基础逻辑。
- 组件初次渲染时,执行一次。
- 组件重新渲染时,通过浅比较检查依赖数组有没有变化。如果没有,不重复执行。
React中的浅比较:
- 浅比较使用的是
Object.is
函数,只比较对象第一层的属性和值,不是使用严格相等 === 运算符; - 通过浅比较,空对象和空数组是等价的;
- 通过浅比较,以数组索引为
key
和数组值为value
的对象是等价的,比如:0: 2, 1: 3
等价于[2, 3]
; - 由于通过
Object.is
比较的+0
和-0
、Number.NaN
和NaN
是不相等的,所以在复杂结构中比较时,这也是适用的; - 虽然 和 [ ] 浅比较是相等的,但是嵌套在对象中对象是不相等的,比如:
someKey:
和someKey: []
是不相等的。
shallowCompare(, ) // => true
shallowCompare( a: 1, b: 2 , a: 1, b: 2 ) // => true
shallowCompare( a: 1, b: 2 , a: 1, c: 2 ) // => false
shallowCompare( a: 1, b: 2 , a: 1, b: 2 ) // => false
React.memo高阶组件
React.memo
:将组件的渲染结果缓存,并有效复用,减少主线程的阻塞。
React.memo语法:
const MemoComponent = React.memo(component, Func)
参数component
:自定义组件
参数Func
:一个函数,用来判断组件需不需要重新渲染。如果省略第二个参数,默认会对该组件的props进行浅比较,当 props 没有改变时,组件就不需要重新渲染
如果 props 中存在回调函数或者多层嵌套的复杂对象,或每次父组件更新时子组件的props都会生成新的内存地址,这样浅比较无效,仅使用react.memo是无法达到目的的,需要自己写比较函数,或者搭配 useCallback 使用。
何时使用 React.memo
- 检查组件是否是 Pure 的,即相同输入,相同输出。
- 检查组件是否经常被相同的 props 重复渲染,且导致了性能问题。
- 如果 props 中有回调函数,可以考虑搭配使用 useCallback 使用。
useMemo
useMemo(派生新状态)
: 可以在函数组件 render 上下文中同步执行一个函数逻辑,这个函数的返回值可以作为一个新的状态缓存起来, 同时保证了返回值的引用地址不变 。 把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算缓存值 ,让组件中的函数跟随状态更新,用于进行高开销、复杂场景的计算 。
useMemo语法:
const memoizedValue = useMemo(create,deps)
//useMemo(() => computeExpensiveValue(a, b), [a, b]);
第一个参数 create
:创建函数,函数的返回值作为缓存值
第二个参数deps
:依赖项数组,为当前 useMemo 的依赖项,在函数组件下一次执行的时候, 通过浅比较对比 deps 依赖项里面的状态,是否有改变,如果有改变重新执行 create ,得到新的缓存值。 没有提供依赖项数组,useMemo在每次渲染时都会计算新的值 。 一般来说,所有create函数中引用的值都应该出现在依赖项数组deps中
返回值memoizedValue
:执行 create 的缓存值
。如果 deps 中有依赖项改变,返回的重新执行 create 产生的值,否则取上一次缓存值。
useMemo 何时使用:
create
导致了页面的卡顿create
需要跟随组件渲染执行。create
本身有优化空间
useMemo基础用法:
- 派生新状态:
function Scope()
const keeper = useKeep()
const cacheDispatch, cacheList, hasAliveStatus = keeper
/* 通过 useMemo 得到派生出来的新状态 contextValue */
const contextValue = useMemo(() =>
return
cacheDispatch: cacheDispatch.bind(keeper),
hasAliveStatus: hasAliveStatus.bind(keeper),
cacheDestory: (payload) => cacheDispatch.call(keeper, type: ACTION_DESTORY, payload )
, [keeper])
return <KeepaliveContext.Provider value=contextValue>
</KeepaliveContext.Provider>
如上通过 useMemo 得到派生出来的新状态 contextValue ,只有 keeper 变化的时候,才改变 Provider 的 value 。
- 缓存计算结果:
function Scope()
const style = useMemo(()=>
let computedStyle =
// 经过大量的计算
return computedStyle
,[])
return <div style=style ></div>
- 缓存组件,减少子组件 rerender 次数:
function Scope ( children )
const renderChild = useMemo(()=> children() ,[ children ])
return <div> renderChild </div>
useCallback
useCallback(保存状态)
: useCallback 与useMemo类似,它们接收的参数一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useCallback 缓存的是函数本身以及它的引用地址,而不是返回值
useCallback语法:
const memoizedCallback = useCallback(
() =>
doSomething(a, b);
,
[a, b],
);
// 不使用 useCallback
const handleLog = (message) => console.log(message)
// 使用 useCallback
const handleLogMessage = useCallback(handleLog, [message])
把回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的缓存版本,该缓存版本的回调函数仅在某个依赖项改变时才会更新 ,跟随状态更新执行。
注意:
- useCallback
返回的是一个函数,不再是值
useMemo
第一次渲染时执行,缓存变量,之后只有在依赖项改变时才会更新缓存,如果依赖不更新,返回的永远是缓存的那个变量useCallback
第一次渲染时执行,缓存函数,之后只有在依赖项改变时才会更新缓存 ,如果依赖不更新,返回的永远是缓存的那个函数- 给子组件中传递
props
的时候,如果当前组件不更新,不会触发子组件的重新渲染(最重要的用途)
使用 useCallback 要对依赖数组做浅比较,对性能带来的负面影响,同时又提升了代码的复杂度。如果使用不当,很可能得不偿失。
2.3 执行副作用型Hooks
useEffect
useEffect(异步执行副作)
: 在函数组件中执行副作用操作 (用于模拟类组件中的生命周期钩子) , 在执行 DOM 更新之后调用 。
可以把 useEffect Hook 看做 componentDidMount
,componentDidUpdate
和 componentWillUnmount
这三个函数的组合。
- componentDidMount: 组件挂载完成 (开启监听, 发送ajax请求)
- componentDidUpdate:组件更新完成
- componentWillUnmount:组件即将卸载(收尾工作, 如: 清理定时器)
副作用
:指那些没有发生在数据向视图转换过程中的逻辑,如 发送ajax请求获取数据、 设置订阅 / 启动定时器及手动修改DOM 。
在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的
不需要清除的effect
: 只想在 React 更新 DOM 之后运行一些额外的代码,如发送网络请求,手动变更 DOM,记录日志
需要清除的 effec
: 如订阅外部数据源 ,清除工作可以防止引起内存泄露
默认情况下,React 会在每次渲染后都会执行useEffect
,通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。 将 useEffect放在组件内部让我们可以在 effect 中直接访问 state 变量(或其他 props),我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中
useEffect(() =>
// 执行副作用操作【挂载+更新】
return () => //返回清除副作用的函数(可选),如订阅,定时器,在组件卸载的时候执行清除操作
, [stateValue]) // 仅在stateValue更改时执行更新
//如果某些特定值在两次重渲染之间没有发生变化,你可以通知React跳过对useEffect的调用,只要传递数组作为 useEffect 的第二个可选参数即可
//如果想执行只运行一次的useEffect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数,这样effect内部的props和state就会一直拥有其初始值
2.4 状态获取与传递型Hooks
useRef
useRef
可以用来获取元素,缓存状态,保存标签对象,接受一个状态 initValue 作为初始值,返回一个 ref 对象 ,ref上有一个 current 属性
就是 ref 对象需要获取的内容。
const refContainer = useRef(initValue);
console.log(refContainer.current)
-
useRef
返回一个可变的 ref 对象,其current
属性被初始化为传入的参数(initValue
)。返回的 ref 对象在组件的整个生命周期内保持不变。 -
useRef
可以在函数组件中存储/查找组件内的标签或任意其它数据,相当于创建一个额外的容器来存储数据,我们可以在外部拿到这个值 -
重新赋值
ref.current
不会触发重新渲染
案例
function TextInputWithFocusButton()
const inputEl = useRef(null);
const onButtonClick = () =>
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
;
return (
<>
<input ref=inputEl type="text" />
<button onClick=onButtonClick>Focus the input</button>
</>
);
useContext
useContext
用来获取父级组件传递过来的 context 值,这个当前值就是最近的父级组件 Provider 设置的 value 值,useContext 参数一般是由 createContext 方式创建的,也可以父级上下文 context 传递的 ( 参数为 context )。useContext 可以代替 context.Consumer 来获取 Provider 中保存的 value 值。
const contextValue = useContext(context)
useContext 接受一个参数,一般都是 context 对象,返回值为 context 对象内部保存的 value 值。
import React, useContext, createContext from 'react'
//创建context对象
const MyContext = React.createContext()
export default function Hook()
const [num, setNum] = React.useState(1)
return (
<h1>
//Provider确定数据共享范围,value分发数据
<Context.Provider value=num>
<Item num=num />
</Context.Provider>
</h1>
)
function Item()
//接收contex对象,并返回该context的值
//该值由上层组件中距离当前组件最近的<MyContext.Provider>的value prop决定
const num = useContext(MyContext)//useContext的参数必须是context对象本身:
//调用了useContext的组件总会在context值变化时重新渲染,上层数据发生改变,肯定会触发重新渲染
return <div>子组件 num</div>
2.5 自定义型 Hooks
自定义 Hook
是一个函数,其名称以 use
开头,是 React Hooks 聚合产物,其内部有一个或者多个 React Hooks 组成,用于解决一些复杂逻辑。 通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
自定义 hooks的步骤:
- 引入 react 和自己需要的 hook
- 创建自己的hook函数
- 返回一个数组,数组中第一个内容是数据,第二个是修改数据的函数
- 暴露自定义 hook 函数出去
- 引入自己的业务组件
例如:模拟数据请求的 Hooks
import React, useState, useEffect from "react";
function useLoadData()
const [num, setNum] = useState(1);
useEffect(() =>
setTimeout(() =>
setNum(2);
, 1000);
, []);
return [num, setNum];
export default useLoadData;
我们希望 reducer 能让每个组件来使用,我们自己写一个 hooks,自定义一个自己的 LocalReducer
import React, useReducer from "react";
const store = num: 1210 ;
const reducer = (state, action) =>
switch (action.type)
case "num":
return ...state, num: action.num ;
default:
return ...state ;
;
function useLocalReducer()
const [state, dispatch] = useReducer(reducer, store);
return [state, dispatch];
export default useLocalReducer;
以上是关于ReactReact全家桶React Hooks的主要内容,如果未能解决你的问题,请参考以下文章