xiaochengzi6 / Blog

个人博客
GNU Lesser General Public License v2.1
0 stars 0 forks source link

React 中的 memoized 及其简单实现 #42

Open xiaochengzi6 opened 2 years ago

xiaochengzi6 commented 2 years ago

memoized

memoized 意指是缓存函数 可以在 React.memo()中能看到它的使用 这里配合着 react 来拓展和实现一下

1、这个组件出现的问题当父组件中发生了状态的改变子组件也会重新渲染

function Son (){
    return (
        <div>"Son"</div>
    )    
}

export default App() {
    const [count, setCount] = useState(false)
    return (
        <div>{count}</div>
        <Son />
    )
}

使用 React.memo() 包裹子组件可以解决这个问题 官方解释

如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

意思说明当传入子组件的props 没有改变就不会触发更新。

默认是做浅层对比、可以使用自定义的函数将其作为 第二参数传入使用

2、当传入的是一个函数这里 React.memo() 就会失效,因为组件的更新会创建了一个新的函数 具有相同的值 但地址完全不同

const a = () => (1)
const b = () => (1)
// 保存的地址可不相同
a !== b 
// 内容相同
// value(a) == value(b)
export default App() {
    const [count, setCount] = useState(0);
    const addCount = () => setCount(count + 1)
    return (
        <div add={addCount}>{count}</div>
        <Son add={addCount}/>
    )
}
function Son (props){
    return (
        <div onclick={props.add}>'add'</div>
    )    
}

使用 React.useCallback() 来对函数做持久性记忆 (memoized) useCallback(fn, deps) 第二参数是它的依赖项 当依赖项发生改变时才会更新

依赖项数组不会作为参数传给回调函数。虽然从概念上来说它表现为:所有回调函数中引用的值都应该出现在依赖项数组中。

export default App() {
    const [count, setCount] = useState(0);
    const addCount = useCallback(() => setCount(count + 1))
    return (
        <div add={addCount}>{count}</div>
        <Son add={addCount}/>
    )
}
function Son (props){
    return (
        <div onclick={props.add}>'add'</div>
    )    
}

3、当子组件接收一个对象时也会造成父组件的跟新组件间虽然接受一个对象但对象未发生改变也会更新的效果

export default App() {
    const [count, setCount] = useState(0);
    const addCount = useCallback(() => setCount({type: 'CHANGEOBJEACT',data: 'obj_0'})
    const obj = {type: 'CHANGEOBJEACT',data: 'obj_1'}
    return (
        <div add={addCount}>{count}</div>
        <Son obj={obj} obj/>
    )
}
function Son (props){
    return (
        <div>{props.obj.obj}</div>
    )    
}

这里和第二点很像都是前后对比的地址不对而造成的重新渲染

这里使用 useMemo() 来去持久化对象

const addCount = useMemo(()=>({type: 'CHANGEOBJEACT',data: 'obj_1'},[])

useCallback()useMemo() 的值很像 二者有这样的关系 useMemo(()=>(fn), deps) == useCallback(fn, deps)

不过 使用 useMemo() 来保存值 使用 useCallback() 保存函数 这两者都使用 memoized 的相关概念。

这里简单实现一个 memoized 函数

// func 回调函数
// deps 作为一个依赖项 
function memoize(func, deps){
    if(typeof func !== 'function'){
        throw TypeError('no function')
    }
    const memoized = function (key){
        const address = deps ? deps.apply(this, arguments) : key;
        const cache = memoized.cache
        if(cache.has(address)){
            return cache.get(address)
        }
        const result = func.apply(this, arguments);
        memoized.cache = cache.set(address, result) || cache
        return result;
    }

    memoized.cache = new Map();
    return memoized;
}

主要这里使用到了 new Map() 来去保存。 实际上的原理:

// function () { value ++ } ==> function (value) { value ++ }

将依赖项通过函数的参数传入

react 使用 useMemo() 或者 useCallback 将函数缓存起来,如果没有依赖项就一直使用缓存的函数,否者就会将最新值以参数的形式传入