React避坑
1.useState的参数(初始值),只在第一次有效
可以把第一次render前执行的代码放入其中
const instance = useRef(null); useState(()=>{ instance.current = 'initial value'; });
2.useState里数据必须为immutable
使用useState时,如果在更新函数里传入同一个对象将无法触发更新
const [list,setList] = useState([1,2,3,4,5]); return ( <> <ol> {list.map(v=><li key={v}>{v}</li>)} </ol> <button onClick={()=>{ //error,传入同一个对象 setList(list.sort((a,b)=>a-b)) //pass,使用slice深拷贝 setList(list.slice().sort((a,b)=>a-b)) }} >sort</button>
</>)
3.useState过时的闭包
function DelayCount(){ const [count,setCount]= useState(0) function handleClickAsync(){ setTimeout(function delay(){ setCount(count+1)
},5000) } function handleClickSync(){ setCount(count+1) }
return(
<div>
{count}
<button onClick={handleClickAsync}>异步+1</button>
<button onClick={handleClickSync}>同步+1</button>
</div>
)
}
点击"异步+1"后多次点击"同步+1",发现count最终回退到1
这是因为delay()是一个过时的闭包
过程:
(1)初始渲染:count值为0
(2)点击“异步+1"按钮,delay()闭包捕获count值0,setTimeout()5秒后调用delay()
(3)点击"同步+1"按钮,调用setCount(count+1)将count值加1,组件重新渲染
(4)5秒之后,setTimeout()执行delay(),但是delay()中闭包保存count的值是初始渲染的值0,所以调用setState(0+1),结果count回退到1
每次render都会产生新的闭包
为了解决这个问题,可以使用函数方法来更新count状态:
//修改handleClickAsync function handleClickAsync(){ setTimeout(function delay(){ setCount(count=>count+1) },5000) }
useEffect、useMemo、useCallback自带闭包,每次组件渲染都会捕获当前组件函数上下文中的状态(state,props),所以每次这三种Hook执行,反应的都是当时的状态,无法获取最新的状态.
获取最新状态可以使用useRef
4.修改状态是异步的
const [count,setCount] = useState(0) function change(){ setCount(count+1) console.log(count)//打印0 }
这样设计的目的是优化性能,争取一次重绘解决所有状态改变,而不是改一次重绘一次
5.useEffect中使用async
useEffect(()=>{ (async()=>{ await fetchSomething() })() },[])
6.useEffect获取最新数据
function Example(){ const [count,setCount] = useState(0) const latestCount = useRef(count) useEffect(()=>{ latestCount.current = count setTimeout(()=>{console.log(`You clicked ${latestCount.current} times`)},3000) }) }
7.函数作为依赖时死循环
//子组件 let Child = React.memo((props)=>{ useEffect(()=>{ props.onChange(props.id) },[props.onChange,props.id) return ( <div>{props.id}</div> ) })
//父组件
let Parent = ()=>{
const [id,setId] = useState(0)
const [count,setCount] = useState(0)
const onChange = (id)=>{
setCount(id)
}
return(
<div>{count}
<Child onChange={onChange} id={id} />
</div>
)
}
每次父组件render,onChange引用值肯定会变.因此子组件Child必定会render,子组件触发useEffect,从而再次触发父组件render,造成死循环.
优化:
//修改父组件onChange函数 const onChange = useCallback(()=>{setCount(id)},[id])
8.组件加载时发起异步任务
import React,{useState,useEffect} from 'react'
const SOME_API = '/api/get/value'
export const MyComponent:React.FC<{}>=()=>{
const [loading,setLoading] = useState(true)
const [value,setValue] = useState(0)
useEffect(()=>{
(async ()=>{
const res = await fetch(SOME_API)
const data = await res.json()
setValue(data.value)
setLoading(false)
})()
},[])
return(
<>
{loading?(<h2>Loading...</h2>):(<h2>value is {value}</h2>)}
</>
)
}
9.组件加载时发起异步任务
假设我们想做一个组件,点击按钮后开启一个5s计时,结束后修改状态,但如果在倒计时未结束时想停止计时器,避免内存泄露,发现开启计时器和清理计时器会在不同的地方,因此就必须记录这个timer.
import React,{useState,useEffect,useRef} from 'react'
export const MyComponent:React.FC<{}>=()=>{
const [value,setValue] = useState(0)
const timer = useRef(0)
useEffect(()=>{
return ()=>{window.clearTimeout(timer.current}
},[])
function dealClick(){
timer.current = window.setTimeout(()=>{setValue(100)},3000)
}
return (
<>
<span>Value is {value}</span>
<button onClick={dealClick}>Click Me!</button>
</>
)
}
useRef返回一个可变的ref对象,其current属性被初始化为传入的参数(initialValue),返回的ref对象在组件的整个生命周期内保持不变.
ref对象可以确保在整个生命周期中不变,且同步更新,是因为ref的返回值始终只有一个实例,所有读写都指向它自己.

浙公网安备 33010602011771号