React & Redux:即使使用 mapStateToProps,组件状态也不会更新

React & Redux: State of component is not updating even with mapStateToProps

我在下面发布了一个答案,但如果有人能解释为什么这是必要的,你就会得到赏金,我看了一个 redux 教程,感觉我没有学习 mapDispatchToProps,只有 mapStateToProps。如果您能更深入地解释 mapStateToPropsmapDispatchToProps 到底在做什么以及它们有何不同,我会给您赏金。


Minimum Reproducible Example


我有一个 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
  }

你的循环将检查 firstNAmeLastName 值是否未定义,测试应该对此做出响应。

问题出在 mapStateToProps 方法中。您的代码在状态对象 中将 firstNAmelastName 设置为 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
  }
}

几个旁注:

  1. 最好使用 props 而不是直接访问 redux store。
  2. 对于调试,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 代码,以便我了解您是如何更新商店状态的。

  1. 在GitHub上看你的MRE,我想说的第一件事是你的按钮只有一个状态,它是isDisabled()方法returns 刷新页面时。这是因为每次您在输入字段上书写时,App 组件都不会刷新,因此您永远无法更改它。

  2. 您需要在 mapStateToProps 函数中订阅正确的状态变量。像这样:

const mapStateToProps = state => {
    return {
        firstName: state.formData.firstName,
        middleName: state.formData.middleName,
        lastName: state.formData.lastName
    }
}
  1. 所以现在你有了这个权利,你必须在你的组件中引入这个道具,就像这样:
     function  App({firstName, lastName}) { ...
  1. 要补充的另一件事是,当您在 reducer.js 中创建减速器时,您没有初始化状态。如果不这样做,firstNamelastName 的初始状态将为空。以下是您应该如何操作:
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
  1. 终于要更新应用了:
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 应该会有帮助。