React & Redux:即使使用 mapStateToProps,组件状态也不会更新
React & Redux: State of component is not updating even with mapStateToProps
我在下面发布了一个答案,但如果有人能解释为什么这是必要的,你就会得到赏金,我看了一个 redux 教程,感觉我没有学习 mapDispatchToProps
,只有 mapStateToProps
。如果您能更深入地解释 mapStateToProps
和 mapDispatchToProps
到底在做什么以及它们有何不同,我会给您赏金。
我有一个 mapStateToProps 函数,看起来像
const mapStateToProps = state => {
return {
firstName: state.firstName,
middleName: state.middleName,
lastName: state.lastName,
}
}
const ReduxTabForm = connect(mapStateToProps)(MyTab)
在我的 MyTab
组件中,我有一个按钮,如果这两个字段没有输入任何内容,它应该是不活动的,但是按钮是否被禁用的状态不会改变
function App() {
const {firstName, lastName} = store.getState().formData
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields[i]){
return true
}
}
return false
}
return (
<div className="App">
<div className='bg-light rounded'>
<div className='px-sm-5 pt-0 px-4 flexCenterCol mx-5'>
<div>
<input
type='text'
className="form-control"
value={store.getState().formData['firstName']}
placeholder="First Name"
onChange={(e) => {
store.dispatch(setFormData({'firstName': e.target.value}))
}}
></input>
<input
type='text'
className="form-control"
value={store.getState().formData['lastName']}
placeholder="Last Name"
onChange={(e) => {
store.dispatch(setFormData({'lastName': e.target.value}))
}}
></input>
</div>
<button
type="submit"
disabled={isDisabled()}
>
Button
</button>
</div>
</div>
</div>
)
}
该警报语句在页面刷新时执行,但在我输入数据后不会执行。我检查过 redux 状态更新,确实如此。但是该按钮不会更新,并且 isDisabled 函数不会 运行 多次
你的状态值作为道具传递给你的组件。所以如果你想在组件内部访问它们,你应该做这样的事情
const {firstName, lastName} = props
我不知道这是否能解决您的问题,但也许是以下情况之一:
1 - 正如 Mohammad Faisal 所说,调用 props 的正确形式应该是
const { firstName, lastName } = props;
2 - 除了 reduxTabForm,也许你可以用这个代替:
export default connect(mapStateToProps)(MyTab);
3 - 最后,可能是“isDisabled”中的一个错误:
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields){
return false
}
}
如果你仔细看,你会发现你没有检查 requeiredFields 中是否有错误,你正在检查它是否不存在 if (!requiredFields)
,也许将条件更改为 if(!requiredFields[i])
所以它检查每个变量而不是数组是否不存在。
编辑:return 条件是否正确?当某些东西不存在时返回 False?
你可以做的是:
<button
type="submit"
disabled={!fistname && !lastname}
/>
这样,如果您的任何字段是假的,按钮将被禁用。 (空字符串是假的)
React redux documentation also explains mapDispatchToProps
如果您从 redux/actions.js
调用名为 setFormData
的操作,那么您会将操作 setFormData
(它是一个对象)映射到您的组件。操作 setFromData
替换 mapDispatchToProps
。这就是文档中关于将操作映射到您的组件的内容
If it’s an object full of action creators, each action creator will be
turned into a prop function that automatically dispatches its action
when called.
要解决您的问题,请将您的连接函数调用更改为此。
const ReduxApp = connect(
setFormData,
mapStateToProps
)(App)
const ReduxTabForm = connect(mapStateToProps,null)(MyTab)
试试这个代码片段,因为你没有将调度函数传递给你的组件。最好传递空值。
mapdispatchtoProps 与 mapStateToProps.You 相同的基本理论是将函数存储在商店中(通常在操作中),并且在渲染组件时将商店中的这些函数附加到道具上。渲染组件后,您将能够 运行 存储中的功能。
您的问题出在这段代码上 isDisabled()
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields){
return false
}
}
return true
}
您正在尝试在循环 !requiredFields
中测试,您创建的数组始终 return 为假,即使它没有值。它是一个引用类型。你现在能做的是
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields[i]){
return false
}
}
return true
}
你的循环将检查 firstNAme
和 LastName
值是否未定义,测试应该对此做出响应。
问题出在 mapStateToProps
方法中。您的代码在状态对象 中将 firstNAme
和 lastName
设置为 formData
并且您正在尝试直接从状态对象访问它。
此 codesandbox 已使用修复程序更新了您的项目。我没有在你的代码中修复任何其他东西所以一切都是因为它只是 mapStateToProps
应该是:
const mapStateToProps = (state) => {
return {
firstName: state.formData["firstName"],
middleName: state.middleName,
lastName: state.formData["lastName"]
};
};
这将解决您的问题。 mapStateToProps
是一种投影函数,它将整个商店投影到某个对象,该对象仅具有基于组件要求的属性。
我看了你的 reducer 代码,它看起来像这样:
...
const reducer = combineReducers({
formData: formReducer,
})
export default reducer
这意味着你的 redux state 结构是这样的:
state = {
formData: {
firstName: <value>,
middleName: <value>,
lastName: <value>,
}
}
解决方案
因此,要在 redux 状态更改时使您的组件 re-render,您需要在 mapStateToProps 函数中订阅正确的状态变量。把它改成这样就可以了:
const mapStateToProps = state => {
return {
firstName: state.formData.firstName, // Note that I added "formData"
middleName: state.formData.middleName,
lastName: state.formData.lastName
}
}
几个旁注:
- 最好使用 props 而不是直接访问 redux store。
- 对于调试,
console.log
优于 alert
。 React DevTools 和 Redux DevTools 甚至更好。
Redux re-render 仅当存储状态发生变化时的组件。检查你是否只更新商店的状态 属性 而不是整个状态,因为 redux 通过比较它们的引用来比较状态,所以如果你在你的减速器中做这样的事情:
State.firstName = action.payload.firstName;
return State;
然后将其更改为:
return {...State, firstName: action.payload.firstName}
注意:如果您无法理解,请也提供您的 reducer 代码,以便我了解您是如何更新商店状态的。
在GitHub上看你的MRE,我想说的第一件事是你的按钮只有一个状态,它是isDisabled()
方法returns 刷新页面时。这是因为每次您在输入字段上书写时,App 组件都不会刷新,因此您永远无法更改它。
您需要在 mapStateToProps 函数中订阅正确的状态变量。像这样:
const mapStateToProps = state => {
return {
firstName: state.formData.firstName,
middleName: state.formData.middleName,
lastName: state.formData.lastName
}
}
- 所以现在你有了这个权利,你必须在你的组件中引入这个道具,就像这样:
function App({firstName, lastName}) { ...
- 要补充的另一件事是,当您在
reducer.js
中创建减速器时,您没有初始化状态。如果不这样做,firstName
和 lastName
的初始状态将为空。以下是您应该如何操作:
import {combineReducers} from 'redux'
import {SET_FORM_DATA} from './actions'
const initialState = {
firstName: "",
lastName: ""
}
const formReducer = (state = initialState, action) => {
if (action.type === SET_FORM_DATA){
return {
...state,
...action.payload
}
}else{
return state
}
}
const reducer = combineReducers({
formData: formReducer,
})
export default reducer
- 终于要更新应用了:
function App({firstName, lastName}) {
return (
<div className="App">
<div className='bg-light rounded'>
<div className='px-sm-5 pt-0 px-4 flexCenterCol mx-5'>
<div>
<input
type='text'
className="form-control"
placeholder="First Name"
onChange={(e) => {
store.dispatch(setFormData({'firstName': e.target.value}));
console.log(firstName === "h");
}}
></input>
<input
type='text'
className="form-control"
placeholder="Last Name"
onChange={(e) => {
store.dispatch(setFormData({'lastName': e.target.value}))
alert(JSON.stringify(store.getState()))
}}
></input>
</div>
<button
type="submit"
disabled={firstName == "" || lastName == ""}
>
Button
</button>
</div>
</div>
</div>
);
}
因此,通过这种方式,您将能够更新商店的状态并获得您正在寻找的动态行为。
在 isDisabled
函数中,您从 form
读取数据:const {firstName, lastName} = store.getState().form
但我猜它们被保存到 formData
.
将 const {firstName, lastName} = store.getState().form
更改为 const {firstName, lastName} = store.getState().formData
应该会有帮助。
我在下面发布了一个答案,但如果有人能解释为什么这是必要的,你就会得到赏金,我看了一个 redux 教程,感觉我没有学习 mapDispatchToProps
,只有 mapStateToProps
。如果您能更深入地解释 mapStateToProps
和 mapDispatchToProps
到底在做什么以及它们有何不同,我会给您赏金。
我有一个 mapStateToProps 函数,看起来像
const mapStateToProps = state => {
return {
firstName: state.firstName,
middleName: state.middleName,
lastName: state.lastName,
}
}
const ReduxTabForm = connect(mapStateToProps)(MyTab)
在我的 MyTab
组件中,我有一个按钮,如果这两个字段没有输入任何内容,它应该是不活动的,但是按钮是否被禁用的状态不会改变
function App() {
const {firstName, lastName} = store.getState().formData
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields[i]){
return true
}
}
return false
}
return (
<div className="App">
<div className='bg-light rounded'>
<div className='px-sm-5 pt-0 px-4 flexCenterCol mx-5'>
<div>
<input
type='text'
className="form-control"
value={store.getState().formData['firstName']}
placeholder="First Name"
onChange={(e) => {
store.dispatch(setFormData({'firstName': e.target.value}))
}}
></input>
<input
type='text'
className="form-control"
value={store.getState().formData['lastName']}
placeholder="Last Name"
onChange={(e) => {
store.dispatch(setFormData({'lastName': e.target.value}))
}}
></input>
</div>
<button
type="submit"
disabled={isDisabled()}
>
Button
</button>
</div>
</div>
</div>
)
}
该警报语句在页面刷新时执行,但在我输入数据后不会执行。我检查过 redux 状态更新,确实如此。但是该按钮不会更新,并且 isDisabled 函数不会 运行 多次
你的状态值作为道具传递给你的组件。所以如果你想在组件内部访问它们,你应该做这样的事情
const {firstName, lastName} = props
我不知道这是否能解决您的问题,但也许是以下情况之一: 1 - 正如 Mohammad Faisal 所说,调用 props 的正确形式应该是
const { firstName, lastName } = props;
2 - 除了 reduxTabForm,也许你可以用这个代替:
export default connect(mapStateToProps)(MyTab);
3 - 最后,可能是“isDisabled”中的一个错误:
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields){
return false
}
}
如果你仔细看,你会发现你没有检查 requeiredFields 中是否有错误,你正在检查它是否不存在 if (!requiredFields)
,也许将条件更改为 if(!requiredFields[i])
所以它检查每个变量而不是数组是否不存在。
编辑:return 条件是否正确?当某些东西不存在时返回 False?
你可以做的是:
<button
type="submit"
disabled={!fistname && !lastname}
/>
这样,如果您的任何字段是假的,按钮将被禁用。 (空字符串是假的)
React redux documentation also explains mapDispatchToProps
如果您从 redux/actions.js
调用名为 setFormData
的操作,那么您会将操作 setFormData
(它是一个对象)映射到您的组件。操作 setFromData
替换 mapDispatchToProps
。这就是文档中关于将操作映射到您的组件的内容
If it’s an object full of action creators, each action creator will be turned into a prop function that automatically dispatches its action when called.
要解决您的问题,请将您的连接函数调用更改为此。
const ReduxApp = connect(
setFormData,
mapStateToProps
)(App)
const ReduxTabForm = connect(mapStateToProps,null)(MyTab)
试试这个代码片段,因为你没有将调度函数传递给你的组件。最好传递空值。 mapdispatchtoProps 与 mapStateToProps.You 相同的基本理论是将函数存储在商店中(通常在操作中),并且在渲染组件时将商店中的这些函数附加到道具上。渲染组件后,您将能够 运行 存储中的功能。
您的问题出在这段代码上 isDisabled()
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields){
return false
}
}
return true
}
您正在尝试在循环 !requiredFields
中测试,您创建的数组始终 return 为假,即使它没有值。它是一个引用类型。你现在能做的是
const isDisabled = () => {
const {firstName, lastName} = store.getState().form
const requiredFields = [firstName, lastName]
alert(requiredFields)
for(let i = 0; i < requiredFields.length; i=i+1){
if (!requiredFields[i]){
return false
}
}
return true
}
你的循环将检查 firstNAme
和 LastName
值是否未定义,测试应该对此做出响应。
问题出在 mapStateToProps
方法中。您的代码在状态对象 中将 firstNAme
和 lastName
设置为 formData
并且您正在尝试直接从状态对象访问它。
此 codesandbox 已使用修复程序更新了您的项目。我没有在你的代码中修复任何其他东西所以一切都是因为它只是 mapStateToProps
应该是:
const mapStateToProps = (state) => {
return {
firstName: state.formData["firstName"],
middleName: state.middleName,
lastName: state.formData["lastName"]
};
};
这将解决您的问题。 mapStateToProps
是一种投影函数,它将整个商店投影到某个对象,该对象仅具有基于组件要求的属性。
我看了你的 reducer 代码,它看起来像这样:
...
const reducer = combineReducers({
formData: formReducer,
})
export default reducer
这意味着你的 redux state 结构是这样的:
state = {
formData: {
firstName: <value>,
middleName: <value>,
lastName: <value>,
}
}
解决方案
因此,要在 redux 状态更改时使您的组件 re-render,您需要在 mapStateToProps 函数中订阅正确的状态变量。把它改成这样就可以了:
const mapStateToProps = state => {
return {
firstName: state.formData.firstName, // Note that I added "formData"
middleName: state.formData.middleName,
lastName: state.formData.lastName
}
}
几个旁注:
- 最好使用 props 而不是直接访问 redux store。
- 对于调试,
console.log
优于alert
。 React DevTools 和 Redux DevTools 甚至更好。
Redux re-render 仅当存储状态发生变化时的组件。检查你是否只更新商店的状态 属性 而不是整个状态,因为 redux 通过比较它们的引用来比较状态,所以如果你在你的减速器中做这样的事情:
State.firstName = action.payload.firstName;
return State;
然后将其更改为:
return {...State, firstName: action.payload.firstName}
注意:如果您无法理解,请也提供您的 reducer 代码,以便我了解您是如何更新商店状态的。
在GitHub上看你的MRE,我想说的第一件事是你的按钮只有一个状态,它是
isDisabled()
方法returns 刷新页面时。这是因为每次您在输入字段上书写时,App 组件都不会刷新,因此您永远无法更改它。您需要在 mapStateToProps 函数中订阅正确的状态变量。像这样:
const mapStateToProps = state => {
return {
firstName: state.formData.firstName,
middleName: state.formData.middleName,
lastName: state.formData.lastName
}
}
- 所以现在你有了这个权利,你必须在你的组件中引入这个道具,就像这样:
function App({firstName, lastName}) { ...
- 要补充的另一件事是,当您在
reducer.js
中创建减速器时,您没有初始化状态。如果不这样做,firstName
和lastName
的初始状态将为空。以下是您应该如何操作:
import {combineReducers} from 'redux'
import {SET_FORM_DATA} from './actions'
const initialState = {
firstName: "",
lastName: ""
}
const formReducer = (state = initialState, action) => {
if (action.type === SET_FORM_DATA){
return {
...state,
...action.payload
}
}else{
return state
}
}
const reducer = combineReducers({
formData: formReducer,
})
export default reducer
- 终于要更新应用了:
function App({firstName, lastName}) {
return (
<div className="App">
<div className='bg-light rounded'>
<div className='px-sm-5 pt-0 px-4 flexCenterCol mx-5'>
<div>
<input
type='text'
className="form-control"
placeholder="First Name"
onChange={(e) => {
store.dispatch(setFormData({'firstName': e.target.value}));
console.log(firstName === "h");
}}
></input>
<input
type='text'
className="form-control"
placeholder="Last Name"
onChange={(e) => {
store.dispatch(setFormData({'lastName': e.target.value}))
alert(JSON.stringify(store.getState()))
}}
></input>
</div>
<button
type="submit"
disabled={firstName == "" || lastName == ""}
>
Button
</button>
</div>
</div>
</div>
);
}
因此,通过这种方式,您将能够更新商店的状态并获得您正在寻找的动态行为。
在 isDisabled
函数中,您从 form
读取数据:const {firstName, lastName} = store.getState().form
但我猜它们被保存到 formData
.
将 const {firstName, lastName} = store.getState().form
更改为 const {firstName, lastName} = store.getState().formData
应该会有帮助。