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 = () => {// 定义一个refconst 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") //更新导致组件rerenderconst 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 中引入
//导入自定义hookimport 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;