问题描述
我正在构建一个基于react redux的文件管理器webui(我的目的是通过这个项目掌握react和redux)
如你所知,文件管理员需要一个树资源管理器。我想构建一个可以自己包含它的组件,每个组件都有自己的状态。如下所示:
TreeNode
可以包含 TreeNode
too.Each TreeNode
保持其状态 {path,children_nodes,right .....}
, children_nodes
是从服务器获取的,路径
由父服务器传递。这就是我的想象。
结构如:
App:
TreeNode
--TreeNode
- --- TreeNode
---- TreeNode
TreeNode
TreeNode
--TreeNode
TreeNode
--TreeNode
---- TreeNode
---- TreeNode
但麻烦来了,因为redux 连接
存储到树根,root下的所有节点都接收相同的状态...
例如,我有一个 OPEN_NODE
操作,它设计用于触发 getFileList fucntion
基于此节点的路径和将此节点的 state.open
设置为 true
。(注意: getFileList fucntion
尚未实现,暂时只提供假数据)
屏幕截图:
点击每个元素,但州等于
。
我的鳕鱼e:
containers / App.js
import React,{ Component,PropTypes}来自'react';来自'redux'的
import {bindActionCreators};来自'react-redux'的
import {connect};
从'../components/Footer'导入页脚;
从'../containers/TreeNode'导入TreeNode;
从'../containers/Home'导入主页;
import *作为来自'../actions/NodeActions'的NodeActions
导出默认类App扩展组件{
componentWillMount(){
//这将更新状态
上的节点this.props.actions.getNodes();
}
render(){
const {nodes} = this.props
console.log(节点)
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>
);
}
}
函数mapStateToProps(state){
return {
nodes:state.opener.nodes,
open:state。 opener.open
};
}
函数mapDispatchToProps(dispatch){
return {
actions:bindActionCreators(NodeActions,dispatch)
};
}
导出默认连接(
mapStateToProps,
mapDispatchToProps
)(App);
containers / TreeNode.js
从'react'中导入React,{Component,PropTypes}
从'redux'导入{bindActionCreators}
从'react-redux'导入{connect}
从'classnames / bind'导入classNames
导入*作为来自'../actions/NodeActions'的NodeActions
导出默认类TreeNode扩展Component {
构造函数(道具,上下文){
super(道具,上下文)
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 = {()=& gt; {this.handleClick()}}>
< a> {info.name}< / a>
{nodes&&
< div> {nodes.map(node =>< TreeNode info = {node} />)}< / div>
}
{!nodes&&
< div>没有孩子< / div>
}
< / div>
);
}
}
TreeNode.propTypes = {
open:PropTypes.bool,
info:PropTypes.object.isRequired,
nodes :PropTypes.array,
操作:PropTypes.object.isRequired
}
actions / NodeActions.js
从'../constants/NodeActionTypes'导入{OPEN_NODE,CLOSE_NODE,GET_NODES};
导出函数openNode(){
return {
type:OPEN_NODE
};
}
导出函数closeNode(){
return {
type:CLOSE_NODE
};
}
导出函数getNodes(){
返回{
类型:GET_NODES
};
}
reducers / TreeNodeReducer.js
从'../constants/NodeActionTypes'导入{OPEN_NODE,CLOSE_NODE,GET_NODES};
const initialState = {
open:false,
nodes:[],
info:{}
}
const testNodes = [
{name:'t1',type:'t1'},
{name:'t2',type:'t2'},
{name:'t3' ,键入:'t3'},
]
函数getFileList(){
return {
nodes:testNodes
}
}
导出默认函数开启器(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
};
默认值:
返回状态;
}
}
有关完整代码,请参阅我的github
组件
class Tree extends Component {
构造函数(道具){
super(道具);
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(文件){
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;
返回(
< div className =row>
< h3> {fileName}< / h3>
< div className = col-md-12>
< table className =table table-hover table-bordered>
< tbody>
{isEmpty(treeFile.tree)?< tr>加载< / tr>:treeFile.tree.map(this.renderList)}
< / tbody>
< / table>
< / div>
< / div>
)
}
}
导出默认树;
行动
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);
}
返回状态;
}
const getTree =(state,action)=> {
const {tree} = action;
返回树;
}
有关完整代码,您可以在github上查看我的存储库
I am building a file manager webui base on react redux(My purpose is to master react and redux through this project)
As you know, a file manager need a tree explorer.I want to build a component which can contain it self and each has self state. like below:
TreeNode
can contain children which are TreeNode
too.Each TreeNode
hold its state {path, children_nodes, right .....}
, children_nodes
is get from server, path
is passed by parent. That's what I imagine.Struct like:
App:
TreeNode
--TreeNode
----TreeNode
----TreeNode
TreeNode
TreeNode
--TreeNode
TreeNode
--TreeNode
----TreeNode
----TreeNode
But trouble come here, because redux connect
store to the tree root, all node under the root receive same state...
For example, I have a OPEN_NODE
action, which is design to trigger getFileList fucntion
base this node's path and set this node's state.open
to true
.(note: getFileList fucntion
not implement yet, just give fake data for now)The screen shot:
Click each element , but states are equal
.
My code:
containers/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);
containers/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;
}
}
For complete code, see my github https://github.com/eromoe/simple-redux-boilerplate
I don't see an example cover such component, and google result nothing helpful.Any idea to overcome this?
update:I see this How to manage state in a tree component in reactjs
But the solution is pass the whole tree to state, can not use in file manager.
I'm implementing a Github like app using React and Redux.
For now, I only list repositories and show its files as well as navigate through them.
I don't know if this is considered a good or bad practice, but this is how I implemented my Tree component.
Inside each Tree Component, I have a Link to itself. And I pass some data on the route, so I'm able to get the next tree when I render it.
Component
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;
Action
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));
});
}
}
Reducer
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;
}
For the complete code, you can check my repository on github
https://github.com/glundgren93/Github-redux
这篇关于如何在react redux中实现自包含组件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!