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>
我正在使用 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>