Использование React для разработки приложений дает фронтенд-инженерам безграничное удовольствие от «комбинирования». Тем не менее, исходя из этого, дизайн приложения, такой как разделение компонентов и потоки данных, определяет красоту и надежность уровня кода.
В то же время, когда дело доходит до карризации в мире React, многие разработчики могут сразу отреагировать на метод connect библиотеки React-redux. Однако было бы жаль, если бы он оставался здесь только механизированным без более гибких приложений.
Эта статья основана на реальном сценарии, начиная с деталей и анализирует, как карризация может упростить сложность и более элегантно достичь требований.
введение сцены
Сценарий спроса – веб-сайт электронной коммерции, продающий продукты питания. В левой части – столбец с фильтром товаров. Пользователи могут фильтровать товары по диапазону цен, возрасту и марке товара. Соответствующие продукты отображаются справа. Как показано ниже:
Как разработчики React, мы знаем, что React состоит из компонентов, и первым шагом будет рассмотрение разделения компонентов в соответствии с диаграммой UE. Этот процесс относительно прост и интуитивно понятен, и мы используем следующий рисунок для представления результата разделения:
Соответствующий код:
<Products>
<Filters>
<PriceFilter/>
<AgeFilter/>
<BrandFilter/>
</Filters>
<ProductResults/>
</Products>
Первичная реализация
React основан на состоянии данных, и вторым шагом является рассмотрение состояния приложения. На данный момент нам не нужно заботиться о данных результатов отображения продукта. Здесь в основном рассматривается наиболее важное состояние приложения, а именноИнформация о состоянии фильтра.
Мы используем объект JavaScript с именем filterSelections для представления информации об условиях фильтра следующим образом:
filterSelections = {
price: ...,
ages: ...,
brands: ...,
}
Эти данные должны храниться в компоненте «Продукты». Потому что оба подкомпонента Filters и ProductResults компонента Products будут зависеть от этого состояния данных.
Компонент Filters получает состояние filterSelections через свойство и дизассемблирует три переданных ему подкомпонента фильтра:
class Filters extends React.Component {
render() {
return (
<div>
<PriceFilter price={this.props.filterSelections.price} />
<AgeFilter ages={this.props.filterSelections.ages} />
<BrandFilter brands={this.props.filterSelections.brands} />
</div>
);
};
}
Точно так же компонент ProductResults также получает состояние filterSelections через свойства для отображения соответствующих продуктов.
Для компонента Filters он должен не только получать данные filterSelections, но и обновлять эти данные. С этой целью мы разрабатываем соответствующую функцию-обработчик в компоненте «Продукты» для обновления информации о фильтре, называем ее updateFilters и отправляем эту функцию-обработчик в качестве реквизита компоненту «Фильтры»:
class Products extends React.Component {
constructor(props) {
super(props);
this.state = {
filterSelections: {
price: someInitialValue,
ages: someInitialValue,
brands: someInitialValue,
}
}
}
updateFilters = (newSelections) => {
this.setState({
filterSelections: newSelections
})
};
render() {
return(
<div>
<Filters
filterSelections={this.state.filterSelections}
selectionsChanged={this.updateFilters}
/>
<Products filterSelections={this.state.filterSelections} />
</div>
);
}
}
Обратите внимание, как мы связываем это здесь. Заинтересованные читатели могут обратиться к другой моей статье:Связывание этого с React, см. Разработка языка JS и проектирование фреймворка..
Как и компонент «Фильтры», функция обработчика также должна быть дополнительно разделена и распределена:
class Filters extends React.Component {
updatePriceFilter = (newValue) => {
this.props.selectionsChanged({
...this.props.filterSelections,
price: newValue
})
};
updateAgeFilter = (newValue) => {
this.props.selectionsChanged({
...this.props.filterSelections,
ages: newValue
})
};
updateBrandFilter = (newValue) => {
this.props.selectionsChanged({
...this.props.filterSelections,
brands: newValue
})
};
render() {
return (
<div>
<PriceFilter
price={this.props.filterSelections.price}
priceChanged={this.updatePriceFilter}
/>
<AgeFilter
ages={this.props.filterSelections.ages}
agesChanged={this.updateAgeFilter}
/>
<BrandFilter
brands={this.props.filterSelections.brands}
brandsChanged={this.updateBrandFilter}
/>
</div>
);
};
}
В соответствии с функцией selectionsChanged, передавая различные типы параметров, мы разработали три метода: updatePriceFilter, updateAgeFilter и updateBrandFilter, и передали их трем компонентам PriceFilter, AgeFilter и BrandFilter соответственно.
Этот подход прост, но работает хорошо. Но в компоненте «Фильтры» есть гораздо больше функций, и эти функции, кажется, выполняют ту же логику. Если в будущем будут добавлены один или несколько фильтров, то будет добавлено такое же количество «двойных» функций. Это явно недостаточно элегантно.
что такое карри
Прежде чем разбирать более элегантные решения, давайте вкратце разберемся, что такое каррирование. Каррирование на самом деле является своего рода преобразованием, оно преобразует функцию f в f', параметр f' получает параметры исходной функции f и возвращает новую функцию f'', f'' получает оставшиеся параметры и возвращает значение функция Результат вычисления f.
Это описание, несомненно, абстрактно, и мы до сих пор понимаем его через код. Вот простая функция суммирования:
add = (x, y) => x + y;
После карри:
curriedAdd = (x) => {
return (y) => {
return x + y;
}
}
Таким образом, при выполнении curriedAdd(1)(2) результатом будет 3. Функция curriedAdd(x) называется частичное приложение, а функции curriedAdd требуется только часть параметров оригинального add(X, y). ) функция.
Каррируя обычную функцию, мы можем применить к ней частичное приложение.
приложение карри
Возвращаясь к предыдущей сцене, мы разрабатываем карри-функцию: updateSelections,
updateSelections = (selectionType) => {
return (newValue) => {
this.props.selectionsChanged({
...this.props.filterSelections,
[selectionType]: newValue,
});
}
};
Его можно еще упростить до:
updateSelections = (selectionType) => (newValue) => {
this.props.selectionsChanged({
...this.props.filterSelections,
[selectionType]: newValue,
})
};
Для частичного применения updateSelections (т.е. частичного применения, упомянутого выше):
updateSelections('ages');
updateSelections('brands');
updateSelections('price');
Я считаю, что все поняли преимущества этого. На этом наш компонент Filters завершен:
class Filters extends React.Component {
updateSelections = (selectionType) => {
return (newValue) => {
this.props.selectionsChanged({
...this.props.selections,
[selectionType]: newValue, // new ES6 Syntax!! :)
});
}
};
render() {
return (
<div>
<PriceFilter
price={this.props.selections.price}
priceChanged={this.updateSelections('price')}
/>
<AgeFilter
ages={this.props.selections.ages}
agesChanged={this.updateSelections('ages')}
/>
<BrandFilter
brands={this.props.selections.brands}
brandsChanged={this.updateSelections('brands')}
/>
</div>
);
};
}
Конечно, каррирование — не единственное решение вышеуказанных проблем. Давайте изучим другой способ сравнения и переваривания, версию функции updateSelections без карри:
updateSelections = (selectionType, newValue) => {
this.props.updateFilters({
...this.props.filterSelections,
[selectionType]: newValue,
});
}
Этот дизайн заставляет каждый компонент фильтра: PriceFilter, AgeFilter, BrandFilter вызывать функцию updateSelections и требует, чтобы сам компонент воспринимал имя свойства filterSelections для обновления соответствующих свойств. Это соединение, полная реализация:
class Filters extends React.Component {
updateSelections = (selectionType, newValue) => {
this.props.selectionsChanged({
...this.props.filterSelections,
[selectionType]: newValue,
});
};
render() {
return (
<>
<PriceFilter
price={this.props.selections.price}
priceChanged={(value) => this.updateSelections('price', value)}
/>
<AgeFilter
ages={this.props.selections.ages}
agesChanged={(value) => this.updateSelections('ages', value)}
/>
<BrandFilter
brands={this.props.selections.brands}
brandsChanged={(value) => this.updateSelections('brands', value)}
/>
</>
);
};
}
На самом деле, я думаю, что в этом сценарии выбор двух схем может быть сделан в соответствии с предпочтениями разработчика.
Суммировать
Содержание этой статьи относительно базовое, но, начиная с деталей, оно показывает прелесть сочетания разработки и написания React с функциональными концепциями. статьяПереведено отсюда, часть содержимого изменена.
коммерческое время:Если вы интересуетесь фронтенд-разработкой, особенно стеком React: возможно, в моей новой книге есть что-то, что вы хотели бы увидеть. Следите за авторомLucas HC, после выхода новой книги будет розыгрыш.
Happy Coding!
ПС: авторРепозиторий на гитхабе а также Знай ссылку на вопрос и ответПриветствуются все формы общения!
Пара других моих статей о стеке React:
Из обсуждения обещания setState познакомьтесь с дизайнерским мышлением команды React.
Из обсуждения обещания setState познакомьтесь с дизайнерским мышлением команды React.
Изучите «лучшие практики» для написания компонентов React на примере
React компонентный дизайн и декомпозиционное мышление
Связывание этого с React, см. Разработка языка JS и проектирование фреймворка.