当存储状态改变时,React组件没有更新

34

以下是我的组件类代码。即使我可以通过在mapStateToProps返回前记录日志来看到状态正在更新,该组件似乎从未执行componentWillUpdate()。 状态绝对在改变,但组件不会刷新。

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { search } from './mapActions'
import L from 'leaflet'


class Map extends Component {
  componentDidMount() {
    L.Icon.Default.imagePath = './images'
    this.map = new L.Map('map', {
      center: new L.LatLng(this.props.lat, this.props.lng),
      zoom: this.props.zoom,
      layers: L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        attribution: '<a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
      })
    })
  }
  componentWillUpdate() {
    console.log('UPDATE MAP')
    L.geoJson(this.props.data).addTo(this.map)
  }
  render() {
    return <div id="map"></div>
  }
}

const mapStateToProps = (state) => {
  return {
    isFetching: state.isFetching,
    data: state.data
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    search: (name) => {
      dispatch(search(name))
    }
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Map)

这里是MapReduce函数:

const initialState = {
  isFetching: false,
  data: {}
}

export const map = (state = initialState, action) => {
  switch(action.type) {
    case 'REQUEST_SEARCH_RESULTS':
      return Object.assign({}, state, {
        isFetching: true
      })
    case 'RECEIVE_SEARCH_RESULTS':
      return Object.assign({}, state, {
        isFetching: false,
        data: action.data
      })
    default:
      return state
  }
}

经过更多的测试和记录,看起来当它将状态映射到prop时,用于映射到prop的状态对象包含正确的数据,因此state.map.data是正确的,并且我可以看到从fetch返回的结果。然而,当我在componentWillUpdate()中记录this.props时,数据对象存在但为空。


你能展示一下你的reducers吗?可能是因为你改变了状态而不是发送新对象,所以redux认为没有更新的必要。 - Mijamo
nothing in the render: <div id="map"></div> - Davin Tryon
@DavinTryon 我正在使用 Leaflet,它处理自己的渲染,但它应该仍然像更新组件一样运作。 - Jacob Mason
@Mijamo,我已经添加了,谢谢。 - Jacob Mason
6个回答

44

我曾经遇到类似的问题,并在阅读以下内容后找到了答案:

通过reducers处理操作的结果来将数据设置/更新/删除到store中。Reducers接收应用程序的一个片段的当前状态,并期望获得新的状态返回。你在reducer中修改现有状态而不是返回带有必要更改的新状态的副本是你的组件可能不重新呈现的最常见原因之一(查看故障排除部分)。当直接改变现有状态时,Redux无法检测到状态的差异,也不会通知您的组件存储已更改。所以我建议您检查reducers并确保您没有修改现有状态。希望这可以帮助您!(https://github.com/reactjs/redux/issues/585)

当我尝试像你一样使用 Object.assign({}, object) 时,它仍然没有起作用。所以当我发现这个时:

Object.assign 只能进行浅拷贝。(https://scotch.io/bar-talk/copying-objects-in-javascript

然后我明白了我必须这样做:JSON.parse(JSON.stringify(object))

或只需这样: {...object}

对于数组: [...theArray]

希望这可以帮助您。


1
我尝试使用Lodash的克隆函数,但它没有起作用。相反,使用{...object}就解决了问题。 - John
var tempArray = imageArrayState tempArray.push(5) setImageArray(tempArray) 变量tempArray等于imageArrayState tempArray.push(5) setImageArray(tempArray) - Joe Giusti
这应该是被接受的答案。 - Nishant Kumar
{...对象}正是我一直在寻找的。现在一切都能够正常运行了! - snowskeleton
它起作用了。我之前设置状态是setShipments(arr);,但在我将其更改为setShipments([...arr]);之前,屏幕上什么也没有显示出来。 - Sami Ullah

31
因为您没有更改引用,所以 React 的浅比较未检测到更新。 我将使用博客文章的简单示例。在您的 reducer 中,您可能正在执行以下操作:
case FETCH_NEW_POSTS
    let posts = state.posts;
    posts.push(action.payload.posts);
    return {
        ...state, 
        posts
    };

相反,你必须做如下操作:

case FETCH_NEW_POSTS
    let posts = [...state.posts]; // we're destructuring `state.posts` inside of array, essentially assigning the elements to a new array.
    posts.push(action.payload.posts);
    return {
        ...state, 
        posts
    };

根据您的使用情况,Object.assign() 或 lodash 的 clone/deepclone 可能更符合惯用语。


谢谢,这正是问题所在,应该是最佳答案。 - Sean
1
只是重新表述一下,在第一种情况下,您正在将其推送到数组中,这会修改它,但数组在内存中保持相同的引用。因此,当您返回帖子时,仍然返回相同的数组,因此状态不会更新。 在第二种情况下,您正在创建数组的副本。 - Scruffy
这个解决方案应该在顶部,它是完美的解决方案。 - Akhil Aravind
1
@dillon.harless,不,JS中的对象是引用类型。在第一个代码块中,您将“posts”设置为与state.posts相同的引用。由于React的浅比较注意到相同的对象指针,因此它不会被更新。在此处查看更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects,特别是底部的“比较对象”部分。 - Nathanael
这个答案仍然有效于2020年12月7日。我花了好几个小时来调试它。 - David
显示剩余2条评论

8

componentWillUpdate 方法会将新的 Props 作为参数传入。此时,this.props 还是旧的 Props。请按以下方式调整你的方法:

void componentWillUpdate(nextProps, nextState) {
    L.geoJson(nextProps.data).addTo(this.map);
}

嗨,Brandon,感谢您的回复。该函数似乎仍未被调用。在我的三个操作中,请求、接收和获取,我已经记录到控制台,并且一切都似乎很顺利。我还在减速器中进行了一些记录,状态肯定会发生变化。 - Jacob Mason
你需要展示你的操作代码以及分发这些操作的代码。 - Brandon
嗨,Brandon,我刚发现了一些奇怪的东西,可能会有所帮助。请查看问题以获取额外细节。 - Jacob Mason
啊,我想我解决了它...在componentDidUpdate()期间数据似乎是存在的,但在componentWillUpdate()期间不是。这样做有意义吗?毕竟你需要更新前的新数据,而不是更新后的数据。 - Jacob Mason

0

在Marina的回答基础上,继续进行编程

var tempArray = imageArrayState
tempArray.push(5)
setImageArray(tempArray)

var tempArray = []
imageArrayState.foreach(value => {tempArray.push(value)})
tempArray.push(5)
setImageArray(tempArray)

让我的应用程序刷新


0

如果你通过地图文件将props传递给组件,请确保在地图文件中监听该store。

export const listenToStores = [CommonStore, store];
@connection(maps.listenToStores, maps.getStateFromStores)

这似乎是我场景的答案,但connection函数从哪里导入的? - MikeyE

0
在我的情况下,我遇到了问题,就像问题中提到的那样,我使用新数组创建并使用扩展运算符来解决它,而不是使用数组推送方法。 错误:rows.push(row)
const onAddRow = useCallback(rowIndex => {
        const rows = props.managefunds
        const row = rows.find((row, index) => {
            return rowIndex === index
        })
        rows.push(row) // while directly push in to array, page is not refreshing
        props.handleSubAccount({label: 'subAccount', value: rows })
    }, [ props.managefunds ])

解决方案: [ ...rows, row ]

const onAddRow = useCallback(rowIndex => {
        const rows = props.managefunds
        const row = rows.find((row, index) => {
            return rowIndex === index
        })
        props.handleSubAccount({label: 'subAccount', value: [ ...rows, row ]}) // while create new array in the following way, the page is refreshing properly
    }, [ props.managefunds ])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接