React状态State2

289 阅读2分钟

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」。

上一篇请看这里

快照

state可以看成是快照,只与本次渲染有关

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

上面代码onClick的事件处理函数调用了三次setNumber,那么最终点击结束<h1>{number}</h1>的值是什么?

答案是1.具体源码见这里

{
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
}

当执行这段代码时,其实是执行了

{
        setNumber(0 + 1);
        setNumber(0 + 1);
        setNumber(0 + 1);
 }

同一个函数执行了3次。

同理,因此下面的代码,完整版

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setTimeout(() => {
          alert(number);
        }, 3000);
      }}>+5</button>
    </>
  )
}

<h1>后面的number是5,但是alert出来的是0

传函数

setState除了传值,还可以传函数,函数的入参是之前的state值,比如下面setNumber里面改成函数。函数的执行时机在事件处理函数之后.

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber((n) => {
        console.log("setNumber callback");
        return n + 1;
        });
        setNumber(n => n + 1);
        setNumber(n => n + 1);
        console.log("event handler")
      }}>+3</button>
    </>
  )
}

onClick点击之后,number的值就变成了3,[完整源码],(codesandbox.io/s/sandpack-…)

先打印event handler,再打印setNumber callback.

因此可以把setState(x),看成setState(x=>n=>x)一种特殊的函数,之前的state的值根本没用。

  setNumber(number + 5);
  setNumber(n => n + 1);
  setNumber(42);

这里的number是42.

第一行,number变成5,第二行number变成6,第三行number直接赋值42.

批处理,为了提升性能,上面即使调用了3次setNumber,最终只会触发一次渲染。

在setState里面更新对象和数组,用纯函数方式。

位置

React组件的状态和在JSX里面的位置有关。

React把组件转成ReactElement。

因此,如果有一个条件判断,返回了2个同类型但是参数不同的组件,React也会把它认为是同一个组件的不同参数。

例如下面的例子,查看完整代码这里

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

这行{isFancy ? ( <Counter isFancy={true} /> ) : ( <Counter isFancy={false} /> )} 虽然貌似根据不同的情况,返回不同的组件,但是在react里面,由于三元运算符操作之后,还是返回<Counter/>,两次render之间,只是isFancy的变化,因此React就会认为是同一个组件,因为之前组件的状态会被保留。

如何避免这个情况

  1. 加关键字key。
{isPlayerA ? (
        <Counter key="Taylor" person="Taylor" />
 ) : (
        <Counter key="Sarah" person="Sarah" />
 )}
  1. 结构不同
{isPlayerA &&
  <Counter person="Taylor" />
 }
 {!isPlayerA &&
   <Counter person="Sarah" />
 }

组件不要定义在组件里面

例如下面,完整看这里

import { useState } from 'react';

export default function MyComponent() {
  const [counter, setCounter] = useState(0);

  function MyTextField() {
    const [text, setText] = useState('');

    return (
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
    );
  }

  return (
    <>
      <MyTextField />
      <button onClick={() => {
        setCounter(counter + 1)
      }}>Clicked {counter} times</button>
    </>
  );
}

MyTextField这个组件每次render都会被重置,因此无法保留状态。