我正在尝试学习钩子,而useState方法使我感到困惑。我正在将初始值分配给数组形式的状态。即使使用spread(...)
或without spread operator
,useState中的set方法对我也不起作用。
我在另一台PC上制作了一个API,我正在调用它并提取要设置为状态的数据。
这是我的代码:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const StateSelector = () => {
const initialValue = [
{
category: "",
photo: "",
description: "",
id: 0,
name: "",
rating: 0
}
];
const [movies, setMovies] = useState(initialValue);
useEffect(() => {
(async function() {
try {
//const response = await fetch(
//`http://192.168.1.164:5000/movies/display`
//);
//const json = await response.json();
//const result = json.data.result;
const result = [
{
category: "cat1",
description: "desc1",
id: "1546514491119",
name: "randomname2",
photo: null,
rating: "3"
},
{
category: "cat2",
description: "desc1",
id: "1546837819818",
name: "randomname1",
rating: "5"
}
];
console.log(result);
setMovies(result);
console.log(movies);
} catch (e) {
console.error(e);
}
})();
}, []);
return <p>hello</p>;
};
const rootElement = document.getElementById("root");
ReactDOM.render(<StateSelector />, rootElement);
setMovies(result)
和setMovies(...result)
无法正常工作。可以在这里使用一些帮助。预先感谢。
我希望将结果变量推入movies数组中。
答案 0 :(得分:17)
类似于通过扩展React.Component
或React.PureComponent
创建的Class组件中的setState,使用useState
钩子提供的更新程序的状态更新也是异步的,不会立即反映和更新,而是会触发重新渲染
setMovies(result);
console.log(movies) // movies here will not be updated
如果要对状态更新执行操作,则需要使用useEffect钩子,就像在类组件中使用componentDidUpdate
一样,因为useState返回的setter没有回调模式
useEffect(() => {
// action on update of movies
}, [movies]);
就更新状态的语法而言,setMovies(result)
将用异步请求中可用的值替换状态中先前的movies
值
但是,如果您想将响应与先前存在的值合并,则必须使用状态更新的回调语法以及正确使用诸如
这样的扩展语法的方法setMovies(prevMovies => ([...prevMovies, ...result]));
答案 1 :(得分:10)
previous answer的其他详细信息:
尽管React的setState
是异步的(类和钩子),并且很想用这个事实来解释观察到的行为,但这不是 原因为什么 发生。
TLDR:这是一个closure范围,围绕一个不变的const
值。
如果唯一的原因是异步,则await setState()
可以等待异步state
引起的setState()
变异。
但是,props
和state
均为assumed to be unchanging during 1 render。
对待
this.state
就像是一成不变的。
使用钩子,可以通过在关键字const
中使用常量值来增强此假设:
const [state, setState] = useState('initial')
两个渲染器之间的值可能有所不同,但在渲染器内部和任何closures内部都保持不变(即使渲染完成后,函数的寿命也更长,例如useEffect
,内部的事件处理程序任何Promise或setTimeout)。
考虑以下伪造但同步的类似React的实现:
// sync implementation:
let internalState
let renderAgain
const setState = (updateFn) => {
internalState = updateFn(internalState)
renderAgain()
}
const useState = (defaultState) => {
if (!internalState) {
internalState = defaultState
}
return [internalState, setState]
}
const render = (component, node) => {
const {html, handleClick} = component()
node.innerHTML = html
renderAgain = () => render(component, node)
return handleClick
}
// test:
const MyComponent = () => {
const [x, setX] = useState(1)
console.log('in render:', x) // ✅
const handleClick = () => {
setX(current => current + 1)
console.log('in handler/effect/Promise/setTimeout:', x) // ❌ NOT updated
}
return {
html: `<button>${x}</button>`,
handleClick
}
}
const triggerClick = render(MyComponent, document.getElementById('root'))
triggerClick()
triggerClick()
triggerClick()
<div id="root"></div>
答案 2 :(得分:3)
在@kentcdobs文章(请参阅下面的参考)之后,我刚刚完成了useReducer的重写,这确实给了我一个可靠的结果,这些问题一点儿也没有。
请参阅:https://kentcdodds.com/blog/how-to-use-react-context-effectively
我将他的可读样板简化为我喜欢的DRYness级别-阅读他的沙箱实现将向您展示其实际工作方式。
享受,我知道我!!
import React from 'react'
// ref: https://kentcdodds.com/blog/how-to-use-react-context-effectively
const ApplicationDispatch = React.createContext()
const ApplicationContext = React.createContext()
function stateReducer(state, action) {
if (state.hasOwnProperty(action.type)) {
return { ...state, [action.type]: state[action.type] = action.newValue };
}
throw new Error(`Unhandled action type: ${action.type}`);
}
const initialState = {
keyCode: '',
testCode: '',
testMode: false,
phoneNumber: '',
resultCode: null,
mobileInfo: '',
configName: '',
appConfig: {},
};
function DispatchProvider({ children }) {
const [state, dispatch] = React.useReducer(stateReducer, initialState);
return (
<ApplicationDispatch.Provider value={dispatch}>
<ApplicationContext.Provider value={state}>
{children}
</ApplicationContext.Provider>
</ApplicationDispatch.Provider>
)
}
function useDispatchable(stateName) {
const context = React.useContext(ApplicationContext);
const dispatch = React.useContext(ApplicationDispatch);
return [context[stateName], newValue => dispatch({ type: stateName, newValue })];
}
function useKeyCode() { return useDispatchable('keyCode'); }
function useTestCode() { return useDispatchable('testCode'); }
function useTestMode() { return useDispatchable('testMode'); }
function usePhoneNumber() { return useDispatchable('phoneNumber'); }
function useResultCode() { return useDispatchable('resultCode'); }
function useMobileInfo() { return useDispatchable('mobileInfo'); }
function useConfigName() { return useDispatchable('configName'); }
function useAppConfig() { return useDispatchable('appConfig'); }
export {
DispatchProvider,
useKeyCode,
useTestCode,
useTestMode,
usePhoneNumber,
useResultCode,
useMobileInfo,
useConfigName,
useAppConfig,
}
用法与此类似:
import { useHistory } from "react-router-dom";
// https://react-bootstrap.github.io/components/alerts
import { Container, Row } from 'react-bootstrap';
import { useAppConfig, useKeyCode, usePhoneNumber } from '../../ApplicationDispatchProvider';
import { ControlSet } from '../../components/control-set';
import { keypadClass } from '../../utils/style-utils';
import { MaskedEntry } from '../../components/masked-entry';
import { Messaging } from '../../components/messaging';
import { SimpleKeypad, HandleKeyPress, ALT_ID } from '../../components/simple-keypad';
export const AltIdPage = () => {
const history = useHistory();
const [keyCode, setKeyCode] = useKeyCode();
const [phoneNumber, setPhoneNumber] = usePhoneNumber();
const [appConfig, setAppConfig] = useAppConfig();
const keyPressed = btn => {
const maxLen = appConfig.phoneNumberEntry.entryLen;
const newValue = HandleKeyPress(btn, phoneNumber).slice(0, maxLen);
setPhoneNumber(newValue);
}
const doSubmit = () => {
history.push('s');
}
const disableBtns = phoneNumber.length < appConfig.phoneNumberEntry.entryLen;
return (
<Container fluid className="text-center">
<Row>
<Messaging {...{ msgColors: appConfig.pageColors, msgLines: appConfig.entryMsgs.altIdMsgs }} />
</Row>
<Row>
<MaskedEntry {...{ ...appConfig.phoneNumberEntry, entryColors: appConfig.pageColors, entryLine: phoneNumber }} />
</Row>
<Row>
<SimpleKeypad {...{ keyboardName: ALT_ID, themeName: appConfig.keyTheme, keyPressed, styleClass: keypadClass }} />
</Row>
<Row>
<ControlSet {...{ btnColors: appConfig.buttonColors, disabled: disableBtns, btns: [{ text: 'Submit', click: doSubmit }] }} />
</Row>
</Container>
);
};
AltIdPage.propTypes = {};
现在,所有页面上的所有内容都可以平稳地保存
很好!
感谢肯特!
答案 3 :(得分:2)
我觉得这很好, 而不是将状态(方法 1)定义为示例,
const initialValue = 1;
const [state,setState] = useState(initialValue)
试试这个方法(方法 2),
const [state = initialValue,setState] = useState()
这在不使用 useEffect 的情况下解决了重新渲染问题,因为我们不关心这种情况下的内部闭包方法。
附言如果您担心在任何用例中使用旧状态,则需要使用带有 useEffect 的 useState,因为它需要具有该状态,因此在这种情况下应使用方法 1。
答案 4 :(得分:1)
有很多很好的答案显示了如何修复您的代码,但是有一个 NPM 包可以让您通过更改 import
来修复它。它叫做react-useStateRef
就你而言:
import useState from 'react-usestateref'
const [movies, setMovies,moviesRef] = useState(initialValue);
....
useEffect(() => {
setMovies(...)
console.log(moviesRef.current) // it will have the last value
})
如你所见。使用此库可让您访问最新状态。
答案 5 :(得分:0)
useEffect具有其自己的状态/生命周期,只有在传递参数中的函数或效果销毁后,它才会更新。
对象和数组的散布或休息在useEffect内不起作用。
React.useEffect(() => {
console.log("effect");
(async () => {
try {
let result = await fetch("/query/countries");
const res = await result.json();
let result1 = await fetch("/query/projects");
const res1 = await result1.json();
let result11 = await fetch("/query/regions");
const res11 = await result11.json();
setData({
countries: res,
projects: res1,
regions: res11
});
} catch {}
})(data)
}, [setData])
# or use this
useEffect(() => {
(async () => {
try {
await Promise.all([
fetch("/query/countries").then((response) => response.json()),
fetch("/query/projects").then((response) => response.json()),
fetch("/query/regions").then((response) => response.json())
]).then(([country, project, region]) => {
// console.log(country, project, region);
setData({
countries: country,
projects: project,
regions: region
});
})
} catch {
console.log("data fetch error")
}
})()
}, [setData]);
答案 6 :(得分:0)
var [state,setState]=useState(defaultValue)
useEffect(()=>{ 变量更新状态 setState(currentState=>{ // 不通过获取更新的状态来改变状态 更新状态=当前状态 返回当前状态 }) alert(updateState) // 当前状态。 })
答案 7 :(得分:0)
使用后台计时器库它解决了我的问题 https://github.com/ocetnik/react-native-background-timer
const timeoutId = BackgroundTimer.setTimeout(() => { // this will be executed once after 1 seconds // even when app is the the background console.log('tac'); }, 1000);
答案 8 :(得分:0)
关闭不是唯一的原因。
基于useState
的源代码(简化如下)。在我看来,价值永远不会被立即分配。
当您调用 setValue
时,会发生更新操作排队的情况。在计划开始后,只有当您进入下一个渲染时,这些更新操作才会应用于该状态。
这意味着即使我们没有关闭问题,useState
的 React 版本也不会立即为您提供新值。新值甚至在下一次渲染之前都不存在。
function useState(initialState) {
let hook;
...
let baseState = hook.memoizedState;
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState); // setValue HERE
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hook.queue.pending);
hook.queue.pending = null;
}
hook.memoizedState = baseState;
return [baseState, dispatchAction.bind(null, hook.queue)];
}
function dispatchAction(queue, action) {
const update = {
action,
next: null
};
if (queue.pending === null) {
update.next = update;
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
isMount = false;
workInProgressHook = fiber.memoizedState;
schedule();
}
还有一篇文章以类似的方式解释了上述内容,https://dev.to/adamklein/we-don-t-know-how-react-state-hook-works-1lp8
答案 9 :(得分:-1)
我在 setState
钩子中发现了一个技巧。您不得使用旧变量。您必须创建新变量并将其传递给钩子。例如:
const [users, setUsers] = useState(['Ayşe', 'Fatma'])
useEffect(() => {
setUsers((oldUsers) => {
oldUsers.push(<div>Emir</div>)
oldUsers.push(<div>Buğra</div>)
oldUsers.push(<div>Emre</div>)
return oldUsers
})
}, [])
return (
<Fragment>
{users}
</Fragment>
)
您只会看到 Ayşe
和 Fatma
用户。因为您正在返回(或传递) oldUsers
变量。此变量与旧状态的引用具有相同的引用。您必须返回新创建的变量。 如果您传递相同的引用变量,则 Reactjs 不会更新状态。
const [users, setUsers] = useState(['Ayşe', 'Fatma'])
useEffect(() => {
setUsers((oldUsers) => {
const newUsers = [] // Create new array. This is so important.
// you must push every old item to our newly created array
oldUsers.map((user, index) => {
newUsers.push(user)
})
// NOTE: map() function is synchronous
newUsers.push(<div>Emir</div>)
newUsers.push(<div>Buğra</div>)
newUsers.push(<div>Emre</div>)
return newUsers
})
}, [])
return (
<Fragment>
{users}
</Fragment>
)
答案 10 :(得分:-8)
// replace
return <p>hello</p>;
// with
return <p>{JSON.stringify(movies)}</p>;
现在您应该看到,您的代码实际上有效。 console.log(movies)
不起作用。这是因为movies
指向旧状态。如果将console.log(movies)
移到useEffect
之外,就在退货正上方,您将看到更新的电影对象。