React第十六张 重新认识HOOK

199 阅读3分钟

一、useEffect

我们可以在一个组件中 多次调用 useState useEffect ,它们是完全独立的。

数据获取,设置订阅、手动更改 React 组件中的 DOM 都属于副作用。

不管你是否知道这些操作,或 是“副作用”这个名字,应该都在组件中使用过它们。

// 使用 state、effect
import React, { useState, useEffect } from "react";

export default function HookPage(props) {

    // 声明⼀一个叫 “count” 的 state 变量量,初始化为0
    const [count, setCount] = useState(0);
    
    // 与 componentDidMount 和 componentDidUpdate相似 
    useEffect(() => {
        document.title = `You clicked ${count} times`; 
    });
    
    return <div>
        <h3>HookPage</h3>
        <p>{count}</p>
        <button onClick={() => setCount(count + 1)}>add</button>
    </div> 
}

在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产⽣莫名其妙的 bug 并破坏 UI 的一致性。

  • 使用 useEffect 完成副作用操作,赋值给useEffect的函数会在组件渲染到屏幕之后执行。

你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。

  • 默认情况下,effect 将在每轮渲染结束后执⾏

二、useEffect 的条件执⾏

此时,只有当 useEffect第⼆个参数组⾥里的数值 改变后才会重新创建订阅。

 
import React, { useState, useEffect } from "react";
export default function HookPage(props) {
  
        // 声明⼀一个叫 “count” 的 state 变量量,初始化为0 
        const [count, setCount] = useState(0);
        const [date, setDate] = useState(new Date());

        // 与 componentDidMount 和 componentDidUpdate相似 
        useEffect(() => {
            document.title = `You clicked ${count} times`; 
        }, [count]);
  
  	useEffect(() => {
    		const timer = setInterval(() => {setDate(new Date())}, 1000);
		}, []);
  
  	return <div>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>add</button> 
            <p>{date.toLocaleTimeString()}</p>
        </div>
}

三、useEffect 的清除

通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器器 ID 等资源。要实现这一点, useEffect函数需返回一个清除函数,以防⽌内存泄漏,清除函数会在组件卸载前执⾏。

useEffect(() => {
  const timer = setInterval(() => {
    setDate(new Date());
  }, 1000);
  
  return () => clearInterval(timer);
  
}, []);

四、自定义Hook

- 资源

1. ⾃定义Hook

2. Hook规则

有时候我们会想要在组件之间重⽤⼀些状态逻辑。

目前为⽌,有两种主流方案来解决这个问题:《⾼阶组件》和 《render+props》。

自定义 Hook 可以让你在不不增加组件的情况下达到同样的目的。

⾃定义 Hook 是⼀个函数,其名称以 “use” 开头,函数内部可以调⽤其他的 Hook

// 函数式组件
import React, { useState, useEffect, useMemo } from "react";
export default function CustomHookPage(props) { 
  
    //定义⼀一个叫count的state变量量,初始化为0
    const [count, setCount] = useState(0); 

    //和didMount、didUpdate类似
    useEffect(() => {
        document.title = `点击了${count}次`;
    }, [count]);

    return <div>
        <h3>⾃自定义Hook</h3>
        <p>{count}</p>
        <button onClick={() => setCount(count + 1)}>add</button>
        <p>{useClock().toLocaleTimeString()}</p>
    </div>
}


// 自定义hook,命名必须以use开头 
function useClock() {
    const [date, setDate] = useState(new Date());

    useEffect(() => { 
        //只需要在didMount时候执行就可以了
        const timer = setInterval(() => {setDate(new Date());}, 1000);  
        
        //清除定时器,类似willUnmount
        return () => clearInterval(timer);
    }, []);

    return date;
}

五、Hook 使用规则

本质: Hook 就是 JavaScript 函数,但是使⽤它们会有两个额外的规则:

1、只能在 函数最外层 调⽤ Hook。

不要在循环、条件判断 或者 子函数中调⽤。

2、只能在 函数组件 调用 Hook。

不要在其他 JavaScript 函数中调用。

(还有⼀个地⽅可 以调用 Hook —— 就是自定义的 Hook 中。)

六、API

官方文档

如果你刚开始接触 Hook,那么可能需要先查阅 Hook 概览。你也可以在 Hooks FAQ 章节中获取有用的信息。

    • useState
    • useEffect
    • useContext
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue``

七、useMemo

把“创建”函数和依赖项数组作为参数传⼊ useMemo ,它仅会在某个依赖项改变时才重新计算memoized 值。

这种优化有助于避免在每次渲染时都进⾏高开销的计算。

 
import React, { useState, useMemo } from "react";

export default function UseMemoPage(props) {
  const [count, setCount] = useState(0);
  const [value, setValue] = useState(""); 
  
  const expensive = useMemo(() => {
		console.log("compute");
		let sum = 0;
		for (let i = 0; i < count; i++) { sum += i; }
		return sum;
		//只有count变化,这⾥里里才重新执⾏行行
	}, [count]);
  
  return <div>
            <h3>UseMemoPage</h3>
            <p>expensive:{expensive}</p>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>add</button>
            <input value={value} onChange={event => setValue(event.target.value)} />
        </div>
}

八、useCallback

把内联回调函数及依赖项数组作为参数传⼊ useCallback ,它将返回该回调函数的 memoized 版本, 该回调函数仅在某个依赖项改变时才会更新。

当你把回调函数 传递给经过优化的并使用引⽤相等性 去避免非必要渲染 (例如 shouldComponentUpdate ) 子组件时 ,它将⾮常有用

import React, { useState, useCallback, PureComponent } from "react";

export default function UseCallbackPage(props) {
  const [count, setCount] = useState(0);
  const [value, setValue] = useState("");
  
  const addClick = useCallback(() => {
    let sum = 0;
    for (let i = 0; i < count; i++) { sum += i; }
    return sum;
  }, [count]);
 
  return <div>
            <h3>UseCallbackPage</h3>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>add</button>
            <input value={value} onChange={event => setValue(event.target.value)} /> 
            <Child addClick={addClick} />
	</div>
}


// 经过优化的并使用引⽤相等性去避免非必要渲染的子组件
class Child extends PureComponent {
  render() {
    console.log("child render"); 
    const { addClick } = this.props; 
    return <div>
            <h3>Child</h3>
            <button onClick={() => console.log(addClick())}>add</button>
    </div>
  } 
}

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

注意:

依赖项数组不会作为参数传给“创建”函数。

虽然从概念上来说它表现为:所有“创建”函数中引⽤的值都应该出现在依赖项数组中。

未来编译器器会更加智能,届时⾃动创建数组将成为可能。