useState本身始终是默认值

时间:2019-05-13 09:01:34

标签: javascript reactjs react-hooks

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

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

5 个答案:

答案 0 :(得分:3)

goto-bus-stop's answer解释了为什么遇到问题的原因。但是这里还有另外一件事要解决:

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

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>

如果状态更改有任何副作用,则这种分隔特别有用,因为您可以指定触发状态的状态。例如,这是上面的组件,当console.log发生变化时,它会触发value,而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>
doctest

答案 1 :(得分:1)

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

第一次运行组件函数时,setValue函数将捕获myState initial 值。第二次运行时,将复制setValue函数-但这是已捕获myState 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>
)

答案 2 :(得分:0)

首先,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)} />

        </>
    )
}

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

答案 3 :(得分:0)

一种更好的方法是

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} />
        </>
    )
}

答案 4 :(得分:-1)

在您的情况下,使用setTimeout应该可以正常工作:

setTimeout(()=>{
  console.log(myState.value)
}, 0)