如何允许子组件在父组件之前对路由事件做出反应?

时间:2016-10-30 16:29:41

标签: javascript reactjs redux react-router

我正在使用react,react-router&终极版。我的应用程序的结构是这样的:

CoreLayout
  -> <MaterialToolbar /> (contains back button)
  -> {children} (react-router)

当用户按下后退按钮(通常由CoreLayout处理)时,我希望当前子组件处理后退按钮而不是父按钮。 (在我的情况下,我希望当前视图检查其数据是否已被修改,并在实际返回之前弹出“你确定要取消吗?”框。)如果孩子不想处理这个问题,父母会这样做。

另一个例子是允许childview在工具栏中设置标题。

我的阅读告诉我,通过ref访问组件并在其上调用方法不是反应方式 - 由于我使用redux-connect,这也变得有点困难。实现此行为的正确方法是什么?

2 个答案:

答案 0 :(得分:0)

我就是这样做的,假设你的意思是导航后退按钮(而不是浏览器后退按钮):

class CoreLayout extends Component {

    handleBack () {
        //... use router to go back
    }

    render () {
        return <div>
            <MaterialToolbar />
            {React.children.map(this.props.children, child => React.cloneElement(child, { onBack: this.handleBack }))}
        </div>  
    }
}

class Child extends Component {

    handleBackButtonClick () {
        // Here perform the logic to decide what to do 
        if (dataHasBeenModifiedAndConfirmed) {
            // Yes, user wants to go back, call function passed by the parent
            this.props.onBack()
        } else {
            // User didn't confirm, decide what to do
        }
    }


    render () {
        return <div onClick={this.handleBackButtonClick.bind(this)}>
            Go Back
        </div>
    }
}

您只需通过props将一个函数从父级传递给子级。然后在子代中,您可以实现逻辑以检查是否确实要将工作委托给父组件。

由于您使用react-router并且您的孩子通过this.props.children传递给您的父组件,要传递onBack功能,您需要映射孩子并使用React.cloneElement通过您的道具(如果您需要更多详细信息,请参阅此答案:React.cloneElement: pass new children or copy props.children?)。

修改

因为看起来你想让孩子们决定,你可以这样做(使用refs):

class CoreLayout extends Component {

    constructor () {
        super()
        this.childRefs = {};
    }

    handleBack () {
        for (let refKey in Object.keys(this.childRefs) {
            const refCmp = this.childRefs[refKey];
            // You can also pass extra args to refCmp.shouldGoBack if you need to
            if (typeof refCmp.shouldGoBack === 'function' && !refCmp.shouldGoBack()) {
                return false;
            }
        }

        // No child requested to handle the back button, continue here...
    }

    render () {
        return <div>
            <MaterialToolbar />
            {React.children.map(this.props.children, (child, n) => React.cloneElement(child, { 
                ref: cmp => { this.childRefs[n] = cmp; }
            }))}
        </div>  
    }
}

class Child extends Component {

    shouldGoBack () {
        // Return true/false if you do/don't want to actually go back
        return true
    }


    render () {
        return <div>
            Some content here
        </div>
    }
}

通常情况下,使用React它会更容易/更容易拥有一个&#34; smart&#34;根据状态决定的父级,但考虑到你的具体情况(父级的后退按钮和子级中的逻辑)并且没有重新实现其他一些东西,我认为使用ref这种方式很好。

或者(使用Redux)作为建议的另一个答案,您需要从可以在父级中使用的子级设置Redux状态的某些东西来决定要做什么。

希望它有所帮助。

答案 1 :(得分:0)

我认为没有正确的方法来解决这个问题,但有很多种方式。如果我正确理解您的问题,大多数情况下后退按钮onClick处理程序将在CoreLayout内处理,但是当呈现特定子项时,该子项将处理onClick事件。这是一个有趣的问题,因为更改后退按钮功能的能力需要全局可用,或至少在CoreLayout和特定子组件中可用。

我没有使用过redux,但是我使用过Fluxible,并熟悉Flux架构和pub / sub模式。

也许您可以利用您的redux商店来确定后退按钮的功能。并且您的CoreLayout组件将处理呈现提示。下面的代码有一个错误,但我想我不会删除我的答案,以便让你知道我在说什么,希望下面的代码可以做到这一点。您需要仔细考虑逻辑才能使其正常工作,但这个想法就在那里。使用商店确定后退按钮的功能。

//Core Layout

componentDidMount() {
    store.subscribe(() => {
        const state = store.getState();

        // backFunction is a string correlating to name of function in Core Layout Component
        if(state.backFunction) {

            // lets assume backFunction is 'showModal'. Execute this.showModal()
            // and let it handle the rest.
            this[state.backFunction]();

            // set function to false so its not called everytime the store updates.
            store.dispatch({ type: 'UPDATE_BACK_FUNCTION', data: false})
        }
    })
}

showModal() {
    // update state, show modal in Core Layout
    if(userWantsToGoBack) {
        this.onBack();

        // update store backFunction to be the default onBack
        store.dispatch({ type: 'UPDATE_BACK_FUNCTION', data: 'onBack'})

    // if they don't want to go back, hide the modal
    } else {
        // hide modal
    }
}

onBack() {
    // handle going back when modal doesn't need to be shown
}

下一步是在子组件安装时更新您的商店

// Child component
componentDidMount(){

    // update backFunction so when back button is clicked the appropriate function will be called from CoreLayout
    store.dispatch({ type: 'UPDATE_BACK_FUNCTION', data: 'showModal'});
}

这样您就不必担心将任何函数传递给子组件,而是让商店的状态决定调用哪个函数{。}}。