【问题标题】:React Component not updating even after duplication of state in Redux reducer即使在 Redux reducer 中复制状态后,React 组件也不会更新
【发布时间】:2021-04-09 05:08:07
【问题描述】:

上下文

目标是当我按下特定键时,在 App.js 中反应渲染一个具有键名的组件,并在另一个组件中注册。信息正在通过 redux 托管状态传递。

问题

这很简单: 我正在我的 redux reducer 中更新我的状态,但即使在复制它时(我可以看到它,这要归功于 redux 开发工具,它允许我观察我的 prevState 和我的 nextState 不同)

问题就这么简单:

为什么我的 App.js 组件即使在连接到 and 后也不会重新渲染 复制我的状态?

我想我确保我的状态通过传播操作复制了,并且我的 redux 开发工具向我显示了一个很好的状态更新,而没有我的 prevState 和 nextState 复制。我浏览了很多帖子,发现只有一些人忘记在他们的减速器中复制他们的状态,而我没有。 那么这里有什么问题呢??

开发工具示例

代码

这里是代码,很简单。有趣的部分是 playSoundplayedKeys

App.js:

import React from 'react'
import './App.css';
import { connect } from 'react-redux';

import KeyComponent from './Components/Key'
import SoundPlayer from './Components/Sounds'

const mapStateToProps = (state) => ({
  ...state.soundReducer
 })

class App extends React.Component {
  constructor(props) {
    super(props);
  }
  
  render(){
    return (
      <div>
      {console.log(this.props)}
        {
          this.props.playedKeys.map(key =>{
            <KeyComponent keyCode={key}>  </KeyComponent>
          })
        }
        <SoundPlayer></SoundPlayer>
      </div>
    );
  }
}

export default connect(mapStateToProps)(App);

减速器

export default (state = {allSounds:{},playedKeys:[]}, action) => {
  switch (action.type) {
    case 'ADD_SOUND':
      return reduce_addSound({...state},action)
    case 'PLAY_SOUND':
      return reduce_playSound({...state,playedKeys : [...state.playedKeys]},action)
    
    default:
    return state
  }
}
  
function reduce_addSound (state,action){

  let i = 0
  state.allSounds[action.payload.key] = { players : new Array(5).fill('').map(()=>(new Audio())) , reader : new FileReader()}

  //load audioFile in audio player
    state.allSounds[action.payload.key].reader.onload = function(e) {
      state.allSounds[action.payload.key].players.forEach(player =>{
        player.setAttribute('src', e.target.result);
        player.load();
        player.id = 'test'+e.target.result+ i++ 
      })
  }
  state.allSounds[action.payload.key].reader.readAsDataURL(action.payload.input.files[0]);
  
  return state
}

function reduce_playSound(state,action){

  state.playedKey = action.payload.key;

  if(!state.playedKeys.includes(state.playedKey))
    state.playedKeys.push(action.payload.key);

  return state
}

动作

export const addSound = (key, input,player) => (dispatch,getState) => {
    dispatch({
        type: 'ADD_SOUND',
        payload: {key : key, input : input}
       })
   }
   
export const playSound = (key) => (dispatch,getState) => {
    dispatch({
        type: 'PLAY_SOUND',
        payload: {key : key}
       })
   }

注册按键的组件



import React from 'react'
import { connect } from 'react-redux';

import { playSound } from '../../Actions/soundActions';

