认识Hook

一、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) 。

注意

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容