Как следует использовать redux с вложенными подкомпонентами, которые не будут использоваться повторно?

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

То, что я не понимаю, является рекомендацией умного/немого компонента и помещением провайдера выше основного компонента и передачи всего вниз через реквизиты. Если я это сделаю, мне нужно будет передать реквизиты вплоть до пятого вложенного элемента, чтобы он мог сделать обратный вызов для отправки действия или посмотреть какое-то состояние, которое ему только нужно, а не его родители. Я понимаю, что это для повторного использования кода, но подкомпоненты никогда не будут использоваться вне основного компонента. Какое рекомендуемое решение здесь? Все еще используете реквизиты?

Примечание: автор этой библиотеки попросил задать вопросы по StackOverflow. Я упоминаю об этом, потому что SO, похоже, ставит под вопрос "лучшие практики" как слишком расплывчатые.

Ответ 1

В то время как answer matt clemens опубликовал это на высоком уровне, но я постараюсь углубиться сюда.

Вы можете использовать connect() на любом уровне. Это делает компонент умным, так как он знает, откуда его props. Компонент немой имеет props, и они могут появляться откуда угодно. Интеллектуальный компонент связан с сокращением; немой компонент не является.

Существуют разные мнения по этому подходу, но он поддерживается и действителен.

Где рисовать эту линию полностью зависит от вас, но давайте посмотрим на пример. У вас есть чат-клиент со стандартным компонентом боковой панели, окно сообщения и поле ввода для отправки новых сообщений.

+---------+--------------------------+
|         |                          |
|Sidebar  |  Messages window         |
|         |                          |
|         |                          |
|         |                          |
|         |                          |
|         +--------------------------+
|         |  New Message Entry     **|
|         |                          |
+---------+--------------------------+

Родитель всех из них будет использовать connect(), чтобы получить данные из redux и передать их этим компонентам через реквизиты. Теперь представьте, что эти две звездочки, кроме new message entry, открывают панель настроек (игнорируйте глупое размещение, это пример). Имеет ли смысл new message entry передавать эти реквизиты? Нет, это не так.

Чтобы решить эту проблему, вы можете создать специальный "контейнер", позвоните ему SettingsContainer, который использовал connect() для получения своих реквизитов, и все, что было сделано, это передать их до SettingsPopup. SettingsPopup не будет знать о redux и все еще может быть проверен/стилизован/повторно использован нормально, а новая запись сообщения должна знать только SettingsContainer, а не какие-либо ее зависимости.

Этот подход хорошо масштабируется, но он имеет два штрафа. Во-первых, интеллектуальные компоненты "обертки", такие как SettingsContainer, должны потребляться другими немыми компонентами. Это усложняет тестирование нового компонента ввода сообщения. Во-вторых, компонент верхнего уровня больше не предоставляет весь график зависимостей данных, что затрудняет рассуждение, не углубляясь в иерархию компонентов.

Эти компромиссы могут стоить того, но вы должны знать о них.

Ответ 2

Вы можете использовать новую функцию React context, используя компонент "response-redux" Provider. Использование провайдера абстрагирует некоторые детали реализации контекста, делает вашу разметку довольно выразительной imho.

В основном вы настраиваете свойство mini-global, которое могут ссылаться на все подкомпоненты, немые или умные:

import React from 'react';
import {render} from 'react-dom';
import {createStore} from 'redux';
import {Provider} from 'react-redux'; //Special Redux component to help
import {reducers} from './reducers.js';


var DeepDumbChild = (props, context) => (
    <div>
        <pre>
            {JSON.stringify(data, null, 2)}
        </pre>
    </div>
)


class SmartChild extends React.Component {
    render() {
        /* Use the global-like context */
        let data = this.context.store.getState();
        return (
            <div>
                <DeepDumbChild data={data}/>
            </div>
        )
    }
}
SmartChild.contextTypes = {
    store: React.PropsTypes.object /* required */
}

/* No messy props :) */
var App = () => (<div>
    <SmartChild/>
</div>);


render(
    <Provider store={createStore(reducers)}>
        <App/>
    </Provider>,
    document.getElementById('app')
);

Ответ 3

ОБНОВЛЕНИЕ:. Если вы хотите попробовать подход ниже, посмотрите https://github.com/artsy/react-redux-controller, который Недавно я опубликовал.

Я думаю, что лучший способ перейти - отобразить селектора в context в корневом (контейнерном) компоненте, а не иметь кучу контейнеров или использовать connect везде. Я сделал это в своем собственном проекте и прекрасно работал. Он добавляет новый образец аннотирующих селекторов с PropType, которые они производят. Это позволяет мне использовать childContextTypes, чтобы все потомки сохраняли те же самые защиты, которые у них есть с propTypes. Это преимущество перед подходом Эшли Кулмана к передаче большого нетипизированного объекта, передаваемого вниз. Я надеюсь, что у меня будет время в ближайшие пару дней, чтобы включить его в свою библиотеку и опубликовать.

В то же время я предлагаю сделать компоненты умными, используя connect либерально, а не подход Tyrsius создать целую кучу контейнеров, но это только мое мнение.