ReactHooks(完结)

上期戳here

一.memo 函数

当父组件被重新渲染的时候,也会触发子组件的重新渲染,这样就多出了无意义的性能开销。使用 React.memo() 可以将组件进行缓存。

1.1 语法格式

const 组件 = React.memo(函数式组件)

import React, { useEffect, useState } from 'react'

// 父组件
export const Father: React.FC = () => {
  // 定义 count 和 flag 两个状态
  const [count, setCount] = useState(0)
  const [flag, setFlag] = useState(false)

  return (
    <>
      <h1>父组件</h1>
      <p>count 的值是:{count}</p>
      <p>flag 的值是:{String(flag)}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>+1</button>
      <button onClick={() => setFlag((prev) => !prev)}>Toggle</button>
      <hr />
      <Son num={count} />
    </>
  )
}

// 子组件:依赖于父组件通过 props 传递进来的 num
//React.memo 包裹的子组件,只有props变化了,才会被重新渲染
export const Son: React.FC<{ num: number }> = React.memo(({ num }) => {
  useEffect(() => {
    console.log('触发了子组件的渲染')
  })
  return (
    <>
      <h3>子组件 --- {num}</h3>
    </>
  )
})

二. useMemo

如果点击 +1 按钮,发现count自增,flag值没有发生变化,但是tips函数也会重新执行。

2.1 问题引入

// 父组件
export const Father: React.FC = () => {
  // 定义 count 和 flag 两个状态
  const [count, setCount] = useState(0)
  const [flag, setFlag] = useState(false)

  // 根据布尔值进行计算,动态返回内容
  const tips = () => {
    console.log('触发了 tips 的重新计算')
    return flag ? <p>哪里贵了,不要睁着眼瞎说好不好</p> : <p>这些年有没有努力工作,工资涨没涨</p>
  }

  return (
    <>
      <h1>父组件</h1>
      <p>count 的值是:{count}</p>
      <p>flag 的值是:{String(flag)}</p>
      {tips()}
      <button onClick={() => setCount((prev) => prev + 1)}>+1</button>
      <button onClick={() => setFlag((prev) => !prev)}>Toggle</button>
      <hr />
      <Son num={count} />
    </>
  )
}

我们希望如果 flag 没有发生变化,则避免 tips 函数的重新计算,从而优化性能。此时需要用到 React Hooks 提供的 useMemo API。

2.2 语法格式

const memorizedValue = useMemo(cb, array)
const memoValue = useMemo(() => {
  return 计算得到的值
}, [value]) // 表示监听 value 的变化
  • cb:这是一个函数,用户处理计算的逻辑,必须使用 return 返回计算的结果;
  • array:这个数组中存储的是依赖项,只要依赖项发生变化,都会触发 cb 的重新执行。
    • 不传数组,每次更新都会重新计算
    • 空数组,只会计算一次
    • 依赖对应的值,对应的值发生变化时会重新执行 cb

2.3 使用 useMemo 解决刚才的问题

//导入 useMemo:
import React, { useEffect, useState, useMemo } from 'react'
//在 Father 组件中,使用 useMemo 对 tips 进行改造:
// 根据布尔值进行计算,动态返回内容
const tips = useMemo(() => {
  console.log('触发了 tips 的重新计算')
  return flag ? <p>哪里贵了,不要睁着眼瞎说好不好</p> : <p>这些年有没有努力工作,工资涨没涨</p>
}, [flag])

三.useCallback

3.1 useMemo和useCallback区别

名称返回值
useMemo变量
useCallback函数

3.2 语法格式

const memoCallback = useCallback(cb, array)

  • cb 是需要被缓存的函数
  • array 是依赖项列表,当 array 中的依赖项变化时才会重新执行 useCallback。
    • 如果省略 array,则每次更新都会重新计算
    • 如果 array 为空数组,则只会在组件第一次初始化的时候计算一次
    • 如果 array 不为空数组,则只有当依赖项的值变化时,才会重新计算
import React, { useState, useCallback } from 'react'

// 用来存储函数的 set 集合
const set = new Set()

export const Search: React.FC = () => {
  const [kw, setKw] = useState('')

  const onKwChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setKw(e.currentTarget.value)
  }, [])

  // 把 onKwChange 函数的引用,存储到 set 集合中
  set.add(onKwChange)
  // 打印 set 集合中元素的数量
  console.log('set 中函数的数量为:' + set.size)

  return (
    <>
      <input type="text" value={kw} onChange={onKwChange} />
      <hr />
      <p>{kw}</p>
    </>
  )
} 

四.useTransition

4.1 问题引入

import React, { useState } from 'react'

