08-hooks进阶-useRef-useContext-React.memo高阶组件

Hooks其他API

useRef hook

import { useRef } from 'react'

 <input
        className="new-todo"
        placeholder="What needs to be done?"
        autoFocus
        onKeyUp={onKeyUp}
        ref={inputRef}
      />
  const App = () => {
  const [color, setColor] = useState('red')
  return (
      // 用Context。Provider 包裹跟组件
    <Context.Provider value={color}>
      <div>
        <h1>我是根组件</h1>
        <div>颜色:{color}</div>
        <button onClick={() => setColor('yellow')}>修改</button>
        <Father></Father>
      </div>
    </Context.Provider>
  )
}


const Child = () => {
  const color = useContext(Context)
  return (
    <div>
      <h5>我是子组件--{color}</h5>
    </div>
  )
}

export default App

useContext hook

import React, { useState, useContext } from 'react'
const Context = React.createContext()

//跟组件
 <Context.Provider value={color}>
      <div>
        <h1>我是根组件</h1>
        <div>颜色:{color}</div>
        <button onClick={() => setColor('yellow')}>修改</button>
        <Father></Father>
      </div>
    </Context.Provider>
//后代组件
const Child = () => {
  const color = useContext(Context)
  return (
    <div>
      <h5>我是子组件--{color}</h5>
    </div>
  )
}


Hooks进阶

概述

根据前面的学习我们知道,Hooks(内置或自定义)只能在函数组件中使用。因此,Hooks 与函数组件是密不可分的。

所以,要想深入理解 Hooks,就必须先理解函数组件的特性,因为这些特性会影响到了 Hooks 的使用。

本节,我们先来理解函数组件的特性。然后,根据这些特性对实际开发产生的影响,我们再来学习 useCallback / useMemo / useRef 等内置 Hooks。

最后,我们来模拟实现 useState / useEffect 这两个 Hooks,来深入理解 Hooks 的实现原理。

函数组件的特性

React 中的函数组件是通过函数来实现的,函数组件的公式:f(state) => UI,即:数据到视图的映射。

函数组件本身很简单,但因为是通过函数实现的,所以,在使用函数组件时,就会体现出函数所具有的特性来。

函数组件的特性说明:

  • 对于函数组件来说,每次状态更新后,组件都会重新渲染。
  • 并且,每次组件更新都像是在给组件拍照。每张照片就代表组件在某个特定时刻的状态。
  • 或者说:组件的每次特定渲染,都有自己的 props/state/事件处理程序 等。
  • 这些照片记录的状态,从代码层面来说,是通过 JS 中函数的闭包机制来实现的。

这就是 React 中函数组件的特性,更加的函数式(利用函数的特性)

import { useState } from 'react'
import ReactDOM from 'react-dom'

// 没有 hooks 的函数组件:
const Counter = ({ count }) => {
  // console.log(count)
  const showCount = () => {
    setTimeout(() => {
      console.log('展示 count 值:', count)
    }, 3000)
  }

  return (
    <div>
      <button onClick={showCount}>点击按钮3秒后显示count</button>
    </div>
  )
}

