10- React Hooks 应用

109 阅读5分钟

一、React Hooks应用

1、useRef

使用场景:在React中进行DOM操作,用来获取DOM

作用:返回一个带有curent属性的可变对象

import ReactDOM from 'react-dom'
import { useRef } from "react"

const App = ()=>{
 const  inputRef =  useRef<HTMLInputElement>(null)
 
 const onClick = ()=>{
   const inputDOM = inputRef.current as HTMLInputElement
   console.log(inputDOM.value)
 }
  return (
  	<div>
    	<input type="text" ref={inputRef}></input>
      <button onClick={onClick}>获取文本框的值</button>
    </div>
  )
}

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

2、非空断言 (TS提供的)

// 对比
const inputDOM = inputRef.current as HTMLInputElement

// 断言
const inputDOM = inputRef.current!
// 断言 
function foo (value:string|null) {
  value!.split('')
}

3、useContext

作用:在函数组件中,获取Context中的值。要配合Context一起使用

1、创建一个context.ts文件

import { createContext } from "react"

const TodoContext = createContext([])

export default TodoContext

2、父组件引用进来包裹

import { Provider } from "./context.ts"

function rendersTodo (){
  return (
  	<Provider value={todos}>
    	<MainSection List={todos}>
      </MainSection>
    </Provider>
  )
}

3、main-section.jsx 文件获取值

import Context from "./context.ts"
import { useContext } from "react"

const MainSection =({list}) => {
  const todos = useContext(Context)
}

4、useReducer

  • 作用:类似于useState Hook,用于为函数组件提供状态
  • 使用场景:当组件的状态逻辑复杂 | 下一个状态取决于上一个状态 用useReducer
  • 第一个参数 reducer函数,(state,action)=>newState根据当前状态计算出新状态并返回
  • 第二个参数:initialState状态初始值。
  • 返回值:状态(state)、修改状态函数 (dispatch)
import { useState } from "react"
import ReactDOM from "react-dom"

type ActionType = {type:"increment" | "decrement"}
// 处理useReducer 对应的状态逻辑代码
const reducer = (state: number,action) => {
  switch(action.type) {
    case 'decrement':
      return state - 1
    case 'increment':
      return state + 1
    default:
      return state
  }
}

// 函数组件
const Counter = () => {
  // const [count,setCount] = useState(0)
  // 将组件内部的state抽离到组件外部
  const [count ,dispatch] = useReducer(reducer,0)
  
  // dispatch
  - 参数:action(动作,表示要做什么)即:带有type属性的对象
  - 对象中的其他属性比如{type:'increment',count:5} 表示还可以携带参数
  return (
  	<div>
    	<h1>{ count }</h1>
      <button onClick={()=>dispatch({type:'increment'})}>+1</button>
      <button onClick={()=>dispatch({type:'decrement'})}>-1</button>
    </div>
  )
}

5、自定义Hooks

使用场景:实现状态逻辑复用

const useTodo = (initialValue) => {
  const [stodo,setTodos] =useState(initialValue)
  // 省略 onAdd 等事件处理程序
  return {todos,onAdd,onToggle,onDelete}
}

解释:

  • 此处,我们封装了useTodos Hooks 用来提取todos状态,添加,切换
  • 自定义Hooks搜一个函数,约定函数名称必须use开头,React就是通过函数名称是否use开头来判断是不是Hooks
  • Hooks只能在函数组件中或其他自定义Hooks中使用,否则会报错
  • 自定义Hooks用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值

二、React Hooks进阶

1、函数组件的特性

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

2、函数组件的特性导致的问题

问题

  1. 组件每次重新渲染时,组件内部的事件处理程序等函数都会重新创建,导致子组件每次都会接受到不同的props从而重新重复的不必要的渲染
  2. 组件内部的事件处理程序等函数中,只能获取到当前渲染时的数据(是合理的)

解决方案

第一个问题

使用React.memo配合useCallback/useMemo 这两个hooks来解决

React.memo 高阶组件

作用:记忆组件上一次的渲染结果,在porps没有变化复用该结果