export const TabsContainer: React.FC = () => {
  // 被激活的标签页的名字
  const [activeTab, setActiveTab] = useState('home')

  // 点击按钮,切换激活的标签页
  const onClickHandler = (tabName: string) => {
    setActiveTab(tabName)
  }

  return (
    <div style={{ height: 500 }}>
      <TabButton isActive={activeTab === 'home'} onClick={() => onClickHandler('home')}>
        首页
      </TabButton>
      <TabButton isActive={activeTab === 'movie'} onClick={() => onClickHandler('movie')}>
        电影
      </TabButton>
      <TabButton isActive={activeTab === 'about'} onClick={() => onClickHandler('about')}>
        关于
      </TabButton>
      <hr />

      {/* 根据被激活的标签名,渲染对应的 tab 组件 */}
      {activeTab === 'home' && <HomeTab />}
      {activeTab === 'movie' && <MovieTab />}
      {activeTab === 'about' && <AboutTab />}
    </div>
  )
}

// Button 组件 props 的 TS 类型
type TabButtonType = React.PropsWithChildren & { isActive: boolean; onClick: () => void }
// Button 组件
const TabButton: React.FC<TabButtonType> = (props) => {
  const onButtonClick = () => {
    props.onClick()
  }

  return (
    <button className={['btn', props.isActive && 'active'].join(' ')} onClick={onButtonClick}>
      {props.children}
    </button>
  )
}

// Home 组件
const HomeTab: React.FC = () => {
  return <>HomeTab</>
}

// Movie 组件
const MovieTab: React.FC = () => {
  const items = Array(100000)
    .fill('MovieTab')
    .map((item, i) => <p key={i}>{item}</p>)
  return items
}

// About 组件
const AboutTab: React.FC = () => {
  return <>AboutTab</>
} 

4.2 语法格式

import { useTransition } from 'react';

function TabContainer() {
  const [isPending, startTransition] = useTransition();
  // ……
}
  • 参数:
    • 调用 useTransition 时不需要传递任何参数
  • 返回值(数组):
    • isPending 布尔值:是否存在待处理的 transition【是否存在待渲染的组件】,如果值为 true,说明页面上存在待渲染的部分,可以给用户展示一个加载的提示;
    • startTransition 函数:调用此函数,可以把状态的更新标记为低优先级的,不阻塞 UI 对用户操作的响应;
import React, { useState, useTransition } from 'react'

export const TabsContainer: React.FC = () => {
  // 被激活的标签页的名字
  const [activeTab, setActiveTab] = useState('home')
  const [, startTransition] = useTransition()

  // 点击按钮,切换激活的标签页
  const onClickHandler = (tabName: string) => {
    startTransition(() => {
      setActiveTab(tabName)
    })
  }
  // 省略其它代码...
} 

4.3 使用 isPending 展示加载状态

//调用 useTransition 期间,接收 isPending 参数:
const [isPending, startTransition] = useTransition()
// 将标签页的渲染,抽离到 renderTabs 函数中:
// 用于渲染标签页的函数
const renderTabs = () => {
  if (isPending) return <h3>Loading...</h3>
  switch (activeTab) {
    case 'home':
      return <HomeTab />
    case 'movie':
      return <MovieTab />
    case 'about':
      return <AboutTab />
  }
} 

4.4 注意事项

  • 传递给 startTransition 的函数必须是同步的。React 会立即执行此函数,并将在其执行期间发生的所有状态更新标记为 transition。如果在其执行期间,尝试稍后执行状态更新,这些状态更新不会被标记为 transition。
  • 标记为 transition 的状态更新将被其他状态更新打断。
  • transition 更新不能用于控制文本输入。

五.useDeferredValue

5.1 问题引入

transition更新不能用于控制文本输入,会导致中间状态丢失

import React, { useState, useTransition } from 'react'

// 父组件
export const SearchBox: React.FC = () => {
  const [kw, setKw] = useState('')
  // 1. 调用 useTransition 函数
  const [, startTransition] = useTransition()

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    // 2. 将文本框状态更新标记为“低优先级”,会导致中间的输入状态丢失
    startTransition(() => {
      setKw(e.currentTarget.value)
    })
  }

  return (
    <div style={{ height: 500 }}>
      <input type="text" value={kw} onChange={onInputChange} />
      <hr />
      <SearchResult query={kw} />
    </div>
  )
}

// 子组件,渲染列表项
const SearchResult: React.FC<{ query: string }> = (props) => {
  if (!props.query) return
  const items = Array(40000)
    .fill(props.query)
    .map((item, i) => <p key={i}>{item}</p>)

  return items
}

5.2 语法格式

