7 принципов инкапсуляции для надежного проектирования компонентов React

JavaScript React.js
7 принципов инкапсуляции для надежного проектирования компонентов React

Перевод: Лю Сяоси

Оригинальная ссылка:Рисовое путешествие avlutin.com/7-architect…

длина оригиналаОченьОно длинное, но содержание слишком привлекательно для меня, поэтому я не могу не перевести его. Эта статья очень полезна для написания повторно используемых и поддерживаемых компонентов React. Но поскольку длина слишком велика, я разделил статью, эта статья посвящена封装. Из-за моего ограниченного уровня некоторые переводы в тексте могут быть недостаточно точными, если у вас есть идеи получше, укажите их в комментариях.

Другие статьи можно нажать: GitHub.com/Иветт Л.А. Ю/Б…

——————————————— Я — разделительная линия ————————————————

упаковка

Упакованный компонент предоставляетpropsКонтролируйте его поведение вместо того, чтобы раскрывать его внутреннюю структуру.

Сцепление — это системное свойство, определяющее степень зависимости между компонентами. В зависимости от степени зависимости компонентов можно выделить два типа сцепления:

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

  • Тесная связь возникает, когда компоненты приложения знают много деталей друг о друге.

Слабая связанность — наша цель при разработке структуры приложения и взаимосвязей между компонентами.

Слабосвязанные приложения (пакетные компоненты)

Слабая связьПринесет следующие преимущества:

  • Блок можно изменить, не затрагивая остальную часть приложения. ,
  • Любой компонент можно заменить другой реализацией
  • Повторно используйте компоненты во всем приложении, чтобы избежать дублирования кода.
  • Автономные компоненты легче тестировать, что увеличивает охват тестами

И наоборот, сильно связанные системы теряют описанные выше преимущества. Основным недостатком является сложность модификации компонентов, сильно зависящих от других компонентов. Даже одна модификация может привести к необходимости модификации ряда зависимых компонентов.

Плотно связанные приложения (компонент неисправен)

упаковкаилисокрытие информацииЭто основной принцип проектирования компонентов и ключ к ослаблению связи.

сокрытие информации

Хорошо инкапсулированный компонент скрывает свою внутреннюю структуру и предоставляет набор свойств для управления его поведением.

Необходимо скрыть внутреннюю структуру. Другие компоненты не обязательно знают или зависят от внутренней структуры или деталей реализации компонента.

ReactКомпоненты могут быть функциональными компонентами или компонентами класса, определять методы экземпляра, устанавливатьref,имеютstateИли используйте методы жизненного цикла. Эти детали реализации инкапсулированы внутри компонента, и другие компоненты не должны знать об этих деталях.

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

коммуникация

Скрытие деталей — это ключ к изоляции компонентов. На этом этапе вам нужен способ взаимодействия компонентов:props.porpsявляется входом компонента.

предложениеpropТип - примитивные данные (например,string,number,boolean):

<Message text="Hello world!" modal={false} />;

При необходимости используйте сложные структуры данных, такие как объекты или массивы:

<MoviesList items={['Batman Begins', 'Blade Runner']} />

propМожет быть обработчиком событий и асинхронной функцией:

<input type="text" onChange={handleChange} />

propДаже быть компонентным конструктором. Примеры компонента могут обрабатывать другие компоненты:

function If({ component: Component, condition }) {
    return condition ? <Component /> : null;
}
<If condition={false} component={LazyComponent} />  

Чтобы не нарушить инкапсуляцию, будьте осторожны при передачеpropsсодержание прошло. набор для подкомпонентовpropsРодительский компонент не должен раскрывать никаких деталей своей внутренней структуры. Например, используйтеpropsпередать весь экземпляр компонента илиrefsОба являются плохой практикой.

Доступ к глобальным переменным также может негативно повлиять на инкапсуляцию.

Практический пример: восстановление упаковки

Экземпляры компонентов и объекты состояния — это детали реализации, инкапсулированные внутри компонента. Таким образом, передача экземпляра родительского компонента с управлением состоянием дочернему компоненту нарушает инкапсуляцию.

Давайте рассмотрим эту ситуацию.

Простое приложение отображает число и две кнопки. Первая кнопка увеличивает значение, а вторая кнопка уменьшает значение:

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = { number: 0 };
    }

    render() {
        return (
            <div className="app">
                <span className="number">{this.state.number}</span>
                <Controls parent={this} />
            </div>
        );
    }
}

