Javascript 从React钩子读取状态

Javascript 从React钩子读取状态,javascript,reactjs,react-hooks,Javascript,Reactjs,React Hooks,我有以下代码 const initialState = { values: { email: '', password: '' }, touched: { email: false, password: false }, errors: false, isValid: false, isLoading: false, submitError: null }; func

我有以下代码

const initialState = {
    values: {
        email: '',
        password: ''
    },
    touched: {
        email: false,
        password: false
    },
    errors: false,
    isValid: false,
    isLoading: false,
    submitError: null
};

function SignIn(props) {
    const [state, setState] = useState(initialState);

    useEffect(() => {
        console.log(state);
    });

    const handleFieldChange = (field, value) => {
        const newState = state;

        newState.submitError = null;
        newState.touched[field] = true;
        newState.values[field] = value;

        newState.errors = true; // set to true just for test

        setState(newState);

    };

    const showEmailError = state.errors.email;


    return (
        <div>
        <TextField
            className={classes.textField}
            label="Email address"
            name="email"
            onChange={event => handleFieldChange('email', event.target.value)}
            type="text"
            value={state.email}
            variant="outlined"

        />

        {showEmailError && (
            <Typography
                className={classes.fieldError}
                variant="body2"
            >
                {state.errors.email[0]}
            </Typography>
        )}
        </div>
    )
    }
const initialState={
价值观:{
电子邮件:“”,
密码:“”
},
感动:{
电子邮件:false,
密码:false
},
错误:错误,
isValid:false,
孤岛加载:false,
错误:空
};
功能标志(道具){
常量[状态,设置状态]=使用状态(初始状态);
useffect(()=>{
console.log(状态);
});
常量handleFieldChange=(字段,值)=>{
const newState=状态;
newState.submitor=null;
newState.toucted[field]=true;
newState.values[字段]=值;
newState.errors=true;//仅为测试设置为true
设置状态(newState);
};
const showmailerror=state.errors.email;
返回(
handleFieldChange('email',event.target.value)}
type=“text”
值={state.email}
variant=“概述”
/>
{showmailerror&&(
{state.errors.email[0]}
)}
)
}

我遇到的问题是,当执行
handFieldChange
时,不会出现电子邮件错误。好像我没有在我的返回声明中得到最新的状态更改。我做错了什么?

首先,要存储大对象,如本例中所示,请使用
useReducer
而不是
useState

通过键入
constnewstate=state您正在分配相同的对象,因此您将使用仍然相同的对象。使用
constnewstate={…state}
或类似的东西

让我们看看下面:

const showEmailError = state.errors.email;
[...]
{state.errors.email[0]}
您的state.errors属性是布尔属性,因此它没有任何嵌套对象


在useffect中,您需要传递依赖项数组,该数组将被跟踪并在任何更改时调用函数。要仅调用一次,请传递空数组:

useEffect(() => {
    console.log(state);
}, []);

如果要记录每个状态的更改,请将状态作为依赖项传递。

setState
认为没有任何更改,因为每次传递的都是相同的
状态
对象。在每次更新时创建一个新对象:

constnewstate={…state};

我在评论中给了你一个提示,但你似乎忽略了它。您不应该直接修改state对象,因为当您调用setState时,React会将其与原始state对象引用进行比较(感谢Ross在注释中指出)。由于您已经对其进行了变异,并且React没有发现任何更改的属性,因此不会更新组件,因此不会调用
useffect
hook。在工作之前创建对状态对象的新引用。以下是您需要做的:

替换

const newState = state;



FYI do
const newState=state
然后使用
newState
,您仍在使用旧的状态对象是的,我知道。但这不是这里的问题。这正是问题所在@bryan只是提醒一下,你
useffect
在这里没有任何作用,只是做
console.log(state)将显示您的状态更改。一旦您修复了状态更改,当然……)@基思:是的,我知道。为了测试组件是否被重新渲染,我添加了它,但因为我使用的是同一个对象,所以它不是:PAh,我忘了删除它。当我将state.errors.email替换为随机文本时,不会显示任何内容。我的问题是,useEffect只记录一次,即使我没有向它传递任何参数。是的,如果要记录每个状态的更改,您需要将状态作为依赖项传递给useEffect,然后每次更改都将被记录。这可能会起作用,但当OP从回调中删除
error=true
时,浅层克隆(以及此方法)将失败,因为您的代码仍在处理同一对象的属性。这样的分解会创建一个浅层克隆。
useState
使用对象引用来确定是否重新渲染。为什么需要深度克隆?因为在问题中,OP修改嵌套属性
(obj.prop1.prop2…
,当您执行像
useState
这样的浅层克隆时,这些属性不会被克隆,它们只与传递给它的对象有关,如果引用发生更改,它将返回一个更新的值。在OP的情况下,整个组件将重新渲染并拾取对嵌套对象的更改。这里不需要深度克隆。@mehuimpt:哦,对不起。是的,ofc!我正在将类组件转换为钩子,但错过了这个。。。。非常感谢。应该提到的是,
解构
不是深度克隆,可能会导致意外行为。另外,在考虑性能时,应该尝试使用自己的/library深度克隆函数,如lodash
。.deepClone
@DennisVash,我是说递归解构。谢谢你指出这一点out@mehulmpt:所以我应该避免像这样进行破坏:{…state}?{…state}如果您只有一个级别的内容需要更改(如state.prop1或state.prop2),那么如果您想要更改
state.prop1.nestedprop1
,那么您必须执行类似
const newstate={…state,prop1:{…state.prop1}}
浅层克隆
state.prop1
,或者只要它是JSON友好的,您就可以将其字符串化并再次解析为JSON(即,没有函数或循环依赖项,在大多数情况下,您不需要担心状态对象中的这一点)@Bryan
const newState = { ...state }