useMemo, useCallback, useReducer杂谈

1,488
  • useMemo

React使用Object.is来进行变量的比较(不论是原始类型还是引用类型 的值),它的运算结果和===类似。 这个会导致不必要的re-render,即使变量未发生变化。

所以,有了memoization。

插一句,React.memo使用起来和PureComponent差不多。

useMemo是另一回事:

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

依赖列表里面的项若没有发生变化,那么,即便所在的组件re-render,computeExpensiveValue也不会重新执行,只会返回上一次计算的结果。computeExpensiveValue重新执行的条件,就是dependencies发生了变化。这是useMemo最重要的用途。特别是要进行expensive运算的时候,是optimal的选择。

    const List = useMemo(
        () => 
            listOfItems.map(item => ({
            ...item,
            itemProp1: expensiveFunction(props.first),
            itemProp2: anotherPriceyFunction(props.second) 
        })),
        [listOfItems]
    )

上面整个例子中,只要listOfItems没有发生变化,List就不会重新生成。

    function BabyGator({fish, insects}) { // because what else do baby gators eat?
        const dinner = {fish, insects};
        useEffect(() => {
            eatFunction(dinner);
        }, [fish, insects])
    }

    function MamaGator() {
        return <BabyGator fish='small edible fish' insects={15}>
    }

上面这例子works perfectly well,因为 useEffect的dependencies是两个原始类型的值。 但是, 也仅仅当依赖项是primitive的值时,才达到了不去进行不必要执行eatFunction的目的,这是关键。

所以,即便组件用了React.memo,但组件内部使用的hooks依赖项是引用类型的值,再不用使用上述缓存hooks的方法的情况下,即便引用类型未发生改变,仍然会重新执行该hooks里面的代码。

why?因为在上面讲过,React使用Object.is进行比较,对于引用类型的值来说,比如[] === [] ,肯定是false。所以,如果在dependencies里面添加的依赖是引用类型,则每次都会执行。所以,这就是useMemo和useCallback的用武之地了,它们就是为了在这种场景下发挥作用而被创造出来的,去缓存引用类型的值。

useCallback缓存函数,useMemo缓存值,useRef也是缓存。

需要注意的是,没必要一开始就使用useMemo,使用不当反而会对性能造成耗费。

  • useContext

为了防止prop drilling而创造。相对于一层层向下传递数据,它是“退一步”把数据挂在React应用之上,可以跨组件共享数据状态,在某些场景下颇为适用,比如UI主题、语言,鉴权等。 在useContext出来之前,如果要在组件内部引用全局context的数据,需要一些繁琐步骤,此处省略100字... 在没有hooks之前,函数组件通常只能接收props,渲染UI,并不能够承担更多任务。但With Hooks we can manage everything we had used class-based components for.

    //定义全局context
    const colors = {
        blue: "#03619c",
        yellow: "#8c8f03",
        red: "#9c0312"
    };
    export const ColorContext = React.createContext(colors.blue);

引入:

    import { ColorContext } from "./ColorContext";
    function App() {
        return (
            <ColorContext.Provider value={colors}>
            <Home />
            </ColorContext.Provider>
        );
    }

在组件内部使用

    import React, { useContext } from "react";
    const MyComponent = () => {
        const colors = useContext(ColorContext);
        return <div style={{ backgroundColor: colors.blue }}>...</div>;
    };
  • useReducer

useReducer接收三个参数:reducer, initialState, lazyInitFunc(可选参数) 第三个可选参数是个函数,它接收initialState作为参数。这个方法提供了更改 initialState的途径,比如在不同情况/场景/条件下动态计算一个初始值,该计算后的值将覆盖initialState。

const people = [
    {name: 'Jay', alive: true},
    {name: 'Kailee', alive: true},
    {name: 'John', alive: true},
    {name: 'Mia', alive: true}
  ]

const reducer = (people, action) => {
    if(action.type == 'chomp') {
        return people.map(person => {
            if(person.name == action.payload) {
                person.alive = false;
            }
            return person;
        })
    }
    if(action.type == 'revive') {
        return people.map(person => {
            if(person.name == action.payload) {
                person.alive = true;
            }
            return person;
        })
    }
}

const deadPeople = () => ([
    { name: 'Jay', alive: false },
    { name: 'Kailee', alive: false },
    { name: 'John', alive: false },
    { name: 'Mia', alive: false }
])

function devour(name) {
    dispatch({ type: 'chomp', payload: name });
}

function spitOut(name) {
    dispatch({ type: 'revive', payload: name });
}

function App() {
    const [state, dispatch] = useReducer(reducer, people, deadPeople);
    return (
        <div>
            {state.map((person, idx) => (
                <div key={idx} style={{display: 'flex', width: '50%', justifyContent: 'space-around'}}>
                    <div>{person.name}</div>
                    {person.alive ?
                    <div> ✨✨ ALIVE! ✨✨ <button onClick={() => devour(person.name)}> 🐊 DEVOUR 🐊 </button> </div> :
                    <div> ☠ ☠ DEAD ☠ ☠ <button onClick={() => spitOut(person.name)}> 🥵 SPIT OUT 🥵 </button> </div>}
                </div>
            ))}
        </div>
    )
}

提一嘴,useState其实就是useReducer的语法糖.

参考来源: alligator.io/react/useme… alligator.io/react/useco… alligator.io/react/usere…