react-redux 初始化 获取 数据,并生成元素加入到页面上

2016-06-20 12:32:02 +08:00
 eromoe

先吐槽一下 V2EX 没有保存草稿的功能,因为挂代理上的,发布的时候代理抽风了,结果写了半天全没了。。。。

言归正传,我这 2,3 周零零碎碎的在看 react,redux,route 想从大项目里一下子把这一揽子全部搞懂(周边还有什么 webpack es6 只弄懂基本, js 的很多原生方法都没搞清楚,比如在 ajax 后面加 bind 之类的的) 看了很多例子,官方教程也看了,发现好多例子写法都不同,在 ajax 上就更不同了。。。虽然有些都是用什么 thunk 但是写的东西感觉不一样。。。小项目初始化都是直接 给 state/props 一个值,然后在 render 里直接传进去,大项目写的我根本看不懂在哪里拿的数据。。。。

现在想自己个项目来弄懂这些问题,准备弄个 bypy 的 file manager webui , 结合了 2 个我觉得比较能看懂的项目(一个 redux demo 一个 react route 的 demo )

搞了半天,route怎么都显示不出来。。。所以先去掉了。。。先搞初始化 因为 file manager 所有点击的节点都是一样的,我想做一个自包含的 componnent ,但是由于初始化传参的问题,感觉怎么写都不对。。。所以想先把这个搞懂,就做了个最简单: App 显示根目录下的所有节点(TreeNode)

但是碰到了个错误 Uncaught TypeError: Cannot read property 'nodes' of null 在 App.render 的地方报错的

主要代码如下

containers/App.js:

import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {getFileList} from '../actions/NodeActions'
import Footer from '../components/Footer';
import TreeNode from '../containers/TreeNode';
import Home from '../containers/Home';


export default class App extends Component {

  componentDidMount() {
    let nodes = getFileList();
    this.setState({
      nodes: nodes
    });
  }