import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [kw, setKw] = useState('');
  // 根据 kw 得到延迟的 kw
  const deferredKw = useDeferredValue(kw);
  // ...
}

useDeferredValue 的返回值为一个延迟版的状态

  • 在组件首次渲染期间,返回值将与传入的值相同
  • 在组件更新期间,React 将首先使用旧值重新渲染 UI 结构,这能够跳过某些复杂组件的 rerender,从而提高渲染效率。随后,React 将使用新值更新 deferredValue,并在后台使用新值重新渲染是一个低优先级的更新。这也意味着,如果在后台使用新值更新时 value 再次改变,它将打断那次更新。
    注意:需要配合React.memo对子组件缓存使用。
// 1. 按需导入 useDeferredValue 这个 Hooks API
import React, { useState, useDeferredValue } from 'react'

// 父组件
export const SearchBox: React.FC = () => {
  const [kw, setKw] = useState('')
  // 2. 基于 kw 的值,为其创建出一个延迟版的 kw 值,命名为 deferredKw
  const deferredKw = useDeferredValue(kw)

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setKw(e.currentTarget.value)
  }

  return (
    <div style={{ height: 500 }}>
      <input type="text" value={kw} onChange={onInputChange} />
      <hr />
      {/* 3. 将延迟版的 kw 值,传递给子组件使用 */}
      <SearchResult query={deferredKw} />
    </div>
  )
}

// 子组件,渲染列表项
// 4. 子组件必须使用 React.memo() 进行包裹,这样当 props 没有变化时,会跳过子组件的 rerender
const SearchResult: React.FC<{ query: string }> = React.memo((props) => {
  if (!props.query) return
  const items = Array(40000)
    .fill(props.query)
    .map((item, i) => <p key={i}>{item}</p>)

  return items
})

5.3 延迟一个值与防抖和节流之间有什么不同

面试可能会问到哦~延迟一个值与防抖和节流之间有什么不同?

  • 防抖: 在用户停止输入一段时间之后再更新列表。【搜索框常用】
  • 节流: 是指每隔一段时间更新列表。
    在这里插入图片描述

5.4 表明内容已过时

// 1. 按需导入 useDeferredValue 这个 Hooks API
import React, { useState, useDeferredValue } from 'react'

// 父组件
export const SearchBox: React.FC = () => {
  const [kw, setKw] = useState('')
  // 2. 基于 kw 的值,为其创建出一个延迟版的 kw 值
  const deferredValue = useDeferredValue(kw)

  const onInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setKw(e.currentTarget.value)
  }

  return (
    <div style={{ height: 500 }}>
      <input type="text" value={kw} onChange={onInputChange} />
      <hr />
      {/* 3. 将延迟版的 kw 值,传递给子组件使用 */}
      <div style={{ opacity: kw !== deferredValue ? 0.3 : 1, transition: 'opacity 0.5s ease' }}>
        <SearchResult query={deferredValue} />
      </div>
    </div>
  )
} 

ReactHooks 到这就完结啦,感谢大家的喜欢❥(^_-)

  • 34
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React Hooks 是 React 16.8 中新增的特性,它允许函数组件中使用 state 和其他 React 特性,从而使函数组件具有类组件的能力。 使用 React Hooks 需要先引入 React 的 useState、useEffect 等钩子函数,然后在函数组件中使用它们。 useState useState 是最常用的 Hook 之一,它可以让我们在函数组件中使用 state。useState 接收一个初始值作为参数,并返回一个数组,数组的第一个元素是当前 state 的值,第二个元素是更新 state 的函数。 例如,下面的代码在函数组件中使用了 useState 来保存一个计数器: ``` import React, { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ``` useEffect useEffect 是另外一个常用的 Hook,它可以在函数组件中使用副作用。副作用包括数据获取、订阅或手动修改 DOM 等操作。useEffect 接收一个函数作为参数,该函数会在组件渲染完成后执行。 例如,下面的代码使用 useEffect 来更新页面标题: ``` import React, { useState, useEffect } from 'react'; function PageTitle() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); } ``` useContext useContext 可以让我们在函数组件中使用 Context。Context 是一种在组件树中传递数据的方法,它可以避免通过 props 层层传递数据。 例如,下面的代码使用 useContext 来获取全局的主题: ``` import React, { useContext } from 'react'; const ThemeContext = React.createContext('light'); function ThemeButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); } ``` 使用 useContext 前需要先创建一个 Context,可以使用 React.createContext 方法来创建。 除了上述三个 Hook,还有 useReducer、useCallback、useMemo、useRef 等 Hook 可以使用。使用这些 Hook 可以让函数组件更加强大和灵活。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值