React.js中的动态子导航(传播事件和属性)

Dynamic children navigation in React.js (propagating events and properties)

本文关键字:事件 传播 属性 导航 js 动态 React      更新时间:2023-09-26

在React.js中,我仍在努力解决的一件事是处理子事件和属性传播的正确方法。已经有很多这样的例子,但实现方式总是大不相同。当然,有一种"正确的方法"可以做到这一点…

任务是创建一个由"NavItem"组件组成的"Nav"组件。下面是我期望工作的代码,但它没有。这些评论解释了我的问题所在。请就最佳解决方案提出建议,并让我知道我做错了什么。

Nav.jsx

var Nav = React.createClass({
    propTypes: {
        active:         React.PropTypes.string,
        onSelect:       React.PropTypes.func
    },
    getInitialState: function () {
        return {
            active: this.props.active
        };
    },
    render: function () {
        return this.transferPropsTo(
            <nav>
                {this.props.children.map(this.renderChild)}
            </nav>
        );
    },
    renderChild: function (child, i) {
        // Here I want to generate a unique 'key' property for each child as well
        // as define a custom onSelect method which will tell the "Nav" component which is
        // the active key.
        return React.addons.cloneWithProps(child, {
            // Here I want to use the 'active' property of the Nav component to drive the
            // the 'active' property of the child.
            active: this.state.active === i ? true, false,
            key: i,
            onSelect: this.handleSelect
        });
    },
    handleSelect: function (event, component) {
        // PROBLEM: Here I want to get the 'key' property of the child. But there seems to be
        // no way to do this.
        var _child_key_ = 'impossible'; 
        this.setState({active: _child_key_});
    }
});

NavItem.jsx

var NavItem = React.createClass({
    propTypes: {
        active:     React.PropTypes.string,
        onSelect:   React.PropTypes.func
    },
    getInitialState: function () {
        return {
            active: this.props.active
        };
    },
    render: function () {
        return this.transferPropsTo(
            <a className="{this.state.active ? 'active' : ''}" onClick={this.handleSelect}>
                {this.props.label}
            </a>
        );
    },
    handleSelect: function (event, component) {
        // Propagate the event up to the parent event property.
        if (this.props.handleSelect) this.props.handleSelect(event, component);
    }
});

MyApp.jsx

var App = React.createClass({
    render: function () {
        return (
            <Nav>
                <NavItem label="Home" />
                <NavItem label="Page1" />
                <NavItem label="Page2" />
            </Nav>
        );
    }
});
React.renderComponent(new App(), document.body)

我相信这是最明智的方法。

var Nav = React.createClass({
    propTypes: {
        active:         React.PropTypes.string,
        onSelect:       React.PropTypes.func
    },
    // no state
    //getInitialState: function () {},
    render: function () {
        // use React.Children.map because children is opaque
        return this.transferPropsTo(
            <nav>
                {React.Children.map(this.props.children, this.renderChild)}
            </nav>
        );
    },
    renderChild: function (child, i) {
        return React.addons.cloneWithProps(child, {
            // use the prop, not state
            active: this.props.active === i,
            key: i,
            // let the parent decide how to handle the data change
            // give it the clicked index
            onSelect: this.props.onSelect.bind(null, i)
        });
    }
});
var NavItem = React.createClass({
    propTypes: {
        // it's a boolean
        active:     React.PropTypes.bool,
        onSelect:   React.PropTypes.func
    },
    // again, no state
    //getInitialState: function () {},
    render: function () {
        // just pass the onSelect handler in directly
        // let the parent handle it
        return this.transferPropsTo(
            <a className={this.props.active ? 'active' : ''} 
               onClick={this.props.onSelect}>
                {this.props.label}
            </a>
        );
    }
});
var App = React.createClass({
    getInitialState: function(){ return {active: 0} },
    handleSelect: function(i){ this.setState({active: i}) },
    render: function () {
        return (
            <Nav onSelect={this.handleSelect} active={this.state.active}>
                <NavItem label="Home" />
                <NavItem label="Page1" />
                <NavItem label="Page2" />
            </Nav>
        );
    }
});