Hooks全面详解
认识Hooks
1.Hook介绍
Hook是 React 16.8 的新增特性,它可以让我们在不编写class
的情况下使用state
以及其他的React
特性 (比如生命周期)
2.class与function组件对比
- 我们先来思考一下
class
组件相对于函数式组件有什么优势?比较常见的是下面的优势:
对比 | class组件 | function组件 |
---|---|---|
state | class 组件可以定义自己的state , 用来保存组件自己内部的状态 | 函数式组件不可以,因为函数每次调用都会产生新的临时变量 |
生命周期 | class 组件有自己的生命周期, 我们可以在对应的生命周期中完成自己的逻辑 ( 比如在componentDidMount 中发送网络请求,并且该生命周期函数只会执行一次 ) | 函数式组件在学习hooks 之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求 |
render渲染 | class 组件可以在状态改变时只会重新执行render 函数以及我们希望重新调用的生命周期函数componentDidUpdate 等 | 函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次 |
- 所以,在
Hook
出现之前,对于上面这些情况我们通常都会编写class
组件。
3.Class组件存在的问题
复杂组件变得难以理解:
- 我们在最初编写一个
class
组件时,往往逻辑比较简单,并不会非常复杂, 但是随着业务的增多,我们的class
组件会变得越来越复杂 - 比如
componentDidMount
中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在componentWillUnmount
中移除) - 而对于这样的
class
实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度
- 我们在最初编写一个
难以理解的class:
- 很多人发现学习
ES6
的class
是学习React
的一个障碍。 - 比如在
class
中,我们必须搞清楚this
的指向到底是谁,所以需要花很多的精力去学习this
- 虽然前端开发人员必须掌握
this
,但是依然处理起来非常麻烦
- 很多人发现学习
组件复用状态很难:
- 在前面为了一些状态的复用我们需要通过高阶组件或
render props
- 像我们之前学习的
redux
中connect
或者react-router
中的withRouter
,这些高阶组件设计的目的就是为了状态的复用 - 或者类似于
Provider、Consumer
来共享一些状态,但是多次使用Consumer
时,我们的代码就会存在很多嵌套 - 这些代码让我们不管是编写和设计上来说,都变得非常困难
- 在前面为了一些状态的复用我们需要通过高阶组件或
4.Hook的出现
Hook
的出现,可以解决上面提到的这些问题简单总结
Hooks
- 它可以让我们在不编写
class
的情况下使用state
以及其他的React
特性 - 但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决
- 它可以让我们在不编写
Hook
的使用场景:- Hook的出现基本可以代替我们之前所有使用
class
组件的地方 (除了一些非常不常用的场景) - 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它
- Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用
- Hook的出现基本可以代替我们之前所有使用
Hooks的体验
计数器案例对比
通过一个计数器案例,来对比一下class
组件和函数式组件结合hooks
的对比
State Hook
认识useState
useState
来自react
, 需要从react
中导入, 它是一个Hook- 参数: 初始化值, 如果不设置为
undefined
返回值: 数组, 包含两个元素
- 元素一: 当前状态的值(第一调用为初始化值)
- 元素二: 设置状态值的函数
- 参数: 初始化值, 如果不设置为
import React, { useState } from 'react'
const [counter, setCounter] = useState(0)
Hook 补充
Hook
就是JavaScript
函数, 这个函数可以帮助你钩入(hook into
)React State
以及生命周期等特性使用
Hook
的两个额外规则:- 只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用
- 只能在 React 的函数组件中调用Hook。不要在其他 JavaScript 函数中调用
useState 解析
useState
useState
会帮助我们定义一个state
变量,useState
是一种新方法,它与class
里面的this.state
提供的功能完全相同。- 一般来说,在函数执行完毕后变量就会"消失",而
state
中的变量会被React
保留。 useState
接收一个唯一参数, 在第一次组件被调用时使用来作为初始化值useState
是一个数组, 可以通过[数组的解构赋值](https://developer.mozilla.org...
)来使用
FAQ:为什么叫
useState
而不叫createState
?- “Create” 可能不是很准确,因为 state 只在组件首次渲染的时候被创建
- 下一次重新渲染时,
useState
返回给我们当前的state
如果每次都创建新的变量,它就不是"state"了
- 这也是
Hook
的名字总是以use
开头的一个原因
- 这也是
<details>
<summary>useState流程解析</summary>
<img src="https://gitee.com/xmkm/cloudPic/raw/master/img/20201028124155.png" alt="useState流程" />
</details>
useState 补充
useState
函数的参数是可以传递一个函数的
const [counter, setCounter] = useState(() => 10)
setState
函数的参数也是可以传递一个函数的
// 进行累加都会生效
setCounter(prevState => prevState + 10)
Effect Hook
认识Effect Hook
useEffect
就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的
componentDidMount
、componentDidUpdate
和componentWillUnmount
具有相同的用途,只不过被合并成了一个 API
Effect Hook
可以让你来完成一些类似于class
中生命周期的功能- 事实上,类似于网络请求、手动更新
DOM
、一些事件的监听,都是React
更新DOM
的一些副作用(Side Effects
) - 所以对于完成这些功能的
Hook
被称之为Effect Hook
import React, { useEffect } from 'react'
useEffect(() => {
console.log('useEffect被执行了')
})
useEffect的解析
- 作用: 通过
useEffect
的Hook, 可以告诉React
需要在渲染后执行某些操作 - 参数:
useEffect
要求我们传入一个回调函数,在React
执行完更新DOM
操作之后,就会回调这个函数 - 执行时机: 首次渲染之后,或者每次更新状态之后,都会执行这个回调函数
需要清除Effect
在
class
组件的编写过程中,某些副作用的代码,我们需要在componentWillUnmount
中进行清除
- 比如我们之前的事件总线或
Redux
中手动调用subscribe
- 都需要在
componentWillUnmount
有对应的取消订阅Effect Hook
通过什么方式来模拟componentWillUnmount
呢?
useEffect
传递的<font color='red'>回调函数A</font>本身可以有一个返回值, 这个返回值是另外一个<font color='red'>回调函数B</font>
type EffectCallback = () => (void | (() => void | undefined))
import React, { useEffect, useState } from 'react'
useEffect(() => {
console.log('订阅一些事件')
// 取消订阅
return () => {
console.log('取消订阅')
}
})
为什么要在
Effect
中返回一个函数?- 这是
Effect
可选的清除机制。每个effect
都可以返回一个清除函数 - 如此可以将添加和移除订阅的逻辑放在一起
- 它们都属于
effect
的一部分
- 这是
React
何时清除Effect
- React 会在组件更新和卸载的时候执行清除操作
- 正如之前学到的,
effect
在每次渲染的时候都会执行
使用多个Effect
使用
Hook
的其中一个目的就是解决class
中生命周期经常将很多的逻辑放在一起的问题:比如网络请求、事件监听、手动修改DOM,这些往往都会放在
componentDidMount
中
- 使用
Effect Hook
,我们可以将它们分离到不同的useEffect
中:
useEffect(() => {
console.log('修改DOM')
})
useEffect(() => {
console.log('订阅事件')
}, [])
useEffect(() => {
console.log('网络请求')
}, [])
Hook允许我们按照代码的用途分离它们, 而不是像生命周期函数那样:
React
将按照effect
声明的顺序依次调用组件中的每一个effect
Effect性能优化
默认情况下,
useEffect
的回调函数会在每次渲染时都重新执行,但是这会导致两个问题:
- 某些代码我们只是希望执行一次即可,类似于
componentDidMount
和componentWillUnmount
中完成的事情;(比如网络请求、订阅和取消订阅)- 另外,多次执行也会导致一定的性能问题
我们如何决定
useEffect
在什么时候应该执行和什么时候不应该执行呢?useEffect
实际上有两个参数- 参数一: 执行的回调函数
- 参数二: 该
useEffect
在依赖state
发生改变时, 才重新执行该回调(受谁的影响)
但是,如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组
[]
- 那么这里的两个回调函数分别对应的就是
componentDidMount
和componentWillUnmount
生命周期函数了
- 那么这里的两个回调函数分别对应的就是
useContext
为什么使用useContext?
在之前的开发中: 我们要在组件中使用共享的
Context
有两种方式- 类组件可以通过:
类名.contextType = myContext
方式,在类中获取context
- 多个
Context
或者在函数式组件通过MyContext.consumer
方式共享context
- 类组件可以通过:
但是多个
Context
共享使存在大量嵌套Context Hook
允许我们通过Hook
来直接获取某个Context
值
useContext的使用
import React, { useContext } from 'react'
import { Theme, User } from '../App'
export default function UseContextDemo() {
// useContext使用
const user = useContext(User)
const theme = useContext(Theme)
console.log(user, theme)
return (
<div>
<h3>UseContextDemo</h3>
</div>
)
}
注意事项:
- 当组件上层最近的
<MyContext.Provider>
更新时,该Hook
会触发重新渲染,并使用最新传递给MyContext provider
的context value
值。
- 当组件上层最近的
useReducer
useRducer介绍
很多人看到useReducer
的第一反应应该是redux
的某个替代品,其实并不是。
useReducer
仅仅是useState
的一种替代方案:- 使用场景: 在某些场景下,如果
state
的处理逻辑比较复杂,我们可以通过useReducer
来对其进行拆分 - 或者这次修改的
state
需要依赖之前的state
时,也可以使用
useReducer使用
// reducer.js
export default function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 }
case 'decrement':
return { ...state, count: state.count - 1 }
default:
return state
}
}
// home.js
import reducer from './reducer'
export default function Home() {
// 参数1: reducer 参数2: 初始state
const [state, setState] = useReducer(reducer, { count: 0 })
return (
<div>
<h2>当前计数: {state.count}</h2>
<button onClick={e => setState({ type: 'increment' })}>+</button>
<button onClick={e => setState({ type: 'decrement' })}>-</button>
</div>
)
}
- 数据是不会共享的,它们只是使用了相同的
counterReducer
的函数而已 - 所以,
useReducer
是useState
的一种替代品, 并不能替代Redux
useCallback
useCallback介绍
- 试想一下: 当你更新 name 属性时, 重新调用 render 之后所有的事件处理函数重新全部定义, 非常浪费性能
- 解决: 当依赖的属性没有改变时, 不希望更新 render 时, 重新定义事件函数
useCallback使用
useCallBack
时机目标是为了进行性能的优化如何性能性能的优化呢?
useCallBack
会返回一个函数memoized
(记忆的) 值- 在依赖不变的情况下, 多定义的时候, 返回值是相同的
const increment2 = useCallback(() => {
console.log('increment2被调用了')
setCount(count + 1)
}, [count])
useCallback使用场景
- 场景: 在将一个组件中的函数, 传递给子元素回调函数使用时, 使用
useCallback
对函数进行处理
import React, { useState, useCallback, memo } from 'react'
const JMButton = memo(props => {
console.log('HYButton重新渲染: ', props.title)
return <button onClick={props.increment}>JMButton+1</button>
})
export default function CallBackHomeDemo2() {
// useCallback: 希望更新父组件的state时,子组件不被render渲染
// 1.使用memo包裹子组件进行性能优化,子组件没有依赖的props或state没有修改,不会进行render
// 2.一个疑问: 为什么 btn1 还是被渲染了?
// (1)因为子组件依赖的 increment1 函数,在父组件没有进行缓存(在函数重新render时,increment1被重新定义了)
// (2)而 increment2 函数在父组件中被缓存了,所以memo函数进行性浅层比较时依赖的increment2是一样的所以没有被重新render渲染
// 3.useCallback在什么时候使用?
// 场景: 在将一个组件中的函数, 传递给子元素进行回调使用时, 使用useCallback对函数进行处理.
console.log('CallBackHomeDemo2重新渲染')
const [count, setCount] = useState(0)
const [show, setShow] = useState(true)
const increment1 = () => {
console.log('increment1被调用了')
setCount(count + 1)
}
const increment2 = useCallback(() => {
console.log('increment2被调用了')
setCount(count + 1)
}, [count])
return (
<div>
<h2>CallBackHomeDemo: {count}</h2>
<JMButton increment={increment1} title="btn1" />
<JMButton increment={increment2} title="btn2" />
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
useMemo
useMemo介绍
useMemo
实际的目的也是为了进行性能的优化。- 如何进行性能的优化呢?
useMemo
返回的也是一个memoized
(记忆的) 值;- 在依赖不变的情况下,多次定义的时候,返回的值是相同的;
// 依赖没有改变的话,是不会进行重新定义局部变量的
const info = useMemo(() => {
return { name: 'kobe', age: 18 }
}, [])
useMemo使用场景
- 场景: 在将一个组件中的函数, 传递给子元素局部变量使用时, 使用
useMemo
对函数进行处理
import React, { useState, memo, useMemo } from 'react'
const User = memo(props => {
console.log('User被渲染了')
return (
<h3>
姓名: {props.info.name} 年龄:{props.info.age}
</h3>
)
})
export default function MemoHookDemo2() {
console.log('MemoHookDemo2被渲染了')
// 需求: 在更新父组件的局部变量时,子组件依赖的props或state没有改变不希望被render渲染
// 1.memo包裹子组件
// 2.为什么子组件User还是被渲染了呢?
// (1)因为: 在父组件重新渲染时,会吃会重新创建info对象,memo在对比时会发现两次创建的info对象不同,会重新render渲染
// const info = { name: 'kobe', age: 18 }
// (2)解决: 使用useMemo
const info = useMemo(() => {
return { name: 'kobe', age: 18 }
}, [])
const [show, setShow] = useState(true)
return (
<div>
<User info={info} />
<button onClick={e => setShow(!show)}>切换</button>
</div>
)
}
useRef
useRef介绍
- 介绍:
useRef
返回一个ref
对象,返回的ref
对象在组件的整个生命周期保持不变 最常用的
ref
是两种场景:- 场景一: 引入
DOM
(或者组件, 需要是class
组件) 元素 - 场景二: 保存一个数据, 这个对象在整个生命周期可以保存不变
- 场景一: 引入
const refContainer = useRef (initialvalue);
引用DOM
import React, { useRef } from 'react'
class ChildCpn extends React.Component {
render() {
return <div>ChildCpn</div>
}
}
export default function RefHookDemo01() {
const titleRef = useRef()
const cpnRef = useRef()
function changeDOM() {
// 修改DOM
titleRef.current.innerHTML = 'hello world
console.log(cpnRef.current)
}
return (
<div>
{/* 1.修改DOM元素 */}
------<h2 ref={titleRef}>RefHookDemo01</h2>------
{/* 2.获取class组件 ✔ */}
<ChildCpn ref={cpnRef} />
<button onClick={changeDOM}>修改DOM</button>
</div>
)
}
使用ref保存上一次的某一个值
import React, { useEffect, useRef, useState } from 'react'
export default function RefHookDemo02() {
// 需求: 使用ref保存上一次的某一个值
const [count, setCount] = useState(0)
// 将上一次的count进行保存,在count发生改变时,重新保存count
// 为什么: 在点击button时,增加count时,会调用useEffect函数,渲染DOM后,会重新将上一次的值进行保存,使用ref保存上一次的某一个值不会触发render
const numRef = useRef(count)
useEffect(() => {
numRef.current = count
}, [count])
return (
<div>
<h3>count上一次的值: {numRef.current}</h3>
<h3>count这一次的值 {count}</h3>
<button onClick={e => setCount(count + 10)}>+10</button>
</div>
)
}
useImperativeHandle
useImperativeHandle引入
我们先来回顾一下
ref
和forwardRef
结合使用:- 通过
forwardRef
可以将ref
转发给子组件 - 子组件拿到父组件创建的
ref
, 绑定到自己的某一个元素中
- 通过
import React, { useRef, forwardRef } from 'react'
// forwardRef可以将ref转发给子组件
const JMInput = forwardRef((props, ref) => {
return <input type="text" ref={ref} />
})
export default function ForwardDemo() {
// forward用于获取函数式组件DOM元素
const inputRef = useRef()
const getFocus = () => {
inputRef.current.focus()
}
return (
<div>
<button onClick={getFocus}>聚焦</button>
<JMInput ref={inputRef} />
</div>
)
}
forwardRef
的做法本身没有什么问题, 但是我们是将子组件的DOM
直接暴露给了父组件:- 直接暴露给父组件带来的问题是某些情况的不可控
- 父组件可以拿到
DOM
后进行任意的操作 - 我们只是希望父组件可以操作的
focus
,其他并不希望它随意操作其他方法
useImperativeHandle介绍
useImperativeHandle(ref, createHandle, [deps])
通过
useImperativeHandle
可以只暴露特定的操作- 通过
useImperativeHandle
的Hook, 将父组件传入的ref
和useImperativeHandle
第二个参数返回的对象绑定到了一起 - 所以在父组件中, 调用
inputRef.current
时, 实际上是返回的对象
- 通过
useImperativeHandle
使用简单总结:- 作用: 减少暴露给父组件获取的
DOM
元素属性, 只暴露给父组件需要用到的DOM
方法 - 参数1: 父组件传递的ref属性
- 参数2: 返回一个对象, 以供给父组件中通过
ref.current
调用该对象中的方法
- 作用: 减少暴露给父组件获取的
import React, { useRef, forwardRef, useImperativeHandle } from 'react'
const JMInput = forwardRef((props, ref) => {
const inputRef = useRef()
// 作用: 减少父组件获取的DOM元素属性,只暴露给父组件需要用到的DOM方法
// 参数1: 父组件传递的ref属性
// 参数2: 返回一个对象,父组件通过ref.current调用对象中方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
},
}))
return <input type="text" ref={inputRef} />
})
export default function ImperativeHandleDemo() {
// useImperativeHandle 主要作用:用于减少父组件中通过forward+useRef获取子组件DOM元素暴露的属性过多
// 为什么使用: 因为使用forward+useRef获取子函数式组件DOM时,获取到的dom属性暴露的太多了
// 解决: 使用uesImperativeHandle解决,在子函数式组件中定义父组件需要进行DOM操作,减少获取DOM暴露的属性过多
const inputRef = useRef()
return (
<div>
<button onClick={() => inputRef.current.focus()}>聚焦</button>
<JMInput ref={inputRef} />
</div>
)
}
useLayoutEffect
useLayoutEffect介绍
useLayoutEffect
看起来和useEffect
非常的相似,事实上他们也只有一点区别而已:useEffect
会将渲染的内容更新到DOM
上后执行, 不会阻塞DOM
更新useLayoutEffect
会将渲染的内容更新到DOM
上之前执行, 会阻塞DOM
更新
- 使用场景: 如果我们希望在执行某些操作之后再
DOM
, 那么这个操作应该放到useLayoutEffect
, 注意会阻塞页面渲染
useLayoutEffect使用
import React, { useEffect, useLayoutEffect, useState } from 'react'
export default function LayoutEffectCountChange() {
const [count, setCount] = useState(10)
// 主要作用: 执行某些操作之后再执行DOM渲染,会阻塞页面渲染
useLayoutEffect(() => {
if (count === 0) {
setCount(Math.random())
}
}, [count])
return (
<div>
<h2>数字: {count}</h2>
<button onClick={e => setCount(0)}>change 0 state for count</button>
</div>
)
}
自定义Hook
自定义Hook 介绍
自定义
Hook
本质上只是一种函数代码逻辑的抽取, 严格意义上来说,它本身并不算React的特性。使用场景: 可以将组件重复的逻辑抽取到可重用的函数中
自定义Hook 使用
- 如何自定义: 自定义 Hook 是一个函数,其名称以 “
use
” 开头,函数内部可以调用其他的 Hook。 - 下面定义的
Hook
作用是: 在组件被创建和卸载时, 都会打印到控制台"当前组件生命周期信息"
import React, { useEffect } from 'react'
// 函数前面添加use 成为自定义Hook,可以使用Hook特性
function useLifeFlow(name) {
useEffect(() => {
console.log(`${name}被创建`)
return () => {
console.log(`${name}被卸载`)
}
}, [])
}
function Home() {
useLifeFlow('Home')
return <h2>Home</h2>
}
function Profile() {
useLifeFlow('Profile')
return <h2>Profile</h2>
}
export default function CustomLifeHookDemo() {
useLifeFlow('CustomLiftHookDemo')
return (
<div>
<h2>CustomLiftHookDemo</h2>
<Home />
<Profile />
</div>
)
}
自定义Hook场景
需求一: Context共享
// 自定义Hook 函数前添加 use (共享多个context,将多个context进行封装)
export default function useUserContext() {
// 获取祖先组件或父组件提供 contetx provide value
const user = useContext(UserContext)
const token = useContext(TokenContext)
// 将同一类型的 contetx provide value 返回
return [user, token]
}
需求二: 获取鼠标滚动位置
export default function useScrollPosition() {
const [scrollY, setScrollY] = useState(0)
// 挂载完DOM后,注册scroll事件
useEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY)
}
document.addEventListener('scroll', handleScroll)
// 组件卸载后移除scroll事件
return () => {
document.removeEventListener('scroll', handleScroll)
}
}, [])
return scrollY
}
需求三: localStorage数据存储
function useLocalStorage(key) {
const [data, setData] = useState(() => {
const data = JSON.parse(window.localStorage.getItem(key))
return data
})
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(data))
}, [data])
return [data, setData]
}
export default useLocalStorage
Redux Hook
useDispatch
- 使用
useDispatch
可以让你再也不用在组件中定义需要依赖的dispatch
派发的action
函数 - 可以直接在组件中直接使用
dispatch
派发action
function JMRecommend(props) {
// redux Hook 组件和redux关联: 获取数据和进行操作
const dispatch = useDispatch()
useEffect(() => {
dispatch(getTopBannersAction())
}, [dispatch])
return (
<div>
<h2>JMRecommend</h2>
</div>
)
}
export default memo(JMRecommend)
useSelector
- 使用
useSelector
后不用在组件中定义依赖的state
, 直接在组件中使用useSelector
传递函数的参数是state
- 函数返回一个对象, 在对象中定义需要依赖的
state
function JMRecommend(props) {
// redux Hook 组件和redux关联: 获取数据和进行操作
const { topBanners } = useSelector(state => ({
topBanners: state.recommend.topBanners,
}))
return (
<div>
<h2>JMRecommend</h2>
<h3>{topBanners.length}</h3>
</div>
)
}
export default memo(JMRecommend)
useSelector性能优化
- 两个组件中都依赖并使用了
redux中的state
一个组件改变了state
另一个组件会被重新渲染, 这个很正常 useSelector
的问题:- 只要
reducer
中state
发生了变化,不管该组件是否依赖state
,都会进行重新渲染
- 只要
<details>
<summary>useSelector问题(图示)</summary><img src="https://mingcloudpic.oss-cn-beijing.aliyuncs.com/img/20201028123347.gif" alt="render" style="zoom:80%;" />
</details>
useSelector的问题?
useSelector为什么会出现这个的问题?
- 因为使用了
useSelector
在决定在组件是否重新渲染的之前会进行一次引用对比: 会和前一次函数返回的
state
对象进行一次引用对比(三等运算符)
- 因为每次调用函数的时候, 创建的对象都是一个全新对象
- 所以每次只要
store中的state
发生了改变, 不管当前组件是否有依赖这个state
组件都会进行重新渲染
useSelector优化
useSelector
优化:useSelector
的第二个参数传递一个ShallowEqual
ShallowEqual
作用: 对一次浅层比较, 和前一次useSelector
返回的对象及进行比较<details>
<summary>useSelector性能优化(图示)</summary><img src="https://mingcloudpic.oss-cn-beijing.aliyuncs.com/img/20201028123443.gif" alt="render" style="zoom:80%;" />
</details>
import React from 'react'
import { shallowEqual, useSelector } from 'react-redux'
export default function About(props) {
console.log('About 组件被重新渲染')
// 使用shallowEqual解决useSelector问题
const { banners, recommends } = useSelector(state => ({
banners: state.banners,
recommends: state.recommends
}), shallowEqual)
return (
<div>
<h1>About</h1>
<h4>组件没有依赖count: 但还是被重新渲染了</h4>
<h4>使用shallowEqual解决useSelector渲染问题</h4>
</div>
)
}
- 以后只要当前组件没有依赖改变的
state
不希望重新渲染, 使用useSelector
结合ShallowEqual
- 注意:
connect
函数是不存在这个问题的
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。