为什么需要Hook
不编写class的情况下使用state以及其他的React特性
class组件
class组件的优势
- class组件可以定义自己的state,用来保存组件自己内部的状态
- 函数式组件不可以,因为函数每次调用都会产生新的临时变量
- class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑
- 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次
- 函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求
- class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等
- 函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次
class组件存在的问题
复杂组件变得难以理解
- 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂
- 比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在
componentWillUnmount中移除)
- 而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度
难以理解的class
- 在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this
- 组件复用状态很难
- 状态的复用我们需要通过高阶组件
- 像我们之前学习的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用
- 或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套
Hook的作用
Hook可以解决上述class和函数组件存在的问题
它可以让我们在不编写class的情况下使用state以及其他的React特性
Hook的使用场景
- Hook的出现基本可以代替我们之前所有使用class组件的地方
- 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它
- Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用
Hooks的使用
Hooks只能在函数组件中使用
为什么Hooks中的函数都叫use,不叫create呢?
- Hooks的一大作用是保存组件的状态,在下一次渲染时,返回当前的state
- 而create的意思是,每次渲染都重新创建,那么就与state只在组件初次渲染时被创建相反
useState的使用
useState主要在函数组件中定义state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React, { memo, useState } from 'react'
function App() { const [count, setCounter] = useState(0) console.log("count", count) console.log("setCounter", setCounter) return ( <div> <h2>count:{count}</h2> {/** * 注意:onClick传入的是一个函数,setCounter(count+1)是一个函数的调用,所以这里只能使用箭头函数,在函数调用里调用setCounter */} {/* <button onClick={setCounter(count+1)}>+1</button> */} <button onClick={()=>{setCounter(count+1)}}>+1</button> </div> ) } export default memo(App)
|
useState如何实现状态
useState原理
Effect Hook
- Effect Hook可以在函数组件中实现类似于生命周期的功能
- 网络请求、手动更新DOM、一些事件的监听,都是React更新DOM的一些副作用(不是页面渲染)
- 对于完成这些功能的Hook被称之为 Effect Hook
Effect Hook的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { memo, useEffect, useState } from 'react'
function App() { const [count, setCounter] = useState(0) useEffect(()=>{ console.log("监听redux中的数据变换,监听eventBus中的事件") }) return ( <div> <h2>count:{count}</h2> <button onClick={()=>{setCounter(count+1)}}>+1</button> </div> ) } export default memo(App)
|
Effect Hook的清除
多个Effect的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { memo, useEffect, useState } from 'react'
function App() { const [count, setCounter] = useState(0) useEffect(()=>{ console.log("监听redux中的数据变换,监听eventBus中的事件") return ()=>{ console.log("清除监听redux中的数据变换,清除监听eventBus中的事件") } }) useEffect(()=>{ console.log("获取网络请求") }) return ( <div> <h2>count:{count}</h2> <button onClick={()=>{setCounter(count+1)}}>+1</button> </div> ) } export default memo(App)
|
Effect的性能优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { memo, useEffect, useState } from 'react'
function App() { const [count, setCounter] = useState(0) const [message, setMessage] = useState("Hello!") useEffect(()=>{ console.log("监听redux中的数据变换,监听eventBus中的事件") return ()=>{ console.log("清除监听redux中的数据变换,清除监听eventBus中的事件") } },[count]) return ( <div> <h2>count:{count}</h2> <h2>message:{message}</h2> <button onClick={()=>{console.log("加count");setCounter(count+1)}}>+1</button> <button onClick={()=>{console.log("修改message");setMessage("你哈个锤子")}}>修改message</button> </div> ) } export default memo(App)
|

