1

React With Reudx Hooks详解

 3 years ago
source link: https://segmentfault.com/a/1190000040075169
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Hooks全面详解

认识Hooks

1.Hook介绍

Hook是 React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性 (比如生命周期)

2.class与function组件对比

  • 我们先来思考一下class组件相对于函数式组件有什么优势?比较常见的是下面的优势:
对比class组件function组件stateclass组件可以定义自己的state, 用来保存组件自己内部的状态函数式组件不可以,因为函数每次调用都会产生新的临时变量生命周期class组件有自己的生命周期, 我们可以在对应的生命周期中完成自己的逻辑 ( 比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次 )函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求render渲染class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次
  • 所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件。

3.Class组件存在的问题

  • 复杂组件变得难以理解:

    • 我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂, 但是随着业务的增多,我们的class组件会变得越来越复杂
    • 比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在 componentWillUnmount中移除)
    • 而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度
  • 难以理解的class:

    • 很多人发现学习ES6class是学习React的一个障碍。
    • 比如在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this
    • 虽然前端开发人员必须掌握this,但是依然处理起来非常麻烦
  • 组件复用状态很难:

    • 在前面为了一些状态的复用我们需要通过高阶组件或render props
    • 像我们之前学习的reduxconnect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用
    • 或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套
    • 这些代码让我们不管是编写和设计上来说,都变得非常困难

4.Hook的出现

  • Hook的出现,可以解决上面提到的这些问题
  • 简单总结Hooks

    • 它可以让我们在不编写class的情况下使用state以及其他的React特性
    • 但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决
  • Hook的使用场景:

    • Hook的出现基本可以代替我们之前所有使用class组件的地方 (除了一些非常不常用的场景)
    • 但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它
    • 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 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 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的回调函数会在每次渲染时都重新执行,但是这会导致两个问题:

  • 某些代码我们只是希望执行一次即可,类似于componentDidMountcomponentWillUnmount中完成的事情;(比如网络请求、订阅和取消订阅)
  • 另外,多次执行也会导致一定的性能问题
  • 我们如何决定useEffect在什么时候应该执行和什么时候不应该执行呢?

    • useEffect实际上有两个参数
    • 参数一: 执行的回调函数
    • 参数二:useEffect在依赖state发生改变时, 才重新执行该回调(受谁的影响)
  • 但是,如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组[]

    • 那么这里的两个回调函数分别对应的就是componentDidMountcomponentWillUnmount生命周期函数了

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 providercontext 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函数而已
  • 所以, useReduceruseState的一种替代品, 并不能替代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引入

  • 我们先来回顾一下refforwardRef结合使用:

    • 通过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, 将父组件传入的refuseImperativeHandle第二个参数返回的对象绑定到了一起
    • 所以在父组件中, 调用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的问题:

    • 只要reducerstate发生了变化,不管该组件是否依赖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函数是不存在这个问题的

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK