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的返回值始终只有一个实例,所有读写都指向它自己.

posted @ 2021-12-08 16:15  HQL97  阅读(46)  评论(0)    收藏  举报