共计 14239 个字符,预计需要花费 36 分钟才能阅读完成。
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
函数是不存在这个问题的