「前端发动机」深刻 React hooks — useState

前言

React Hooks的基本用法,官方文档 已经很是详细。本文的目的,是想经过一个简单的例子详细分析一些使人疑惑的问题及其背后的缘由。这是系列的第一篇,主要讲解 useState。javascript

我的博客地址 🍹🍰 fe-codehtml

思考

一块儿来看看这个栗子。前端

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            // console.log(count);
            setCount(count + 1);
        }, 1000);
    }, []);

    return <h1>{count}</h1>;
}
复制代码

咱们指望,useEffect 只执行一次,且后续每隔 1s,count 自动 + 1。然而, 实际上 count 从 0 到 1 后,再没有变化,一直都是 1。难道是 setInterval 没执行?因而咱们很疑惑的加上了打印。java

image.png

事实是,setInterval 每次执行的时候,拿到的 count 都是 0。很天然的咱们会想到闭包,可是闭包能彻底解释这个现象吗。咱们稍加修改再看下这个例子。react

function Counter() {
    const [count, setCount] = useState(0);
    let num = 0;
    useEffect(() => {
        const id = setInterval(() => {
            // 经过 num 来给 count 提供值
            console.log(num);
            setCount(++num);
        }, 1000);
    }, []);

    return <h1>{count}</h1>;
}
复制代码

image.png

咱们能够看到,借助 num 这个中间变量,咱们能够获得想要的结果。可是,一样是闭包,为何 num 就能记住以前的值呢?其实问题出在 count 上,继续往下看:git

function Counter() {
    // ...
    console.log('我是 num', num);

    return <h1>{count}-----{num}</h1>;
}
复制代码

image.png

渲染的 num 和定时器中的 num 为何会不同呢?github

每次都是从新执行

到这里我想说的究竟是什么呢?咱们能够清晰看见渲染出的 num 和 setInterval 中的 num,是不一样的。这是由于在 React 中,对于函数式组件来说,每次更新都会从新执行一遍函数。也就是说,每次更新都会在当前做用域从新声明一个 let num = 0,因此,定时器中闭包引用的那个 num,和每次更新时渲染的 num,根本不是同一个。固然,咱们能够很轻易的把它们变成同一个。数组

let num = 0; // 将声明放到渲染组件外面
function Counter() {
    // ...
    return <h1>{count}-----{num}</h1>;
}
复制代码

嗯,说了这么多,跟 count 有什么关系呢?同理,正由于函数组件每次都会总体从新执行,那么 Hooks 固然也是这样。微信

function Counter() {
    const [count, setCount] = useState(0);
    // ...
}
复制代码

useState 应该理解为和普通的 javascript 函数同样,而不是 React 的什么黑魔法。函数组件更新的时候,useState 会从新执行,对应的,也会从新声明 [count, setCount] 这一组常量。只不过 React 对这个函数作了一些特殊处理。好比:首次执行时,会将 useState 的参数初始化给 count,而之后再次执行时,则会直接取上次 setCount (若是有调用) 赋过的值(React 经过某种方式保存起来的)。闭包

有了这个概念,就不难知道,定时器里的setCount(count + 1) ,这个 count 和每次更新从新声明的 count,也是彻底不一样的两个常量,只不过它们的值,可能会相等。

好比,咱们尝试把以前的 num,直接用 count 替代。

function Counter() {
    // 注意这里变成 let
    let [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            // 这种写法是很差的
            setCount(++count);
        }, 1000);
    }, []);
    console.log(count);
    return <h1>{count}</h1>;
}
复制代码

这时候不管是打印仍是页面表现都和你指望的同样,可是这违背了 React 的原则,并且也让程序变得更让人迷惑。也就致使你并不能清楚地知道:此时渲染的 count 和 setInterval 中的 count 已经不是同一个了。尽管他们的值是相等的。

固然,这种场景下 React 也提供了可行的方法,可以每次拿到 count 的最新值,就是给 setCount 传递一个回调函数。

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            // 注意:这里变成回调了
            setCount(count => count + 1);
        }, 1000);
    }, []);

    return <h1>{count}</h1>;
}
复制代码

执行图解

回过头再看看开始的例子:

function Counter() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        const id = setInterval(() => {
            setCount(count + 1);
        }, 1000);
    }, []);

    return <h1>{count}</h1>;
}
复制代码

image.png

小结

count 每次都被从新声明了,setInterval 由于 useEffect 设置了只执行一次的缘故,在第一次更新时闭包引用的 count 始终是 0,后续更新的 count 和它不要紧。

交流群

微信群:扫码回复加群。

mmqrcode1566432627920.png

后记

若是你看到了这里,且本文对你有一点帮助的话,但愿你能够动动小手支持一下做者,感谢🍻。文中若有不对之处,也欢迎你们指出,共勉。好了,又耽误你们的时间了,感谢阅读,下次再见!

感兴趣的同窗能够关注下个人公众号 前端发动机,好玩又有料。

相关文章
相关标签/搜索