登高远望 發表於 2021-4-28 00:11:00

React Hook用法详解(6个常见hook)

<h2>1、useState:让函数式组件拥有状态</h2>
<p>用法示例:</p>
<pre><code>// 计数器
import { useState } from 'react'
const Test = () =&gt; {
    const = useState(0);
    return (
      &lt;&gt;
            &lt;h1&gt;点击了{count}次&lt;/h1&gt;
            &lt;button onClick={() =&gt; setCount(count + 1)}&gt;+1&lt;/button&gt;
      &lt;/&gt;
    );
}
export default Test
</code></pre>
<p>PS:class组件中this.setState更新是state是合并, useState中setState是替换。例如:</p>
<pre><code>// 错误示例
import { useState } from 'react'
const Test = () =&gt; {
    const = useState({
      num1: 0,
      num2: 0
    });
    return (
      &lt;&gt;
            &lt;h1&gt;num1:{counts.num1}&lt;/h1&gt;
            &lt;h1&gt;num2:{counts.num2}&lt;/h1&gt;
            &lt;button onClick={() =&gt; setCounts({ num1: counts.num1 + 1})}&gt;num1+1&lt;/button&gt;
            &lt;button onClick={() =&gt; setCounts({ num2: counts.num2 + 1})}&gt;num2+1&lt;/button&gt;
      &lt;/&gt;
    );
}
export default Test
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210427204853443-215314223.gif" alt="" loading="lazy"><br>
可以看到useState中setState是替换,不会合并,正确更新:</p>
<pre><code>import { useState } from 'react'
const Test = () =&gt; {
    const = useState({
      num1: 0,
      num2: 0
    });
    return (
      &lt;&gt;
            &lt;h1&gt;num1:{counts.num1}&lt;/h1&gt;
            &lt;h1&gt;num2:{counts.num2}&lt;/h1&gt;
            &lt;button onClick={() =&gt; setCounts({ ...counts, num1: counts.num1 + 1})}&gt;num1+1&lt;/button&gt;
            &lt;button onClick={() =&gt; setCounts({ ...counts, num2: counts.num2 + 1})}&gt;num2+1&lt;/button&gt;
      &lt;/&gt;
    );
}
export default Test
</code></pre>
<h2>2、useEffect:副作用,取代生命周期</h2>
<p>用法示例,在class组件中如果需要在组件挂载后和数据更新后做同一件事,我们会这样做:</p>
<pre><code>componentDidMount() {
    // 做一些事
}
componentDidUpdate() {
    // 做一些事
}
</code></pre>
<p>可以看出来,如果逻辑复杂后,代码看起来不优雅,且容易造成逻辑混乱,而使用useEffect:</p>
<pre><code>useEffect(() =&gt; {
    // 做一些事
});
</code></pre>
<p>此刻已经看到了useEffect的基本用法,除此之外,他还可以绑定触发更新的依赖状态,默认是状态中任何数据发生变化副作用都会执行,如:</p>
<pre><code>import { useState, useEffect } from 'react'
const Test = () =&gt; {
    const = useState(0);
    const = useState(0);
    useEffect(() =&gt; {
      console.log('useEffect触发了')
    });
    return (
      &lt;&gt;
            &lt;h1&gt;count1:{count1}&lt;/h1&gt;
            &lt;h1&gt;count2:{count2}&lt;/h1&gt;
            &lt;button onClick={() =&gt; setCount1(count1 + 1)}&gt;count1+1&lt;/button&gt;
            &lt;button onClick={() =&gt; setCount2(count2 + 1)}&gt;count2+1&lt;/button&gt;
      &lt;/&gt;
    );
}
export default Test
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210427212414726-855122189.gif" alt="" loading="lazy"><br>
将上述代码useEffect第二个参数传入需要绑定的状态,可绑定多个:</p>
<pre><code>// 语法:useEffect(回调函数,[依赖值])
useEffect(() =&gt; {
    console.log('useEffect触发了')
}, );
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210427213003337-34215221.gif" alt="" loading="lazy"><br>
可以看到,只有绑定的count1发生变化才会触发,如果传空数组则任何状态发生变化都不会触发,此时useEffect的作用就类似class组件中的componentDidMount,所以发送请求通常也会在此执行。</p>
<p><strong>清理副作用</strong><br>
在上面的操作中都不用清理的副作用,然而,有些副作用是需要去清理的,不清理会造成异常甚至内存泄漏,比如开启定时器,如果不清理,则会多次开启,从上面可以看到useEffect的第一个参数是一个回调函数,可以在回调函数中再返回一个函数,该函数可以在状态更新后第一个回调函数执行之前调用,具体实现:</p>
<pre><code>useEffect(() =&gt; {
    // 设置副作用
    return () =&gt; {
      // 清理副作用
    }
});
</code></pre>
<h2>3、useContext:跨组件共享数据</h2>
<p>React.createContext();创建一个TestContext对象<br>
TestContext.Provider包裹子组件<br>
数据放在&lt;TestContext.Provider value={value}&gt;的value中<br>
子组件中通过useContext(TestContext)获取值</p>
<pre><code>import React, { useContext, useState } from 'react';
const TestContext = React.createContext();
const Parent = () =&gt; {
    const = useState(0);
    return (
      &lt;div&gt;
            {(() =&gt; console.log("Parent-render"))()}
            &lt;button onClick={() =&gt; setValue(value + 1)}&gt;value + 1&lt;/button&gt;
            &lt;TestContext.Provider value={value}&gt;
                &lt;Child1 /&gt;
                &lt;Child2 /&gt;
            &lt;/TestContext.Provider&gt;
      &lt;/div&gt;
    );
}
const Child1 = () =&gt; {
    const value = useContext(TestContext);
    return (
      &lt;div&gt;
            {(() =&gt; console.log('Child1-render'))()}
            &lt;h3&gt;Child1-value: {value}&lt;/h3&gt;
      &lt;/div&gt;
    );
}
const Child2 = () =&gt; {
    return (
      &lt;div&gt;
            {(() =&gt; console.log('Child2-render'))()}
            &lt;h3&gt;Child2&lt;/h3&gt;
      &lt;/div&gt;
    );
}
export default Parent
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210427224021140-593823436.gif" alt="" loading="lazy"><br>
至此数据实现共享了,但是可以看到在TestContext中的共享数据只要发生变化,子组件都会重新渲染,Child2并没有绑定数据,不希望他做无意义的渲染,可以使用React.memo解决,实现:</p>
<pre><code>const Child2 = React.memo(() =&gt; {
    return (
      &lt;div&gt;
            {(() =&gt; console.log('Child2-render'))()}
            &lt;h3&gt;Child2&lt;/h3&gt;
      &lt;/div&gt;
    );
});
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210427224812030-823286841.gif" alt="" loading="lazy"></p>
<h2>4、useCallback:性能优化</h2>
<p>语法:</p>
<pre><code>// useCallback(回调函数,[依赖值])
const handleClick = useCallback(()=&gt; {
    // 做一些事
}, );
</code></pre>
<p>useCallback返回的是一个 memoized(缓存)函数,在依赖不变的情况下,多次定义的时候,返回的值是相同的,他的实现原理是当使用一组参数初次调用函数时,会缓存参数和计算结果,当再次使用相同的参数调用该函数时,会直接返回相应的缓存结果。</p>
<p>优化性能例子:</p>
<pre><code>import React, { useState, useCallback, memo } from 'react';
const Parent = () =&gt; {
    const = useState(0);
    const = useState(0);
    const handleClick1 = useCallback(()=&gt; {
      setValue1(value1 + 1);
    }, );
    const handleClick2 = useCallback(()=&gt; {
      setValue2(value2 + 1);
    }, );
    return (
      &lt;&gt;
            {(() =&gt; console.log("Parent-render"))()}
            &lt;h3&gt;{value1}&lt;/h3&gt;
            &lt;h3&gt;{value2}&lt;/h3&gt;
            &lt;Child1 handleClick1={handleClick1} /&gt;
            &lt;Child2 handleClick2={handleClick2} /&gt;
      &lt;/&gt;
    );
}
const Child1 = memo(props =&gt; {
    return (
      &lt;div&gt;
            {(() =&gt; console.log("Child1-render"))()}
            &lt;button onClick={() =&gt; props.handleClick1()}&gt;value1 + 1&lt;/button&gt;
      &lt;/div&gt;
    );
});
const Child2 = memo(props =&gt; {
    return (
      &lt;div&gt;
            {(() =&gt; console.log("Child2-render"))()}
            &lt;button onClick={() =&gt; props.handleClick2()}&gt;value2 + 1&lt;/button&gt;
      &lt;/div&gt;
    );
});
export default Parent
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210427233111019-855276018.gif" alt="" loading="lazy"></p>
<p>useCallback返回的是一个memoized回调函数,仅在其中绑定的一个依赖项变化后才更改可防止不必要的渲染,在跨组件共享数据中举例的事件是在父组件中点击触发,而现在是使用状态提升,在父组件中传递方法供子组件调用,每次render时函数也会变化,导致子组件重新渲染,上面例子useCallback将函数进行包裹,依赖值未发生变化时会返回缓存的函数,配合React.memo即可优化无意义的渲染。</p>
<h2>5、useMemo:性能优化</h2>
<p>语法:</p>
<pre><code>// useMemo(回调函数,[依赖值])
useMemo(() =&gt; {
    // 做一些事情
},);
</code></pre>
<p>先看一个例子:</p>
<pre><code>import React, { useState } from 'react'
const Test = ()=&gt; {
    const = useState(0);
    const = useState(1);
    const getDoubleCount = () =&gt; {
      console.log('getDoubleCount进行计算了');
      return count * 2;
    };
    return (
      &lt;div&gt;
            &lt;h2&gt;value: {value}&lt;/h2&gt;
            &lt;h2&gt;doubleCount: {getDoubleCount()}&lt;/h2&gt;
            &lt;button onClick={() =&gt; setValue(value + 1)}&gt;value+1&lt;/button&gt;
      &lt;/div&gt;
    )
}
export default Test
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210428091727226-1409729091.gif" alt="" loading="lazy"></p>
<p>可以看到getDoubleCount依赖的是count,但value发生变化它也重新进行了计算渲染,现在只需要将getDoubleCount使用useMemo进行包裹,如下:</p>
<pre><code>import React, { useState, useMemo } from 'react'
const Test = ()=&gt; {
    const = useState(0);
    const = useState(1);
    const getDoubleCount = useMemo(() =&gt; {
      console.log('getDoubleCount进行计算了');
      return count * 2;
    },);
    return (
      &lt;div&gt;
            &lt;h2&gt;value: {value}&lt;/h2&gt;
            &lt;h2&gt;doubleCount: {getDoubleCount}&lt;/h2&gt;
            &lt;button onClick={() =&gt; setValue(value + 1)}&gt;value+1&lt;/button&gt;
      &lt;/div&gt;
    )
}
export default Test
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210428092308175-1888719042.gif" alt="" loading="lazy"><br>
现在getDoubleCount只有依赖的count发生变化时才会重新计算渲染。</p>
<p><strong>useMemo和useCallback的共同点:</strong></p>
<ul>
<li>接收的参数都是一样的,第一个是回调函数,第二个是依赖的数据</li>
<li>它们都是当依赖的数据发生变化时才会重新计算结果,起到了缓存作用</li>
</ul>
<p><strong>useMemo和useCallback的区别:</strong></p>
<ul>
<li>useMemo计算结果是return回来的值,通常用于缓存计算结果的值</li>
<li>useCallback计算结果是一个函数,通常用于缓存函数</li>
</ul>
<h2>6、useRef</h2>
用法:例如要实现点击button按钮使input输入框获得焦点:
<pre><code>import React, { useRef } from 'react';
const Test = () =&gt; {
    const inputEl = useRef();
    return (
      &lt;&gt;
            &lt;input ref={inputEl} /&gt;
            &lt;button onClick={() =&gt; inputEl.current.focus()}&gt;focus&lt;/button&gt;
      &lt;/&gt;
    );
}
export default Test
</code></pre>
<p>这样看起来非常像React.createRef(),将上面代码中的useRef()改成React.createRef()也能实现同样的效果,那为什么要设计一个新的hook?难道只是为了加上use,统一hook规范?<br>
事实上,它们确实不一样。</p>
<p>官网的说明如下:</p>
<pre><code>useRef returns a mutable ref object whose .current property is initialized to the passed
argument (initialValue). The returned object will persist for the full lifetime of the component.
</code></pre>
<p>翻译:<br>
<img src="https://img2020.cnblogs.com/blog/2208352/202104/2208352-20210428000248545-1748296822.png" alt="" loading="lazy"></p>
<p>简单来说,useRef就像一个储物箱,你可以随意存放任何东西,再次渲染时它会去储物箱找,createRef每次渲染都会返回一个新的引用,而useRef每次都会返回相同的引用。</p><br><br>
来源:https://www.cnblogs.com/hymenhan/p/14711516.html
頁: [1]
查看完整版本: React Hook用法详解(6个常见hook)