Redux Observable、React、componentDidMount 从 API 获取无法从 objects 获取属性

Redux Observable, React, componentDidMount fetch from API cannot get properties from objects

import { fetchPosts  } from '../actions';
import React, {Component} from 'react';
import {bindActionCreators, dispatch} from 'redux';
import {connect} from 'react-redux';
import PropTypes  from 'prop-types';
import map from 'lodash/fp/map';
import flatten from 'lodash/fp/flatten';
import sortBy from 'lodash/fp/sortBy';
import compose from 'lodash/fp/compose';
import take from 'lodash/fp/take';
import _ from 'lodash';


class PostsIndex extends Component {

state = {
    posts: []
};

    componentDidMount() {

        this.props.fetchPosts();


    }

    displayPosts () {
        //console.log(post) works, console.log(post.title) returns undefined.
        return  _.map(this.props.posts, (post)=>{debugger;console.log(post.title);});
    }

    render() {
        if(this.props.posts.length === 0) {
            return (<div>Loading...</div>);
        }
        return (<ul>{this.displayPosts()}</ul>);
    }
}

function mapStateToProps(state) {
    return {
        posts: (state.posts) ? state.posts : []
    };
}

function mapDispatchToProps (dispatch) {
    return  bindActionCreators({fetchPosts: fetchPosts}, dispatch);
   }

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

我可以在

中做到console.log(post)
    displayPosts () {
        //console.log(post) works, console.log(post.title) returns undefined.
        return  _.map(this.props.posts, (post)=>{debugger;console.log(post.title);});
    }

但是如果我尝试 console.log(post.title) 我会得到未定义的结果。

同样,如果我尝试做类似的事情:

    displayPosts () {
        //console.log(post) works, console.log(post.title) returns undefined.
        return  _.map(this.props.posts, (post)=>{return <div>post.title</div>;});
    }

我什么也没得到。

在这里您可以看到 console.log() 的结果。 前者来自史诗后者来自组件

这里有一个 link 我打开的回购: https://github.com/Deviad/redux-router-playground

问题实际上出在你的 reducer 中(从你的链接 repo 中找到):

export default function postsReducer(state=[], action) {
    switch (action.type) {
        case ActionTypes.FETCH_POSTS_FULLFILLED:
            // THIS IS THE PROBLEM:
            return [
                ...state,
                action.payload
            ];

        default:
            return state;
    }
};

action.payload 已经是一个数组,但是你把它放在另一个数组里面,所以是一个数组的数组。因此,当您映射 this.props.posts 时,您只会得到 1 个结果,即实际的帖子数组。当您记录日志时,您可能只是没有注意到。

// THIS IS THE PROBLEM:
return [
    ...state,
    action.payload
];

相反,您可以直接return数组原样:

return action.payload;

虽然上述解决方案是 IMO 可以接受的(老实说,传送代码是第一优先级),但这实际上仍然不是使用 redux 的最惯用方式。相反,将 redux 视为数据库。您将如何在数据库中存储(又名规范化)这些内容?如果你回答 "indexed by ID" 你是对的!

所以你的 redux 状态可能看起来像这样:

{
    posts: {
        '123': {
            id: '123',
            title: 'first title'
        },
        '456': {
            id: '456',
            title: 'second title'
        }
    }
}

您可以采用以下一种方法:

export default function postsReducer(state = {}, action) {
    switch (action.type) {
        case ActionTypes.FETCH_POSTS_FULLFILLED:
            return action.payload.reduce((acc, post) => {
                acc[post.id] = post;
                return acc;
            }, { ...state });
            // use Object.assign if object-spread
            // syntax isn't supported

        default:
            return state;
    }
};

因为这种变体很常见,嵌套很多次,有些人会选择使用 normalizr 库:

import { normalize, schema } from 'normalizr';

const postSchema = new schema.Entity('posts');

export default function postsReducer(state = {}, action) {
    switch (action.type) {
        case ActionTypes.FETCH_POSTS_FULLFILLED:
            // use Object.assign if object-spread
            // syntax isn't supported
            return {
                ...state,
                ...normalize(action.payload, [postSchema]).entities.posts
            };
// etc

由于在您的情况下您需要将它们作为 UI 容器中的帖子数组返回,因此它也具有用于非规范化的实用程序,或者如果支持,您可以使用 Object.values 来实现:

function mapStateToProps(state) {
    return {
        posts: Object.values(state.posts)
    };
}

normalize() 还调用 return 一个 ID 数组,您可能会发现它对存储在 redux 中的某个地方很有用,或者您可以在需要时按需使用 Object.keys(state.posts)它——这就是我所做的,因为它可以防止同步问题,但如果项目数量很大,出于性能原因,它并不总是实用。当它发生时,我会担心这个问题。

您可能想知道,如果我们只是要再次对其进行非规范化,我们到底为什么要这么麻烦;完整的答案是冗长的,但简短的要点是一致性、未来状态更新的便利性以及对以后视图的快速 "post by ID" 查找。这是使 redux 伟大的一个主要部分,否则它主要变成一个时间旅行的美化 getter/setter,这几乎不值得样板。

这在 redux 文档的 Normalizing State Shape 部分进行了讨论。