【问题标题】:How to implement a self contained component in react redux?如何在 React Redux 中实现自包含组件?
【发布时间】:2016-10-22 11:46:03
【问题描述】:

我正在基于react redux构建一个文件管理器webui(我的目的是通过这个项目掌握react和redux)

如您所知,文件管理器需要一个树浏览器。我想构建一个可以包含它自己并且每个都有自己状态的组件。如下:

TreeNode 也可以包含TreeNode 的子代。每个TreeNode 都保持其状态{path, children_nodes, right .....}children_nodes 是从服务器获取的,path 是由父代传递的。这就是我的想象。 结构如下:

App:
TreeNode
--TreeNode
----TreeNode
----TreeNode
TreeNode
TreeNode
--TreeNode
TreeNode
--TreeNode
----TreeNode
----TreeNode

但是麻烦来了,因为reduxconnect存储到树根,根下的所有节点接收到相同的状态...

例如,我有一个OPEN_NODE 动作,旨在触发getFileList fucntion 基于此节点的路径并将此节点的state.open 设置为true。(注意:getFileList fucntion 尚未实现,只需给出暂时的假数据) 屏幕截图:

点击每个元素,但states are equal

我的代码:

容器/App.js

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

export default class App extends Component {

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

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

        {/*<Footer />*/}
      </div>
    );
  }
}

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


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

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

容器/TreeNode.js

import React, { Component, PropTypes } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import classNames from 'classnames/bind'
import * as NodeActions from '../actions/NodeActions'

export default class TreeNode extends Component {

  constructor(props, context) {
    super(props, context)
    this.props = {
      open: false,
      nodes: [],
      info:{}
    }
  }

  handleClick() {
    let {open} = this.props
    if (open) {
      this.props.actions.closeNode()
    } else {
      this.props.actions.openNode()
    }
  }

  render() {
    const { actions, nodes, info } = this.props
    return (
      <div className={classNames('tree-node', { 'open':this.props.open})} onClick={ () => {this.handleClick()} }>
        <a>{info.name}</a>
        {nodes &&
          <div>{nodes.map(node => <TreeNode info={node} />)}</div>
        }
        {!nodes &&
          <div>no children</div>
        }
      </div>
    );
  }
}

TreeNode.propTypes = {
  open:PropTypes.bool,
  info:PropTypes.object.isRequired,
  nodes:PropTypes.array,
  actions: PropTypes.object.isRequired
}

actions/NodeActions.js

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

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

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


export function getNodes() {
  return {
    type: GET_NODES
  };
}

reducers/TreeNodeReducer.js

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

const initialState = {
  open: false,
  nodes: [],
  info: {}
}

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


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


export default function opener(state = initialState, action) {
  switch (action.type) {
  case OPEN_NODE:
    var {nodes} = getFileList()
    return {
      ...state,
      open:true,
      nodes:nodes
    };
  case CLOSE_NODE:
    return {
      ...state,
      open:false
    };
  case GET_NODES:
    var {nodes} = getFileList()
    return {
      ...state,
      nodes:nodes
    };
  default:
    return state;
  }
}

完整代码见我的githubhttps://github.com/eromoe/simple-redux-boilerplate

我没有看到涵盖此类组件的示例,并且谷歌结果没有任何帮助。 有什么办法克服这个问题吗?

更新: 我看到了这个How to manage state in a tree component in reactjs

但解决方案是将整个树传递给状态,不能在文件管理器中使用。

【问题讨论】:

标签: javascript reactjs components redux react-redux


【解决方案1】:

你所有的TreeNode 与redux 的状态相同,因为你的mapStateToProps 都是相同的。

mapStateToProps 可以将ownProps(被包装组件的props)作为第二个参数。你可以用它来区分你的TreeNodes。在您的情况下,path 是一个不错的选择。

考虑编写像getChildrenNodes(state, path) 这样的状态选择器并相应地返回节点。

您可能需要考虑先阅读 redux 文档,尤其是本节:http://redux.js.org/docs/recipes/ComputingDerivedData.html

【讨论】:

    【解决方案2】:

    我正在使用 React 和 Redux 实现一个类似 Github 的应用程序。

    目前,我只列出存储库并显示其文件以及浏览它们。

    我不知道这是一种好的做法还是坏的做法,但这就是我实现 Tree 组件的方式。

    在每个树组件中,我都有一个指向自身的链接。而且我在路线上传递了一些数据,所以当我渲染它时我能够得到下一棵树。

    组件

    class Tree extends Component {
      constructor(props) {
        super(props);
    
        this.renderList = this.renderList.bind(this);
      }
    
      componentWillMount() {
        this.props.getTree(this.props.params.sha);
      }
    
      componentWillReceiveProps(nextProps) {
        if(nextProps.params.sha !== this.props.params.sha) {
          this.props.getTree(nextProps.params.sha);
        }
      }
    
      renderList(file) {
        return (
          <tr key={ file.sha }>
            { file.type == 'tree'
           ? <td><Link to={`/repository/${this.props.params.repoName}/tree/${file.path}/${file.sha}`}>{ file.path }</Link></td>
           : <td><Link to={`/repository/${this.props.params.repoName}/blob/${file.sha}/${file.path}`}>{ file.path }</Link></td>}
          </tr>
        )
      }
    
      render() {
        const treeFile = this.props.tree;
        const fileName = this.props.params.path;
    
        return (
          <div className="row">
            <h3>{ fileName }</h3>
            <div className="col-md-12">
              <table className="table table-hover table-bordered">
                <tbody>
                  { isEmpty(treeFile.tree) ? <tr>Loading</tr> : treeFile.tree.map(this.renderList) }
                </tbody>
              </table>
            </div>
          </div>
        )
      }
    }
    export default Tree;
    

    动作

    const setTree = (tree) => {
      return {
        type: actionTypes.GET_TREE,
        tree
      };
    };
    
    export const getTree = (sha) => {
    
      return (dispatch, getState) => {
        const { repository, profile } = getState();
        const repo = GitHubApi.getRepo(profile.login, repository.name);
    
        repo.getTree(sha, function(err, data) {
          dispatch(setTree(data));
        });
      }
    }
    

    减速器

    const initialState = "";
    
    export const tree = (state = initialState, action) => {
      switch (action.type) {
        case actionTypes.GET_TREE:
          return getTree(state, action);
      }
      return state;
    }
    
    const getTree = (state, action) => {
      const { tree } = action;
      return tree;
    }
    

    完整代码可以查看我的github仓库

    https://github.com/glundgren93/Github-redux

    【讨论】:

      猜你喜欢
      • 2019-03-10
      • 1970-01-01
      • 2021-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-12
      相关资源
      最近更新 更多