您看到旧值的原因admin
in the click
功能是由于闭包导致陈旧的道具和状态问题。JavaScript 闭包如何工作?
JavaScript 中的每个函数都维护对其外部的引用
词汇环境。该参考用于配置执行
调用函数时创建的上下文。该参考使
函数内部的代码可以“查看”函数外部声明的变量
函数,无论函数何时何地被调用。
在 React 函数组件中,每次组件重新渲染时,都会重新创建闭包,因此一个渲染中的值不会泄漏到另一个渲染中。出于同样的原因,您想要的任何状态都应该在useState
or useReducer
钩子,因为它们将保留在重新渲染之后。
有关其反应方面的更多信息,请参见反应常见问题解答
除了问题的主要答案之外,您还应该useState
的值user
and pass
在你的组件中。否则,这些值不会在重新渲染之间保留。
否则,您可以不使用受控组件,但不能使用两者的奇怪组合。
下面的工作示例:
const initialstate = {
user: '',
password: '',
};
const adminReducer = (state = initialstate, action) => {
switch (action.type) {
case 'admin':
return (state = {
user: action.user,
password: action.password,
});
default:
return initialstate;
}
};
const adminAction = (user, password) => {
return {
type: 'admin',
user: user,
password: password,
};
};
const store = Redux.createStore(Redux.combineReducers({ admin: adminReducer }));
const App = () => {
const store = ReactRedux.useStore();
const admin = ReactRedux.useSelector((state) => state.admin);
const dispatch = ReactRedux.useDispatch();
const [user, setUser] = React.useState('');
const [pass, setPass] = React.useState('');
const adminChangeUsername = (event) => {
setUser(event.target.value);
};
const adminChangePassword = (event) => {
setPass(event.target.value);
};
const click = (event) => {
event.preventDefault();
dispatch(adminAction(user, pass));
console.log('store has the correct value here', store.getState());
console.log('admin is the previous value of admin due to closures:', admin);
};
return (
<div>
<input
onChange={adminChangeUsername}
value={user}
placeholder="username"
/>
<input
onChange={adminChangePassword}
value={pass}
type="password"
placeholder="password"
/>
<button onClick={click}>submit</button>
<p>This shows the current value of user and password in the store</p>
<p>User: {admin.user}</p>
<p>Pass: {admin.password}</p>
</div>
);
};
ReactDOM.render(
<ReactRedux.Provider store={store}>
<App />
</ReactRedux.Provider>,
document.querySelector('#root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js" integrity="sha256-7nQo8jg3+LLQfXy/aqP5D6XtqDQRODTO18xBdHhQow4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js" integrity="sha256-JuJho1zqwIX4ytqII+qIgEoCrGDVSaM3+Ul7AtHv2zY=" crossorigin="anonymous"></script>
<div id="root"/>
使用原始变量代替的示例useState
。您会注意到,在这种情况下,通过使用 var 并在输入上保留 value 属性,它会阻止输入密码。它对用户输入“起作用”的原因是因为用户变量一开始是未定义的而不是空字符串。
const initialstate = {
user: '',
password: '',
};
const adminReducer = (state = initialstate, action) => {
switch (action.type) {
case 'admin':
return (state = {
user: action.user,
password: action.password,
});
default:
return initialstate;
}
};
const adminAction = (user, password) => {
return {
type: 'admin',
user: user,
password: password,
};
};
const store = Redux.createStore(Redux.combineReducers({ admin: adminReducer }));
const App = () => {
const store = ReactRedux.useStore();
const admin = ReactRedux.useSelector((state) => state.admin);
const dispatch = ReactRedux.useDispatch();
var user,pass='';
const adminChangeUsername = (event) => {
user = event.target.value;
};
const adminChangePassword = (event) => {
pass = event.target.value;
};
const click = (event) => {
event.preventDefault();
dispatch(adminAction(user, pass));
console.log('store has the correct value here', store.getState());
console.log('admin is the previous value of admin due to closures:', admin);
};
return (
<div>
<input
onChange={adminChangeUsername}
value={user}
placeholder="username"
/>
<input
onChange={adminChangePassword}
value={pass}
type="password"
placeholder="password"
/>
<button onClick={click}>submit</button>
<p>This shows the current value of user and password in the store</p>
<p>User: {admin.user}</p>
<p>Pass: {admin.password}</p>
</div>
);
};
ReactDOM.render(
<ReactRedux.Provider store={store}>
<App />
</ReactRedux.Provider>,
document.querySelector('#root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js" integrity="sha256-7nQo8jg3+LLQfXy/aqP5D6XtqDQRODTO18xBdHhQow4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js" integrity="sha256-JuJho1zqwIX4ytqII+qIgEoCrGDVSaM3+Ul7AtHv2zY=" crossorigin="anonymous"></script>
<div id="root"/>
完全不受控制的输入的示例。这根本不需要变量:
const initialstate = {
user: '',
password: '',
};
const adminReducer = (state = initialstate, action) => {
switch (action.type) {
case 'admin':
return (state = {
user: action.user,
password: action.password,
});
default:
return initialstate;
}
};
const adminAction = (user, password) => {
return {
type: 'admin',
user: user,
password: password,
};
};
const store = Redux.createStore(Redux.combineReducers({ admin: adminReducer }));
const App = () => {
const store = ReactRedux.useStore();
const admin = ReactRedux.useSelector((state) => state.admin);
const dispatch = ReactRedux.useDispatch();
const click = (event) => {
event.preventDefault();
const user = event.currentTarget.user.value;
const pass = event.currentTarget.pass.value;
dispatch(adminAction(user, pass));
console.log('store has the correct value here', store.getState());
console.log('admin is the previous value of admin due to closures:', admin);
};
return (
<div>
<form onSubmit={click}>
<input name="user" placeholder="username" />
<input name="pass" type="password" placeholder="password" />
<button type="submit">submit</button>
</form>
<p>This shows the current value of user and password in the store</p>
<p>User: {admin.user}</p>
<p>Pass: {admin.password}</p>
</div>
);
};
ReactDOM.render(
<ReactRedux.Provider store={store}>
<App />
</ReactRedux.Provider>,
document.querySelector('#root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js" integrity="sha256-7nQo8jg3+LLQfXy/aqP5D6XtqDQRODTO18xBdHhQow4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js" integrity="sha256-JuJho1zqwIX4ytqII+qIgEoCrGDVSaM3+Ul7AtHv2zY=" crossorigin="anonymous"></script>
<div id="root"/>
为什么关闭与该帖子相关
由于原始代码的同步性质,它是possible说的原因是admin.user
记录的值不是新值只是因为组件尚未重新渲染。然而,重点仍然是admin
该渲染的值是一成不变的,永远不会因为关闭而改变。即使 React 渲染与 redux 状态更新同步,admin
不会改变。
正是由于这样的原因,即使在更简单的情况下,考虑闭包以及一切如何充分发挥作用也很重要,这样您就不会在更复杂的情况下陷入困境。
例如,不正确的心智模式会如何阻碍你,想象一下你假设原因是console.log(admin.user)
原始示例中没有显示正确的值solely因为该组件尚未重新渲染。您可能会认为设置超时等待重新渲染会让您看到新值。
想象一下,尝试添加自动保存功能,您希望每 10 秒将 admin 的当前值保存到本地存储或 API。您可以设置一个间隔 10 秒的效果来记录admin
使用空的依赖项数组,因为您希望它在组件卸载之前不会重置。这显然是不正确的,因为它不包括admin
依赖项中的值,但它会突出显示,无论您提交值并更改多少次admin
在redux状态下,这个effect的值永远不会改变,因为这个effect总是从初始值开始运行admin
在第一次渲染时。
useEffect(()=>{
const intervalId = setInterval(()=>{console.log(admin)},10000);
return ()=>clearInterval(intervalId);
},[])
闭包是一个整体问题,如果你对事情的运作方式有错误的思维模式,你很容易就会遇到麻烦。
有关关闭的更多信息:
闭包是捆绑在一起(封闭)的函数的组合
参考其周围状态(词汇环境)。在
换句话说,闭包可以让你访问外部函数的作用域
来自内部函数。在 JavaScript 中,每次都会创建闭包
函数在函数创建时创建。
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
正如 MDN Web 文档中提到的,每次创建函数时,该函数都会永久访问周围代码中的变量。本质上,这些函数可以访问它们范围内的所有内容。
在 React 函数组件中,每个渲染都会创建新函数。
在这个例子中,当App被渲染时:
const App = ()=>{
const admin = useSelector(state => state.admin);
const dispatch = useDispatch();
var user,pass = '';
const adminChangeUsername = (event) => {
user = event.target.value;
}
const adminChangePassword = (event) => {
pass = event.target.value;
}
const click= (event) => {
event.preventDefault();
dispatch(adminAction(user,pass));
console.log(store.getState())
console.log(admin.user)
}
}
- admin 变量根据 redux 存储的当前状态初始化到 App 的范围内(即
{ user: '', password: ''}
).
- 调度变量被初始化为 redux 的
store.dispatch
- 用户被初始化为
undefined
- pass 被初始化为
''
- adminChangeUsername 和 adminChangePassword 函数已初始化。他们可以访问(和使用)用户并从他们创建的闭包中传递。
- 单击已初始化。它可以在关闭时访问(并使用)用户、通行证、商店和管理员。
click
始终只能在创建时访问当前重新渲染的管理值,因为单击函数在创建时在其周围创建了一个闭包。一旦点击事件发生,并且 adminAction 被调度,click
不会改变它来自哪个渲染,所以它可以访问admin
初始化时的变量it曾是。并且每次渲染时,admin
变量被重新初始化,因此是一个不同的变量。
这里的主要问题不仅仅是异步性质,而是事实
函数根据当前状态使用状态值
闭包和状态更新将反映在下一次重新渲染中
现有的关闭不受影响,但会创建新的关闭。现在
在当前状态下,钩子中的值是通过现有的获得的
闭包,当重新渲染发生时,闭包将根据
关于是否再次重新创建函数
- usestate-set-method-not-reflecting-change-immediately - Shubham Khatri
无论您是否在点击处理程序中设置超时,admin
由于该关闭, value 永远不会成为新值。即使 React 碰巧重新渲染立即地致电调度后。
您可以使用useRef
创造一个可变的价值current
财产will在重新渲染之间保持不变,此时,React 重新渲染的异步特性实际上开始发挥作用,因为在 React 重新渲染组件之前,您仍然拥有旧值几毫秒。
在这个例子中https://codesandbox.io/s/peaceful-neumann-0t9nw?file=/src/App.js,可以看到超时记录的admin值还是旧值,但是超时记录的adminRef.current值却是新值。原因是 admin 指向在上一个渲染中初始化的变量,而 adminRef.current 在重新渲染之间保持相同的变量。以下代码沙箱中的日志示例:
关于事件的顺序,唯一可能真正让你感到惊讶的是useSelector
用新数据调用before发生重新渲染。原因是每当 redux 存储发生更改时,react-redux 都会调用该函数以确定if由于更改,组件需要重新渲染。完全可以设置adminRef.current
内的财产useSelector
始终可以访问 admin 的最新值,而无需store.getState()
.
在这种情况下,只需使用就足够简单了store.getState()
如果您需要访问钩子关闭之外的最新状态,则获取当前的 redux 状态。我经常倾向于在长期运行的效果中使用类似的东西,因为store
更改时不会触发重新渲染,并且可以帮助提高性能关键位置的性能。不过,出于同样的原因,应该谨慎使用它,因为它总是能让您访问最新状态。
我在这个例子中使用了超时,因为这是突出闭包效果的实际性质的最佳方式。想象一下,如果您愿意,超时是一些异步数据调用,需要一些时间才能完成。