react内置hooks
useState
如何让页面动起来(实时更新)
import React,{FC,useState} from "react";
const Demo:FC=()=>{
let count=0; //普通js变量无法触发组件更新
function add(){
count++;
console.log("count: ",count);
}
return <div>
<button onClick={add}>add {count}</button>
</div>
}
export default Demo;
为什么采用
state的改变可以触发函数组件的更新
(如果js变量不在jsx中使用,别用useState)
import React,{FC,useState} from "react";
const Demo:FC=()=>{
// let count=0; //普通js变量无法触发组件更新
const[count,setCount]=useState(0); //useState 可以触发组件更新
function add(){
// count++;
setCount(count+1)
console.log("count: ",count);
}
return <div>
<button onClick={add}>add {count}</button>
</div>
}
export default Demo;
特点
-
不可变数据
不修改原数据,而是传入新的值 -
异步更新|
函数中无法直接获取最新的值 -
可能被合并
使用函数state可以解决这个问题
setCount(()=>{
return count+5
})
immer
是一个插件
解决state不可变数据的影响
安装 npm install -D immer
import {FC,useState} from "react";
import {produce} from "immer";
const Demo : FC =()=>{
const [list,setList]=useState(['x','y'])
function add(){
// setList(list.concat('z'));
setList(
produce(draft=>{
draft.push('z')//直接在原数组上修改
})
)
}
return (
<div>
<h2>state 不可变数据</h2>
<div>{JSON.stringify(list)}</div>
<button onClick={add}>add item</button>
</div>
)
}
export default Demo;
useEffect
为什么
组件是一个函数,返回的是JSX片段,
组件初次渲染完成,即函数执行完成,
一般情况下函数执行完成就结束了
但是在state更新时,会触发组件更新,函数组件再次执行
为了解决以上问题,使用useEffect
import {FC,useEffect,useState} from "react"
const Demo:FC=()=>{
useEffect(()=>{
console.log("组件初次渲染完成");
return ()=>{
console.log("组件销毁时执行");
}
},[])//无依赖项,只执行一次
const[count,setCount]=useState(0); //useState 可以触发组件更新
function add(){
// count++;
setCount(()=>{
return count+5
})
console.log("count: ",count);
}
return <div>
<button onClick={add}>add {count}</button>
</div>
}
export default Demo;
注意
发现useEffect 执行两次
在react 18 中,useEffect 在开发环境中执行两次,在生产环境中执行一次
(模拟组件生命周期,以便尽早暴露问题)
开发环境 npm run build 生存环境
useRef
- 操作DOM
- 可传入普通js变量,但是更新不会触发rerender
与vue3 ref 不同
import { FC,useRef } from "react";
const Demo:FC = () => {
// 定义一个ref
const inputRef = useRef<HTMLInputElement>(null) //dom节点
const nameRef=useRef("pink") //不是dom节点,是一个普通的js变量
function selectInput(){
const input = inputRef.current;
if(input){
input.select(); // 选中输入框中的内容(Dom节点操作API)
}
}
function changeName(){
nameRef.current="blue"//修改ref的值,不引起rerender(state修改会触发组件更新)
console.log(nameRef.current);
}
return (
<>
<input ref={inputRef} defaultValue={"hello world"}/>
<button onClick={selectInput}>选中 input</button>
<p>{nameRef.current}</p>
<button onClick={changeName}>修改名字</button>
</>
)
}
export default Demo;
useMemo
缓存数据
不用每次执行函数组件(比如说 state修改)都重新生成 , 实现性能优化
import {FC,useMemo,useState} from "react"
const Demo : FC =()=>{
const[num1,setNum1]=useState(0)
const[num2,setNum2]=useState(0)
const[text,setText]=useState("hello") //更新导致组件rerender
const sum =useMemo(()=>{
console.log("计算两数之和");//缓存
return num1+num2;
},[num1,num2])
return (
<>
<p>sum = {sum}</p>
<button onClick={()=>{setNum1(num1+1)}}>num1 : {num1}</button>
<button onClick={()=>{setNum2(num2+1)}}>num2 : {num2}</button>
<button onClick={()=>{setText(text+"1")}}>text : {text}</button>
</>
)
}
export default Demo;
useCallback
缓存函数
import {FC,useCallback,useState} from "react"
const Demo : FC =()=>{
const [text,setText] = useState("hello")
const fn1=()=>{
console.log("fn1 text",text);
}
const fn2=useCallback(()=>{
console.log("fn2 text",text);
},[text])
return(
<>
<button onClick={fn1}>fn1</button>
<button onClick={fn2}>fn2</button>
<div>
<input value={text} onChange={e=>setText(e.target.value)}></input>
</div>
</>
)
}
export default Demo;
自定义hooks
抽离公共部分,复用代码
同步案例
监听鼠标位置
- 自定义hook
import { useState,useEffect } from "react";
//获取鼠标位置
function useMouse(){
const [x,setX]=useState(0);
const [y,setY]=useState(0);
const mouseMoveHandler=(e:MouseEvent)=>{
setX(e.clientX);
setY(e.clientY);
}
useEffect(()=>{
//监听鼠标事件
window.addEventListener('mousemove',mouseMoveHandler);
//组件销毁时,一定要解绑DOM事件!!(可能会导致内存泄漏问题)
return ()=>{
window.removeEventListener('mousemove',mouseMoveHandler);
}
},[])
return {x,y}
}
export default useMouse;
- app.tsx 中引入
//导入自定义hook
import useMouse from "./hooks/useMouse.ts";
function App() {
const {x, y} = useMouse();
return(
<>
<p>
App Page
</p>
<div>x: {x}</div>
<div>y: {y}</div>
</>
)
}
export default App
异步案例
模拟加载效果
import { useState,useEffect } from "react";
//异步获取消息
function getInfo():Promise<string>{
return new Promise(resolve=>{
setTimeout(()=>{
resolve(Date.now().toString())
},1500)
})
}
//自定义钩子
const useGetInfo=()=>{
const[loading,setLoading]=useState(true)
const[info,setInfo]=useState("")
useEffect(()=>{
getInfo().then(info=>{
setLoading(false)
setInfo(info)
})
},[])
return {loading,info}
}
export default useGetInfo;
第三方hooks
提高开发效率
ahooks
react-hooks
hooks使用规则
- useXxx命名
- 组件内 获 其他hook 中可以调用
- 保证每次调用顺序一致(不能处于 if/for 内部)
面试题(闭包陷阱)
无法获取最新值
import {FC,useState} from 'react'
const Demo:FC =()=>{
const[count,setCount]=useState(0)
function add(){
setCount(count+1)
}
function alertFn(){
setTimeout(()=>{
alert(count);
},3000)
}
return(
<>
<p>闭包陷阱</p>
<div>
<span>{count}</span>
<button onClick={add}>add</button>
<button onClick={alertFn}>alert</button>
</div>
</>
)
}
export default Demo;
使用ref解决
import {FC,useState,useRef, useEffect} from 'react'
const Demo:FC =()=>{
const[count,setCount]=useState(0)
const countRef = useRef(0)
function add(){
setCount(count+1)
}
useEffect(()=>{
countRef.current=count
},[count])
function alertFn(){
setTimeout(()=>{
// alert(count); //count 值类型
alert(countRef.current); // ref 引用类型
},3000)
}
return(
<>
<p>闭包陷阱</p>
<div>
<span>{count}</span>
<button onClick={add}>add</button>
<button onClick={alertFn}>alert</button>
</div>
</>
)
}
export default Demo;