为了账号安全,请及时绑定邮箱和手机立即绑定

React UseState - 使用先前的状态与不使用先前的状态

React UseState - 使用先前的状态与不使用先前的状态

白衣非少年 2022-01-13 15:48:35
我想知道下面两个示例之间有什么区别。在一个示例中,我使用先前的状态,而在另一个示例中,我直接使用当前值。他们都给了我相同的结果。在哪些情况下我应该使用一种方式而不是另一种方式?提前致谢。import React,{useState} from "react";import ReactDOM from "react-dom";import "./styles.css";function App() {  const [count, setCount] = useState(0);  const [count2, setCount2] = useState(0);  return (    <div className="App">      Count: {count}      <button onClick={() => setCount(0)}>Reset</button>      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>      <br/>      <br/>      Count: {count2}      <button onClick={() => setCount2(0)}>Reset</button>      <button onClick={() => setCount2(count2 - 1)}>-</button>      <button onClick={() => setCount2(count2 + 1)}>+</button>    </div>  );}const rootElement = document.getElementById("root");ReactDOM.render(<App />, rootElement);
查看完整描述

2 回答

?
慕无忌1623718

TA贡献1744条经验 获得超4个赞

对于您的示例,没有区别。但在某些情况下,这很重要。


const [val, setVal] = useState(0);


return (<div onClick={() => setVal(val + 1)}>

 <span onClick={() => setVal(val + 1)}>{val}</span>

</div>);

每次点击只会将值增加 1 (0 -> 1 -> 2 -> 3)。现场示例:

const {useState} = React;


function Example() {

  const [val, setVal] = useState(0);


  return (<div onClick={() => setVal(val + 1)}>

   <span onClick={() => setVal(val + 1)}>{val}</span>

  </div>);

}


ReactDOM.render(<Example/>, document.getElementById("root"));

<div id="root"></div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>


const [val, setVal] = useState(0);


return (<div onClick={() => setVal(oldVal => oldVal + 1)}>

 <span onClick={() => setVal(oldVal => oldVal + 1)}>{val}</span>

</div>);

每次点击将增加 2 个值(0 -> 2 -> 4 -> 6)。现场示例:

const {useState} = React;


function Example() {

  const [val, setVal] = useState(0);


  return (<div onClick={() => setVal(oldVal => oldVal + 1)}>

   <span onClick={() => setVal(oldVal => oldVal + 1)}>{val}</span>

  </div>);

}


ReactDOM.render(<Example/>, document.getElementById("root"));

<div id="root"></div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>


查看完整回答
反对 回复 2022-01-13
?
HUWWW

TA贡献1874条经验 获得超12个赞

因为这些对状态设置器的调用在点击处理程序中,所以保证在处理另一次点击之前重新渲染您的组件。因此,在大多数情况下,您不必使用 setter 的回调版本,您可以直接使用现有状态。(即使在并发模式下。)(请注意,如果您在多个位置处理相同的单击 [例如,一个元素及其后代],并且您希望这两个处理程序都更新值,是另一回事- 有关示例,请参见skyboyer 的答案。)

并非所有事件都是如此(例如,mousemove 没有此保证),但对于单击却是正确的。

我在这个线程的推特上从 Dan Abramov 那里得到了这些信息。当时,像点击这样有这种保证的事件被称为“交互式”事件。此后,该名称已更改为“离散”事件。你可以在 React 代码的这个源文件中找到一个列表。

当然,并不是所有的状态变化都直接来自事件。假设您的代码中有一个单击处理程序,它连续执行几个 ajax 调用,并且它会更新您的值以响应完成每个调用。即使您尝试非常彻底,直接更新版本也会不正确useCallback;回调版本将是正确的:

const {useState, useCallback} = React;


function ajaxGet() {

    return new Promise(resolve => setTimeout(resolve, 10));

}


function Example() {

    const [directValue, setDirectValue] = useState(0);

    const [callbackValue, setCallbackValue] = useState(0);


    const doThis = useCallback(() => {

        setDirectValue(directValue + 1);

        setCallbackValue(callbackValue => callbackValue + 1);

    }, [directValue, callbackValue]);

    

    const doThat = useCallback(() => {

        setDirectValue(directValue + 1);

        setCallbackValue(callbackValue => callbackValue + 1);

    }, [directValue, callbackValue]);


    const handleFirstFulfilled = useCallback(() => {

        // ...

        doThis();

        // ...

        return ajaxGet("something else");

    }, [doThis]);

    

    const handleSecondFulfilled = useCallback(() => {

        // ...

        doThat();

        // ...

    }, [doThat]);

    

    const handleClick = useCallback(() => {

        ajaxGet("something")

        .then(handleFirstFulfilled)

        .then(handleSecondFulfilled)

        .catch(error => {

            // ...handle/report error...

        });

    }, [handleFirstFulfilled, handleSecondFulfilled]);


    const cls = directValue !== callbackValue ? "diff" : "";


    return (

        <div className={cls}>

          <input type="button" onClick={handleClick} value="Click Me" />

          <div>

          Direct: {directValue}

          </div>

          <div>

          Callback: {callbackValue}

          </div>

        </div>

    );

}


ReactDOM.render(<Example />, document.getElementById("root"));

.diff {

    color: #d00;

}

<div id="root"></div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

出于这个原因,每当我设置一个基于先前值的新值时,我都会使用回调版本,除非它是专用处理click程序或类似的处理程序,在这种情况下我可以直接使用。


回到事件,并发模式使非“离散”事件更容易叠加。在 cdnjs.com (v16.10.2) 上的当前版本的 React 中,我无法获得以下为 、 和 具有不同数字directValuecallbackValue内容manualValue

const {useState} = React;


// Obviously this is a hack that only works when `Example` is used only once on a page

let manualValue = 0;

const manualDisplay = document.getElementById("manualDisplay");

function Example() {

    const [directValue, setDirectValue] = useState(0);

    const [callbackValue, setCallbackValue] = useState(0);

    

    const handleMouseMove = () => {

        setDirectValue(directValue + 1);

        setCallbackValue(callbackValue => callbackValue + 1);

        manualDisplay.textContent = ++manualValue;

    };


    const different = directValue !== callbackValue || directValue !== manualValue;

    document.body.className = different ? "diff" : "";


    return (

        <div onMouseMove={handleMouseMove}>

          Move the mouse rapidly over this element.

          <div>

          Direct: {directValue}

          </div>

          <div>

          Callback: {callbackValue}

          </div>

        </div>

    );

}


const ex = <Example />;

if (ReactDOM.createRoot) {

    document.body.insertAdjacentHTML("beforeend", "<div>Concurrent</div>");

    ReactDOM.createRoot(document.getElementById("root")).render(ex);

} else {

    ReactDOM.render(ex, document.getElementById("root"));

    document.body.insertAdjacentHTML("beforeend", "<div>Legacy</div>");

}

.diff {

    color: #d00;

}

<div id="root"></div>

<div>

Manual: <span id="manualDisplay">0</span>

</div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>

也许这只是我没有在足够多的平台上进行测试,但我无法让它们在 React 的“传统模式”中出现分歧。但是,将相同的代码与具有并发模式的实验版本一起使用,通过在其上快速摆动鼠标,很容易使directValue滞后于它,这表明事件处理程序在渲染之间运行了不止一次。callbackValuemanualValue


查看完整回答
反对 回复 2022-01-13
  • 2 回答
  • 0 关注
  • 355 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号