【问题标题】:React & Redux: State of component is not updating even with mapStateToPropsReact & Redux:即使使用 mapStateToProps,组件的状态也不会更新
【发布时间】:2021-01-12 02:03:21
【问题描述】:

我在下面发布了一个答案,但如果有人能解释为什么这是必要的,你会得到赏金,我经历了一个 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 mapStateToProps = state => ({ firstName: state.firstName, middleName: state.middleName, lastName: state.lastName, }) 你能像我上面写的那样写吗?
  • @Aftab22 没用,抱歉

标签: reactjs redux mapstatetoprops mapdispatchtoprops


【解决方案1】:

isDisabled 函数中,您从form 读取数据:const {firstName, lastName} = store.getState().form 但我猜它们已保存到formData

const {firstName, lastName} = store.getState().form 更改为const {firstName, lastName} = store.getState().formData 应该会有所帮助。

【讨论】:

    【解决方案2】:
    1. 查看你在 GitHub 上的 MRE,我首先想说的是,你的按钮只有一个状态,它是刷新页面时 isDisabled() 方法返回的状态。这是因为 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. 最后你要更新App:
    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>
      );
    }
    

    因此,通过这种方式,您将能够更新商店中的状态并获得您正在寻找的动态行为。

    【讨论】:

      【解决方案3】:

      Redux 仅在存储状态更改时重新渲染组件。检查你是否只更新 store 的 state 属性而不是整个 state,因为 redux 通过比较它们的引用来比较 state,所以如果你在 reducer 中做这样的事情:

      State.firstName = action.payload.firstName;

      return State;

      然后将其更改为:

      return {...State, firstName: action.payload.firstName}

      注意:如果您无法理解,请也提供您的 reducer 代码,以便我看到您如何更新商店状态。

      【讨论】:

        【解决方案4】:

        我查看了你的 reducer 代码,它看起来像这样:

        ...
        const reducer = combineReducers({
          formData: formReducer,
        })
        
        export default reducer
        

        这意味着你的 redux 状态结构是这样的:

        state = {
          formData: {
            firstName: <value>,
            middleName: <value>,
            lastName: <value>,
          }
        }
        

        解决方案

        所以,要让你的组件在 redux 状态改变时重新渲染,你需要在你的 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 更好。

        【讨论】:

          【解决方案5】:

          问题出在mapStateToProps 方法中。您的代码将firstNAmelastName 设置为formData 在状态对象内,并且您正尝试直接从状态对象访问它。

          codesandbox 已使用修复更新了您的项目。我没有在你的代码中修复任何其他东西,所以一切都只是mapStateToProps应该是:

          const mapStateToProps = (state) => {
            return {
              firstName: state.formData["firstName"],
              middleName: state.middleName,
              lastName: state.formData["lastName"]
            };
          };
          

          这将解决您的问题。 mapStateToProps 是一种投影函数,它将整个存储投影到某个对象,该对象仅具有基于组件要求的属性。

          【讨论】:

            【解决方案6】:

            您的问题在于此代码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 中进行测试,这是您创建的一个数组,即使它没有值,它也总是返回 false。它是一个引用类型。你现在能做的是

            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 值是否未定义,并且测试应该对此做出响应。

            【讨论】:

            • 这不是问题,这是附带问题,状态没有映射到道具
            • 您尝试过但没有成功吗?您的代码很糟糕,您设置的条件将永远不会改变。一个是纠正是你,你说那不是问题。
            【解决方案7】:
            const ReduxTabForm = connect(mapStateToProps,null)(MyTab)
            

            试试这个代码 sn-p,因为你没有将调度函数传递给你的组件。最好传递空值。 mapdispatchtoProps 与 mapStateToProps 的基本理论相同。您将函数存储在存储中(通常在操作中),并且在渲染组件时,您将存储中的这些函数附加到您的道具。渲染组件后,您将能够运行存储中的功能。

            【讨论】:

              【解决方案8】:

              React redux documentation also explains mapDispatchToProps

              如果您从redux/actions.js 调用您的操作setFormData,那么您将把操作setFormData(它是一个对象)映射到您的组件。操作setFromData 替换mapDispatchToProps。这就是文档中关于将操作映射到组件的内容

              如果它是一个充满动作创建者的对象,每个动作创建者将 变成了一个自动调度其动作的道具函数 调用时。


              要解决您的问题,请将您的连接函数调用更改为此。

              const ReduxApp = connect(
                setFormData,
                mapStateToProps
              )(App)
              

              【讨论】:

              • 其实这个例子在我把它转移到我的实际程序中时并没有起作用。我不知道我能做什么
              • 我添加了一个可以解决您的问题的答案。如果您需要任何解释,我可以分享更多详细信息。
              【解决方案9】:

              你可以做的是这样的:

              <button 
                    type="submit"
                    disabled={!fistname && !lastname}
                  />
              

              这样,如果您的任何字段是虚假的,按钮就会被禁用。 (空字符串是假的)

              【讨论】:

              • 但是以后永远不会启用
              • @Sam 删除 true 并保持它像 !fistname && !lastname
              • 它不能解决任何问题。您可以向 MRE 查询
              【解决方案10】:

              我不知道这是否会解决您的问题,但可能是以下情况之一: 1 - 正如 Mohammad Faisal 所说,调用道具的正确形式应该是

              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]),所以它检查每个变量而不是如果数组不存在。

              编辑:返回条件是否正确?当某些东西不存在时返回 False?

              【讨论】:

              • 警报应该仍然发生
              • 您的组件在您键入时重新呈现?
              • 我发布了一个MRE,是的,它应该重新渲染,但我不认为它是
              【解决方案11】:

              您的状态值作为道具传递给您的组件。所以如果你想在组件内部访问它们,你应该做这样的事情

               const {firstName, lastName} = props
              

              【讨论】:

              • 所以这也行不通,我知道道具是未定义的
              • 抱歉,我可以尝试做一个 mre,可能一个小时后就到了
              • 只有一件事......你的组件应该像下面这样开始。 export default function MyTab(props) { ..your component code here.. }
              • 是的,我在这里发布的代码只是一个 sn-p
              • 我发了MRE
              猜你喜欢
              • 2021-04-09
              • 2021-07-27
              • 1970-01-01
              • 2017-12-01
              • 2021-10-23
              • 2018-07-26
              • 2020-01-30
              • 2021-07-26
              • 1970-01-01
              相关资源
              最近更新 更多