предисловие
Эта статья - первый случай, когда переводчик сделал полный полный перевод. Основная цель - изучить стиль написания таких статей, поэтому я выбрал статью, которая является относительно вводной, от мелкой до глубокой, и вся статья переведена дословно, даже если автору иногда кажется, что она довольно многословна, но исходное содержание все же переведено.
исходный адрес:8 React conditional rendering methods
По сравнению с Javascript JSX — отличное расширение, которое позволяет нам определять компоненты пользовательского интерфейса. Однако он не обеспечивает встроенной поддержки условных циклических выражений (добавление условных выражений впроблемаобсуждалось в).
Примечание переводчика. Условные выражения и циклические выражения, как правило, являются самым основным синтаксисом, предоставляемым механизмом шаблонов по умолчанию.
Предположим, вам нужно просмотреть список, отобразить несколько компонентов или реализовать некоторую логику условного суждения, вы должны использовать JS. Но в большинстве случаев вариантов очень мало,Array.prototype.map
может удовлетворить потребности.
Но как насчет условных выражений?
Это другая история.
у вас есть много вариантов
Есть несколько способов реализовать условные выражения в React. Также для разных сценариев подходят разные методы, в зависимости от того, с какой именно проблемой вам нужно разобраться.
В этой статье рассматриваются несколько наиболее распространенных методов условного рендеринга:
- If/Else
- Возврат null предотвращает рендеринг
- Переменная
- Тернарный оператор
- Оператор короткого замыкания (&&)
- Самоисполняющаяся функция (IIFE)
- Подсборка
- Компоненты высшего порядка (HOC)
Чтобы проиллюстрировать, как используются эти методы, в этой статье реализован компонент, который переключается между состояниями редактирования и отображения:
Вы можете запускать и играть со всем примером кода в JSFiddle.
Примечание переводчика: JSFiddle слишком медленно открывается в стене, поэтому в этой статье не публикуется полный адрес примера.При необходимости вы можете просмотреть исходную ссылку самостоятельно. Если есть подходящий альтернативный продукт, сообщите нам об этом.
If/Else
Сначала мы создаем базовый компонент:
class App extends React.Component {
state = {
text: '',
inputText: '',
mode: 'view',
}
}
text
Свойство хранит сохраненную копию,inputText
Свойство хранит введенную копию,mode
Атрибут для хранения текущего состояния редактирования или состояния отображения.
Затем мы добавляем несколько методов для обработки ввода и переключения состояний:
class App extends React.Component {
state = {
text: '',
inputText: '',
mode: 'view',
}
handleChange = (e) => {
this.setState({ inputText: e.target.value });
}
handleSave = () => {
this.setState({text: this.state.inputText, mode: 'view'});
}
handleEdit = () => {
this.setState({mode: 'edit'});
}
}
это здесьrender
метод, нам нужно определить состояние вmode
свойство, чтобы решить, отображать ли кнопку редактирования или текстовый ввод + кнопку сохранения:
class App extends React.Component {
// …
render () {
if(this.state.mode === 'view') {
return (
<div>
<p>Text: {this.state.text}</p>
<button onClick={this.handleEdit}>
Edit
</button>
</div>
);
} else {
// 译者注:如果if代码块里有return时,一般不需要写else代码块,不过为了贴合标题还是保留了
return (
<div>
<p>Text: {this.state.text}</p>
<input
onChange={this.handleChange}
value={this.state.inputText}
/>
<button onClick={this.handleSave}>
Save
</button>
</div>
);
}
}
If/Else — это самый простой способ реализовать условный рендеринг, но я уверен, что вы не думаете, что это хороший способ.
Его преимущество в том, что его легко использовать в простых сценариях, и каждый программист понимает, как его использовать; его недостаток в том, что будет некоторое дублирование кода, и метод рендеринга станет раздутым.
Тогда давайте упростим это.Мы поместили всю логику условного суждения в два метода рендеринга, один для рендеринга поля ввода, а другой для рендеринга кнопки:
class App extends React.Component {
// …
renderInputField() {
if (this.state.mode === 'view') {
return <div />;
} else {
return (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText}
/>
</p>
);
}
}
renderButton() {
if (this.state.mode === 'view') {
return (
<button onClick={this.handleEdit}>
Edit
</button>
);
} else {
return (
<button onClick={this.handleSave}>
Save
</button>
);
}
}
render() {
return (
<div>
<p>Text: {this.state.text}</p>
{this.renderInputField()}
{this.renderButton()}
</div>
);
}
}
Обратите внимание, что в примереrenderInputField
Когда функция находится в режиме просмотра, она возвращает пустой div. Вообще говоря, это не рекомендуется.
Возврат null предотвращает рендеринг
Если вы хотите скрыть компонент, вы можете вернуть его, позволив функции рендеринга компонента вернутьnull
, нет необходимости использовать пустой div или другой элемент в качестве заполнителя.
Следует отметить, что даже если возвращается значение null, компонент является «невидимым», но его жизненный цикл все равно будет выполняться.
Например, в следующем примере счетчик реализуется с двумя компонентами:
class Number extends React.Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
render() {
if (this.props.number % 2 == 0) {
return (
<div>
<h1>{this.props.number}</h1>
</div>
);
} else {
return null;
}
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 }
}
onClick(e) {
this.setState(prevState => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<Number number={this.state.count} />
<button onClick={this.onClick.bind(this)}>Count</button>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Number
Компоненты отображаются только в том случае, если их четное количество. Потому что, когда число нечетное, функция рендеринга возвращает null. Однако, когда вы смотрите на консоль,componentDidUpdate
Функция будет выполняться каждый раз, независимо отrender
что возвращает функция.
Возвращаясь к примеру в этой статье, мы имеемrenderInputField
Функция немного изменена:
renderInputField() {
if (this.state.mode === 'view') {
return null;
} else {
return (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText}
/>
</p>
);
}
}
Кроме того, еще одно преимущество возврата null вместо пустого div заключается в том, что это может немного повысить производительность всего приложения React, поскольку React не нужно размонтировать пустой div при обновлении.
Например, если он возвращает пустой div, в консоли вы можете обнаружить, что под корневым узломdiv
Элементы всегда обновляются:
И наоборот, если он возвращает null, когдаEdit
При нажатии на кнопку этоdiv
Элементы не будут обновляться:
ты сможешьздесьПродолжайте узнавать больше о том, как React обновляет элементы DOM и как работает алгоритм согласования.
В этом простом примере, возможно, этот разрыв в производительности тривиален, но если это большой компонент, разрыв в производительности нельзя игнорировать.
Ниже я продолжу обсуждение влияния условного рендеринга на производительность. Но пока давайте продолжим фокусироваться на этом примере.
Переменная
Иногда мне не нравится иметь несколько в одном методеreturn
. Итак, я буду использовать переменную, чтобы указать на этот элемент JSX, и только если условиеtrue
время для инициализации.
renderInputField() {
let input;
if (this.state.mode !== 'view') {
input =
<p>
<input
onChange={this.handleChange}
value={this.state.inputText}
/>
</p>;
}
return input;
}
renderButton() {
let button;
if (this.state.mode === 'view') {
button =
<button onClick={this.handleEdit}>
Edit
</button>;
} else {
button =
<button onClick={this.handleSave}>
Save
</button>;
}
return button;
}
Возвращаемые результаты этих методов совпадают с результатами, возвращаемыми двумя методами из предыдущего раздела.
Функция рендеринга теперь намного читабельнее, но в этом случае нет необходимости использовать блок if/else (или переключатель) или несколько методов рендеринга.
Мы можем написать это немного более кратко.
Тернарный оператор
Мы можем использовать тернарный оператор вместо блока if/else:
condition ? expr_if_true : expr_if_false
Весь оператор можно поместить в jsx{}
, каждое выражение можно использовать с()
обернуть JSX для улучшения читабельности.
Тернарный оператор можно использовать в разных местах компонента (?), давайте посмотрим его в действии на примере.
Примечание переводчика: лично я не понимаю помеченного предложения?
я удаляю первыйrenderInputField
а такжеrenderButton
метод, а вrender
Добавьте переменную, чтобы указать, находится ли компонент вview
режим ещеedit
модель:
render () {
const view = this.state.mode === 'view';
return (
<div>
</div>
);
}
Затем добавьте тернарный оператор - когда вview
режим возвращает ноль; вedit
режиме, вернитесь в поле ввода:
// ...
return (
<div>
<p>Text: {this.state.text}</p>
{
view
? null
: (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText} />
</p>
)
}
</div>
);
С помощью тернарного оператора вы можете отобразить кнопку сохранения/редактирования, изменив метку или функцию обратного вызова внутри компонента:
// ...
return (
<div>
<p>Text: {this.state.text}</p>
{
...
}
<button
onClick={
view
? this.handleEdit
: this.handleSave
} >
{view ? 'Edit' : 'Save'}
</button>
</div>
);
оператор короткого замыкания
В некоторых случаях тернарный оператор можно упростить. Например, когда вы либо визуализируете компонент, либо нет, вы можете использовать&&
оператор.
В отличие от&
оператор если&&
Если результат подтвержден выполнением выражения слева, выражение справа не будет выполнено.
Например, если левое выражение оценивается как false (false && ...
), то следующее выражение не нужно выполнять, потому что результат всегда ложный.
В React вы можете использовать это так:
return (
<div>
{ showHeader && <Header /> }
</div>
);
еслиshowHeader
Результатtrue
,Так<Header />
компонент будет возвращен; еслиshowHeader
результат неверный, то<Header />
Компонент будет проигнорирован, а возврат будет пустым.div
.
В приведенном выше коде:
{
view
? null
: (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText} />
</p>
)
}
можно изменить на:
!view && (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText} />
</p>
)
Теперь полный пример выглядит следующим образом:
class App extends React.Component {
state = {
text: '',
inputText: '',
mode: 'view',
}
handleChange = (e) => {
this.setState({ inputText: e.target.value });
}
handleSave = () => {
this.setState({ text: this.state.inputText, mode: 'view' });
}
handleEdit = () => {
this.setState({mode: 'edit'});
}
render () {
const view = this.state.mode === 'view';
return (
<div>
<p>Text: {this.state.text}</p>
{
!view && (
<p>
<input
onChange={this.handleChange}
value={this.state.inputText} />
</p>
)
}
<button
onClick={
view
? this.handleEdit
: this.handleSave
}
>
{view ? 'Edit' : 'Save'}
</button>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Это выглядит намного лучше?
Однако иногда тернарный оператор может сбивать с толку, например следующий сложный код:
return (
<div>
{ condition1
? <Component1 />
: ( condition2
? <Component2 />
: ( condition3
? <Component3 />
: <Component 4 />
)
)
}
</div>
);
Вскоре этот код превратится в беспорядок, поэтому иногда вам понадобятся некоторые другие приемы, такие как самовыполняющиеся функции.
самовыполняющаяся функция
Как следует из названия, самовыполняющиеся функции выполняются сразу после их определения, нет необходимости вызывать их явно.
Обычно функции определяются и выполняются следующим образом:
function myFunction() {
// ...
}
myFunction();
Если вы хотите, чтобы функция выполнялась сразу после ее определения, вам нужно заключить все определение в круглые скобки (используя функцию как выражение), а затем передать параметры, которые будут использоваться.
Пример выглядит следующим образом:
( function myFunction(/* arguments */) {
// ...
}(/* arguments */) );
или:
( function myFunction(/* arguments */) {
// ...
} ) (/* arguments */);
Если функция больше нигде не будет вызываться, имя можно опустить:
( function (/* arguments */) {
// ...
} ) (/* arguments */);
или используйте функции стрелок:
( (/* arguments */) => {
// ...
} ) (/* arguments */);
В React вы можете обернуть всю самовыполняющуюся функцию в фигурную скобку, поместить внутрь всю логику (если/иначе, переключатель, тернарные операторы и т. д.) и вернуть то, что вам нужно для рендеринга.
Например, при использовании самовыполняющейся функции для отображения кнопки редактирования/сохранения код будет выглядеть следующим образом:
{
(() => {
const handler = view
? this.handleEdit
: this.handleSave;
const label = view ? 'Edit' : 'Save';
return (
<button onClick={handler}>
{label}
</button>
);
})()
}
Подсборка
Иногда самовыполняющиеся функции выглядят как черная технология.
Лучшая практика с React — максимально разделить логику внутри компонентов и использовать функциональное программирование вместо императивного программирования.
Поэтому хорошим решением будет поместить логику условного рендеринга в дочерний компонент, а дочерний компонент будет рендерить другой контент через пропсы.
Но здесь я этого не делаю, ниже я покажу вам более декларативный, функциональный способ написания.
Сначала я создаюSaveComponent
:
const SaveComponent = (props) => {
return (
<div>
<p>
<input
onChange={props.handleChange}
value={props.text}
/>
</p>
<button onClick={props.handleSave}>
Save
</button>
</div>
);
};
Через реквизиты он принимает достаточно данных для отображения. Аналогично напишу ещеEditComponent
:
const EditComponent = (props) => {
return (
<button onClick={props.handleEdit}>
Edit
</button>
);
};
render
Теперь метод будет выглядеть так:
render () {
const view = this.state.mode === 'view';
return (
<div>
<p>Text: {this.state.text}</p>
{
view
? <EditComponent handleEdit={this.handleEdit} />
: (
<SaveComponent
handleChange={this.handleChange}
handleSave={this.handleSave}
text={this.state.inputText}
/>
)
}
</div>
);
}
Если компонент
Некоторые библиотеки, такие какJSX Control Statements, они расширяют JSX для поддержки условных состояний:
<If condition={ true }>
<span>Hi!</span>
</If>
Эти библиотеки предоставляют более продвинутые компоненты, однако, если нам нужен только какой-то простой if/else, мы можем написать компонент, как Майкл Дж. Райан в этомissueупоминается в ответе:
const If = (props) => {
const condition = props.condition || false;
const positive = props.then || null;
const negative = props.else || null;
return condition ? positive : negative;
};
// …
render () {
const view = this.state.mode === 'view';
const editComponent = <EditComponent handleEdit={this.handleEdit} />;
const saveComponent = <SaveComponent
handleChange={this.handleChange}
handleSave={this.handleSave}
text={this.state.inputText}
/>;
return (
<div>
<p>Text: {this.state.text}</p>
<If
condition={ view }
then={ editComponent }
else={ saveComponent }
/>
</div>
);
}
компоненты более высокого порядка
Компоненты высшего порядка (HOC)Относится к функции, которая принимает существующий компонент и возвращает новый компонент с некоторыми новыми методами:
const EnhancedComponent = higherOrderComponent(component);
Применительно к условному рендерингу компонент более высокого порядка может возвращать разные компоненты через некоторые условия:
function higherOrderComponent(Component) {
return function EnhancedComponent(props) {
if (condition) {
return <AnotherComponent { ...props } />;
}
return <Component { ...props } />;
};
}
Эта статья Робина Вирухаотличная статья, он более подробно рассматривает использование компонентов более высокого порядка для выполнения условного рендеринга.
Благодаря этой статье я готов учиться уEitherComponent
Концепция чего-либо.
В функциональном программированииEther
Часто используется в качестве оболочки для возврата двух разных значений.
Давайте сначала определим функцию, которая принимает два параметра типа function, первая функция будет возвращать логическое значение (результат выполнения условного выражения), другая — когда результатtrue
Компонент вернулся, когда .
function withEither(conditionalRenderingFn, EitherComponent) {
}
Имя этого компонента более высокого порядка обычноwith
начало.
Эта функция вернет функцию, которая принимает исходный компонент в качестве параметра и возвращает новый компонент:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
}
}
Компонент, возвращаемый внутренней функцией, будет тем, что вы используете в своем приложении, поэтому для работы ему необходимо принять некоторые свойства:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
return function FinalComponent(props) {
}
}
}
Поскольку внутренняя функция может получить параметры внешней функции, поэтому на основеconditionalRenderingFn
возвращаемое значение, вы можете вернутьEitherComponent
или оригиналComponent
:
function withEither(conditionalRenderingFn, EitherComponent) {
return function buildNewComponent(Component) {
return function FinalComponent(props) {
return conditionalRenderingFn(props)
? <EitherComponent { ...props } />
: <Component { ...props } />;
}
}
}
В качестве альтернативы используйте функции стрелок:
const withEither = (conditionalRenderingFn, EitherComponent) => (Component) => (props) =>
conditionalRenderingFn(props)
? <EitherComponent { ...props } />
: <Component { ...props } />;
Вы можете использовать ранее определенныеSaveComponent
а такжеEditComponent
создатьwithEditConditionalRendering
Компоненты более высокого порядка, в конечном счете, создаютEditSaveWithConditionalRendering
Компоненты:
const isViewConditionFn = (props) => props.mode === 'view';
const withEditContionalRendering = withEither(isViewConditionFn, EditComponent);
const EditSaveWithConditionalRendering = withEditContionalRendering(SaveComponent);
Примечание переводчика: уже поздно убивать курицу бычьим ножом
Наконец, вrender
, вы передаете все свойства, которые вам нужно использовать:
render () {
return (
<div>
<p>Text: {this.state.text}</p>
<EditSaveWithConditionalRendering
mode={this.state.mode}
handleEdit={this.handleEdit}
handleChange={this.handleChange}
handleSave={this.handleSave}
text={this.state.inputText}
/>
</div>
);
}
Вопросы производительности
Условный рендеринг иногда неуловим, и многие из упомянутых выше методов различаются по производительности.
Однако в большинстве сценариев эти различия незначительны. Но когда вам нужно это сделать, вам нужно хорошо понимать, как работает виртуальный DOM React, и иметь некоторыеСоветы по оптимизации:
Вот статья об оптимизации условного рендерингастатья, рекомендую к прочтению.
Суть в том, что если условно отображаемый компонент вызывает изменение положения, это вызовет перекомпоновку, что приведет к монтированию/размонтированию компонента в приложении.
Примечание переводчика. Перестановка здесь относится не к перестройке рендеринга в браузере, а к концепции виртуального DOM.
На основе примеров в тексте я сделал следующие два примера.
Первый использует if/else, чтобы показать/скрытьSubHeader
Компоненты:
const Header = (props) => {
return <h1>Header</h1>;
}
const Subheader = (props) => {
return <h2>Subheader</h2>;
}
const Content = (props) => {
return <p>Content</p>;
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
if(this.state.isToggleOn) {
return (
<div>
<Header />
<Subheader />
<Content />
<button onClick={this.handleClick}>
{ this.state.isToggleOn ? 'ON' : 'OFF' }
</button>
</div>
);
} else {
return (
<div>
<Header />
<Content />
<button onClick={this.handleClick}>
{ this.state.isToggleOn ? 'ON' : 'OFF' }
</button>
</div>
);
}
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Другой использует оператор короткого замыкания (&&
)выполнить:
const Header = (props) => {
return <h1>Header</h1>;
}
const Subheader = (props) => {
return <h2>Subheader</h2>;
}
const Content = (props) => {
return <p>Content</p>;
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<div>
<Header />
{ this.state.isToggleOn && <Subheader /> }
<Content />
<button onClick={this.handleClick}>
{ this.state.isToggleOn ? 'ON' : 'OFF' }
</button>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
Откройте консоль и нажмите кнопку несколько раз, вы найдетеContent
Производительность компонента несовместима между двумя реализациями.
Примечание переводчика: в примере 1 контент будет перерисовываться каждый раз
В заключение
Как и все остальное в программировании, в React существует множество способов реализации условного рендеринга.
Вы можете выбрать любой способ, кроме первого (if/else и содержит много возвратов).
Вы можете найти лучшее решение для вашего текущего сценария по следующим причинам:
- ваш стиль программирования
- Сложность условной логики
- Насколько вам комфортно с передовыми концепциями в Javascript, JSX и React (например, компонентами более высокого порядка)
Конечно, некоторые вещи всегда важны, и это делает их простыми и удобочитаемыми.