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 的函数。在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。setState 函数用于更新 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> <div>
<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> <button onClick={() => { setCount(count + 1)}}>点我</button>
<span style="color: rgba(0, 128, 128, 1)">7</span> </div>
<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(() =><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> <div>
<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> <button onClick={() => { setCount(count + 1)}}>点我</button>
<span style="color: rgba(0, 128, 128, 1)"> 9</span> </div>
<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) =><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> <>
<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> <button onClick={handleClick}>+</button>
<span style="color: rgba(0, 128, 128, 1)">15</span> <button onClick={handleClickFn}>+</button>
<span style="color: rgba(0, 128, 128, 1)">16</span> </>
<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(() =><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(() =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">10</span> setCount((prevCount) =><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> <>
<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> <button onClick={handleClick}>+</button>
<span style="color: rgba(0, 128, 128, 1)">19</span> <button onClick={handleClickFn}>+</button>
<span style="color: rgba(0, 128, 128, 1)">20</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></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> <button onClick={onButtonClick}>{data.number}</button>
<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 = () => 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> <div>
<span style="color: rgba(0, 128, 128, 1)">15</span> <input type="text" value={name} onChange={e => setName(e.target.value)} />
<span style="color: rgba(0, 128, 128, 1)">16</span> <Child onButtonClick={addClick} data={data} />
<span style="color: rgba(0, 128, 128, 1)">17</span> </div>
<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> <button onClick={onButtonClick}>{data.number}</button>
<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(() => 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(() =><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> <div>
<span style="color: rgba(0, 128, 128, 1)">17</span> <input type="text" value={name} onChange={e => setName(e.target.value)} />
<span style="color: rgba(0, 128, 128, 1)">18</span> <Child onButtonClick={addClick} data={data} />
<span style="color: rgba(0, 128, 128, 1)">19</span> </div>
<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(() => 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]