
Разобравшись с этим материалом, вы освоите следующее:
- Компоненты React.
- Рендеринг ReactDOM.
- Классы компонентов и функциональных компоненты.
- JSX.
- Состояние (state).
- Обработка событий.
- Асинхронный метод setState.
- Свойства (props).
- Ссылки (refs).
Это — практически всё, что нужно знать для того, чтобы создавать и поддерживать React-приложения.
Предварительная подготовка
Рассмотрим такую ситуацию: к вам за помощью обращается маленький стартап. Они создали приятную страницу, пользуясь которой пользователи могут загружать в их сервис музыку и проигрывать её. Им хочется, чтобы вы сделали самое сложное — вдохнули в эту страницу жизнь.
Для начала создайте новую директорию проекта и добавьте туда три файла. Вот они на GitHub, а вот их код.
body {
background: #f9f9f9;
font-family: 'Open Sans', sans-serif;
text-align: center;
}
#container {
position: relative;
z-index: 2;
padding-top: 100px;
}
.play {
display: block;
width: 0;
height: 0;
border-top: 50px solid transparent;
border-bottom: 50px solid transparent;
border-left: 60px solid #2c3e50;
margin: 100px auto 50px auto;
position: relative;
z-index: 1;
transition: all 0.3s;
-webkit-transition: all 0.3s;
-moz-transition: all 0.3s;
left: 10px;
}
.play:before {
content: '';
position: absolute;
top: -75px;
left: -115px;
bottom: -75px;
right: -35px;
border-radius: 50%;
border: 10px solid #2c3e50;
z-index: 2;
transition: all 0.3s;
-webkit-transition: all 0.3s;
-moz-transition: all 0.3s;
}
.play:after {
content: '';
opacity: 0;
transition: opacity 0.6s;
-webkit-transition: opacity 0.6s;
-moz-transition: opacity 0.6s;
}
.play:hover:before, .play:focus:before {
transform: scale(1.1);
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
}
.play.active {
border-color: transparent;
}
.play.active:after {
content: '';
opacity: 1;
width: 25px;
height: 80px;
position: absolute;
right: 8px;
top: -40px;
border-right: 20px solid #2c3e50;
border-left: 20px solid #2c3e50;
}
h1 {
text-transform: uppercase;
color: #34495e;
letter-spacing: 2px;
font-size: 2em;
margin-bottom: 0;
}
canvas {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
audio {
position: fixed;
left: 10px;
bottom: 10px;
width: calc(100% - 20px);
}
var ALPHA,
AudioAnalyser,
COLORS,
MP3_PATH,
NUM_BANDS,
NUM_PARTICLES,
Particle,
SCALE,
SIZE,
SMOOTHING,
SPEED,
SPIN,
TIMES_CALLED,
ANALYSER;
NUM_PARTICLES = 150;
NUM_BANDS = 128;
TIMES_CALLED = 0;
SMOOTHING = 0.5;
MP3_PATH = 'music.mp3';
SCALE = {
MIN: 5.0,
MAX: 80.0
};
SPEED = {
MIN: 0.2,
MAX: 1.0
};
ALPHA = {
MIN: 0.8,
MAX: 0.9
};
SPIN = {
MIN: 0.001,
MAX: 0.005
};
SIZE = {
MIN: 0.5,
MAX: 1.25
};
COLORS = [
'#69D2E7',
'#1B676B',
'#BEF202',
'#EBE54D',
'#00CDAC',
'#1693A5',
'#F9D423',
'#FF4E50',
'#E7204E',
'#0CCABA',
'#FF006F'
];
function getAnimation(file) {
AudioAnalyser = (function() {
AudioAnalyser.AudioContext = self.AudioContext || self.webkitAudioContext;
AudioAnalyser.enabled = AudioAnalyser.AudioContext != null;
function AudioAnalyser(audio, numBands, smoothing) {
var src;
this.audio = audio != null ? audio : new Audio();
this.numBands = numBands != null ? numBands : 256;
this.smoothing = smoothing != null ? smoothing : 0.3;
this.audio = document.getElementById('audio');
if (!this.audio) {
return;
}
try {
this.audio.src = window.URL.createObjectURL(file);
} catch (err) {
console.log(err);
}
this.context = new AudioAnalyser.AudioContext();
this.jsNode = this.context.createScriptProcessor(2048, 1, 1);
this.analyser = this.context.createAnalyser();
this.analyser.smoothingTimeConstant = this.smoothing;
this.analyser.fftSize = this.numBands * 2;
this.bands = new Uint8Array(this.analyser.frequencyBinCount);
this.audio.addEventListener(
'play',
(function(_this) {
return function() {
if (TIMES_CALLED === 1) {
return;
}
ANALYSER.start();
TIMES_CALLED++;
_this.source = _this.context.createMediaElementSource(_this.audio);
_this.source.connect(_this.analyser);
_this.analyser.connect(_this.jsNode);
_this.jsNode.connect(_this.context.destination);
_this.source.connect(_this.context.destination);
return (_this.jsNode.onaudioprocess = function() {
_this.analyser.getByteFrequencyData(_this.bands);
if (!_this.audio.paused) {
return typeof _this.onUpdate === 'function'
? _this.onUpdate(_this.bands)
: void 0;
}
});
};
})(this)
);
}
AudioAnalyser.prototype.start = function() {
return this.audio.play();
};
AudioAnalyser.prototype.stop = function() {
return this.audio.pause();
};
return AudioAnalyser;
})();
Particle = (function() {
function Particle(x1, y1) {
this.x = x1 != null ? x1 : 0;
this.y = y1 != null ? y1 : 0;
this.reset();
}
Particle.prototype.reset = function() {
this.level = 1 + floor(random(4));
this.scale = random(SCALE.MIN, SCALE.MAX);
this.alpha = random(ALPHA.MIN, ALPHA.MAX);
this.speed = random(SPEED.MIN, SPEED.MAX);
this.color = random(COLORS);
this.size = random(SIZE.MIN, SIZE.MAX);
this.spin = random(SPIN.MAX, SPIN.MAX);
this.band = floor(random(NUM_BANDS));
if (random() < 0.5) {
this.spin = -this.spin;
}
this.smoothedScale = 0.0;
this.smoothedAlpha = 0.0;
this.decayScale = 0.0;
this.decayAlpha = 0.0;
this.rotation = random(TWO_PI);
return (this.energy = 0.0);
};
Particle.prototype.move = function() {
this.rotation += this.spin;
return (this.y -= this.speed * this.level);
};
Particle.prototype.draw = function(ctx) {
var alpha, power, scale;
power = exp(this.energy);
scale = this.scale * power;
alpha = this.alpha * this.energy * 1.5;
this.decayScale = max(this.decayScale, scale);
this.decayAlpha = max(this.decayAlpha, alpha);
this.smoothedScale += (this.decayScale - this.smoothedScale) * 0.3;
this.smoothedAlpha += (this.decayAlpha - this.smoothedAlpha) * 0.3;
this.decayScale *= 0.985;
this.decayAlpha *= 0.975;
ctx.save();
ctx.beginPath();
ctx.translate(this.x + cos(this.rotation * this.speed) * 250, this.y);
ctx.rotate(this.rotation);
ctx.scale(
this.smoothedScale * this.level,
this.smoothedScale * this.level
);
ctx.moveTo(this.size * 0.5, 0);
ctx.lineTo(this.size * -0.5, 0);
ctx.lineWidth = 1;
ctx.lineCap = 'round';
ctx.globalAlpha = this.smoothedAlpha / this.level;
ctx.strokeStyle = this.color;
ctx.stroke();
return ctx.restore();
};
return Particle;
})();
Sketch.create({
particles: [],
setup: function() {
var analyser, error, i, intro, j, particle, ref, warning, x, y;
for (i = j = 0, ref = NUM_PARTICLES - 1; j <= ref; i = j += 1) {
x = random(this.width);
y = random(this.height * 2);
particle = new Particle(x, y);
particle.energy = random(particle.band / 256);
this.particles.push(particle);
}
if (AudioAnalyser.enabled) {
try {
analyser = new AudioAnalyser(MP3_PATH, NUM_BANDS, SMOOTHING);
analyser.onUpdate = (function(_this) {
return function(bands) {
var k, len, ref1, results;
ref1 = _this.particles;
results = [];
for (k = 0, len = ref1.length; k < len; k++) {
particle = ref1[k];
results.push((particle.energy = bands[particle.band] / 256));
}
return results;
};
})(this);
analyser.audio = window.audio;
ANALYSER = analyser;
intro = document.getElementById('intro');
intro.style.display = 'none';
if (
/Safari/.test(navigator.userAgent) &&
!/Chrome/.test(navigator.userAgent)
) {
warning = document.getElementById('warning2');
return (warning.style.display = 'block');
}
} catch (_error) {
error = _error;
}
} else {
warning = document.getElementById('warning1');
return (warning.style.display = 'block');
}
},
draw: function() {
var j, len, particle, ref, results;
this.globalCompositeOperation = 'lighter';
ref = this.particles;
results = [];
for (j = 0, len = ref.length; j < len; j++) {
particle = ref[j];
if (particle.y < -particle.size * particle.level * particle.scale * 2) {
particle.reset();
particle.x = random(this.width);
particle.y =
this.height + particle.size * particle.scale * particle.level;
}
particle.move();
results.push(particle.draw(this));
}
return results;
}
});
}
function handleFileSelect(evt) {
var files = evt.target.files;
getAnimation(files[0]);
}
getAnimation(null);
document
.getElementById('files')
.addEventListener('change', handleFileSelect, false);
<link rel="stylesheet" href="app.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,800">
<div id="container">
<div id="hook"></div>
<h1>Play Music</h1>
<input type="file" id="files" name="files[]" multiple />
</div>
<script crossorigin src="https://unpkg.com/react@15/dist/react.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@15/dist/react-dom.js"></script>
<script src="https://npmcdn.com/babel-core@5.8.38/browser.min.js"></script>
<script src="https://soulwire.github.io/sketch.js/js/sketch.min.js"></script>
<script src="app.js"></script>
<script type="text/babel">
// Тут будет код на React.
</script>
Для успешного прохождения этого руководства вам понадобится свежая версия браузера Google Chrome, иначе не будут работать анимации. Выражаем благодарность Стивену Фабре за CSS для кнопки проигрывания и Джастину Виндлу за код визуализации (оригинал можно посмотреть здесь).
Откройте
index.html
в редакторе кода и в браузере. Пришло время познакомиться с React.Что такое React?
React — это инструмент для создания пользовательских интерфейсов. Его главная задача — обеспечение вывода на экран того, что можно видеть на веб-страницах. React значительно облегчает создание интерфейсов благодаря разбиению каждой страницы на небольшие фрагменты. Мы называем эти фрагменты компонентами.
Вот пример разбивки страницы на компоненты:

Каждый выделенный фрагмент страницы, показанной на рисунке, считается компонентом. Но что это значит для разработчика?
Что такое компонент React?
Компонент React — это, если по-простому, участок кода, который представляет часть веб-страницы. Каждый компонент — это JavaScript-функция, которая возвращает кусок кода, представляющего фрагмент страницы.
Для формирования страницы мы вызываем эти функции в определённом порядке, собираем вместе результаты вызовов и показываем их пользователю.
Напишем компонент внутри тега
<script>
файла index.html
с type
, установленным в "text/babel"
:<script type="text/babel">
function OurFirstComponent() {
return (
// Тут будет код, представляющий элемент пользовательского интерфейса
);
}
</script>
Когда мы вызываем функцию
OurFirstComponent()
, в ответ приходит фрагмент страницы.Функции можно писать и так:
const OurFirstComponent = () => {
return (
// То, что нужно для создания компонента, идёт сюда
);
}
React использует язык программирования, называемый JSX, который похож на HTML, но работает внутри JavaScript, что отличает его от HTML.
Вы можете добавить сюда обычный HTML для того, чтобы он попал в пользовательский интерфейс:
<script type="text/babel">
function OurFirstComponent() {
return (
<h1>Hello, I am a React Component!</h1>
);
}
</script>
Когда мы вызываем функцию
OurFirstComponent()
, она возвращает фрагмент JSX-кода. Мы можем использовать так называемый ReactDOM для вывода того, что представляет этот код, на страницу:<script type="text/babel">
function OurFirstComponent() {
return (
<h1>Hello, I am a React Component!</h1>
);
}
const placeWeWantToPutComponent = document.getElementById('hook');
ReactDOM.render(OurFirstComponent(), placeWeWantToPutComponent);
</script>
Теперь тег
<h1>
окажется внутри элемента с ID hook
. Когда вы обновите страницу браузера, она должна выглядеть так:
Можно и написать собственный компонент на JSX. Делается это так:
ReactDOM.render(<OurFirstComponent />, placeWeWantToPutComponent);
Это — стандартный подход — вызывать компоненты так, будто вы работаете с HTML.
Сборка компонентов
Компоненты React можно помещать в другие компоненты.
<script type="text/babel">
function OurFirstComponent() {
return (
<h1>I am the child!</h1>
);
}
function Container() {
return (
<div>
<h1>I am the parent!</h1>
<OurFirstComponent />
</div>
);
}
const placeWeWantToPutComponent = document.getElementById('hook');
ReactDOM.render(<Container />, placeWeWantToPutComponent);
</script>
Вот что выведет вышеприведённый код:

Именно так страницы собирают из фрагментов, написанных на React — вкладывая компоненты друг в друга.
Классы компонентов
До сих пор мы писали компоненты в виде функций. Их называют функциональными компонентами. Однако, компоненты можно писать и иначе, в виде классов JavaScript. Их называют классами компонентов.
class Container extends React.Component {
render() {
return (
<div>
<h1>I am the parent!</h1>
<OurFirstComponent />
</div>
);
}
}
const placeWeWantToPutComponent = document.getElementById('hook');
ReactDOM.render(<Container />, placeWeWantToPutComponent);
Классы компонентов должны содержать функцию, называемую
render()
. Эта функция возвращает JSX-код компонента. Их можно использовать так же, как функциональные компоненты, например, обращаясь к ним с помощью конструкции:<AClassComponent />
В том случае, если вас интересуют компоненты без состояния, предпочтение следует отдать функциональным компонентам, их, в частности, легче читать. О состоянии компонентов мы поговорим ниже.
JavaScript в JSX
В JSX-код можно помещать переменные JavaScript. Выглядит это так:
class Container extends React.Component {
render() {
const greeting = 'I am a string!';
return (
<div>
<h1>{ greeting }</h1>
<OurFirstComponent />
</div>
);
}
}
Теперь текст
«I am a string»
окажется внутри тега <h1>
.Кроме того, тут можно делать и вещи посложнее, вроде вызовов функций:
class Container extends React.Component {
render() {
const addNumbers = (num1, num2) => {
return num1 + num2;
};
return (
<div>
<h1>The sum is: { addNumbers(1, 2) }</h1>
<OurFirstComponent />
</div>
);
}
}
Вот как будет выглядеть страница после обработки вышеприведённого фрагмента кода:

Подводные камни JSX
Переименуйте
OurFirstComponent()
в PlayButton
. Нам надо, чтобы этот компонент возвращал следующее:<a href="#" title="Play video" class="play" />
Однако, тут мы сталкиваемся с проблемой:
class
— это ключевое слово JavaScript, поэтому использовать его мы не можем. Как же назначить класс play
элементу <a>
?Для того, чтобы этого добиться, нужно воспользоваться свойством
className
:<script type="text/babel">
function PlayButton() {
return <a href="#" title="Play video" className="play" />;
}
class Container extends React.Component {
render() {
return (
<div>
<PlayButton />
</div>
);
}
}
const placeWeWantToPutComponent = document.getElementById('hook');
ReactDOM.render(<Container />, placeWeWantToPutComponent);
</script>
Особенности создаваемого компонента
Компоненты, основанные на классах, могут хранить информацию о текущей ситуации. Эта информация называется состоянием (state), она хранится в JS-объекте. В нижеприведённом коде показан объект, представляющий состояние нашего компонента. Его ключ — это
isMusicPlaying
, с ним связано значение false
. Этот объект назначен this.state
в методе constructor
, который вызывается при первом использовании класса.class Container extends React.Component {
constructor(props) {
super(props);
this.state = { isMusicPlaying: false };
}
render() {
return (
<div>
<PlayButton />
</div>
);
}
}
Метод
constructor
компонента React всегда должен вызвать super(props)
прежде чем выполнять что угодно другое.Итак, а что нам делать с этим «состоянием»? Зачем оно придумано?
Изменение компонента React на основе его состояния
Состояние — это инструмент, позволяющий обновлять пользовательский интерфейс, основываясь на событиях. Тут мы будем использовать состояние для изменения внешнего вида кнопки проигрывания музыки, основываясь на щелчке по ней. Кнопка может отображаться в одном из двух вариантов. Первый указывает на возможность запуска проигрывания, второй — на то, что музыка проигрывается, и этот процесс можно приостановить. Когда пользователь щёлкает по кнопке, меняется состояние, а затем обновляется пользовательский интерфейс.
Вот с чего мы начнём. Узнаем состояние компонента с помощью конструкции
this.state
. В следующем коде мы проверяем состояние и используем его для принятия решения о том, какой текст показать пользователю.class Container extends React.Component {
constructor(props) {
super(props);
this.state = { isMusicPlaying: false };
}
render() {
const status = this.state.isMusicPlaying ? 'Playing' : 'Not playing';
return (
<div>
<h1>{ status }</h1>
<PlayButton />
</div>
);
}
}
В функции
render
ключевое слово this
всегда ссылается на компонент, внутри которого она находится.
Всё это не особенно полезно, если у нас нет способа изменять
this.state.isMusicPlaying
.Как компонент реагирует на события?
Пользователь может взаимодействовать с компонентом, щёлкая по кнопке проигрывания музыки. Мы хотим реагировать на эти события. Делается это посредством функции, которая занимается обработкой событий. Эти функции так и называются — обработчики событий.
class Container extends React.Component {
constructor(props) {
super(props);
this.state = { isMusicPlaying: false };
}
handleClick(event) {
// Отреагировать на щелчок мышью
};
render() {
let status = this.state.isMusicPlaying
? 'Playing :)'
: 'Not playing :(';
return (
<div>
<h1 onClick={this.handleClick.bind(this)}>{ status }</h1>
<PlayButton />
</div>
);
}
}
Когда пользователь щёлкает по тексту, представленному тегом
<h1>
, компонент вызывает функцию handleClick
. Функция получает объект события в качестве аргумента, а это значит, что она может, при необходимости, им пользоваться.Мы используем метод
.bind
функции handleClick
для того, чтобы ключевое слово this
ссылалось на весь компонент, а не только на <h1>
.Как должен работать компонент
Когда меняется состояние компонента, он снова вызовет функцию
render
. Мы можем изменить состояние с помощью this.setState()
, если передадим этой функции объект, представляющий новое состояние. Компонент на странице всегда будет представлять своё текущее состояние. React самостоятельно обеспечивать такое поведение компонентов.handleClick() {
if (this.state.isMusicPlaying) {
this.setState({ isMusicPlaying: false });
} else {
this.setState({ isMusicPlaying: true });
}
};
Теперь, разобравшись с этим механизмом, займёмся обработкой щелчка по кнопке.
Обмен данными между компонентами
Компоненты могут «общаться» друг с другом. Посмотрим, как это работает. Мы можем сообщить
PlayButton
, проигрывается музыка или нет, используя так называемые свойства (props). Свойства — это информация, коллективно используемая родительским компонентом и компонентами-потомками.Свойств в JSX выглядят так же, как HTML-свойства. Мы назначаем
PlayButton
свойство, называемое isMusicPlaying
, которое является тем же самым, что и isMusicPlaying
в this.state
.class Container extends React.Component {
constructor(props) {
super(props);
this.state = { isMusicPlaying: false };
}
handleClick() {
if (this.state.isMusicPlaying) {
this.setState({ isMusicPlaying: false });
} else {
this.setState({ isMusicPlaying: true });
}
};
render() {
return (
<div>
<PlayButton isMusicPlaying={this.state.isMusicPlaying} />
</div>
);
}
}
Когда состояние
Container
меняется, свойство PlayButton
также меняется, и функция PlayButton
вызывается снова. Это означает, что вид компонента на экране обновится.Внутри
PlayButton
мы можем реагировать на изменения, так как PlayButton
принимает свойства как аргумент:function PlayButton(props) {
const className = props.isMusicPlaying ? 'play active' : 'play';
return <a href="#" title="Play video" className={className} />;
}
Если мы поменяем состояние на
this.state = { isMusicPlaying: true };
и перезагрузим страницу, на ней должна появиться кнопка паузы:
События как свойства
Свойства необязательно должны представлять собой какие-то данные. Они могут быть и функциями.
function PlayButton(props) {
const className = props.isMusicPlaying ? 'play active' : 'play';
return <a onClick={props.onClick} href="#" title="Play video" className={className} />;
}
class Container extends React.Component {
constructor(props) {
super(props);
this.state = { isMusicPlaying: false };
}
handleClick() {
if (this.state.isMusicPlaying) {
this.setState({ isMusicPlaying: false });
} else {
this.setState({ isMusicPlaying: true });
}
};
render() {
return (
<div>
<PlayButton
onClick={this.handleClick.bind(this)}
isMusicPlaying={this.state.isMusicPlaying}
/>
</div>
);
}
}
Теперь, когда мы щёлкаем по кнопке
PlayButton
, она меняет состояние Container
, которое изменит props
PlayButton
, что приведёт к обновлению кнопки на странице.Неприятная особенность setState
При вызове
setState
изменение состояния не производится мгновенно. React ждёт немного для того, чтобы увидеть, не нужно ли внести ещё какие-то изменения, и только потом производит изменение состояния. Это означает, что нельзя точно знать, каким будет состояние компонента после вызова setState
.Поэтому вот так поступать не следует:
handleClick() {
this.setState({ isMusicPlaying: !this.state.isMusicPlaying });
};
Если вы изменяете состояние, основываясь на предыдущем состоянии, нужно делать это по-другому. А именно, следует передать
setState
функцию, а не объект. Эта функция принимает старое состояние как аргумент и возвращает объект, представляющий новое состояние.Выглядит это так:
handleClick() {
this.setState(prevState => {
return {
isMusicPlaying: !prevState.isMusicPlaying
};
});
};
Эта конструкция сложнее, но она необходима только в том случае, если вы используете старое состояние для формирования нового состояния. Если нет — можно просто передавать
setState
объект.Что такое ссылки?
Пришло время включить музыку. Для начала добавим тег
<audio>
:class Container extends React.Component {
constructor(props) {
super(props);
this.state = { isMusicPlaying: false };
}
handleClick() {
this.setState(prevState => {
return {
isMusicPlaying: !prevState.isMusicPlaying
};
});
};
render() {
return (
<div>
<PlayButton
onClick={this.handleClick.bind(this)}
isMusicPlaying={this.state.isMusicPlaying}
/>
<audio id="audio" />
</div>
);
}
}
Нам нужен способ обратиться к тегу
<audio>
и вызвать либо его метод play()
, либо pause()
. Сделать это можно с помощью конструкции document.getElementById('audio').play()
, но React предлагает кое-что получше.Мы назначаем элементу атрибут, называемый
ref
, который принимает функцию. Эта функция, в качестве первого аргумента, принимает элемент <audio>
, и присваивает его this.audio
.<audio id="audio" ref={(audioTag) => { this.audio = audioTag }} />
Эта функция будет вызываться каждый раз, когда выводится
Container
, то есть, this.audio
всегда будет в актуальном состоянии и будет указывать на тег <audio>
.Теперь мы можем запускать и приостанавливать воспроизведение музыки:
handleClick() {
if (this.state.isMusicPlaying) {
this.audio.pause();
} else {
this.audio.play();
}
this.setState(prevState => {
return {
isMusicPlaying: !prevState.isMusicPlaying
};
});
};
Выгрузим на страницу музыкальный файл (лучше — в формате .mp3) с использованием кнопки
Choose files
, нажмём на кнопку запуска воспроизведения и послушаем музыку.React за пределами index.html
Как вы, возможно, догадались, React-код не должен «жить» исключительно внутри тега
<script>
. React поддерживает множество конфигураций сборки. К счастью, с помощью инструментов вроде Create React App всю рутинную работу по формированию каркаса приложения можно автоматизировать. Установите create-react-app
, создайте новый проект, посмотрите руководство и приступайте к работе с JS-файлами в папке проекта src
, применяя все те знания по React, которые вы сегодня получили.Итоги
Примите поздравления! Вы сделали первые шаги в сфере разработки React-приложений, освоили основы, позволяющие приступать к созданию собственных проектов и продуктивно учиться дальше.
Уважаемые читатели! Если сегодня состоялось ваше первое знакомство с React — просим поделиться впечатлениями.
Комментарии (55)
BOM
23.11.2017 16:06Единственное, что меня смущает в туториалах реакта, это тотальное смешивание html и js кода. Разве это не считается bad practice? Можно ли обойтись совсем без смешивания одного с другим или это неотъемлемая часть JSX?
defaultvoice
23.11.2017 18:40JSX это НЕ html, он только притворяется им (с помощью babel). Это просто удобное представление для createElement(component, props, ...children).
mayorovp
24.11.2017 09:04Тем не менее, он:
1. синтаксически похож на html;
2. предназначен для описания желаемого DOM — то есть для того же самого для чего предназначен html.faiwer
24.11.2017 09:26- Не только DOM. Вложенные компоненты тоже описываются как XML-элементы. При желании можно вообще никакого DOM не создавать, а делать всякие sideEffect-ы.
VolCh
26.11.2017 10:42Формально, JSX — это надмножество, расширение JavaScript, дополняющего его возможностью использовать XML-подобный синтаксис для некоторых целей.
faiwer
23.11.2017 19:04Если вы писали на knockout или angular, то вспомните все эти большие шаблоны с data-bind, v-
& angular attributes. Где таки довольно много логики, бывают фильтры и пр… В запущенном случае там находятся даже двух-трёх этажные js-выражения. И не всегда удаётся провести удачную грань, что тащить в класс, а что оставить в шаблоне. В случае React и JSX, то этот самый шаблон и есть React класс с JSX. Можно воспринимать это как язык шаблонов с расширением js, jsx. JS-шаблонизатор, если хотите.
В то время как логика работы с данными лежит, как правило, отдельно. И там нет JSX. В случае redux это action-ы, actionCreator-ы, reducer-ы, connect-классы, селекторы и пр…
Если вам правда интересно, то вот суть подхода.leschenko
23.11.2017 23:38Мы используем knockout. Да, data-bind'ов очень много. Я бы даже сказал что на этом все и построено. Но! Мы можем отделить ViewModel от View и от Model. У нас одна и та же ViewModel может быть привязана к разным View. И это работает.
Как быть в случае с спагетти от React?
Как отделить этот HTML-не HTML от бизнес логики? Как сделать так, чтобы одни и те же данные отображались по разному в разных частях общей View (компонента если хотите)?faiwer
24.11.2017 07:19В большинстве больших приложений с React используется внешний state manager. Например Redux или MobX. Данные хранятся там. Взаимодействие с ними хранится где либо ещё. В случае redux работа с данными и вообще вся не UI логика лежит во множестве слоёв: action-ы, actionCreator-ы, reducer-ы, selector-ы, container-компоненты. И там нет JSX. Как правильно это самостоятельные JS файлы без единой XML строчки.
А React компонент представляет из себя уже UI часть для этих данных. Он их получает свыше. Менять он их не умеет, но умеет, гхм, дёргать рычаги, которые приведут к их изменению. Опять же, рычаги эти проброшены сверху. В общем никакой прямой обработки данных. Что-то вроде шаблонизатора.
^ всё это немного упрощённо, лишь по сути. И да, перейдите по моей ссылке. Многое станет понятнее, я думаю.
leschenko
24.11.2017 10:10Не спора ради, а разобраться для…
Правильно ли я понимаю что React это инструмент для создания только View. За остальное (если здесь уместно говорить про MVVM, то VM и M) отвечают другие инструменты. Есть ли здесь (или около React) что-то напоминающее Binding'и и если есть, то каким инструментом организовано?mayorovp
24.11.2017 10:27Есть ли здесь (или около React) что-то напоминающее Binding'и и если есть, то каким инструментом организовано?
Так JSX же...
leschenko
24.11.2017 10:29Мне показалось что это очень отдаленный односторонний биндинг. Как быть с друсторонними?
mayorovp
24.11.2017 10:32DOM устроен таким образом, что двусторонний биндинг к базовым элементам без оберток невозможен в принципе.
В своих же компонентах вы можете сделать двусторонний биндинг если придумаете как он будет выглядеть.
VolCh
26.11.2017 10:45Двустороннего нет и обычно попытки его как-то неявно реализовать считаются антипаттерном в мире реакта.
faiwer
24.11.2017 10:40Есть ли здесь (или около React) что-то напоминающее Binding'и и если есть, то каким инструментом организовано?
Да. Просто пишем что-то вроде
onClick="this.props.open"
. В финале это будетaddEventListener
, а не аттрибут. Если скажем нужноonChange
на<input/>
или<textarea/>
, то тут начинаются сложности, т.к. никаких удобных механизмов работы с этим из коробки нет. Более тогоonChange
ведёт себя какonInput
(oO). Есть два подхода для работы с контролами. Оба мне не нравятся. Но, что есть, то есть. В общем дву-направленных bind-ов для работы с контролами тут нет. Из коробки. Люди пишут свои абстракции, либо используют что-нибудь типаredux-form
.
faiwer
24.11.2017 07:49Попробую на примере. Вот есть у вас скажем список чего-то, и есть снизу кнопка "добавить запись". И есть кнопки "удалить" на панели каждого элемента списка. Типичный такой TODO, только ещё проще. В итоге у нас:
- есть презентационные компоненты: Item, List, AddButton. Они с JSX, т.е. там внутри есть XML. Они на входе попросят данные для отображения и 2 функции: для удаления и для добавления. Им "не интересно" ни откуда данные пришли, ни что это за функции. Они просто внутри своего XML пропишут
onClick={this.props.add}
, аналогdata-bind="click: add"
. - у нас есть state. Это древо всех данных приложения. И в нём есть тот самый список. Данные просто данные, т.е. сериализуемые. Никаких функций, хитрых геттеров и сеттеров. Нет, просто тупо данные.
- у нас есть action-ы (опустим для простоты actionCreator-ы). Это такие простые объекты типа
{ type: 'ADD_NEW_ITEM' }
&{ type 'DEL_ITEM', idx: 12 }
. Они сами на данные не влияют. Они просто объекты. Они высказывают намерения. Или как это сказать… Это что-то вроде "эй store, держи мой ADD_NEW_ITEM. обработай его как надо". Причём о store они не знают. Это просто тупо сериализуемые объекты-намерения. Их можно подготовить, к примеру, но не использовать. Просто объекты. - у нас есть reducer-ы. Это чистые функции, которые возьмут старый store, посмотрят на переданный туда action и сделают новый store, но уже с запрошенными изменениями. Вот тут как раз вся бизнес логика по изменению данных приложения. Тут не только JSX нет и в помине, тут вообще весь код… своеобразный. Иммутабельность же.
- у нас есть container-компоненты. Они имеют связь со store. По сути store это ko.observable. И container-компоненты на него subscribe-ны. Если store меняется, то они получают новые данные store из него. Их задача — получить данные из store, выдрать из них только то, что нужно конкретно взятому компоненту из пункта 1, и подготовить все эти add и delItem методы для него же. Эти add и delItem-ы опять же, будут просто толкать в store (в его reducer) все те action-ы из пункта 3. Т.е. и тут бизнес логики практически нуль. Подготовив все эти данные container-компонент передаёт их своему приемнику, т.е. презентационному компоненту.
Наверное сильно запутал. Ну там всё не настолько просто, чтобы в паре предложений описать. В общем суть — нет в больших React приложениях никакого нагромождения XML внутри JS, так, чтобы бизнес-логика и вьюхи были рядом. Ещё суть — если вы бросите knockout и возьмёте стандартную связку react + redux, то последнее, что станет для вас проблемой, это этот несчастный JSX. Мат трёхэтажный у вас будет стоять от огромного количества бойлерплейта (мне кажется порой раз в 15 больше суеты чем в knockout-е), и вырвиглазной (имхо) работы с immutable значениями (ну не haskell у нас, не haskell, как умеем, как могём). А когда что-нибудь начнёт тормозить, то пойдут и нормализация данных в store, и selector-ы (мемоизированные функции) и прочая чертовщина. В общем прямолинейный в knockout.js js-код будет разбросан по десяткам файлов и вообще даже близко не будет напоминать обычное словесное описание того, что вам нужно. Тут конвеерная несколькозвенная обработка изменения иммутабельного состояния. Никаких больше:
this.list.add(newItem)
. Теперь это будет через карусель:actionCreator -> action -> reducer -> mapStateToProps -> view
.
Из плюшек же: всё работает очень предсказуемо и отслеживаемо. Вот прямо до предела. Вплоть до того, что можно перезагружать страницу целиком и на экране ничего не поменяется, т.к. данные в store те же (скажем localStorage) и всё остальное лишь чистые функции по работе с ними. Тестируется опять же на раз два.
У меня бывало уходили часы работы на поиск хитрого бага в сложном knockout приложении (3-4 года работы в одно рыло, большая кодовая база, много legacy). Потому что stack-трейсы для асинхронных observable бесполезны. А если там 1 computed зависит от 2-го, а 2-й от 3-го и они ещё разных типов, то во-первых — это всё работает непредсказуемо и ну очень непонятно. Последовательность пересчёта всего этого клубка (а knockout располагает к сложным клубкам) может быть разной в зависимости от нюансов. Сами эти связи могут быть совершенно не очевидными, стоит только отвлечься и забыть контекст. Никогда нельзя сказать, что вот тут у нас сложный баг, давайте просто посмотрим что от чего зависит… потому что это может быть ну очень непросто и в каждом модуле устроено по своему. Вспоминаю сейчас и испарина на лбу.
Поработав 1 год на redux+react и 3 на knockout, я бы для больших проектов выбирал react+redux, т.к. такой проект спустя годы будет иметь меньше технических долгов и куда проще поддерживаться. А vue (вместо knockout)-а для мелких и средних проектов, чтобы не утонуть в этом бойлерплейте.
Всё вышеописанное лишь моё ИМХО. Никому свою точку зрения не навязываю )
- есть презентационные компоненты: Item, List, AddButton. Они с JSX, т.е. там внутри есть XML. Они на входе попросят данные для отображения и 2 функции: для удаления и для добавления. Им "не интересно" ни откуда данные пришли, ни что это за функции. Они просто внутри своего XML пропишут
mayorovp
24.11.2017 09:06React — это библиотека для построения View и только View. Для других частей предполагается использовать другие библиотеки.
VolCh
26.11.2017 11:01JSX и есть возможность смешивать JS и XML. Вы сами определяете, где смешивать, а где нет. Это с одной стороны. С другой, в props и context (что-то вроде DI-контейнера) компоненту можно передавать любое JS значение, в том числе полноценные объекты и функции, реализующие любые сайд-эффекты, используя компоненты в качестве тупых шаблонизаторов. .state не является обязательным для использования, в некоторых практиках его использование вообще запрещено. Но ответственность за своевременный ререндеринг тогда полностью ложится на вас.
zorn_v
23.11.2017 16:26+1Честно говоря не понимаю хайпа вокруг реакта. Для меня мешанина хтмл с жс, называемая «правильным подходом» как то отталкивает.
И очень удивлен почему НИ РАЗУ не видел статью на хабре по emberjs. Даже грешным делом сам хотел написать, но все времени нет и т.п.
Vue с компонентным подходом мне больше импонирует (может потому что фарш не устраивает), чем эта модная «jQuery лапша» именуемая гордо РЕАКТ и JSX.ru_vds Автор
23.11.2017 16:27Была статья 27 октября в нашем блоге Ember.js: отличный фреймворк для веб-приложений
zorn_v
23.11.2017 17:22К сожалению не могу комментить там.
Первое — там обычный «хеллоу ворлд» который можно почитать и на самом сайте (ну может у вас чуть другой).
Я планировал статью немного другого плана. Типа как у Symfony раньше была — «Зачем нужен фреймфорк».
Ну и самое главное не упомянули про emberobserver.com
А я например через него узнал про такую замечательную штуку как PouchDB (искал адаптеры для баз).
mayorovp
23.11.2017 16:37Во Vue тоже мешанина, только в другую сторону: код на js пишется прямо в html-файлах. :-(
zorn_v
23.11.2017 16:57Не, там хотя бы разделяешь <template> <script> и <style>
И этот подход по мне правильный по части компонентов.
zorn_v
23.11.2017 20:21Хотя может мы о разных подходах говорим. Я про однофайловые компоненты ru.vuejs.org/v2/guide/single-file-components.html
faiwer
24.11.2017 07:57О разном вы говорите. Я думаю mayorovp говорит про все "v-" аттрибуты. Типичный презентационный React компонент с JSX это то, что во vue лежит внутри
template
.mayorovp
24.11.2017 09:09Нет, я как раз об однофайловых компонентах и говорил. Казалось бы, столько усилий потратили чтобы вынести js в отдельные файлы… и вот на тебе.
justboris
23.11.2017 20:56мешанина хтмл с жс
Легко обозвать незнакомую технологию мешаниной и тем самым оправдать своё невежество.
Гораздо сложнее понять, как так вышло, что 3 года назад такой подход появился и до сих пор успешно используется.
zorn_v
23.11.2017 21:49Легко обозвать незнакомого человека невеждой, познав одну технологию и проповедовать все что она дает (своих мыслей то нет).
Гораздо сложнее понять что существуют другие, более удобные технологии.
Но это же реакт и фасебук да?justboris
24.11.2017 12:56Легко обозвать незнакомого человека невеждой, познав одну технологию
До Реакта я писал на Marionette и Angular. Со знанием альтернативных технологий у меня все в порядке.
Но это же реакт и фасебук да?
А Angular это Google. Но почему-то не так успешно летит Angular, несмотря на поддержку большой компании.
VolCh
26.11.2017 11:05Я использовал многие технологии на фронте за 20 лет в веб-разработке. Лично мне React кажется наиболее удобным.
romy4
23.11.2017 18:34плохой совет:
onClick={this.handleClick.bind(this)}
бинд внутри рендера делать категорически не стоит.Ungla
26.11.2017 15:57«категорически не стоит», было бы хорошо прокомментировать почему, или дать ссылку на man, но я предпочёл бы комментарий от живого опытного человека. Я во многих уроках встречал такую строчку, но первый раз слышу почему этого не стоит делать. Не сочтите за труд, напишите пожалуйста почему. Спасибо.
faiwer
26.11.2017 16:09.bind
порождает новую функцию. Это всегда будет новый экземпляр. В данном случае это будет происходить при каждом вызове.render()
. И тот компонент которому этотonClick=
назначен всегда будет получать новый экземплярonClick
в своихprops
. И если такой компонент этоpureComputed
то он будет всегда заного рендерить свой virtualDom. Без разбору, всегда. Надо не надо… будет. Потому чтоshallow
-проверка будет сверять по===
.Ungla
26.11.2017 16:28Спасибо. По реакту немного основательных статей, а бестпрактисес статей ещё меньше. После Python где всё зарегламентировано по самое небалуйся жутко непривычно, постоянно кажется, и часто так и есть, что позоришься в коде.)
skyline405
24.11.2017 08:27Функции можно писать и так:
const OurFirstComponent = () => { return ( // То, что нужно для создания компонента, идёт сюда ); }
stateless компоненты можно писать с еще чуть меньшей вложенностью)
const OurFirstComponent = () => ( <h1>Hello world</h1> );
faiwer
24.11.2017 09:30Ваш случай можно ещё больше упростить и слегка ускорить :)
const helloWorld = <h1>Hello, World</h1>; // declare // ... {helloWorld} // use
MAXHO
24.11.2017 09:28Уважаемые читатели! Если сегодня состоялось ваше первое знакомство с React — просим поделиться впечатлениями.
Оптимистичное заявление на Хабре ;)
Чтобы не разочаровывать топикпарсера отвечаю…
Я не профессиональный программист. Я педагог преподающий в кружке информатику. И когда встал вопрос про выбор между Angular, React и Vue, то я остановился на Vue.
Почему? Как мне показалось Vue код ближе всего к JavaScript и HTML (с уклоном в HTML). Причем не смешивает оно и другое. Меньше нужно какие то места объяснять «так принято», «они решили сделать по другому», «это МАГИЯ» и пр.
Хотя React, бесспорно, тоже просмотрю. Говорят с React 16 там лицензия MIT. А это было одним из минусов, которые подтолкнули меня делать курс не на нем.
Как плюс React могу отметить большое количество руководств как сделать ToDo
Salexer
24.11.2017 11:12Классы компонентов и функциональных компоненты
читая хабр я знаю, что тут умные люди. Поэтому вчитываясь в очепятку я думаю, что так и задумано, перепрочитывая фразу несколько раз
CynepHy6
24.11.2017 13:03Вчера только начал реакт тыкать. И тут прям подарок — статья на хабре. Спасибо
vue — это хорошо и приятно, но вакансий пока больше на реакте
Vmist
24.11.2017 13:06Уважаемые читатели! Если сегодня состоялось ваше первое знакомство с React — просим поделиться впечатлениями.
1е знакомство. Вроде и понятно, но недосказанности.
Как селекторы под css добавлять после по такому коду jsx — удобно?
Отсюда: получается это нечто temlate-ора для одной-пары уникальных страниц на сайте?
В чем глобальное преимущество над js или jquery?
Совсем непонятноЭта конструкция сложнее, но она необходима только в том случае, если вы используете старое состояние для формирования нового состояния. Если нет — можно просто передавать setState объект.
это про (if) a = !a;?mayorovp
24.11.2017 13:50Отличия view-библиотеки от шаблонизатора — работа на уровне объектов, а не на уровне строк. Соответственно, нет необходимости бояться инъекций и задумываться над экранированием. Плюс обработчики событий можно привязывать напрямую, а не после рендера с поиском элемента по какому-нибудь id. Но если все это отбросить — то да, это по сути шаблонизатор.
это про (if) a = !a;?
Да, именно эта операция тут так делается. Но есть библиотеки которые позволяют делать ее проще (mobx-react).
VolCh
26.11.2017 11:14Для стилизации селекторы обычные, но лучше, имхо, избегать вложенных типа .block .element, поскольку от блока до элемента может оказаться десяток компонентов.
В получении дом-элемента по селектору обычно необходимости нет, собственно когда кажется, что она есть, то велика вероятность, что что-то сильно не так делаешь.
search
24.11.2017 14:00+1Если сегодня состоялось ваше первое знакомство с React — просим поделиться впечатлениями.
Несколько месяцев назад моя компания оплатила мне курсы реакта. Чтож, фреймворк создаёт очень приятные впечатления возможностью невероятно быстро стартануть/запрототипировать проект и наличием огромного комьюнити. А редакс оказался настолько просто и хорош собой что мы стали использовать его на Angular 2 проекте (ngrx/store, если быть точным).
Многие в комментариях жалуются на смешение HTML и JS кода в одной функции. Но глядя на любой проект в котором я участвовал, я вижу либо пилёж логики в самом шаблоне, либо ад из дата-атрибутов. Для себя я решил относиться к JSX коду как как к view-слою, который легко протестировать и понять и меня отпустило. Даже понравилось, если честно.
Такие дела.
sinneren
Ну, для начала JSX не «язык программирования». Реакт знаю плохо, прав ли я, что ref плохой способ обратиться к элементу, и имеет место лишь в определенных случаях? (я понимаю, что тут пример для ньюби и это просто пример, потому он и указан). Если прав, то было бы круто услышать об иных способах. Ну и ждём-с статью про подробный разбор состояний, поднятие, редакс и т.п.
mayorovp
ref — канонический способ обратиться к элементу. Но, конечно же, число случаев когда его следует использовать — ограничено.
Просто потому что в большинстве случаев нет никакого смысла обращаться напрямую к элементу.
VolCh
Чем JSX не язык программирования?
sinneren
Тем же, чем и тайпскрипт. В общем-то вы и сами на свой вопрос отвечали ниже…
samizdam
Тем, что язык разметки?
VolCh
JSX — надмножество JS, это не XML-подобный язык, а JS c возможностью включать XML-подобный синтаксис для некоторых целей, например с целью трансляции в вызовы React.createElement() или любые другие на ваше усмотрение.