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 部分进行了讨论。
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 部分进行了讨论。