const App = () => {
  const [count, setCount] = useState(0)

  return (
    <div>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <hr />
      {/* 子组件 */}
      <Counter count={count} />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

函数组件特性带来的问题

函数组件的特性:组件的每次特定渲染,都有自己的 props/state/事件处理程序 等。

该特性,导致了几个问题:

  • 组件每次重新渲染时,组件内部的事件处理程序等函数都会重新创建,导致子组件每次都会接收到不同的 props,从而重复进行不必要的渲染(性能问题)
  • 组件内的事件处理程序等函数中,只能获取到那一次特定渲染时的数据,这是合理的(闭包的原因)。

说明:函数组件配合 Hooks 使用时,会不会因为闭包以及每次都创建新的函数等,让组件变慢?答案:不会!

注意:在没有发现性能问题前,避免过早的性能优化。如果要优化,一定要考虑优化成本是否大于优化后的价值。

对于第一个问题,我们使用 React.memo 配合 useCallback/useMemo 这两个 Hooks 来解决。

对于第二个问题,我们使用 useRef Hook 来解决。

React.memo高阶组件

介绍

React.memo 高阶组件的使用场景说明:

React 组件更新机制:只要父组件状态更新,子组件就会无条件的一起更新。

  • 子组件 props 变化时更新过程:组件代码执行 -> JSX Diff(配合虚拟 DOM)-> 渲染(变化后的内容)【 DOM 操作】。
  • 子组件 props 无变化更新过程:组件代码执行 -> JSX Diff(配合虚拟 DOM)【无 DOM 操作】。

注意:此处更新指的是组件代码执行、JSX 进行 Diff 操作(纯 JS 的操作,速度非常快,不会对性能产生太多影响)。

  • 如果组件 props 改变了,那么,该组件就必须要更新,才能接收到最新的 props。
  • 但是,如果组件 props 没有改变时,组件也要进行一次更新。实际上,这一次更新是没有必要的。

如果要避免组件 props 没有变化而进行的不必要更新(Diff),这种情况下,就要使用 React.memo 高阶组件。

注:对于 class 组件来说,可以使用 PureComponent 或 shouldComponentUpdate 钩子函数来实现

import { useState } from 'react'
import ReactDOM from 'react-dom'

const Child2 = ({ count }) => {
  console.log('Child2 子组件代码执行了')
  return <div style={{ backgroundColor: '#abc' }}>子组件2:{count}</div>
}

const Child1 = () => {
  console.log('Child1 子组件代码执行了')
  return <div style={{ backgroundColor: '#def' }}>子组件1</div>
}

const App = () => {
  const [count, setCount] = useState(0)

  return (
    <div style={{ backgroundColor: 'pink', padding: 10 }}>
      <h1>计数器:{count}</h1>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <hr />

      {/* 子组件 */}
      <Child1 />
      <br />
      <Child2 count={count} />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

语法

使用场景:当你想要避免函数组件 props 没有变化而产生的不必要更新时,就要用到 React.memo 了。

作用:记忆组件上一次的渲染结果,在 props 没有变化时复用该结果,避免函数组件不必要的更新

在这里插入图片描述

解释:

  • React.memo 是一个高阶组件,用来记忆(memorize)组件。
  • 参数(Child):需要被记忆的组件,或者说是需要避免不必要更新的组件。
  • 返回值(MemoChild):React 记住的 Child 组件。

原理:通过对比检查更新前后 props 是否相同,来决定是否复用上一次的渲染结果,

  • 如果相同,复用上一次的渲染结果;
  • 如果不同,重新渲染组件。

并不是所有的组件都适合使用memo,比如child2组件,每次都需要重新渲染,使用memo反而会使性能变得更低,逻辑也变得更复杂

浅层对比

默认情况下,React.memo 只会对更新前后的 props 进行浅对比(shallow compare)与 PureComponent 相同。

也就是说,对于对象类型的 prop 来说,只会比较引用

  • 如果更新前后的引用相同,复用上一次的渲染结果(不会重新渲染该组件)。
  • 如果更新前后的引用不同,重新渲染该组件。

如果你要手动控制比较过程,可以使用 React.memo 的第二个参数:

在这里插入图片描述

解释:

  • 第二个参数:用来比较更新前后 props 的函数。
  • 返回值:如果返回 true,表示记住(不重新渲染)该组件;如果返回 false,表示重新渲染该组件。

useCallback

使用场景

在使用 React.memo 时,对于对象类型的 props,只会比较引用(浅对比)。

但是,因为组件每次更新都会创建新的 props 值,比如,新的对象、事件处理程序等(函数组件的特性)。

这就导致:React.memo 在处理对象类型的 props 时,会失效(每次的 props 都是新对象)。

但是,我们还是想让 React.memo 在处理对象类型的 props 时,也有效。

为了让 React.memo 处理对象类型的 props 有效,只要在组件更新期间保持对象类型引用相等,就可以了。

这时候,就要用到以下两个 Hooks:

  • useCallback Hook:记住函数的引用,在组件每次更新时返回相同引用的函数。
  • useMemo Hook:记住任意数据(数值、对象、函数等),在组件每次更新时返回相同引用的数据【功能之一】

基本使用

使用场景:在使用 React.memo 时,为了组件每次更新时都能获取到相同引用的函数,就要用到 useCallback Hook

注意:需要配合 React.memo 高阶函数一起使用

作用:记忆传入的回调函数,这个被记住的回调函数会一直生效,直到依赖项发生改变

 作用:缓存一个函数,除非依赖项发生了变化,重新缓存,,,,,配合React.memo

    const newFn = useCallback(fn, [depts])

解释:

  • 第一个参数:必选,需要被记忆的回调函数。
  • 第二个参数:必选,依赖项数组,用于指定回调函数中依赖(用到)的数据(类似于 useEffect 的第二个参数)。
  • 即使没有依赖,也得传入空数组([]),此时,useCallback 记住的回调函数就会一直生效。
  • 返回值:useCallback 记住的回调函数。
  • useCallback 记住的回调函数会一直生效(或者说会一直返回同一个回调函数),直到依赖项发生改变。

hooks总结

/*

  1. 为什么要有hooks
    1.1 代码逻辑复用:mixins(废弃) render-props HOC hooks 自定义hooks useXXX
    1.2 class的缺点
    class中的this指向总是让人难以理解
    考虑到底使用函数组件还是class组件,,,需要学习两套组件的用法
    class组件不利于代码的压缩和优化
    class组件提供了生命周期函数,导致一个功能被拆开到多个钩子函数中。
  hooks解决的什么问题
    1. 逻辑复用
    2. 不在使用this
    3. 减轻开发者的心智负担,,,不用考虑函数组件和类组件
    4. 使用函数方便代码压缩和优化(tree shaking)
    5. 一个功能写到一起

  hooks是16.8开始的,,,没有准备去移除class组件


2. useState
  作用:给函数组件提供状态以及修改状态的方法
  语法:const [count, setCount] = useState(100)
       const [money, setMoney] = useState(() => { return xxx })
  
  注意:hooks只能在函数组件中或者自定义hooks中使用功能,而且不能包含在if else for while语句中

3. useEffect
  作用:处理react中的副作用,,,,实现class组件中的钩子函数的功能。
  语法: useEffect(() => {})  组件第一次渲染以及每一次更新后都会执行
        useEffect(() =>{}, [count])  组件第一次渲染以及count发生改变后都会执行
        useEffect(() => {}, [])  组件第一次渲染
  
  清理副作用
        useEffect( () => {
          return () => {
            // 清理函数
          }
        })
        清理函数 会在销毁的时候以及每一次回调函数执行之前执行。



        useEffect( () => {
          return () => {
            // 清理函数
          }
        }, [])
        清理函数只会在组件销毁的时候执行


4. useRef
  作用:用于操作DOM或者组件
    const inputRef = useRef(null)

    <div ref={inputRef}></div>

    inputRef.current 操作DOM

5. useContext
  作用:可以获取到context中的数据

  <Context.Provider value={value}></Context.Provider>


  const value = useContext(Context)


6. React.memo()
  作用:高阶组件,用于缓存一个组件,,,对比组件的props和state是否发生改变,如果不变,就不会更新。

  如果给组件传递一个函数或者复杂类型,会导致memo失效,,,因为浅层对比

7. useCallback()
  作用:缓存一个函数,除非依赖项发生了变化,重新缓存,,,,,配合React.memo

    const newFn = useCallback(fn, [depts])

8. useMemo
  作用:缓存任意类型的数据

    const memoData = useMemo(() => data, [depts])

  提供计算属性,避免昂贵的计算

9. useReducer 替代redux  action 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值