useState 本身始终是默认值

新手上路,请多包涵

我正在使用 useState 来管理 状态,它工作正常。但是当我在 itseft 内部返回状态时,它总是初始值

import react, {useState} from 'react'

const MyComponent = () => {
    const [myState, setMyState] = useState({
        value: 'initial value',
        setValue: (newValue) => {
            setMyState({...myState, value: newValue})
            console.log(myState.value) //<--always is 'initial value'
        }
    })

    return(
        <>
            <p>{myState.value}</p> //<-- it's working fine
            <input value={myState.value} onChange={(e) => myState.setValue(e.target.value)} /> //<--working fine too

        </>
    )
}

我希望 console.log 是输入值,但实际输出始终是初始值

原文由 mikenlanggio 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
2 个回答
const [myState, setMyState] = useState({
    value: 'initial value',
    setValue: (newValue) => {
        setMyState({...myState, value: newValue})
        console.log(myState.value) //<--always is 'initial value'
    }
})

第一次运行组件函数时, setValue 函数捕获 myState初始 值。第二次运行时,您复制了 setValue 函数——但这是捕获了 myState 初始 值的函数。它从不更新。

由于函数永远不会改变,所以您不应该首先将它放在 useState() 中。您可以单独定义函数。

 const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = (newValue) => {
  setMyState({ ...myState, value: newValue })
}

现在,每次运行组件函数时都会创建一个新的 setValue 副本。捕获变量时,可以使用 useCallback() 进行优化;如果值没有改变,React 将重用该函数的旧副本。

 const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = useCallback((newValue) => {
  setMyState({ ...myState, value: newValue })
}, [myState]) // ← this bit ensures that the value is always up to date inside the callback

正如 Shubham Khatri 所提到的,在这种情况下有一种更快更好的方法:使用 setState 的函数形式

 const [myState, setMyState] = useState({ value: 'initial value' })
const setValue = useCallback((newValue) => {
  setMyState((prev) => ({ ...prev, value: newValue }))
}, []) // ← now we use an empty array, so React will never update this callback

不过,这三种方法中的任何一种都可以使用;对于大多数用例,它们都可以正常工作并表现得足够好。


根据评论,您正在尝试创建一个通过上下文传递下来的对象。一种方法是在单独的步骤中创建上下文对象,类似于我们创建回调函数的方式。这一次,我们使用 useMemo ,它类似于 useCallback 但适用于任何类型的对象。

 // Per T.J. Crowder's answer, you can use separate `useState` calls if you need multiple values.
const [myState, setMyState] = useState('initial value')
const ctx = useMemo(() => ({
  value: myState,
  setValue: (newValue) => {
    setMyState(newValue)
  }
}), [myState])

return (
  <Provider value={ctx}>
    {children}
  </Provider>
)

原文由 goto-bus-stop 发布,翻译遵循 CC BY-SA 4.0 许可协议

goto-bus-stop 的回答 解释了您遇到问题的原因。但是这里还有一件事要解决:

从您提供的代码来看,您似乎正在使用一个对象作为您的状态值。特别是,这一行:

 setMyState({...myState, value: newValue})

..建议您打算 myState 包含多个内容,其中 value 只是其中之一。

这不是你用钩子做的。将一个对象作为状态值很好,但通常当您要在状态更改时更新(即替换) 整个 对象时这样做。如果您正在更新状态的各个部分(如上文所建议的),则使用单独的 useState 调用而不是对象。看评论:

 const {useState} = React;

const MyComponent = () => {
    // Separate `useState` calls for each state item
    const [value, setValue] = useState('initial value');
    const [anotherValue, setAnotherValue] = useState('another value');

    // No need to create your own update function, that's what `setValue` and
    // `setAnotherValue` are, just use them:

    return(
        <React.Fragment>
            <p>{value}</p>
            <input value={value} onChange={(e) => setValue(e.target.value)} />
            <p>{anotherValue}</p>
            <input value={anotherValue} onChange={(e) => setAnotherValue(e.target.value)} />
        </React.Fragment>
    );
}

ReactDOM.render(<MyComponent />, document.getElementById("root"));
 <div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

如果状态更改有任何副作用,这种分离特别有用,因为您可以指定触发副作用的状态。例如,上面的组件触发了 console.logvalue 发生变化时,而不是当 anotherValue 发生变化时 发生 的另一个效果:

 const {useState, useEffect} = React;

const MyComponent = () => {
    // Separate `useState` calls for each state item
    const [value, setValue] = useState('initial value');
    const [anotherValue, setAnotherValue] = useState('another value');
    // A counter for all changes; we use -1 because
    // our effect runs on initial mount
    const [changes, setChanges] = useState(-1);

    // No need to create your own update function, that's what `setValue` and
    // `setAnotherValue` are, just use them:

    // A side-effect for when `value` changes:
    useEffect(() => {
        console.log(`value changed to ${value}`);
    }, [value]); // <=== Notice that we declare what triggers this effect

    // A side-effect for when *either* `value` or `anotherValue` changes:
    useEffect(() => {
        setChanges(changes + 1);
    }, [value, anotherValue]);

    return(
        <React.Fragment>
            <p>Total changes: {changes}</p>
            <p>{value}</p>
            <input value={value} onChange={(e) => setValue(e.target.value)} />
            <p>{anotherValue}</p>
            <input value={anotherValue} onChange={(e) => setAnotherValue(e.target.value)} />
        </React.Fragment>
    );
}

ReactDOM.render(<MyComponent />, document.getElementById("root"));
 <div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

原文由 T.J. Crowder 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