useState 本身始终是默认值

useState always is default value in itself

我正在使用 useState 来管理 状态 ,它工作正常。但是当 i return state inside itseft 时,它总是 initial

的值
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是输入值,但实际输出始终是初始值

首先,useState 参数中的函数不知道更新,因为它只被调用一次并且具有来自其闭包的值。其次,您使用 useState 的方式不正确,您必须只拥有 useState 中的值,并将处理程序置于外部

另外你必须使用回调模式

import react, {useState} from 'react'

const MyComponent = () => {
    const [myState, setMyState] = useState('initial value');   
    const setValue = (newValue) => {
         setMyState(newValue)
    }
    console.log(myState);
    return(
        <>
            <p>{myState}</p> 
            <input value={myState} onChange={(e) => setValue(e.target.value)} />

        </>
    )
}

此外,状态更新是异步的,因此不会在下一次渲染后立即反映更新

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

您的组件函数第一次是 运行 时,setValue 函数捕获 myState 初始 值。第二次是 运行,你复制了 setValue 函数——但是这个函数已经捕获了 [=16] 的 initial 值=].它永远不会更新。

因为函数永远不会改变,所以你不应该首先把它放在 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 所提到的,在这种情况下有一种更快更好的方法:使用 functional form of 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>
)

更好的方法是

import react, {useState} from 'react'

const MyComponent = () => {
    const [ value, setValue ] = useState('initial value');
    const handleChange = (e) => {
        setValue(e.target.value);
    }

    return(
        <>
            <p>{myState}</p>
            <input value={myState.value} onChange={handleChange} />
        </>
    )
}

解释了您遇到问题的原因。但这里还有一件事要解决:

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

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

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

这不是你用钩子做的。将对象作为状态值很好,但通常在状态更改时要更新(即替换)entire 对象时这样做。如果您正在更新状态的各个部分(如上文所建议的),您可以使用单独的 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>

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

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>