const mapStateToProps = (state) => ({
  ...state.soundReducer
 })

 const mapDispatchToProps = dispatch => ({
    playSound: (keyCode) => dispatch(playSound(keyCode))
 })

 class SoundPlayer extends React.Component {

    constructor(props) {
        super(props);
    }
    componentDidMount () {
        this.playSoundComponent = this.playSoundComponent.bind(this)
        document.body.addEventListener('keypress', this.playSoundComponent);
    }

    keyCodePlayingIndex = {};

    playSoundComponent(key){
        if(this.props.allSounds.hasOwnProperty(key.code)){

            if(!this.keyCodePlayingIndex.hasOwnProperty(key.code))
                this.keyCodePlayingIndex[key.code] = 0

            this.props.allSounds[key.code].players[this.keyCodePlayingIndex[key.code]].play()

            this.keyCodePlayingIndex[key.code] = this.keyCodePlayingIndex[key.code] + 1 >= this.props.allSounds[key.code].players.length ? 0 : this.keyCodePlayingIndex[key.code] + 1
            console.log(this.keyCodePlayingIndex[key.code])
        }

        this.props.playSound(key.code);
    }

    render(){
        return <div>
            <h1 >Played : {this.props.playedKey}</h1>
            {Object.keys(this.keyCodePlayingIndex).map(key =>{
                return <p>{key} : {this.keyCodePlayingIndex[key]}</p>
            })}
        </div>
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(SoundPlayer);

【问题讨论】:

    标签: javascript reactjs react-redux state


    【解决方案1】:

    问题

    你正在改变你的状态对象。

    • state.allSounds[action.payload.key] = ...
    • state.playedKey = action.payload.key;

    解决方案

    更新您的 reducer 函数以返回新的 state 对象,记住正确浅拷贝正在更新的每个深度级别。

    export default (state = { allSounds: {}, playedKeys: [] }, action) => {
      switch (action.type) {
        case 'ADD_SOUND':
          return reduce_addSound({ ...state },action);
    
        case 'PLAY_SOUND':
          return reduce_playSound({ ...state, playedKeys: [...state.playedKeys] }, action);
        
        default:
        return state
      }
    }
      
    function reduce_addSound (state, action) {
      const newState = {
        ...state,  // shallow copy existing state
        allSounds: {
          ...state.allSounds, // shallow copy existing allSounds
          [action.payload.key]: {
            players: new Array(5).fill('').map(()=>(new Audio())),
            reader: new FileReader(),
          },
        }
      };
    
      // load audioFile in audio player
      newState.allSounds[action.payload.key].reader.onload = function(e) {
        newState.allSounds[action.payload.key].players.forEach((player, i) => {
          player.setAttribute('src', e.target.result);
          player.load();
          player.id = 'test' + e.target.result + i // <-- use index from forEach loop
        })
      }
      newState.allSounds[action.payload.key]
        .reader
        .readAsDataURL(action.payload.input.files[0]);
      
      return newState;
    }
    
    function reduce_playSound (state, action) {
      const newState = {
        ...state,
        playedKey: action.payload.key,
      };
    
      if(!newState.playedKeys.includes(newState.playedKey))
        newState.playedKeys = [...newState.playedKeys, action.payload.key];
    
      return newState
    }
    

    【讨论】:

    • 感谢您的快速回答,我尝试了您的方法(感谢您添加每个循环的索引;))但遗憾的是它并没有改变我的行为。所以如果我理解正确,在我使用reduce_addSound({ ...state },action)reduce_playSound({ ...state, playedKeys: [...state.playedKeys] } 的那一刻,我不是在复制整个对象吗?因此必须在我的减速器中浅拷贝我的对象的每一层?因为如果问题是reducer的突变,似乎还不够。
    • 如果您有兴趣查看完整代码并发现它不会改变行为,这里是当前分支的链接:github.com/pazka/my-custom-soundboard/tree/dynamic-keys/src。你只需要做经典的 checkout / npm i / npm start
    • @Pazka 哎呀,是的,这确实复制了状态对象,但您不一定要复制更深的嵌套状态。很好的收获,谢谢。
    • 没问题,反正很有指导意义,谢谢您的宝贵时间!
    【解决方案2】:

    好的,我知道了,它总是我们不检查的最简单最愚蠢的事情。

    澄清

    所以我的状态与reduce_addSound({ ...state },action)reduce_playSound({ ...state, playedKeys: [...state.playedKeys] 正确复制,就像我在问题中写的那样,这不是问题!

    问题

    尽其所能,我没有在我的渲染函数中返回一个组件..:

    在 App.js 中:

      render(){
        return (
          <div>
            {
              this.props.soundReducer.playedKeys.map(key =>{
                <KeyComponent keyCode={key}>  </KeyComponent> //<-- NO return or parenthesis !!
              })
            }
            <SoundPlayer></SoundPlayer>
          </div>
        );
      }
    

    回答

    带括号的App.js渲染函数:

      render(){
        return (
          <div>
            {
              this.props.soundReducer.playedKeys.map(key =>(
                <KeyComponent key = {key} keyCode={key}>  </KeyComponent> //<-- Here a component is returned..
              ))
            }
            <SoundPlayer></SoundPlayer>
          </div>
        );
      }
    

    【讨论】:

      猜你喜欢
      • 2019-07-18
      • 2021-01-12
      • 1970-01-01
      • 2021-07-26
      • 2021-02-06
      • 2021-01-08
      • 1970-01-01
      • 1970-01-01
      • 2017-08-08
      相关资源
      最近更新 更多