那么这里的两个回调函数分别对应的就是componentDidMount和componentWillUnmount生命周期函数了
useContext的使用
获取Context的方式:
- 类组件可以通过 类名.contextType = MyContext方式,在类中获取context
- 多个Context或者在函数式组件中通过 MyContext.Consumer 方式共享context
但是多个Context共享时会存在大量的嵌套——Context Hook允许我们通过Hook来直接获取某个Context的值
但是Provider依然会有嵌套
Provider代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { HashRouter } from 'react-router-dom'; import { ColorContext, BgrContext } from './Context';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ColorContext.Provider value={{color:"red"}}> <BgrContext.Provider value={{fontSize:36}}> <HashRouter> <App/> </HashRouter> </BgrContext.Provider> </ColorContext.Provider> );
|
useContext代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React, { memo, useContext, useEffect, useState } from 'react' import { ColorContext, BgrContext } from './Context'
function App() { const colorContext = useContext(ColorContext) const bgrContext = useContext(BgrContext) return ( <div> <h2 style={{color:colorContext.color,fontSize:bgrContext.fontSize}}>哈哈哈</h2> </div> ) } export default memo(App)
|

useReducer的使用
useReducer仅仅是useState的一种替代方案——与Redux关系不大
- 如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分
- 或者这次修改的state需要依赖之前的state时,也可以使用
- 数据是不会共享的,它们只是使用了相同的counterReducer的函数而已
- useReducer只是useState的一种替代品,并不能替代Redux
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import React, { memo, useReducer } from 'react'
const counterReducer = function(state, action){ switch(action.type){ case "increment": return {...state, counter:state.counter+1} case "decrement": return {...state, counter:state.counter-1} case "addNum": return {...state, counter:state.counter+action.num} case "decNum": return {...state, counter:state.counter-action.num} } } function App() { const [state, dispatch] = useReducer(counterReducer,{message:"Hello!",counter:100}) return ( <div> <h2>当前计数:{state.counter}</h2> <button onClick={()=>{dispatch({type:"increment"})}}>+1</button> <button onClick={()=>{dispatch({type:"decrement"})}}>-1</button> <button onClick={()=>{dispatch({type:"addNum", num:5})}}>+5</button> <button onClick={()=>{dispatch({type:"decNum", num:10})}}>-10</button> </div> ) } export default memo(App)
|
useReducer的参数与返回值:
- 第一个参数:reducer函数
- reducer函数的参数一:上一次的state/useReducer的第二个参数
- reducer函数的参数二:dispatch所传入的参数
- reducer的返回值:新的状态(…state是为了保障除了这次改变的其余状态依然在新的状态中)
- 第二个参数:state初始值
- 第一个返回值:更新后的state值
- 第二个返回值:用于发起改变state的函数
useCallback的使用
useCallback实际的目的是为了进行性能的优化
- 函数式组件在每次刷新时都会重新执行函数,那么里面的函数就会重新定义,虽然刷新后,原来定义的函数会被回收,但每次都定义也会影响性能,同时会生成新的foo
- useCallback就可以实现当满足某些条件时重新定义函数,但得到相同的foo,而如果有子组件依赖这个foo,相同的foo不会引起子组件的刷新
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { memo, useCallback, useState } from 'react' import ChildComponent from './component/ChildComponent'
function App() { const [count, setCount] = useState(100) const [message, setMessage] = useState(22) const changeCount = ()=>{ console.log("修改count") setCount(count+1) } const changeMessage = ()=>{ console.log("修改message") setMessage(Math.round()) } return ( <div> <h2>当前计数:{count}</h2> <h2>Message:{message}</h2> <ChildComponent changeMessage={changeMessage}/> <button onClick={changeCount}>+1</button> </div> ) } export default memo(App)
|
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { memo } from 'react'
function ChildComponent(props) { const {changeMessage} = props console.log("子组件刷新") return ( <div> <button onClick={changeMessage}>子组件按钮修改Message</button> </div> ) } export default memo(ChildComponent)
|

可以看出每次修改count都会刷新不依赖count子组件,会造成性能浪费
useCallback的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { memo, useCallback, useState } from 'react' import ChildComponent from './component/ChildComponent'
function App() { const [count, setCount] = useState(100) const [message, setMessage] = useState(22) const changeCount = useCallback(()=>{ console.log("修改count") setCount(count+1) },[count]) const changeMessage = useCallback(()=>{ console.log("修改message") setMessage(Math.round()) },[]) return ( <div> <h2>当前计数:{count}</h2> <h2>Message:{message}</h2> <ChildComponent changeMessage={changeMessage}/> <button onClick={changeCount}>+1</button> </div> ) } export default memo(App)
|

