useCallback【记忆函数】
eg:代表代码对照
若文章有误,欢迎读者指出
【记忆函数,实现了类似记忆组件、计算属性的功能,也能进行scu优化】
useCallback记忆函数,效果类似记忆组件memoize-one,把某个函数进行缓存,然后把当前函数体返回给你
- 正常情况下,父组件中当前数据改变,当前父组件无条件刷新,当然子组件也随之刷新,当前子组件依赖的数组没有发生改变,咱么就可以通过scu技术,不让他刷新
parent.jsx父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import React, { useState, useCallback } from 'react' import Child from './Child'
const Parent = () => { const [num, setNum] = useState(0) const [arr, setArr] = useState([1, 2, 3, 4]) return ( <div> <h3>Parent</h3> <p>{num}</p> { /* 正常情况下,父组件中当前数据改变,当前父组件无条件刷新,当然子组件也随之刷新 */ } {/* 当前数据改变,当前组件无条件刷新,当然子组件也随之刷新 */} {/* 当前子组件依赖的数组没有发生改变,咱么就可以通过scu技术,不让他刷新 */} <button onClick={() => setNum(num + 1)}>点击+1</button> <Child arr={arr} /> </div> ) }
export default Parent
|
Child.jsx子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React,{memo} from 'react'
const Child = (props) => { console.log('Child刷新了') return ( <div> <h3>Child</h3> <p>{props.arr}</p> </div> ) }
export default memo(Child)
|
- 如果给子组件传递个函数进去,就算是依赖的数据和函数没有变化,scu也会被打破,子组件被动随着这个函数刷新[尽管它没执行]
问题:每次组件刷新,组件里面的执行函数都会从新创建,所以刷新前后不是同一函数
分析问题:这里多说一句,一般把函数式组件理解为class组件render函数的语法糖,所以每次重新渲染的时候,函数式组件内部所有的代码都会重新执行一遍。所以上述代码中每次render,handleClick都会是一个新的引用,所以也就是说传递给SomeComponent组件的props.onClick一直在变(因为每次都是一个新的引用),所以才会说这种情况下,函数组件在每次渲染的时候如果有传递函数的话都会重渲染子组件。
解决方法:有了useCallback就不一样了,你可以通过useCallback获得一个记忆后的函数,那么改变父组件中的num就不会影响子组件刷新,因为函数也做了缓存,注意子组件也要使用memo高阶组件
parent.jsx父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import React, { useState, useCallback } from 'react' import Child from './Child'
const Parent = () => { const [num, setNum] = useState(0) const [arr, setArr] = useState([1, 2, 3, 4]) const handleAdd = useCallback(() => { setArr([...arr, 5]) }, [arr]) return ( <div> <h3>Parent</h3> <p>{num}</p> { /* 正常情况下,父组件中当前数据改变,当前父组件无条件刷新,当然子组件也随之刷新 */ } <button onClick={() => setNum(num + 1)}>点击+1</button> {/* 当前数据改变,当前组件无条件刷新,当然子组件也随之刷新 */} {/* 当前子组件依赖的数组没有发生改变,咱么就可以通过scu技术,不让他刷新 */} {/* 如果给子组件传递个函数进去,就算是依赖的数据和函数没有变化,scu也会被打破,子组件被动随着这个函数刷新[尽管它没执行] */} {/* 问题:每次组件刷新,组件里面的执行函数都会从新创建,所以刷新前后不是同一函数 */} <Child arr={arr} handleAdd={handleAdd} /> </div> ) }
export default Parent
|
Child.jsx子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React,{memo} from 'react'
const Child = (props) => { console.log('Child刷新了') return ( <div> <h3>Child</h3> <p>{props.arr}</p> <button onClick={props.handleAdd}>点击改变父组件中的arr</button> </div> ) }
export default memo(Child)
|
userMemo【记忆组件,不需要scu】
useCallback的功能完全可以由useMemo所取代,如果你想通过使用useMemo返回一个记忆函数也是完全可以的
所以前面使用useCallback的例子可以使用useMemo进行改写:
1 2 3 4 5
| const handleAdd = () => { setArr([...arr, 5]) } var child = useMemo(()=><Child arr={ arr } handleAdd={ handleAdd }/>,[arr])
|
parent.jsx父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import React, { useState, useCallback, useMemo } from 'react' import Child from './Child'
const Parent = () => { const [num, setNum] = useState(0) const [arr, setArr] = useState([1, 2, 3, 4])
const handleAdd = () => { setArr([...arr, 5]) } const child = useMemo(() => <Child arr={arr} handleAdd={handleAdd} />, [arr]) return ( <div> <h3>Parent</h3> <p>{num}</p> { /* 正常情况下,父组件中当前数据改变,当前父组件无条件刷新,当然子组件也随之刷新 */} <button onClick={() => setNum(num + 1)}>点击+1</button> {/* 当前数据改变,当前组件无条件刷新,当然子组件也随之刷新 */} {/* 当前子组件依赖的数组没有发生改变,咱么就可以通过scu技术,不让他刷新 */} {/* 如果给子组件传递个函数进去,就算是依赖的数据和函数没有变化,scu也会被打破,子组件被动随着这个函数刷新[尽管它没执行] */} {/* 问题:每次组件刷新,组件里面的执行函数都会从新创建,所以刷新前后不是同一函数 */} {/* <Child arr={arr} handleAdd={handleAdd} /> */} {/* 使用useMemo来实现useCallback的功能,落脚点传递的是值,同时也不需要scu */} {child} </div> ) }
export default Parent
|
Child.jsx子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React from 'react'
const Child = (props) => { console.log('Child刷新了') return ( <div> <h3>Child</h3> <p>{props.arr}</p> <button onClick={props.handleAdd}>点击改变父组件中的arr</button> </div> ) }
export default Child
|
唯一的区别是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。
所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。
从例子可以看出来,useMemo只有在第二个参数数组的值发生变化时,才会触发子组件的更新。
useRef【保存引用值】
- 受控组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React, { useRef, useState } from 'react'
export default function UseRefDemo() { var [name, setName] = useState('') const handleClick = () => { console.log('name', name) } return ( <div> <div>用户名:<input value={name} onInput={(e) => setName(e.target.value)} type="text" /></div> <button onClick={handleClick}>登陆</button> </div> ) }
|
- 非受控组件ref
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React, { useRef, useState } from 'react'
export default function UseRefDemo() { var myName = useRef() const handleClick = () => { console.log('myName', myName.current.value) } return ( <div> <div>用户名:<input ref={myName} type="text" /></div> <button onClick={handleClick}>登陆</button> </div> ) }
|
UseImperativeHandleDemo【透传,了解即可】
通过 useImperativeHandle 用于让父组件获取子组件内的索引,这种方式,App 组件可以获得子组件的 input 的 DOM 节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react"
function ChildInputComponent(props, ref) { const inputRef = useRef(null) useImperativeHandle(ref, () => inputRef.current) return <input type="text" name="child input" ref={inputRef} /> }
const ChildInput = forwardRef(ChildInputComponent)
function App() { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return ( <div> <h3>透传</h3> <ChildInput ref={inputRef} /> </div> ) } export default App
|
自定义hooks
自定义hooks是在react-hooks基础上的一个拓展,可以根据业务需要制定满足业务需要的hooks,更注重的是逻辑单元。通过业务场景不同,我们到底需要react-hooks做什么,怎么样把一段逻辑封装起来,做到复用,这是自定义hooks产生的初衷。
我们设计的自定义react-hooks应该是长的这样的。
1
| const [ xxx , ... ] = useXXX(参数A,参数B...)
|
在我们在编写自定义hooks的时候,要关注的是传进去什么,返回什么。返回的东西是我们真正需要的。更像一个工厂,把原材料加工,最后返回我们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import React, { useState, useMemo } from 'react'
export default function Parent() { const [num, setNum] = useState(0) const [arr, setArr] = useState(['a', 'b', 'c', 'd']) var newArr = useCallUpperCase(arr) return ( <div> <p>{num}</p> {/* <button onClick={ ()=>setNum(num+1) }>+1</button> */} <button onClick={() => setArr([...arr, 'e'])}>+1</button> <ul> { newArr.map((item, index) => { return <li key={index}>{item}</li> }) } </ul> </div> ) }
function useCallUpperCase(arr) { return useMemo(() => { console.log('调用') return arr.map(item => { return item.toUpperCase() }) }, [arr]) }
|