只要父组件状态更新,子组件就会无条件更新

  • 子组件props变化时:组件代码执行 -> JSX Diff(配合虚拟DOM) -> 渲染 [DOM操作]
  • 子组件props无变化时:组件代码执行 -> JSX Diff(配合虚拟DOM) 【无DOM操作】

注意:此处的更新是指组件代码执行、JSX进行Diff操作

import { useState,memo} from "react"
import ReactDOM from "react-dom"

// 函数组件
const Child =  memo(() => {
  return (
  	<div>
    	<h1>{ count }</h1>
      <button>+1</button>
      <button>-1</button>
    </div>
  )
})


const App = ()=>{
  return (
  	<div>
    	<Child />
    </div>
  )
}
ReactDOM.render(<App />,document.getElementById('root'))
  • 默认memo只会对前后的props进行浅对比
  • 对于对象类型的props来说,只会比较引用
  • 其他基本类型的props,正常比较

(知道即可)可以使用第二个参数比较

React.memo(Child,(prevProps,nextProps)=>{
  return prevProps === nextProps
  
  true  不重新渲染
  false 重新渲染
})
useCallback

useCallback: 记住函数的引用,在组件每次更新时返回相同引用的函数。

useMemo:记住任意数据(数值、对象、函数等),在组件每次更新时返回相同引用的函数。

  • 第一个参数,需要被记忆的回调函数
  • 第二个参数,依赖项数组,用于指定回调函数中依赖的数据(即使没有也要穿个空数组)
  • 返回值:useCallback记住的回调函数。
  • useCallback记住的回调函数会一直生效,直到依赖项发生变化。
import {useState,memo,useCallback} from 'react'
import ReactDOM from 'react-dom'

const Child = memo({click}) => {
  console.log('Child 组件更新')
  
  return (
  	<div style={backgroundColor:'skybuld'}>
    子组件:<button onClick={click}>点击</button>
    </div>
  )
}

const App = () => {
  const [count,setCount] = useState(0)
  const [theme,setTheme] = useState('pink')
  
  // 回调函数
  const handleClick =useCallback(()=>{
    console.log('调用了')
  },[])
  
  return(
  	<div>例子</div>
    <h1>计数器:{count}</h1>
    <button onClick={()=>setCount(count + 1)}>+1</button>
    
    <Child click={handleClick} />
  )
}
ReactDOM.render(<App />,document.getElementById('root'))
useMemo

用法特点作用 和 useCallback一样

  • 不同点:useMemo的第一个参数的返回值,才是要记忆的数据
const user = useMemo(()=>{
  return {age:count+1}
},[count])
  • 第二个用法 相当于 vue中的compute
import {useState, useMemo} from 'react'
import ReactDOM from 'react-dom'

const App = () => {
  const [count,setCount] = useState(0)
  const [theme,setTheme] = useState('pink')
  
 	// 昂贵的计算
  const computeExpensiveValue = (count:number)=>{
    // 创建一个长度为count并填充每个元素为100
    const numArrs = new Array(count).fill(100)
    return numArrs.reduce((total,item)=>item+total,0)
  }
  
  // 使用useMemo 来记忆
  const value = useMemo(()=>computeExpensiveValue(count))
  
  return(
  	<div>例子</div>
    <h1>计数器:{count}</h1>
    <button onClick={()=>setCount(count + 1)}>+1</button>
    <div>计算:{value}</div>
    )
}
ReactDOM.render(<App />,document.getElementById('root'))
useRef高级用法
  • 特点:useRef创建的对象将在组件更新期间保持(引用)不变
获取最新值
import { useState,useEffect,useRef } from 'react'
import ReactDOM from 'react-dom'

const App = ()=>{
  const [count,setCount] = useState(0)
  const countRef = useRef(count)
  
  useEffect(()=>{
    countRef.current=count
  },[count])
  
  const showCount = () => {
    setTimeout(()=>{
      console.log('展示count值',count)
      console.log('展示count最新值',countRef.current)
    },3000)
  }
  
  const onClick = ()=>{
    setCount(count+1)
  }
  
  return (
  	<div>
    	<h1>计数器:{count}</h1>
      <button onClick={onClick}>+1</button>
      <button onClick={showCount}>显示</button>
    </div>
  )
}