一、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、函数组件的特性导致的问题
问题
- 组件每次重新渲染时,组件内部的事件处理程序等函数都会重新创建,导致子组件每次都会接受到不同的props从而重新重复的不必要的渲染
- 组件内部的事件处理程序等函数中,只能获取到当前渲染时的数据(是合理的)
解决方案
第一个问题
使用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>
)
}