可以看出点击更改count后,子组件并未刷新
useCallback的参数与返回值:
- 参数一:需要处理的函数
- 参数二:该函数改变依赖的状态,如果该依赖的状态不变,就返回相同的函数,相同的函数不会引起依赖该函数子组件的刷新
- 返回值:返回一个函数的 memoized(记忆的) 值
使用useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存
- 并不是不会新定义函数,新定义函数是一定的
- 只针对子组件的渲染次数做优化,在本组件层面是没有优化的
useMemo的使用
useMemo也是为了进行性能的优化:
- 同样是返回一个memoized(记忆的)值
- 在依赖不变的情况下,多次定义的时候,返回的值是相同的
- useCallback是在依赖不变的情况下,多次定义的时候,返回的函数是相同的
- useMemo针对变量进行优化,useCallback针对函数进行优化
useMemo的使用案例:
- 进行大量的计算操作,是否有必须要每次渲染时都重新计算
- 对子组件传递相同内容的对象时,使用useMemo进行性能的优化
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { memo, useCallback, useState } from 'react' import ChildComponent from './component/ChildComponent'
function App() { const [count, setCount] = useState(100) function changeCount(){ console.log("修改count") setCount(count+1) } function foo(information){ console.log("生成information") return {...information,message:"helloWorld"} } const information = foo({message:message}) return ( <div> <h2>当前计数:{count}</h2> <h2>Message:{information.message}</h2> <ChildComponent information={information}/> <button onClick={changeCount}>+1</button> </div> ) } export default memo(App)
|
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12
| import React, { memo } from 'react'
function ChildComponent(props) { const {information} = props console.log("子组件刷新") return ( <div> <h3>子组件:{information.message}</h3> </div> ) } export default memo(ChildComponent)
|

可以看出:
- 每次更新count后,message是不变的,那么information,也就不必要每次都去运行foo函数,当foo函数逻辑赋值时会引起性能浪费
- 子页面仅依赖于message,这里明显每次都是得到的information值是一样的,但由于是不同的对象,子组件每次也会刷新
useMemo的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React, { memo, useMemo, useState } from 'react' import ChildComponent from './component/ChildComponent'
function useFoo(message){ const res = useMemo((message)=>{ console.log("生成information") return {message:message+"修改后"} },[message]) return res } function App() { const [count, setCount] = useState(100) const [message, setMessage] = useState("helloWorld") function changeCount(){ console.log("修改count") setCount(count+1) } let information = useFoo(message) return ( <div> <h2>当前计数:{count}</h2> <h2>Message:{message}</h2> <ChildComponent information={information}/> <button onClick={changeCount}>+1</button> </div> ) } export default memo(App)
|

可以看出:
修改count后,依赖于message的函数不再重新执行,子组件也不再刷新
注意:
- useMemo这种Hook函数,只能在函数组件和hook函数中使用,自定义hook函数,以use开头,use后的第一个字母为大写
- useMemo不会传递参数,因为useMemo是由React调用的,我们不知道其在上面时候调用,也就无法传参数进行
- useMemo的参数与返回值
- 参数一:useMemo判断是否执行的函数
- 参数二:useMemo依赖的值,如果这个值没变,就不会执行传入的函数,直接返回上一次的计算值
- 返回值:返回一个带记忆的值
useRef的使用
useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变
useRef基本使用
- 用法一:引入DOM(或者组件,但是需要是class组件)元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { memo, useRef} from 'react' function App() { const inputRef = useRef() const buttonClick = ()=>{ console.log(inputRef.current) inputRef.current.focus() } return ( <div> <input type="text" ref={inputRef}/> <button onClick={buttonClick}>修改</button> </div> ) } export default memo(App)
|
- 用法二:保存一个数据,这个对象在整个生命周期中可以保存不变(解决闭包陷阱)
- 由于函数里的count是获取上层作用域里的,也就是闭包,那么无论页面刷新多少次,函数里的count都是函数定义是的count 0,因此无论多少次点击得到的结果都是1(当然这里可以设置依赖count——仅颜色useRef的作用,所以没有添加)
- 而使用inputRef所得到的对象每次本身就是一样的,那么有没有闭包陷阱也就无所谓,因为我们本身就是要获取相同的值,而通过修改inptRef这个对象里面的值,从而获得相同的对象不同的值,解决闭包陷阱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React, { memo, useCallback, useRef, useState} from 'react' function App() { const [count,setCount] = useState(0) const buttonClick = useCallback(function(){ console.log("+1",count+1) setCount(count+1) },[]) const inputRef = useRef() inputRef.count = count const buttonClick1 = useCallback(function(){ console.log("-1",inputRef.count-1) setCount(inputRef.count-1) },[]) return ( <div> <h2>count:{count}</h2> <button onClick={buttonClick}>+1</button> <button onClick={buttonClick1}>-1</button> </div> ) } export default memo(App)
|

