美文网首页
认识Hook

认识Hook

作者: 奔跑的大橙子 | 来源:发表于2023-10-06 08:49 被阅读0次

    一、useState和useEffect

    1. 什么是Hook

    Hook 是一个特殊的函数,它可以让你“钩⼊” React 的特性。例如,useState 是允许你在 React 函数组件中添加state的Hook。

    什么时候我会用 Hook? 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其转化为 class组件。现在你可以在现有的函数组件中使⽤Hook。

    import React, { useState } from 'react'

    export default function HookPage(props) {

        // 声明一个叫“count”的state变量,初始化为0

        const [count, setCount] = useState(0)

        return (

            <div>

                <h3>HookPage</h3>

                <p>{count}</p>

                <button onClick={() => setCount(count+1)}>add</button>

            </div>

        )

    }

    2. 什么是副作用

    Effect Hook 可以让你在函数组件中执行副作用操作。

    数据获取,设置订阅以及⼿动更改 React 组件中的 DOM 都属于副作用。不管你知不知道这些操作或是“副作⽤”这个名字,应该都在组件中使⽤过它们。

    import React, { useState, useEffect } from 'react'

    export default function HookPage(props) {

        // 声明一个叫“count”的state变量,初始化为0

        const [count, setCount] = useState(0)

        // useEffect相当于componentDidMount、componentDidUpdate、componentWillUnmount的集合

        useEffect(() => {

            console.log("effect count");

            // 只需要在count发生改变的时候执行

            document.title = `点击了${count}次`

        }, [count]);

        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 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执⾏

    effect 的条件执⾏

    默认情况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发⽣变化,它就会被重新创建。

    如果我们不需要在每次组件更新时创建新的订阅,⽽是仅需要在 source props 改变时重新创建。 要实现这⼀点,可以给 useEffect 传递第⼆个参数,它是 effect 所依赖的值数组。更新后的示例如下:

    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())

        // useEffect相当于componentDidMount、componentDidUpdate、componentWillUnmount的集合

        useEffect(() => {

            console.log("effect count");

            // 只需要在count发生改变的时候执行

            document.title = `点击了${count}次`

        }, [count]);

        useEffect(() => {

            console.log("effect date");

            // 只需要在didMount的时候执行

            const timer = setInterval(() => {

                setDate(new Date())

            }, 1000);

            // 清除定时器,类似willUnmount

            return () => clearInterval(timer)

        }, []);

        return (

            <div>

                <h3>HookPage</h3>

                <p>{count}</p>

                <button onClick={() => setCount(count+1)}>add</button>

                <p>{date.toLocaleTimeString()}</p>

            </div>

        )

    }

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

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

    二、自定义Hook

    有时候我们会想要在组件之间重⽤一些状态逻辑。目前为止,有两种主流方案来解决这个问题:⾼阶组件和 render props。自定义 Hook 可以让你在不增加组件的情况下达到同样的⽬的。

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

    import React, { useState, useEffect } from 'react'

    export default function HookPage(props) {

        const [count, setCount] = useState(0)

        // 错误写法

        // if(count) {

        //    const [num, setNum] = useState(0)

        // }

        useEffect(() => {

            console.log("effect count");

            // 只需要在count发生改变的时候执行

            document.title = `点击了${count}次`

        }, [count]);

        return (

            <div>

                <h3>HookPage</h3>

                <p>{count}</p>

                <button onClick={() => setCount(count+1)}>add</button>

                <p>{useClock().toLocaleTimeString()}</p>

            </div>

        )

    }

    // 错误写法

    // function getNum() {

    //    const [num, setNum] = useState(0)

    //    return num;

    // }

    // 自定义个hook,命名要以use开头

    function useClock() {

        const [date, setDate] = useState(new Date())

        useEffect(() => {

            console.log("effect date");

            // 只需要在didMount的时候执行

            const timer = setInterval(() => {

                setDate(new Date())

            }, 1000);

            // 清除定时器,类似willUnmount

            return () => clearInterval(timer)

        }, []);

        return date

    }

    Hook 使⽤规则

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

    只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

    只能在React 的函数组件中调用 Hook,不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是⾃定义的 Hook 中)

    三、useMemo和useCallback

    1. useMemo

    “创建”函数依赖项数组 作为参数传入 useMemo ,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进⾏⾼开销的计算。

    import React, { useState, useMemo } from 'react'

    export default function UseMemoPage(props) {

        const [count, setCount] = useState(0);

        const [value, setValue] = useState("");

        // 当前计算只和count有关

        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>count: {count}</p>

                <p>expensive: {expensive}</p>

                <button onClick={() => setCount(count+1)}>add</button>

                <input value={value} onChange={(e) => setValue(e.target.value)} />

            </div>

        )

    }

    以上代码执行结果分析:

    1.点击add按钮的时候,count的值会增加,会重新渲染页面;

    2.改变输入框里面的内容,也会改变value的值,也会重新渲染页面;

    3.但是由于加了 useMemo ,所以只依赖于 count 值的变化,不依赖 value 的值变化;

    4. count值的变化的时候,会打印compute;

    5. value值的变化的时候,不会打印compute;

    2. 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: {count}</p>

                <button onClick={() => setCount(count+1)}>add</button>

                <input value={value} onChange={(e) => setValue(e.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())}>modify</button>

                </div>

            )

        }

    }

    以上代码执行结果分析:

    1.点击add按钮的时候,count的值会增加,会重新渲染页面;

    2.改变输入框里面的内容,也会改变value的值,也会重新渲染页面;

    3.但是由于加了 useCallback ,所以只依赖于 count 值的变化,不依赖 value 的值变化;

    4. count值的变化的时候,会打印 child render;

    5. value值的变化的时候,不会打印 child render;

    6.点击modify按钮的时候,打印返回的sum值,不会重新渲染页面

    注意,子组件Child一定要继承纯组件PureComponent,而不是Component,否则即使加了useCallback,当count值和value值发生变化时,还是会重新渲染页面。

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

    注意

    依赖项数组不会作为参数传给“创建”函数。虽然从概念上来说它表现为:所有“创建”函数中引用的值都应该出现在依赖项数组中。未来编译器会更加智能,届时自动创建数组将成为可能。

    相关文章

      网友评论

          本文标题:认识Hook

          本文链接:https://www.haomeiwen.com/subject/gqtcbdtx.html