2016-07-29 93 views
3

我無法弄清楚如何編寫ui窗口小部件縮減器並且同時對渲染樹進行反應,這樣我就可以稍後使用react-redux將縮減器的結果redux存儲庫映射到渲染樹中的道具。如何將redux存儲區中的封裝ui狀態映射到react-redux的道具?

假設像這樣的設置。我試圖做一個NameWidget,我可以在任何應用程序在任何地方使用:

NameView.jsx: 
... 
render() { 
    return <div> Name: {this.props.name} </div>; 
} 
... 

====

NameAction.js: 
... 
export const CHANGE_NAME = 'CHANGE_NAME'; 
... 
export function changeNameAction(newName) { 
    return { 
    type: CHANGE_NAME, 
    payload: newName, 
    }; 
} 

====

NameReducer.js: 

import { CHANGE_NAME } from './NameAction'; 

function ui(state = {name: ''}, action) { 
    switch(action.type) { 
    case(CHANGE_NAME): 
     return Object.assign({}, state, {name: action.payload}); 
     break; 
    default: 
     return state; 
    } 
} 

export default ui; 

====

NameWidget.js: 

import { connect } from 'react-redux'; 
import { NameView } from './NameView'; 

const mapStateToProps = (state) => { 
    return { 
    name: state.name, 
    }; 
}; 

const NameWidget = connect(
    mapStateToProps 
)(NameView); 

export default NameWidget; 

如果我直接使用NameWidget,我將沒有問題em:

NameApp.jsx: 

import { createStore } from 'redux'; 
import app from './NameReducers'; 
let store = createStore(app); 
... 
function render() { 
    return (
    <Provider store={store}> 
     <NameWidget/> 
    </Provider> 
); 
} 

但是,我沒有看到如何使這個模塊化。我實際上無法將這個Widget放入其他任何東西中。假設我想這樣做:

EncapsulatingView.jsx: 
... 
render() { 
    return (
    <div> 
     <NameWidget/> 
     <SomeOtherReduxWidget/> 
    </div> 
); 
} 
... 

====

EncapsulatingReducer.js: 
import { combineReducers } from 'redux' 
import nameWidget from '../name/NameReducer'; 
import someOtherWidget from '../someOther/SomeOtherReduxWidgetReducer'; 

export default combineReducers({nameWidget, someOtherWidget}); 

(我預計剩餘的代碼,包括connect()和createStore()用於封裝*是顯而易見的。)

幾乎工作,只要所有封裝的視圖中的所有操作具有唯一類型。但問題是NameWidget的mapStateToProps;它需要從上面改爲使用新的路徑,以它的商店的部分:

... 
const mapStateToProps = (state) => { 
    return { 
    name: state.nameWidget.name, 
    }; 
}; 
... 

等等 - 在某種程度上取決於祖先combineReducers如何()來定義其不斷宏偉的超級小工具和應用程序,後裔必須改變mapStateToProps()的補償方式。這打破了代碼的封裝和可重用性,我看不到如何在react-redux中解決它。我怎樣才能通過組合combineReducers()來定義小部件,然後將結果存儲映射到這些小部件的道具上,這種方式在任何地方都是一次寫入使用的?

(沒有任何答案,似乎很奇怪重複的combineReducers()會創建一個自下而上的形狀,但是mapStateToProps()似乎會採用自頂向下的「絕對路徑」方法來查找該形狀。

+0

你打算在這個出口代碼到另一個項目,或者您是否打算在同一個項目的另一個設置中重新使用該小部件? – Gennon

+0

短期是項目特定的,但如果有一個好的模式/解決方案,希望它會支持這兩種。 – jdowdell

+0

你有沒有看過[redux-form](http://redux-form.com/5.3.1/#/getting-started),他們使用一個定義的名稱(表單)作爲他們自己的root-reducer。這樣,組件將查看所有值的state.form。 – Gennon

回答

1

有趣的問題。我想知道它是否會滿足您的目的,要求由父級生成GUID並通過道具傳遞,然後您只需將其用作您的窗口小部件實例的狀態對象的索引。因此,無論何時您要創建此小部件的新實例,您都需要在父項中爲其創建一個ID,然後在道具中傳遞該ID,然後將其用於connect方法以用於mapStateToProps和mapDispatchToProps 。我想在理論上你甚至可以自己創建這個父組件,它在使用時可能是透明的,並且只需使用componentDidMount來生成一個新的GUID以存儲在組件狀態並傳遞給子組件。

+0

謝謝約翰!我曾想過類似的東西,但我不知道如何「在道具中傳遞這些東西」 - 你能詳細說明一下嗎?在我看來,connect()基本上是在我導入它返回的類型時調用的,而不是在我使用該類型時的render()中。我想我可以將connect()包裝在函數中,然後在render()過程中動態調用它,並將該函數傳遞給您提到的ID;但我不確定每個調用渲染動態生成的類型的影響...... – jdowdell

+0

所以我說你會有一個父組件,並在componentDidMount(實際上componentWillMount可能是更好的地方),你本質上調用'this.setState({childId:newGuid()})',然後在渲染方法中使用''來呈現子容器組件。通過容器組件,我只是指一個'redux only'組件,它是調用'connect'與你的實際子組件的結果。 – John

+0

然後在傳遞函數mapStateToProps和mapDispatchToProps的參數時,在你的連接函數中,在_those_函數中使用它們的第二個參數(它是組件的道具)來使用id。例如,您的連接功能可能如下所示:https://jsbin.com/joyodaxazi/1/edit?js – John

0

我給了約翰這個問題的功勞,因爲他基本上爲答案提供了正確的魔法,通過mapStateToProps()的ownProps函數arg傳遞商店「路由」邏輯。但我更喜歡自己的旋律。從本質上講,當你在EncapsulatingReducer.js combineReducers(),你要記住傳遞一個uiStorePath道具在EncapsulatingView.js:

New NameWidget.js: 

const _ = require('lodash'); 
import { connect } from 'react-redux'; 
import { NameView } from './NameView'; 

const mapStateToProps = (state, props) => { 
    const uiStore = (
    (ownProps && _.has(ownProps, 'uiStorePath') && ownProps.uiStorePath && 
     ownProps.uiStorePath.length) ? 
     _.get(state, ownProps.uiStorePath) : // deep get...old versions of lodash would _.deepGet() 
     state 
); 

    return { 
    name: uiStore.name, 
    }; 
}; 

const NameWidget = connect(
    mapStateToProps 
)(NameView); 

export default NameWidget; 

====

EncapsulatingView.js: 

... 
render() { 
    const uiStorePathBase = ((this.props.uiStorePath &&   
     this.props.uiStorePath.length) ? 
    this.props.uiStorePath + '.' : 
    '' 
); 
    return (
    <div> 
     <NameWidget uiStorePath={uiStorePathBase+"nameWidget"}/> 
     <SomeOtherWidget uiStorePath={uiStorePathBase+"someOtherWidget"/> 
    </div> 
); 
} 
... 
相關問題