【问题标题】:Redux Observable, React, componentDidMount fetch from API cannot get properties from objectsRedux Observable、React、componentDidMount 从 API 获取无法从对象获取属性
【发布时间】:2017-08-20 17:55:40
【问题描述】:
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) in

    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()的结果。 前者来自史诗,后者来自组件。

这里有一个指向我打开的存储库的链接: https://github.com/Deviad/redux-router-playground

【问题讨论】:

    标签: reactjs redux rxjs redux-observable


    【解决方案1】:

    问题实际上出在你的 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 action.payload;
    

    虽然上述解决方案是 IMO 可以接受的(老实说,运输代码是第一优先),但这实际上仍然不是使用 redux 最惯用的方式。相反,将 redux 视为数据库。你将如何在数据库中存储(又名标准化)这些?如果您回答“按 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() 调用还返回一个 ID 数组,您可能会发现它们对存储在 redux 中的某个地方有用,或者您可以在需要时按需使用 Object.keys(state.posts) -- 这就是我所做的,因为它可以防止同步问题,但如果项目数量很大,出于性能原因,它并不总是实用的。这是我在发生时会担心的问题。

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

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

    【讨论】:

    • @jayhelps 谢谢。你澄清了我不清楚的地方,还提供了一个很好的替代方法来使用 lodash 中的 _.mapKeys()。
    • @jayhelps,鉴于我对您的回答的整合,哪一个是迭代可迭代结构的最佳方式?我问这个是因为在某些语言中,考虑到性能问题,foreach 和其他一些 map 更好。例如,在 PHP 中,foreach 的性能可能比 5.6 上的 map 好。在其他语言中它是更好的地图。
    • 看起来最好的选择是第一个。在一小部分数据上,他们在同一时间执行相同的任务:在这种情况下为 7/10 秒。从内存消耗的角度来看,带有 Array.prototype.slice.call() 的后者已经占用了 10 MB 以上的数据,并且有一小部分数据进入。
    • 还有另一种不需要复制到变量中的解决方案:displayPosts () { return Object.values(this.props.posts).map((post)=>{ return ({post.title}
      ); }); }
  • 嘿@DavidePugliese 不客气,很高兴它有帮助!后续问题应作为新的 Stack Overflow 问题提出。
  • 猜你喜欢
    • 2019-12-23
    • 2019-04-17
    • 1970-01-01
    • 1970-01-01
    • 2017-11-05
    • 1970-01-01
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多