class Controls extends React.Component {
    render() {
        return (
            <div className="controls">
                <button onClick={() => this.updateNumber(+1)}>
                    Increase
          </button>
                <button onClick={() => this.updateNumber(-1)}>
                    Decrease
          </button>
            </div>
        );
    }

    updateNumber(toAdd) {
        this.props.parent.setState(prevState => ({
            number: prevState.number + toAdd
        }));
    }
}

ReactDOM.render(<App />, document.getElementById('root')); 

<Controls>Отвечает за отрисовку кнопки и настройку обработчиков событий для нее.При нажатии кнопки пользователем будет обновляться состояние родительского компонента:numberплюс 1 или минус 1 ((метод updateNumber()`)

// 问题: 使用父组件的内部结构
class Controls extends Component {
    render() {
        return (
            <div className="controls">
                <button onClick={() => this.updateNumber(+1)}>
                    Increase
          </button>
                <button onClick={() => this.updateNumber(-1)}>
                    Decrease
          </button>
            </div>
        );
    }

    updateNumber(toAdd) {
        this.props.parent.setState(prevState => ({
            number: prevState.number + toAdd
        }));
    }
}

Что не так с текущей реализацией?

  • Первый вопрос:<App>Инкапсуляция нарушена, потому что ее внутренняя структура передается в приложение.<App>ошибочно разрешено<Controls>непосредственно изменить егоstate.

  • Вторая проблема: дочерние компонентыControlsЗнает слишком много родительских компонентов<App>внутренние детали, он может получить доступ к экземпляру родительского компонента, знать, что родительский компонент является компонентом с отслеживанием состояния, знать родительский компонентstateдетали объекта (знатьnumberявляется родительским компонентомstateproperties) и знать, как обновить родительский компонентstate.

Это приводит к:<Controls>Будет трудно протестировать и использовать повторно. правильно<App>Незначительные изменения конструкции могут привести к необходимости<Controls>Внесите изменения (для более крупных приложений также заставьте аналогичные связанные компоненты нуждаться в модификации).

решениеСпроектировать удобный коммуникационный интерфейс с учетом слабой связи и инкапсуляции. Улучшим структуру и свойства обоих компонентов, чтобы восстановить инкапсуляцию.

Только сам компонент должен знать структуру своего состояния.<App>Государственное управление должно начинаться с<Controls>(updateNumber()метод) в нужное место: т.е.<App>в компоненте.

<App>был изменен на<Controls>установить свойстваonIncreaseа такжеonDecrease. это обновления<App>Функция обратного вызова статуса:

// 解决: 恢复封装
class App extends Component {
    constructor(props) {
        super(props);
        this.state = { number: 0 };
    }

    render() {
        return (
            <div className="app">
                <span className="number">{this.state.number}</span>
                <Controls
                    onIncrease={() => this.updateNumber(+1)}
                    onDecrease={() => this.updateNumber(-1)}
                />
            </div>
        );
    }

    updateNumber(toAdd) {
        this.setState(prevState => ({
            number: prevState.number + toAdd
        }));
    }
}

Сейчас,<Controls>Получите обратные вызовы для увеличения и уменьшения значений, обратите внимание, что при восстановлении развязки и инкапсуляции:<Controls>Доступ к экземпляру родительского компонента больше не требуется. Он не будет напрямую изменять состояние родительского компонента.

а также,<Controls>Преобразован в функциональный компонент:

// 解决方案: 使用回调函数去更新父组件的状态
function Controls({ onIncrease, onDecrease }) {
    return (
        <div className="controls">
            <button onClick={onIncrease}>Increase</button>
            <button onClick={onDecrease}>Decrease</button>
        </div>
    );
}

<App>Инкапсуляция компонента восстановлена ​​и состояние управляется само собой, как и должно быть.

также,<Controls>больше не зависит<App>детали реализации,onIncreaseа такжеonDecreaseВызывается при нажатии кнопки,<Controls>Не знаю (и не должен знать) внутреннюю реализацию этих обратных вызовов.

<Controls>Значительно повышается возможность повторного использования и тестируемость компонентов.

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

Наконец, спасибо за вашу готовность потратить свое драгоценное время на чтение этой статьи. Если эта статья дала вам небольшую помощь или вдохновение, пожалуйста, не скупитесь на лайки и звезды. двигаться вперед.GitHub.com/Иветт Л.А. Ю/Б…

Подпишитесь на официальный аккаунт и присоединитесь к группе технического обмена