11 ошибок, которых следует избегать при создании React приложений
2 года назад·7 мин. на чтение
Некоторые распространенные ошибки в разработке React приложений, которых следует избегать.
Поскольку React становится все более и более популярным, все больше и больше React разработчиков сталкиваются с различными проблемами в процессе разработки.
В этой статье, мы обобщим некоторые распространенные ошибки в разработке React приложений, чтобы помочь вам их избежать.
Если вы только начинаете использовать React, рекомендуется внимательно ознакомиться с этой статьей. Если вы уже используете React для разработки проектов, также рекомендуется проверить и заполнить пробелы.
Прочитав эту статью, вы узнаете, как избежать эти 11 ошибок React:
Ошибка: При рендеринге списка не используется
Проблема
Когда мы впервые изучали React, мы отображали список следующим образом:
Ошибка: Использование состояния сразу после выполнения
Проблема
Когда мы изменяем данные через
Ошибка: Появление бесконечного цикла при использовании
Проблема
Когда мы напрямую вызываем метод
Приведем 4 случая использования
Ошибка: Отсутствие очистки побочных эффектов в
Проблема
В классовых компонентах мы используем метод жизненного цикла
- При рендеринге списка не используется
key
- Изменение значения состояния прямым присваиванием
- Привязка значения состояния непосредственно к свойству value инпута
- Использование состояния сразу после выполнения
setState
- Появление бесконечного цикла при использовании
useState
+useEffect
- Отсутствие очистки побочных эффектов в
useEffect
- Неправильное использование логических операторов
- Тип пропсов компонента не типизирован
- Передача строк в качестве значений компонентам
- Имя компонента не начинается с заглавной буквы
- Неверная привязка события к элементу
Ошибка: При рендеринге списка не используется key
Проблема
Когда мы впервые изучали React, мы отображали список следующим образом:
После рендеринга консоль выдаст предупреждение, что для элементов списка необходимо указать ключ. Решение Вам просто нужно последовать этой подсказке и добавитьconst items = [ { id: 1, value: 'item1' }, { id: 2, value: 'item2' }, { id: 3, value: 'item3' }, { id: 4, value: 'item4' }, { id: 5, value: 'item5' } ]; const listItems = items.map((item) => { return <li>{item.value}</li> });
key
к каждому элементу:
const items = [ { id: 1, value: ‘item1’ }, { id: 2, value: ‘item2’ }, { id: 3, value: ‘item3’ }, { id: 4, value: ‘item4’ }, { id: 5, value: ‘item5’ } ]; const listItems = items.map((item) => { return <li key={item.id}>{item.value}</li> });
key
помогает React определить, какие элементы были изменены, например, добавлены или удалены. Поэтому нам нужно установить уникальное значение ключа для каждого элемента в массиве.
Для значения ключа лучше всего установить уникальное значение. В приведенном выше примере используется id
. Можно использовать индекс массива, но такой подход не рекомендуется.
Уникальный ключ помогает React следить за изменениями списка - какой элемент удалился или переместился.
Ошибка: Изменение значения состояния прямым присваиванием
Проблема В React нельзя назначать состояние и изменять напрямую, иначе это вызовет проблемы.В этот момент будет выдано предупреждение не изменять состояние напрямую, а использовать// классовый компонент handleChange = () => { this.state.name = "John"; };
setState()
.
Решение
Классовые компоненты могут быть изменены с помощью setState()
, а функциональные компоненты могут быть изменены с помощью useState()
:
// Классовые компоненты: используйте setState() this.setState({ name: "John" }); // Функциональные компоненты:используйте useState() const [name, setName] = useState(""); setName("John");
Ошибка: Привязка значения состояния непосредственно к свойству value инпута
Проблема Когда мы напрямую привязываем значение состояния к свойствуvalue
инпута, мы обнаружим, что независимо от того, что мы вводим в поле ввода, содержимое поля ввода не изменится.
Это связано с тем, что мы используем переменную состояния в качестве значения по умолчанию для присвоения значенияexport default function App() { const [count, setCount] = useState(0); return <input type="text" value={count} />; }
<input>
, а состояние в функциональном компоненте может быть изменено только функцией set*
, возвращаемым useState
. Таким образом, решение также очень простое, просто используйте функцию set*
при изменении. Подробнее о том как работать с инпутом в React можно прочитать в этой статье.
Решение
Просто привяжите событие onChange
к <input>
и измените его, вызвав setCount
:
export default function App() { const [count, setCount] = useState(0); const handleChange= (event) => setCount(event.target.value); return <input type="text" value={count} onChange={handleChange} />; }
Ошибка: Использование состояния сразу после выполнения setState
Проблема
Когда мы изменяем данные через setState()
и сразу же хотим получить новые данные, возникнет ситуация, что возвращаются старые данные:
Это связано с тем, что// Классовые компоненты // инициализация состояния this.state = { name: "John" }; // обновление состояния this.setState({ name: "Hello, John!" }); console.log(this.state.name); // => John
setState()
является асинхронным. Когда setState()
выполняется, реальная операция обновления будет помещена в асинхронную очередь для выполнения, а код, который будет выполняться следующим (т.е. console.log
в примере), выполняется синхронно, поэтому выводимое в консоль состояние не является последним значением.
Решение
Просто передайте последующую операцию, которая будет выполняться как функция, в качестве второго параметра setState()
, эта функция обратного вызова будет выполнена после завершения обновления.
Теперь обновленное значение выводится правильно.this.setState({ name: "Hello, John!" }, () => { console.log(this.state.name); // => Hello, John! });
Ошибка: Появление бесконечного цикла при использовании useState
+ useEffect
Проблема
Когда мы напрямую вызываем метод set*()
, возвращаемый useState()
внутри useEffect()
, и не устанавливаем второй параметр в useEffect()
, мы столкнемся с бесконечным циклом:
После этого можно увидеть, что данные на странице обновляются, и функцияexport default function App() { const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }); return <div className="App">{count}</div>; }
useEffect()
вызывается бесконечно, входя в состояние бесконечного цикла.
Решение
Это распространенная проблема неправильного использования useEffect()
. useEffect()
можно рассматривать как комбинацию трех функций жизненного цикла: componentDidMount
, componentDidUpdate
и componentWillUnmount
в классовых компонентах. useEffect(effect, deps)
принимает 2 аргумента:
effect
функция, которая должна выполниться (побочный эффект)deps
массив зависимостей
deps
выполняется функция эффекта. Чтобы изменить метод, вам нужно всего лишь передать []
в качестве второго аргумента useEffect()
:
export default function App() { const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }, []); return <div className="App">{count}</div>; }
useEffect
:
- Если второй параметр не передан: при обновлении любого состояния будет запущена функция эффекта
useEffect
.
useEffect(() => { setCount(count + 1); });
- Если второй параметр - это пустой массив: функция эффекта
useEffect
срабатывает только при монтировании и размонтировании.
useEffect(() => { setCount(count + 1); }, []);
- Если второй параметр представляет собой массив с одним значением: функция эффекта
useEffect
будет запускаться только при изменении значения.
useEffect(() => { setCount(count + 1); }, [name]);
- Если второй параметр представляет собой массив c несколькими значениями: функция эффекта
useEffect
будет запускаться при изменении хотя бы одного из значений из списка зависимостей.
useEffect(() => { setCount(count + 1); }, [name, age]);
Ошибка: Отсутствие очистки побочных эффектов в useEffect
Проблема
В классовых компонентах мы используем метод жизненного цикла componentWillUnmount()
для очистки некоторых побочных эффектов, таких как таймеры, слушатели событий и т. д.
Решение
Из функции эффекта useEffect()
может быть возвращена функция очистки, которая аналогична роли метода жизненного цикла componentWillUnmount()
:
useEffect(() => { // ... return () => clearInterval(id); }, [name, age]);
Ошибка: Неправильное использование логических операторов
Проблема В синтаксисе JSX/TSX мы часто используем логические значения для управления отображаемыми элементами, и во многих случаях мы используем оператор&&
для обработки этой логики:
Мы думаем, что в это время страница будет отображать пустой контент, но на самом деле на ней отобразитсяconst count = 0; const Comp = () => count && <h1>Chris1993</h1>;
0
.
Решение
Причина в том, что ложное выражение приводит к тому, что элементы после &&
пропускаются, и будет возвращено значение ложного выражения. Поэтому нужно стараться написать условие оценки как можно более полным, не полагаясь на истинное и ложное логическое значение JavaScript для сравнения:
Теперь страница будет отображать пустой контент, как и ожидается.const count = 0; const Comp = () => count > 0 && <h1>Chris1993</h1>;
Ошибка: Типы просов компонента не типизированы
Проблема Если компоненты, разработанные разными членами команды, не имеют четко определенных типов для просов, то для коллег будет не очевидно, как использовать компоненты, например:Решениеconst UserInfo = (props) => { return ( <div> {props.name} : {props.age} </div> ); };
- Определить типы пропсов компонента, используя TypeScript.
// Классовые компоненты interface AppProps { value: string; } interface AppState { count: number; } class App extends React.Component<AppProps, AppStore> { // ... } // Функциональные компоненты interface AppProps { value?: string; } const App: React.FC<AppProps> = ({ value = "", children }) => { //... };
- Без использования TypeScript типы пропсов могут быть определены с помощью
propTypes
.
const UserInfo = (props) => { return ( <div> {props.name} : {props.age} </div> ); }; UserInfo.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number.isRequired, };
Ошибка: Передача строк в качестве значений компонентам
Проблема Так как React имеет шаблонный синтаксис, очень похожий на HTML, часто бывает так, что числа передаются напрямую компонентам в пропсы, что приводит к неожиданному результату:Сравнение<MyComp count="99"></MyComp>
props.count === 99
в компоненте MyComp
вернет false
.
Решение
Правильный способ должен заключаться в использовании фигурных скобок для передачи пропсов:
Передача строковых просов будет выглядеть следующим образом:<MyComp count={99}></MyComp>
<MyComp count={"99"}></MyComp>
Ошибка: Имя компонента не начинается с заглавной буквы
Проблема Начинающие разработчики часто забывают называть свои компоненты с заглавной буквы. Компоненты, начинающиеся со строчной буквы в JSX/TSX, компилируются в элементы HTML, такие как<div />
для тегов HTML.
Решение Просто измените первую букву на заглавную:class myComponent extends React.component {}
class MyComponent extends React.component {}
Ошибка: Неверная привязка события к элементу в классовых компонентах
ПроблемаПри нажатии на кнопкуimport { Component } from "react"; export default class HelloComponent extends Component { constructor() { super(); this.state = { name: "John", }; } update() { this.setState({ name: "Hello John!" }); } render() { return ( <div> <button onClick={this.update}>update</button> </div> ); } }
update
консоль сообщит об ошибке, что невозможно прочитать свойства undefined
(чтение setState
)
Решение
Это происходит потому, что this
не привязан к тому контексту, который мы ожидаем. Есть несколько решений:
- Привязать контекст в конструкторе при помощи метода
bind
constructor() { super(); this.state = { name: "John" }; this.update = this.update.bind(this); }
- Использовать стрелочные функции
update = () => { this.setState({ name: "Hello John!" }); };
- Привязать прямо в функции рендеринга
<button onClick={this.update.bind(this)}>update</button>
- Использовать стрелочные функции в функции рендеринга (не рекомендуется, т.к. это создает новую функцию каждый раз при рендеринге компонента, что влияет на производительность)
<button onClick={() => this.update()}>update</button>
Что такое JSX в React
2 года назад·2 мин. на чтение
Новички в React, вероятно, путаются в том, почему мы пишем HTML внутри JavaScript.
React без JSX
Давайте напишем React код без использования JSX, чтобы мы могли лучше знать, почему мы используем JSX в React.let h1 = React.createElement('h1',{style:{color:"green"}}," h1 element"); let p = React.createElement('p',{style:{color:"red"}},"p element"); let div = React.createElement('div',{className:"container"},h1,p); ReactDOM.render(div,document.querySelector('#app'))
React.createElement(type,props,children)
принимает три аргумента.
type
: Это означает тип HTML-элемента, который нам нужен. (Пример: h1
, h2
, p
, div
и т.д.)
props
: Любые пропсы, необходимые для этого элемента.
children
: Данные, которые нам нужно добавить внутрь элемента html (пример: обычный текст или дочерние элементы)
React с JSX
Теперь заменим приведенный выше код на код с JSX.JSX, который мы пишем внутри React, часто преобразуется в JavaScript с помощью транспилятора babel.let green = {color:"green"}; let red = {color:"red"}; let h1 = <h1 style ={green}>h1 element</h1>; let p = <p style={red}>p element</p>; let div = <div className="container">{h1}{p}</div> ReactDOM.render(div,document.querySelector('#app'))
Что такое JSX?
JSX позволяет нам писать HTML-синтаксис внутри JavaScript. Используя JSX, мы можем сделать наш код более читабельным. JSX используется не только в React, но и в некоторых других фреймворках.Выражения в JSX
В JSX мы можем встраивать выражения JavaScript, обернув фигурными скобками{ }
.
let h1 = <h1> Odd number {2+3}</h1>
В приведенном выше коде мы использовали методlet users = ['user1', 'user2', 'user3'] let ul = ( <ul> {users.map((user,i)=>( <li>{user}</li> ))} </ul> )
map
для перебора массива и создали три элемента li
.
Атрибуты в JSX
Встроенные (инлайновые) стили
Для встроенного стиля нам нужно передать свойства стиля как объект внутри фигурных скобок, так как объект является выражением JavaScript. Мы также можем передать объект стиля непосредственно в фигурные скобки вместо использования дополнительной переменной.// объект let greenColor = {color: "green"} let h1 = <h1 style={greenColor}>This is heading</h1>
let h1 = <h1 style={{color:"green"}}>This is heading</h1>
Внешние стили, использующие classNames
Нам нужно использовать className
вместо обычного атрибута class
, который мы используем в HTML, потому что внутри JavaScript уже присутствует ключевое слово class
.
let h1 = <h1 className="header-h1">This is heading</h1>
Компоненты React
Компонент представляет собой многократно используемый фрагмент кода в React, который возвращает React элемент.function Button(props){ return <button>{props.name}</button> }
Условные выражения в JSX
Мы можем упростить приведенный выше код, используя тернарный оператор.function ShowHide(props) { if(props.show) { return <button>Show</button> } else { return <button>Hide</button> } } ReactDOM.render(<ShowHide show="true" />,document.querySelector('#app'))
function ShowHide(props){ return <button>{props.show ? "Show" : "Hide"}</button> }
Оператор spread в JSX
Предположим, нам нужно передать данные компонентуUser
с помощью пропсов.
Передадим те же пропсы с помощью оператора spread.function User(props) { return ( <div> <h1>{props.name}</h1> <ul> <li>{props.email}</li> <li>{props.mobile }</li> </ul> </div> ) } <User name="John" email="user@example.com" mobile={11233} />
let user = { name: "John", email: "user@example.com", mobile: 11233 } <User {...user} />