在 Redux thunk 中创建的数组未正确传递到状态树

Array created in a Redux thunk is not being passed properly to the State tree

我正在构建一个 React/Redux 项目,该项目对 S3 存储桶进行几次调用以抓取图像,然后将它们渲染到应用程序中。

我发现由于某些原因,我无法遍历我在状态树中设置的数组,因为这些调用将它们呈现到应用程序上。我相信我可能正在处理一个 类数组对象 或我创建的 Promises 中的某些东西导致了这种突变的发生。

首先,我将这个文件放在名为 utils:

的文件夹中
const bucketName = 'foo';
const roleARN = 'arn:aws:s3:::foo';
const identityPoolId = 'us-west-2:anActualIdHere';

AWS.config.update({
  region: 'us-west-2',
  credentials: new AWS.CognitoIdentityCredentials({
    IdentityPoolId: identityPoolId
  })
});

const bucket = new AWS.S3({
  params: {
    Bucket: bucketName,
  }
});

const textDecoder = new TextDecoder('utf8');

export function listObjects (callback) {
  return bucket.listObjects((error, data) => {
    if (error) {
      console.error('error: ', error);
      return;
    }

    callback(data.Contents);
  });
}

export function getSingleObject (key) {
  let getObject = new Promise((resolve, reject) => {
    bucket.getObject({
      Bucket: bucketName,
      Key: key
    }, (error, data) => {
      if (error) {
        console.error('error: ', error);
      }

      resolve(data.Body.toString('base64'));
    });
  })

  return getObject.then((result) => {
    return result;
  })
}

这里发生的是 listObjects 将 return 特定 S3 存储桶中所有项目的数组。

然后,getSingleObject 函数根据所有项目列表中提供的键获取单个对象的内容,获取其 Uint8Array 并将其转换为 base-64 字符串。

这两个函数在 thunk 操作中被调用:

import { listObjects, getSingleObject } from '../utils/index.js';

export function loadPhotos () {
  return function (dispatch) {
    listObjects((results) => {
      let photos = [];

      let mapPhotosToArray = new Promise((resolve, reject) => {
        results.forEach((singlePhoto) => {
          let getSinglePhoto = new Promise((resolve, reject) => {
            resolve(getSingleObject(singlePhoto.Key));
          });

          getSinglePhoto.then((result) => {
            photos.push(result);
          });
        });
        resolve(photos);
      })

      mapPhotosToArray.then((result) => {
        dispatch(savePhotos(result));
      });
    });
  }
}

function savePhotos (photos) {
  return {
    type: 'GET_PHOTOS',
    photos
  }
}

loadPhotos 是暴露给我的 Redux 容器的 thunk 动作。它 return 是一个函数,它首先调用 utils 文件中的 listObjects 函数,向它传递一个回调,创建一个名为 photos 的数组。

然后,它创建一个新的 Promise,循环遍历 return 由 listObjects 效用函数编辑的结果数组。在此结果数组的每次迭代中,我实例化另一个调用 getSingleObject 实用程序的新 Promise。

在那次迭代中,我将 getSingleObject 的结果推送到 photos 数组中,该数组是在传递给 listObjects 的回调中创建的。

我在 loadPhotos thunk 操作中做的最后一件事是调用外部 Promise,然后将结果分派给 savePhotos 操作,最终将有效负载对象分派到我的 reducer 的存储中赶上。

这是我的 reducer 的样子:

const defaultState = {
  photos: []
}

const photos = (state = defaultState, action) => {
  switch (action.type) {
    case 'GET_PHOTOS':
      return Object.assign({}, state, {
        photos: action.photos
      })
    default:
      return state;
  }
};

export default photos;

以下是我设置应用程序入口点的方式:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, compose, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

import photos from './reducers/';

import App from './components/app';

import styles from './styles/styles.css';

const createStoreWithMiddleware = compose(applyMiddleware(thunk))(createStore);

const store = createStoreWithMiddleware(photos, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

ReactDOM.render(
  <Provider store={ store }>
    <App />
  </Provider>,
  document.getElementById('root')
)

这是呈现的 App 组件:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

import AllPhotos from '../containers/AllPhotos';
import Navbar from './Navbar';

import '../styles/styles.css';

export default class App extends Component {
  constructor (props) {
    super (props);
  }

  render () {
    return (
      <div className="app">
        <Navbar />
        <AllPhotos />
      </div>
    )
  }
}

这是它呈现的 AllPhotos 容器:

import { connect } from 'react-redux';

import AllPhotos from '../components/AllPhotos';

import {
  loadPhotos
} from '../actions/index.js';

const mapDispatchToProps = (dispatch) => {
  return {
    loadPhotos: () => {
      dispatch(loadPhotos());
    }
  }
}

const mapStateToProps = (state) => {
  return {
    photos: state.photos
  }
}

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

最后,这是 AllPhotos 组件:

import React, { Component } from 'react';
import _ from 'lodash';

export default class AllPhotos extends Component {
  constructor(props) {
    super(props);
  }

  componentWillMount () {
    this.props.loadPhotos();
  }

  render () {
    return (
      <div>
        <h1>Test</h1>
        { this._renderImages() }
      </div>
    )
  }

  _renderImages () {
    _.map(this.props.photos, (image) => {
      return (
        <img
          src={ 'data:image/png;base64,' + image }
          width={ 480 }
        />
      )
    })
  }
}

这是我尝试在 _renderImages 中记录 this.props.photos 时发生的情况:

第一个日志发生在照片数组被填充并加载到我的状态之前。

但是,如果我在记录数组本身的同一区域中记录 this.props.photos 的长度,这就是我所看到的:

当我在同一行的 this.props.photos 上调用 Array.isArray 时,这就是我得到的:

我也曾尝试使用 Array.from 将其转换为数组,但没有成功。

再深入一点,我试图从我的 reducer 中的操作负载中找到 photos 数组的长度,但仍然收到 0 作为输出。我也在 savePhotos 操作中尝试了这个,发现了相同的结果。

因此,我相信我可能没有正确编写我的 Promise。有人可以帮我指明正确的方向吗?

您可以尝试将 listObjectsgetSingleObject 重写为 return Promise 并利用 Promise.all

那你可以写loadPhotos

export function loadPhotos () {
  return function (dispatch) {
    listObjects().then((results) => 
      Promise.all(results.map((singlePhoto) => getSingleObject(singlePhoto.Key)))
        .then((result) => dispatch(savePhotos(result)))
    )
  }
}