  render() {
    const { actions } = this.props;
    const { nodes } = this.state;
    return (
      <div className="main-app-container">
        <Home />
        <div className="main-app-nav">Simple Redux Boilerplate</div>
        {nodes.map(node =>
          <TreeNode key={node.name} node={node} {...actions}  />
        )}
        <Footer />
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    test: state.test
  };
}


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

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

actions/NodeActions.js:

import { OPEN_NODE, CLOSE_NODE } from '../constants/ActionTypes';

export function openNode() {
  return {
    type: OPEN_NODE
  };
}

export function closeNode() {
  return {
    type: CLOSE_NODE
  };
}

class NodeModel {
    constructor(name, path, type, right) {
        this.name = name;
        this.path = path;
        this.type = type;
        this.right = right;
    }
}

const testNodes = [
  new NodeModel('t1','t1', 'd', '777'),
  new NodeModel('t2','t2', 'd', '447'),
  new NodeModel('t3','t3', 'd', '667'),
]

export function getFileList() {
  return {
    nodes: testNodes
  }
}

export function ansyncGetFileList() {
  return dispatch => {
    setTimeout(() => {
      dispatch(getFileList());
    }, 1000);
  };
}

1.我觉得可能是 testNodes 不能那么初始化,就直接改成了

const testNodes = [
  {name:'t1',type:'t1'},
  {name:'t2',type:'t2'},
  {name:'t3',type:'t3'},
]

没效果

2.可能是 scope 的原因,就把 testNodes 移到 getFileList 里,还是同样的错。

真的 have no idea.

13431 次点击
所在节点    React
13 条回复
spritevan
2016-06-20 13:11:16 +08:00
let nodes = getFileList();
// getFileList = () => { nodes: [...] }
// this.state.nodes = { nodes: [] }

App.render() 里面应该要用 `nodes.nodes.map()` 吧?
yesmeck
2016-06-20 13:19:07 +08:00
```javascript
// state 一个初始值
// 因为 componentDidMount 在 render 后执行,所以第一次 rander 的时候, state 还没 nodes
constructor(props) {
super(props)

this.state = {
nodes: []
}
}
```

```javascript
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(getFileList, dispatch) // getFileList 并不是个 action creator ,不需要 bind
};
}
```
eromoe
2016-06-20 14:00:54 +08:00
@spritevan 噢!谢谢指出, 这里是不是用 var { xx } = xxx; 或者 {...nodes} 更好?

@yesmeck
加了 constructor ,我在想是不是能在 constructor 里直接赋初始值,但是好像又在哪里看到 react 不建议这么做,又好像没有。。。
代码改成下面这样
1.
```
render() {
const { actions } = this.props
const { nodes } = this.state
console.log(nodes)
return (
<div className="main-app-container">
<Home />
<div className="main-app-nav">Simple Redux Boilerplate</div>
{nodes.nodes.map(node =>
<TreeNode key={node.name} node={node} {...actions} />
)}
<Footer />
</div>
);
}
```
log 出来如下,而且页面不显示

[]

错误:App.js?4495:35 Uncaught TypeError: Cannot read property 'map' of undefined

2.

```
render() {
const { actions } = this.props
const { nodes } = this.state
console.log(nodes)
return (
<div className="main-app-container">
<Home />
<div className="main-app-nav">Simple Redux Boilerplate</div>
{nodes.map(node =>
<TreeNode key={node.name} node={node} {...actions} />
)}
<Footer />
</div>
);
}
```
不用 nodes.nodes.map ,会 log 2 遍,然后显示出页面 ,并报错

[]
Object {nodes: Array[3]}

错误 : App.js?4495:35 Uncaught TypeError: nodes.map is not a function

3. 修改赋值部分
```
componentDidMount() {
let {nodes} = getFileList()
this.setState({
nodes: nodes
})
}
```
使用 2. 原始的 render 就不会出错,按理来说
var { nodes} = getFileList();
nodes.map

应该等于
var nodes = getFileList();
nodes.nodes.map

好奇怪。。。
yesmeck
2016-06-20 14:07:06 +08:00
评论代码不能高亮太难看了,可以的话你把代码放 github 上我可以帮你看看。
eromoe
2016-06-20 14:23:18 +08:00
eromoe
2016-06-20 14:36:01 +08:00
一开始没想到,只要下面这么简单就行了,因为总是觉得 reducer 只是用来更新的,不能初始化,又戳破了一个盲点~

componentWillMount() {
// this will update the nodes on state
this.props.getFileList();
}

render() {
// will be re-rendered once store updated
const {nodes} = this.props;
// use nodes
}

function mapStateToProps(state) {
return {
nodes: state.nodes
};
}

export default connect(
mapStateToProps,
{ getFileList: ansyncGetFileList }
)(App);
yesmeck
2016-06-20 14:54:55 +08:00
@eromoe github 上回复你了
eromoe
2016-06-20 15:11:55 +08:00
@yesmeck 非常感谢! 我刚刚也是发现能直接用这个 this.props.getFileList(); 然后豁然开朗, 现在感觉都串起来了~谢谢~
eromoe
2016-06-20 17:24:16 +08:00
@yesmeck
又发现个问题。。。 我想把 node 的信息显示出来
App.render:
```
{nodes.map(node =>
<TreeNode key={node.name} info={node} />
)}
```

在 TreeNode.render:
```
const { actions, nodes, info } = this.props
return (
<a>{info.name}</a>
);
```

貌似 info 没有传进去。。。 log 显示是 undefined


warning.js?8a56:45 Warning: Failed propType: Required prop `info` was not specified in `TreeNode`. Check the render method of `Connect(TreeNode)`.

TreeNode.js?10ab:57 Uncaught TypeError: Cannot read property 'name' of undefined


google 了一圈 没看到问类似问题的,都是什么 parent 传数据到 child 的问题,他们的 child 都是已经存在的。。。
如果这些信息没办法判断问题的话, 我也更新了 gitbub
yesmeck
2016-06-20 17:29:18 +08:00
没看到 github 有更新
eromoe
2016-06-20 17:33:38 +08:00
@yesmeck Oh! 不好意思。。。换了台机子, ssh 有 passphrase , git push 之后就没注意。。。
eromoe
2016-06-20 20:25:15 +08:00
@yesmeck 找到问题了~ 我在 TreeNode 下面也写了 connect , 原来所有子元素都是需要继承父元素的啊,除非是不同的根元素,否组不能加 connect 。
ericls
2016-06-20 20:26:42 +08:00
建议用 thunk

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/286995

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX