【青训营】- React Hook出新手村之路 - P2√

240 阅读4分钟

useState:状态管理; 使用频率:★★★★★

const [state, setState] = useState(initialState)

initialState是初始值,返回结果是数组类型[state, setState],第一个元素就是state状态,第二个元素setState其实是一个方法,这个方法对state进行修改。

完整示例代码可以在Demo中查看

image.png

在父组件调用<OfficialExample initialCount={0} />的关键代码如下

function OfficialExample({ initialCount }) {
    // 注意:如果直接设置useState(0)的话,在setCount里需要对count进行类型转换,即String转成Number
    const [count, setCount] = useState(initialCount);
    return (
        <>
            <h2>Count: {count}</h2>
            <button onClick={() => setCount(initialCount)}>Reset</button>
            <button onClick={() => setCount((prevCount) => prevCount - 1)>-</button>
            <button onClick={() => setCount((prevCount) => prevCount + 1)>+</button>
        </>
    );
}

useEffect:副作用; 使用频率:★★★★★

useEffect(() => {effectFn(); return clearFn}, [...dependencies]) 第一个参数effectFn()是一个带有副作用逻辑的函数。它的里面可以有一个返回值clearFn,这个返回值在useEffect每次被执行的时候,都会执行上一次useEffect的返回函数clearFn,因此我们可以在clearFn里写一些清楚副作用的逻辑;

如果你没有return的话,尽管你在本页面点击链接跳到了另一个页面,effectFn这个函数仍会在后台执行,十分占用资源。

第二个参数[...dependencies]是一个数组类型的参数,可选。就是说如果我们不传第二个参数的话,useEffect每次执行的时候第一个参数effectFn都会被执行。

如果我们传入了第二个参数,但是第二个参数传入的是一个空数组,那么useEffect里的副作用函数只会被执行一次,也就是组件被挂载完成后它只会被执行一次,后面都不会去执行。

// timer只会被执行1次,启动setInterval只需要执行1次
// 如果没有return的话,跳转到其它页面时,setInterval仍在执行
useEffect(()=>{
    const timer = setInterval(() => {
        // 逻辑
    }, 1000);
    return () => {
        clearInterval(timer);
    };
}, [])

如果我们传入了第二个参数,且第二个参数有值,那么当值被改变一次,effectFn就会被执行一次。

// 只要x的值改变了,就会执行一次effectFn逻辑
useEffect(()=>{
    // effectFn逻辑
}, [x])

完整代码可以在Demo中查看

image.png

useContext:上下文;使用频率:★★★☆☆

const value = useContext(MyContext) 接受一个参数MyContext,是createContext(initVal)的返回值(initVal可以不填),在上层组件给MyContext.Provider配置value属性后,该组件就可以通过useContext获取对应的value。

const themes = {
    light: {foreground: "#000000",background: "#eeeeee"},
    dark: {foreground: "#ffffff",background: "#222222"},
}

const ThemeContext = createContext(themes.light);

function OfficialExample() {
    <ThemeContext.Provider value={themes.dark}>
        <ThemedButton />
    </ThemeContext.Provider>
}

function ThemedButton() {
    const theme = useContext(ThemeContext);
    return (
        <button style={{ background: theme.background, color: theme.foreground}}>
            I am styled by theme context!
        </button>
    )
}

完整代码可以在Demo中查看

image.png

useReducer:useState的代替方案;使用频率:★★☆☆☆

const [state, dispatch] = useReducer(reducer, initialCount, initFn)

相当于const [state, dispatch] = useReducer(reducer, initFn(initialCount))

如果你不了解reducer,还可以在学习Redux的时候复习巩固它。题外话:Redux例子

reducer的责任大概是下图这种感觉: Redux.jpg

完整代码可以在Demo中查看

image.png

useCallback:回调;使用频率:★★★★☆

useCallback(fn, [...dependencies])

它返回的是fn

关键代码

const [x, setX] = useState(1);
// 如果把依赖项x清空什么也不填只留一个[],那么y的值将永远是x的初始值(1)的计算结果。
const getY = useCallback(() => {
    return 2 * x + 1;
}, [x])

完整代码可以在Demo中查看

image.png

useMemo:相当于useCallback;使用频率:★★★★☆

useMemo(fn, [...dependencies])

它返回的是fn(),即函数执行的返回值。所以useMemo(() => fn, [deps])相当于useCallback(fn, [deps]),传入useMemo的() => fn这个函数的返回值是fn;而useCallback是传入什么就返回什么,它的返回值就是传入的fn,所以两者相当。

完整代码可以在Demo中查看

image.png

useRef:整个生命周期里保存可变值;使用频率:★★★☆☆

const refContainer = useRef(initialValue) 它可以很方便地保存任何可变值且在组件的整个生命周期内持续存在。

关键代码:

    function TextInputWithFocusBtn() {
        const inputEl = useRef(null);
        const onBtnClick = () => {
           // current指向已挂载到DOM上的文本输入元素
           inputEl.current.focus();
        };
        return (
            <>
                <input ref={inputEl} type="text" /> 
                <button onClick={onBtnClick}>Focus the input</button>
            </>
        )
    }

完整代码可以在Demo中查看

image.png

useImperativeHandle:使用ref时自定义暴露给父组件指定的属性;使用频率:★★☆☆☆

useImperativeHandle(ref, createHandle, [...dependencies])

它的使用伴随着useRef和forwardRef。

关键代码:

    function FancyInput(props, ref) {
        const x = props.deps;
        const inputRef = useRef();
        useImperativeHandle(
            ref, 
            () => ({
                show: () => {
                    inputRef.current.value = x + 1;
                }
            }),
            [x]
        );
        return <input ref={inputRef} placeholder={x} />;
    }
    
    function App() {
        const [x, setX] = useState(1);
        const ref = useRef(null);
        function callChild() {
            setX(x + 1);
            ref.current.show();
        }
        return (
            <>
                <FancyInput ref={ref} deps={x} />
                <button onClick={callChild}>x+1</button>
            </>
        )
    }

完整代码可以在Demo中查看

image.png

useLayoutEffect:和useEffect几乎相同,只有调用时机不同;使用频率:★☆☆☆☆

useLayoutEffect(() => {effectFn(); return clearFn}, [...dependencies])

useLayoutEffect是在Dom渲染前调用effectFn;而useEffect是在Dom渲染后调用effectFn,这就是调用时机不同。

完整代码可以在Demo中查看

image.png

image.png