Подтвердить что ты не робот

Редактирование богатой структуры данных в React.js

Я пытаюсь создать простой редактор на основе сетки для структуры данных, и у меня возникает пара концептуальных проблем с React.js. Их документация не очень помогает в этом, поэтому я надеюсь, что кто-то здесь может помочь.

Во-первых, каков правильный способ передачи состояния из внешнего компонента во внутренний компонент? Возможно ли иметь изменения состояния во внутреннем компоненте "пузырь" вверх по внешнему компоненту (компонентам)?

Во-вторых, могут ли два отдельных компонента обмениваться данными, так что мутация в одном видима в другом?

Ниже приведен упрощенный пример того, что я хочу сделать (версия JSFiddle):

У меня есть объект company, содержащий массив объектов employee. Я хочу выложить список employee в редактируемой сетке. Когда я нажимаю кнопку, я хочу увидеть результирующий объект company, а также любые мутации (запись на консоль).

/** @jsx React.DOM */

var Cell = React.createClass({
    getInitialState: function () {
        return {data: ""};
    },

    componentWillMount: function () {
        this.setState({data: this.props.data});
    },
    onChange: function (evt) {
        console.log(this.state, evt.target.value);
        this.setState({data: evt.target.value});
    },
    render: function () {
        var data = this.props.data;
        return <input value={this.state.data} onChange={this.onChange} />
    }
});

var Row = React.createClass({
    render: function () {
        return (<div className="row">
            <Cell data={this.props.data.name} />
            <Cell data={this.props.data.location} />
            <Cell data={this.props.data.phone} />
        </div>);
    }
});

var Grid = React.createClass({
    render: function () {
        var rows = this.props.data.map(function (rowData, index) {
            return <Row key={index} data={rowData}>Row</Row>;
        });
        return <div className="table">{rows}</div>;
    }
});

var Button = React.createClass({
    getInitialState: function () {
        return {data: {}}
    },
    componentWillMount: function () {
        this.setState({data: this.props.data});
    },
    onClick: function () {
        console.log(this.state);
    },
    render: function () {
        return <button onClick={this.onClick}>Update</button>;
    }
});

var company = {
    name: "Innotech",
    employees: [
        {id: "1", name: "Peter", location: "IT", phone: "555-1212"},
        {id: "2", name: "Samir", location: "IT", phone: "555-1213"},
        {id: "3", name: "Milton", location: "loading dock", phone: "none"}
    ]
};


React.renderComponent(
    <div><Grid data={company.employees} /><Button data={company} /></div>,       
    document.getElementById('employees')
);
4b9b3361

Ответ 1

Я думаю, что это самая недокументированная часть React прямо сейчас. Предлагаемый способ взаимодействия между компонентами - просто установить реквизиты при общении от родителя к ребенку и передавать обратные вызовы через реквизиты при общении от потомка к родителю.

Когда вы чувствуете, что хотите обмениваться данными между одноуровневыми компонентами, это означает, что должен быть родительский компонент, управляющий состоянием и передающий его обоим компонентам. В большинстве случаев ваше состояние должно находиться в верхней части иерархии компонентов, а каждая часть информации должна находиться (не более) в одном состоянии компонента, не более.

Подробнее об этом читайте в блоге Пита Ханта Thinking in React.


Имея это в виду, я обновил вашу скрипку.

Я изменил Grid так, чтобы он не поддерживал свое собственное состояние, а вместо этого всегда отображал данные, переданные через его реквизиты, и вызывает onCellChange из своих реквизитов, когда он хочет запросить изменение данных у своего родителя. (Компонент Grid будет ожидать, что его родитель обновит свойство сетки data с измененными данными. Если родитель отказывается (возможно, из-за неудачной проверки данных или подобного), вы в конечном итоге получаете сетку только для чтения.)

Вы также заметите, что я создал новый компонент Editor, чтобы обернуть сетку и кнопку ее родного брата. Компонент Editor теперь по существу управляет всей страницей. В реальном приложении вполне вероятно, что содержимое сетки понадобится где-то еще, и поэтому состояние будет перемещено выше. Я удалил ваш компонент Button, потому что он мало чем отличался от встроенного тега <button>; Я оставил Cell, но его тоже можно было удалить - Row мог легко использовать теги <input> напрямую.

Надеюсь, это имеет смысл. Не стесняйтесь спрашивать, если что-то неясно. В IRC-комнате #reactjs обычно также есть люди, если вы хотите больше поговорить об этом.

Ответ 2

Я изучаю ReactJS на прошлой неделе или около того. Мой вклад в ваш вопрос задает новый вопрос: почему вы отделяете компонент Cell от компонентов Row и Grid?

Исходя из фона Backbone, Cell, Row и Grid имеют смысл, иметь гранулированный контроль над отдельными Backbone.Views. Тем не менее, похоже, что подробный контроль и обновление DOM - это то, что ReactJS пытается решить для вас, что для меня говорит о наличии компонента Grid, который реализует Row/Cell внутри себя:

var Grid = React.createClass({
    onChange: function(evt, field) {
        this.props.data[field] = evt.target.value;
    },

    render: function () {
        var rows = this.state.data.map(function (rowData, index) {
            return (
                <div className="row" key={index}>
                    <input value={rowData.name} onChange={this.onChange.bind(null, "name")} />
                    <input value={rowData.location} onChange={this.onChange.bind(null, "location")} />
                    <input value={rowData.phone} onChange={this.onChange.bind(null, "phone")} />
                </div>
            );
        });

        return <div className="table">
                   {rows}
               </div>;
    }
});

(Игнорировать включенную * Изменить обработку, место для улучшения там. Неподтвержденный код)

Вопрос: неужели вы когда-либо повторно использовали Cell или Row в качестве отдельных компонентов в другом месте? Для меня ответ "очень вероятно нет". В этом случае мое решение выше имеет смысл, ИМХО, и избавляется от проблемы передачи данных и изменений вверх и вниз.

Ответ 3

Другой способ обмена данными между компонентами-братьями, когда родительский компонент не имеет смысла, - это использовать события между компонентами. Например, вы можете использовать Backbone.Events, Node.js Emitter, портированный в браузер или любую другую lib. Вы даже можете использовать Bacon.js, если вы предпочитаете реактивные потоки. Здесь есть большой и простой пример объединения Bacon.js и React.js здесь: http://joshbassett.info/2014/reactive-uis-with-react-and-bacon/