思意有 發表於 2020-12-23 11:12:00

React函数式组件值之useState()

<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  react hooks 是 React 16.8 的新增特性。 它可以让我们在函数组件中使用 state 、生命周期以及其他 react 特性,而不仅限于 class 组件。react hooks 的出现,标示着 react 中不会在存在无状态组件了,只有类组件和函数组件。</span></p>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  状态是隐藏在组件中的信息,组件可以在父组件不知道的情况下修改其状态。相比类组件,函数组件足够简单,要使函数组件具有状态管理,可以useState() Hook。</span></p>
<h1><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">一、基础用法</span></h1>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  定义一个state,以及更新 state 的函数。在初始渲染期间,返回的状态&nbsp;(state) 与传入的第一个参数 (initialState) 值相同。setState&nbsp;函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: arial, helvetica, sans-serif; font-size: 16px"><span style="color: rgba(0, 128, 128, 1)">1</span> const =<span style="color: rgba(0, 0, 0, 1)"> useState(initialState);
</span><span style="color: rgba(0, 128, 128, 1)">2</span> setState(newState);</span></pre>
</div>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">例子:</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: arial, helvetica, sans-serif; font-size: 16px"><span style="color: rgba(0, 128, 128, 1)">1</span>   <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> App () {
</span><span style="color: rgba(0, 128, 128, 1)">2</span>   const [ count, setCount ] = useState(0<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">3</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)">4</span>       &lt;div&gt;
<span style="color: rgba(0, 128, 128, 1)">5</span> <span style="color: rgba(0, 0, 0, 1)">      点击次数: { count }
</span><span style="color: rgba(0, 128, 128, 1)">6</span>         &lt;button onClick={() =&gt; { setCount(count + 1)}}&gt;点我&lt;/button&gt;
<span style="color: rgba(0, 128, 128, 1)">7</span>       &lt;/div&gt;
<span style="color: rgba(0, 128, 128, 1)">8</span> <span style="color: rgba(0, 0, 0, 1)">      )
</span><span style="color: rgba(0, 128, 128, 1)">9</span>   }</span></pre>
</div>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  当我们在使用 useState 时,修改值时传入同样的值,组件不会重新渲染,这点和类组件setState保持一致。</span></p>
<h1><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">二、初始值与初始函数</span></h1>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  useState 支持我们在调用的时候直接传入一个值,来指定 state 的默认值,比如这样 useState(0), useState({ a: 1 }), useState([ 1, 2 ]),还支持我们传入一个函数,来通过逻辑计算出默认值,比如这样</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: arial, helvetica, sans-serif; font-size: 16px"><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> App (props) {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   const [ count, setCount ] = useState(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>       <span style="color: rgba(0, 0, 255, 1)">return</span> props.count || 0
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 0, 1)">    })
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>       &lt;div&gt;
<span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">      点击次数: { count }
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>         &lt;button onClick={() =&gt; { setCount(count + 1)}}&gt;点我&lt;/button&gt;
<span style="color: rgba(0, 128, 128, 1)"> 9</span>       &lt;/div&gt;
<span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)">      )
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   }</span></pre>
</div>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  useState 中的函数只会在初始化的时候执行一次。</span></p>
<h1><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">三、函数式更新</span></h1>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。下面的计数器组件示例展示了 setState 的两种用法:</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: arial, helvetica, sans-serif; font-size: 16px"><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Counter() {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   const = useState(0<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>   <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> handleClick() {
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   setCount(count + 1<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>   <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> handleClickFn() {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>   setCount((prevCount) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>       <span style="color: rgba(0, 0, 255, 1)">return</span> prevCount + 1
<span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 0, 1)">    })
</span><span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)">12</span>   &lt;&gt;
<span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)">      Count: {count}
</span><span style="color: rgba(0, 128, 128, 1)">14</span>       &lt;button onClick={handleClick}&gt;+&lt;/button&gt;
<span style="color: rgba(0, 128, 128, 1)">15</span>       &lt;button onClick={handleClickFn}&gt;+&lt;/button&gt;
<span style="color: rgba(0, 128, 128, 1)">16</span>   &lt;/&gt;
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">18</span> }</span></pre>
</div>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  handleClick和handleClickFn一个是通过一个新的 state 值更新,一个是通过函数式更新返回新的 state。现在这两种写法没有任何区别,但是如果是异步更新的话,区别就显现出来了。</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: arial, helvetica, sans-serif; font-size: 16px"><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Counter() {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   const = useState(0<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>   <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> handleClick() {
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   setTimeout(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span>       setCount(count + 1<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span>   }, 3000<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span>   <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> handleClickFn() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>   setTimeout(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">10</span>       setCount((prevCount) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">11</span>         <span style="color: rgba(0, 0, 255, 1)">return</span> prevCount + 1
<span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)">      })
</span><span style="color: rgba(0, 128, 128, 1)">13</span>   }, 3000<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">15</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)">16</span>   &lt;&gt;
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)">      Count: {count}
</span><span style="color: rgba(0, 128, 128, 1)">18</span>       &lt;button onClick={handleClick}&gt;+&lt;/button&gt;
<span style="color: rgba(0, 128, 128, 1)">19</span>       &lt;button onClick={handleClickFn}&gt;+&lt;/button&gt;
<span style="color: rgba(0, 128, 128, 1)">20</span>   &lt;/&gt;
<span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">22</span> }</span></pre>
</div>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  当设置为异步更新,点击按钮延迟到3s之后去调用setCount函数,当快速点击按钮时,也就是说在3s多次去触发更新,但是只有一次生效,因为 count 的值是没有变化的。而当使用函数式更新 state 的时候,这种问题就没有了,因为它可以获取之前的 state 值,也就是代码中的 prevCount 每次都是最新的值。</span></p>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  其实这个特点和类组件中 setState 类似,可以接收一个新的 state 值更新,也可以函数式更新。如果新的 state 需要通过使用先前的 state 计算得出,那么就要使用函数式更新。因为setState更新可能是异步,当你在事件绑定中操作 state 的时候,setState更新就是异步的。</span></p>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  一般操作state,因为涉及到 state 的状态合并,react 认为当你在事件绑定中操作 state 是非常频繁的,所以为了节约性能 react 会把多次 setState 进行合并为一次,最后在一次性的更新 state,而定时器里面操作 state 是不会把多次合并为一次更新的。</span></p>
<h1><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">四、使用优化</span></h1>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: arial, helvetica, sans-serif; font-size: 16px"><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Child({ onButtonClick, data }) {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   console.log('Child Render'<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   &lt;button onClick={onButtonClick}&gt;{data.number}&lt;/button&gt;
<span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> App() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>   const = useState(0<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">10</span>   const = useState('hello') <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 表单的值</span>
<span style="color: rgba(0, 128, 128, 1)">11</span>   const addClick = () =&gt; setNumber(number + 1<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">12</span>   const data =<span style="color: rgba(0, 0, 0, 1)"> { number }
</span><span style="color: rgba(0, 128, 128, 1)">13</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)">14</span>   &lt;div&gt;
<span style="color: rgba(0, 128, 128, 1)">15</span>       &lt;input type="text" value={name} onChange={e =&gt; setName(e.target.value)} /&gt;
<span style="color: rgba(0, 128, 128, 1)">16</span>       &lt;Child onButtonClick={addClick} data={data} /&gt;
<span style="color: rgba(0, 128, 128, 1)">17</span>   &lt;/div&gt;
<span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">19</span> }</span></pre>
</div>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  上述代码中,子组件引用了number相关数据,但是当name相关数据发生变化,也会重绘整个组件,子组件虽然没有任何变化,也会重绘。为了避免不必要的子组件的重渲染,需要使用useMemo和useCallback的Hook。</span></p>
<div class="cnblogs_code">
<pre><span style="font-family: arial, helvetica, sans-serif; font-size: 16px"><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> Child({ onButtonClick, data }) {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>   console.log('Child Render'<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   &lt;button onClick={onButtonClick}&gt;{data.number}&lt;/button&gt;
<span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> Child =<span style="color: rgba(0, 0, 0, 1)"> memo(Child)
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>
<span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> App() {
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   const = useState(0<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">12</span>   const = useState('hello') <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 表单的值</span>
<span style="color: rgba(0, 128, 128, 1)">13</span>   const addClick = useCallback(() =&gt; setNumber(number + 1<span style="color: rgba(0, 0, 0, 1)">), )
</span><span style="color: rgba(0, 128, 128, 1)">14</span>   const data = useMemo(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> ({ number }), )
</span><span style="color: rgba(0, 128, 128, 1)">15</span>   <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(0, 128, 128, 1)">16</span>   &lt;div&gt;
<span style="color: rgba(0, 128, 128, 1)">17</span>       &lt;input type="text" value={name} onChange={e =&gt; setName(e.target.value)} /&gt;
<span style="color: rgba(0, 128, 128, 1)">18</span>       &lt;Child onButtonClick={addClick} data={data} /&gt;
<span style="color: rgba(0, 128, 128, 1)">19</span>   &lt;/div&gt;
<span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">21</span> <span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 128, 1)">22</span>
<span style="color: rgba(0, 128, 128, 1)">23</span> export <span style="color: rgba(0, 0, 255, 1)">default</span> App;</span></pre>
</div>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。</span></p>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  useCallback返回一个 memoized 回调函数。useCallback(fn, deps) 相当于 useMemo(() =&gt; fn, deps)。</span></p>
<p><span style="font-family: arial, helvetica, sans-serif; font-size: 16px">  useCallback 和 useMemo 参数相同,第一个参数是函数,第二个参数是依赖项的数组。主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。</span></p><br><br>
来源:https://www.cnblogs.com/guanghe/p/14177473.html
頁: [1]
查看完整版本: React函数式组件值之useState()