React Diary

330 阅读4分钟

文章用于记录开发中对于react的学习和体会。
仅包含 function components 和 hooks ,摒弃class component。
可能包含 TS+React 相关内容。

Hooks

hooks的灵魂:闭包。

useState

浅层对比

更新时间:2021.4.12
场景:上传图片场景中,删除待上传列表中某张指定图片。

const [data, setData] = useState<number>(0);
const [list, setList] = useState<string []>([]);

复杂类型的踩坑点

React在调用 setData / setList 进行 diff 并 rerender 的过程中,只会对state进行浅层对比;

// list === ["a" , "b"]
list.push("c")
setList(list) // 错误!!!

换句话说,对复杂类型(如上方list)需要特别注意:直接在原 list 上修改,并且使用setList(list)是不会触发render的。

理想做法

使用新对象 / 新数组,或者 [...list] / {...obj} 方式进行setState。

setList([...list, "hello world"]);

独立渲染

更新时间:2021.4.7

每次一渲染都是一次函数执行。

  1. 拥有独立(相对于上一次渲染和下一次渲染)的state,props。
    换句话说,触发渲染的一般方式为:props改变 or setState

  2. 拥有独立的函数声明。
    如下函数声明,会导致每次在组件渲染过程中,生成完全不同的handleEvent变量;如果该函数作为props传入子组件,会使子组件做多次跟随父组件的冗余渲染。

    const Dad = () => {
    const handleEvent = () => { ... }
        return < Child handleEvent={handleEvent} />
    }
    const Child = props => {
        return <div onClick={handleEvent}> button </div>
    }
    

    解决方法:使用useCallback进行包裹,该函数会作为回调挂在在组件对应的Fiber节点上,在依赖不变的情况下直接取出使用。

    const handleEvent = useCallback(()=>{ ... }, [xxx] )
    
  3. 每一次渲染都形成了自己的作用域,于是有了形成闭包的可能。
    典型场景就是异步任务(ajax,setTimeout)中保存了旧作用域中的变量形成闭包,造成不符合预期的效果。
    解决方法:一般使用 useRef 解决。ref 可以摆脱作用域和闭包的影响。

useCallback、useMemo

使用场景:一般用作性能优化

闭包依赖

更新时间:2021.3.24 场景:两个函数之间出现引用关系(A引用B)且B使用useCallback包裹。因为A未将B列入依赖导致运行出错。

错因分析

  • 当依赖数组中元素未发生改变时,目标函数不会重新声明。因此导致目标函数中会一直记录旧作用域中的变量(可能为函数变量),形成闭包。

理想做法

何时使用

更新时间:2021.4.12
keyword:性能优化也要付出性能代价。

  1. 逻辑简单清晰的函数一般不使用useCallback。
  2. 在父组件中声明,并需要传给子组件的函数,一般要使用useCallback(避免子组件的冗余渲染)。

useEffect

绑定解绑

更新时间:2021.4.13 在useEffect做事件绑定的时候要在return的函数中注销绑定。否则易发生冗余绑定情况 useEffect(() => { on(tapTopRight, handler); return () => { on(tapTopRight, handler); } }, [] )

携参调用

  • 错误写法 1

    useEffect(() => {
        on(tapTopRight, () => handler(data) );
        return () => {
            on(tapTopRight, () => handler(data) );
        }
    }, [] )
    
    
    • 显然匿名函数是无法被简单解绑的,反而会在按钮上多次绑定不合预期的事件。
  • 错误写法 2

    const someHandler = () => handler(data)
    useEffect(() => {
        on(tapTopRight, someHandler );
        return () => {
            on(tapTopRight, someHandler );
        }
    }, [] )
    
    • 在 useEffect 中调用用函数时,最好将该函数在 useEffect 中声明
    • 尽量避免将useEffect中调用的函数放到外部声明,然后在 useEffect 中调用 (因为往往容易忘记外部声明的函数有那哪些依赖!!)
  • 理想写法

    useEffect(() => {
        const someHandler = () => handler(data)
        on(tapTopRight, someHandler );
        return () => {
            on(tapTopRight, someHandler );
        }
    }, [data, handler] )
    

    可以清楚看出effect的依赖为 状态data / handler

useEffect 不能接收 async 作为回调函数

更新时间:2021.4.13

async函数的本质

  • async函数运行后会返回一个promise对象。可以利用 then / await 取出返回值 useEffect的返回值
  • useEffect的返回值应为一个cleanup函数,而async的返回值是一个promise对象这显然是不对的 useEffect(async () => { const result = await axios('url'); setData(result.data); },[]);

理想方式

useEffect(()=>{
    const axiosTask = async () => {
        const result = await axios('url');
        setData(result.data);
    }
    axiosTask();
},[])

useRef

引用不变

更新时间:2021.4.13

使用方法:

const flagRef = useRef<boolean>(flag)

flagRef 的引用在其整个生命周期不会发生改变,且flagRef.current的改变不会影响渲染。这在某些场景下是非常重要的功能,例如:各种无需渲染的各种flag、异步任务等等。

绑定DOM

更新时间:2021.4.13
使用场景:在父组件中声明ref,传给自定义文本框TextArea并作控制。

一般使用

const inputRef = useRef<HTMLInputElement>(null)
const handleBtnClick = () => { inputRef.current.focus() }
...
return (
    <div>
        <input ref={inputRef} /> 
        <button onClick={handleBtnClick}> focus </button>
    </div>
)

声明的ref可以直接通过ref关键字绑定到指定元素,通过ref.current获取dom对象 跨组件使用ref

  • 因为函数组件没有实例,所以函数组件无法像类组件一样可以接收 ref 属性
  • 解决方法是使用 forwardRef,forwardRef 可以将父组件中的 ref 对象转发到子组件中的 dom 元素上
const Child = forwardRef((props, ref) => {     //转发ref
    return ( <input type="text" ref={ref}/> ) 
})
function Parent(){ 
    let [number,setNumber] = useState(0); 
    const inputRef = useRef(null);     //父组件中声明Ref
    function getFocus(){ 
        inputRef.current.focus(); 
    } 
    return ( <> 
        <Child ref={inputRef}/> 
        <button onClick={()=>setNumber(i=>i+1})}>+</button> 
        <button onClick={getFocus}>获得焦点</button> 
    </> )
}

写码理念及优化

Memo

减少非必要渲染

更新时间:2021.4.12
类比class component的pureComponent。对包裹组件的props做浅层对比,若props未发生改变,则不做更新。