我是redux的新手,并且已将其与我的react应用程序集成在一起,但是我有一个关于小测试的说明。
在下一个示例中,我看到第二次点击增加了用户价值。
减速器
const initialState = {
user: '',
password: ''
}
export const admin = (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
}
}
export default adminAction;
更改商店中的状态。
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.user 的值仍为空。
再次单击时: 商店 值已更新 已添加 admin 值。
我的问题是为什么只有第二次单击才能触发从商店中检索值?
答案 0 :(得分:2)
在admin
函数中看到click
的旧值的原因是由于闭包导致过时的道具和状态问题。 How do JavaScript closures work?
JavaScript中的每个函数都对其外部保持引用 词汇环境。该参考用于配置执行 调用函数时创建的上下文。此参考启用 函数内部的代码以“查看”在外部声明的变量 函数,无论何时何地调用该函数。
在React函数组件中,每次重新渲染该组件时,都会重新创建闭包,因此来自一个渲染的值不会泄漏到另一个渲染中。出于同样的原因,您想要的任何状态都应位于useState
或useReducer
挂钩之内,因为它们将保留过去的重新渲染。
除了该问题的主要答案外,您还应该useState
来获取组件中user
和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
的示例。在这种情况下,您会注意到使用vars并在输入中保留值prop,这会阻止输入密码输入。它在用户输入上“起作用”的原因是因为用户变量开始时是未定义的,而不是空字符串。
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"/>
输入完全不受控制的示例。这根本不需要var:
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"/>
由于原始代码具有同步性,因此有可能说,记录的admin.user
值不是新值的原因仅仅是因为该组件没有重新渲染了。但是,重点仍然在于,该渲染的admin
值设置为固定值,并且永远不会因关闭而改变。即使React与Redux状态更新同步呈现,admin
也不会改变。
出于这样的原因,重要的是考虑闭包以及即使在较简单的情况下一切也能完全正常工作的情况,因此您不要弄乱较复杂的闭包。
例如关于不正确的心理模型如何阻碍您的示例,假设您假设console.log(admin.user)
在原始示例中未显示正确值的原因仅仅是 ,因为该组件尚未重新渲染。您可能会假设超时等待重新渲染将使您看到新值。
想象一下,尝试添加自动保存功能,您希望每10秒将admin的当前值保存到localstorage或API。您可能会以10秒的间隔放置一个效果,该效果会使用空的依赖项数组记录admin
,因为您希望在组件卸载之前不重置它。这显然是不正确的,因为它在依赖项中不包含admin
值,但是它将突出显示,无论您提交值多少次并在redux状态下更改admin
,该值都将此效果永远不会改变,因为该效果将始终从第一个渲染器上的admin
的初始值开始运行。
useEffect(()=>{
const intervalId = setInterval(()=>{console.log(admin)},10000);
return ()=>clearInterval(intervalId);
},[])
关闭是整体问题,如果您对事物的工作方式有错误的思维模式,则很容易遇到麻烦。
闭包是捆绑在一起的功能的组合(闭包) 并参考其周围状态(词汇环境)。在 换句话说,闭包使您可以访问外部函数的作用域 从内部功能。在JavaScript中,每次都会创建闭包 在函数创建时就创建了一个函数。
如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)
}
}
{ user: '', password: ''}
)将admin变量初始化为App的范围。store.dispatch
undefined
''
click
在创建时始终只能从当前重新渲染中访问admin值,因为click函数在创建时围绕它创建了一个闭包。一旦发生click事件并调度了adminAction,click
将不会更改其来源,因此它可以访问在 it admin变量。 >是。并且每个渲染admin
变量都会重新初始化,因此是一个不同的变量。
这里的主要问题不仅是异步性质,还有事实 状态值由函数根据其当前值使用 关闭和状态更新将反映在下一次重新渲染中 现有的关闭不受影响,但是会创建新的关闭。现在 在当前状态下,挂钩中的值是通过现有的 闭包,并且当重新渲染发生时,闭包将根据更新 关于是否重新创建功能
无论您是否在点击处理程序中添加了超时,admin
值都不会因为关闭而成为新值。即使React在调用dispatch之后碰巧立即重新渲染了 。
您可以使用useRef
创建一个可变值,其current
属性 在重新渲染之间保持不变,这时,React的异步特性重新渲染实际上起了作用,因为在React重新渲染该组件之前,您还有几毫秒的旧值。
在此示例https://codesandbox.io/s/peaceful-neumann-0t9nw?file=/src/App.js中,您可以看到在超时中记录的admin值仍然具有旧值,但是在超时中记录的adminRef.current值具有新值。原因是admin指向在上一个渲染中初始化的变量,而adminRef.current在重新渲染之间保持相同的变量。下面的代码沙箱中的日志示例:
唯一令您感到惊讶的是,事件发生的顺序是在重新渲染发生之前,用新数据 调用useSelector
。这样做的原因是,每当更改Redux存储时,react-redux都会调用该函数,以确定 if 是否由于更改而需要重新呈现组件。完全有可能将adminRef.current
中的useSelector
属性设置为始终不使用store.getState()
就可以访问admin的最新值。
在这种情况下,如果需要访问钩子闭包之外的最新状态,只需使用store.getState()
就可以获取当前的redux状态,这很容易。我经常倾向于在运行时间更长的效果中使用类似的方法,因为store
不会在更改时触发重新渲染,并且可以在性能关键的位置帮助提高性能。不过,出于相同的原因,应谨慎使用它,因为它始终可以使您访问最新状态。
在此示例中,我使用了超时,因为这是突出显示闭包效果的实际性质的最佳方法。试想一下,如果您将超时称为某个异步数据调用,则需要花费一些时间才能完成。
答案 1 :(得分:1)
在执行完整个点击回调后,将在下一个组件重新呈现过程后馈送admin
对象。
实际上,它基于useSelector
提供的值,该值是在渲染过程中一次触发的。
admin.user
在click
回调中被检查,该组件尚未重新定义,因此在第一个交叉处显示当前空字符串值。
1)首次渲染组件。
2)useSelector
被调用。
3)点击。
4)已调度动作。
5)admin.user
仍然为空,因为2)尚未再次运行。
6)click
回调完成后,将要进行重新渲染,因此将再次触发useSelector
,抓住管理员value
!
故事的道德感:
不要期望从useSelector
提供的值会立即在点击回调中同步。
它需要重新渲染过程。
此外,为了改善您的代码,您可以替换:
return state = {
user: action.user,
password: action.password
}
通过:
return {
user: action.user,
password: action.password
}