父组件传ref给子组件
- 方式一:作为参数放到props中传递过去——参数名不能使用ref
- 方式二:利用forwardRef传递给子组件
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React, { memo, useRef, useState} from 'react' import ChildComponent from './component/ChildComponent' function App() { const inputRef = useRef() const buttonClick = ()=>{ console.log(inputRef.current) inputRef.current.focus() inputRef.current.value = "" } return ( <div> <ChildComponent ref = {inputRef}/> <button onClick={buttonClick}>showref</button> </div> ) } export default memo(App)
|
子组件代码:
1 2 3 4 5 6 7 8 9 10
| import React, { forwardRef, memo } from 'react'
function ChildComponent(props, ref) { return ( <div> <input type="text" ref={ref}/> </div> ) } export default memo(forwardRef(ChildComponent))
|
注:要使用ref参数,子组件需用forwardRef包裹,且要使用memo的话,memo在forwardRef外层
useImperativeHandle的使用
- 上述通过父组件中的ref传递给子组件中的元素,可以在父组件中获得子组件元素,对子组件元素进行操作
- 将整个元素给到父组件,就容易在父组件中过渡使用,从而在某种情况下产生某种bug,如上述代码,点击showref会在聚焦子组件input的同时,将其值修改为空
- useImperativeHandle就是为了解决样的问题,不直接把子组件元素给到父组件,而是把子组件元素的某些操作给到父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React, { forwardRef, memo, useImperativeHandle, useRef } from 'react'
function ChildComponent(props, ref) { const inputRef = useRef() useImperativeHandle(ref, ()=>{ return { focus:()=>{ inputRef.current.focus() } } }) return ( <div> <input type="text" ref={inputRef}/> </div> ) } export default memo(forwardRef(ChildComponent))
|
useImperativeHandle的基本使用
- 参数一:父组件传给子组件的ref
- 参数二:回调函数,函数返回结果是传递给父组件的属性值对象,如这里只传了focus函数,那么父组件中就只有focus属性生效,其余属性无效
useLayoutEffect使用
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:
- useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新
- useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新

useEffect与useLayoutEffect执行顺序演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React, { memo, useEffect, useLayoutEffect} from 'react' function App() { useEffect(()=>{ console.log("useEffect") }) useLayoutEffect(()=>{ console.log("useLayoutEffect") }) console.log("render app") return ( <div> <h2>哈哈哈</h2> </div> ) } export default memo(App)
|

注:先”render app”,等数据更新后,渲染到DOM前执行useLayoutEffect,渲染结束后执行useEffect
useLayoutEffect的作用
在页面即将刷新错误数据前及时拦截
使用useEffect代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { memo, useEffect, useState} from 'react' function App() { const [count, setCount] = useState(10) useEffect(()=>{ if(count===0){ setCount(10) } console.log("useEffect",count) }, [count]) console.log("render app") function buttonClick(){ setCount(count-1) } return ( <div> <h2>count:{count}</h2> <button onClick={buttonClick}>-1</button> </div> ) } export default memo(App)
|

使用useLayoutEffect代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { memo, useLayoutEffect, useState} from 'react' function App() { const [count, setCount] = useState(10) useLayoutEffect(()=>{ if(count===0){ setCount(10) } console.log("useEffect",count) }, [count]) console.log("render app") function buttonClick(){ setCount(count-1) } return ( <div> <h2>count:{count}</h2> <button onClick={buttonClick}>-1</button> </div> ) } export default memo(App)
|

自定义Hook
自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性
注:
- 自定义Hook以use开头,且use后的字母要大写
自定义Hook——所有的组件在创建和销毁时都进行打印
- 组件被创建:打印 组件被创建了
- 组件被销毁:打印 组件被销毁了


自定义Hook——Context的共享

自定义Hook——获取滚动位置

redux hooks的使用
- 在之前的redux开发中,为了让组件和redux结合起来,我们使用了react-redux中的connect
- 但是这种方式必须使用高阶函数结合返回的高阶组件
- 且必须编写:mapStateToProps和 mapDispatchToProps映射的函数
- useSelector的作用是将state映射到组件中
- 参数一:将state映射到需要的数据中
- 参数二:可以进行比较来决定是否组件重新渲染;(后续讲解)
- useSelector默认会比较我们返回的两个对象是否相等
- 也就是我们必须返回两个完全相等的对象才可以不引起重新渲染
- useDispatch非常简单,就是直接获取dispatch函数,之后在组件中直接使用即可
- 我们还可以通过useStore来获取当前的store对象
redux的常规使用——使用connect函数
整个项目index.js代码:
1 2 3 4 5 6 7 8 9 10 11 12
| import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { Provider } from 'react-redux'; import store from './stero'; const root = ReactDOM.createRoot(document.getElementById('root'));
root.render( <Provider store={store}> <App/> </Provider> );
|
countSlice.js代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { createSlice } from "@reduxjs/toolkit";
const countSlice = createSlice({ name:"count", initialState:{ count:5 }, reducers:{ addCountAction(state, action){ state.count += action.payload } } })
export const {addCountAction} = countSlice.actions const countReducer = countSlice.reducer export default countReducer
|
导出的stero的index.js代码:
1 2 3 4 5 6 7 8 9
| import { configureStore } from "@reduxjs/toolkit"; import countReducer from "./countSlice";
const store = configureStore({ reducer:{ counter:countReducer } }) export default store
|
stero使用的jsx代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import React, { memo, useLayoutEffect, useState} from 'react' import { connect } from 'react-redux'; import { addCountAction } from './stero/countSlice';
function App(props) { const {count, addCount} = props function buttonCLick(){ addCount(3) } return ( <div> <h2>count:{count}</h2> <button onClick={buttonCLick}>+3</button> </div> ) }
function mapStateToProps(state){ return { count:state.counter.count } } function mapAction20Props(dispatch){ return { addCount(count){ dispatch(addCountAction(count))
} } } export default connect(mapStateToProps, mapAction20Props)(memo(App))
|
注:
- 这种方式必须使用高阶函数结合返回的高阶组件
- 并且必须编写:mapStateToProps和 mapDispatchToProps映射的函数
redux的Hook使用
useSelector与useDispatch的使用
App.jsx代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import React, { memo} from 'react' import { useDispatch, useSelector } from 'react-redux'; import ChildComponent from './component/ChildComponent'; import { addCountAction } from './stero/countSlice';
function App(props) { console.log("App渲染")
const {count} = useSelector((state)=>({ count:state.counter.count }))
const dispatch = useDispatch() function buttonCLick(){ dispatch(addCountAction(4)) } return ( <div> <h2>count:{count}</h2> <ChildComponent/> <button onClick={buttonCLick}>改变count</button> </div> ) } export default memo(App)
|
注:
可以看到能够直接使用useSelector对stero中的数据进行映射,直接通过useDispatch获取dispatch
但是上述会出现一些问题:
- useSelector是监听的是state,也就是state改变后,所有使用useSelector的组件都会刷新
ChildComponent代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React, { memo } from 'react' import { useSelector } from 'react-redux'
function ChildComponent(props) { console.log("子组件渲染") const {message} = useSelector((state)=>({ message:state.counter.message })) return ( <div> <h2>子组件:{message}</h2> </div> ) } export default memo(ChildComponent)
|
子组件只与state中的message有关,但当count改变时子组件依然会刷新,会引起性能的降低

给useSelector设置shallowEqual就可以避免上述问题,实际上是进行一个浅层比较,如果相同就不更新
1 2 3 4 5 6 7
| const {message} = useSelector((state)=>({ message:state.counter.message }),shallowEqual)
const {count} = useSelector((state)=>({ count:state.counter.count }),shallowEqual)
|
