React - библиотека для построения интерфейса на JS. Представлена Facebook в 2013.
Изначально была задумана для Web-приложений (браузер), но впоследствии появилась библиотека React Native, где встроенные элементы интерфейса мобильных ОС скриптуются на JS в библиотеке-близнеце React.
На React удобно строить SPA.
В крупных веб-приложениях много DOM-элементов, что вызывает определенное снижение производительности интерфейса. Для решения этой проблемы в React используется не настоящий, а виртуальный DOM, который хранит минимум нужной информации и синхронизируется с настоящим DOM оптимальным образом.
React предоставляет компонентный подход к работе с интерфейсом. Компонент - обособленный элемент интерфейса, который обладает своим отображением и логикой. Компоненты реализуются как классы ES6 и могут быть легко переиспользованы путем подключения/копипасты класса в другой проект.
Отображение компонента в-основном реализуется с помощью синтаксиса JSX, который представляет собой аналог HTML, однако вместо тэгов используются имена классов компонентов, а атрибуты тэга попадают как "Базовые настройки" в объект компонента. При этом в любом месте JSX может быть "включен" JavaScript с любыми выражениями.
JSX транспилируется в вызов функций React для создания компонентов. Всё, что вы
видите в JSX попадает в параметры этих функций. Поэтому там допустимы только выражения, но
не управляющие конструкции типа for
.
Возможность строить интерфейс как "HTML со своими тэгами", причем тэги эти (т. е. компоненты) могут быть любой сложности - сильно упрощает и способствует структуризации кода и интерфейса.
Супер скрипт, который позволяет вам создать каркас приложения React за одну команду.
Устанавливается с помощью npm
:
npm install -g create-react-app # ставим глобально, что бы (в идеале) появилась команда в PATH
create-react-app projectName # создает проект в текущей директории, подтягивает всё нужное опять же используя npm
После таких действий в папке projectName
появятся файлы с проектом. Корнем приложения является файл App.js
в
папке src
.
Пример ниже иллюстрирует композитный компонент - компонент, который использует для своего отображения другой компонент.
class LoginFields extends Component {
render(){
return (
<div>
<input placeholder='Login' /><br/>
<input type="password" placeholder='Password' />
</div>
)
}
}
class Form extends Component {
render() {
return (
<div>
<LoginFields />
<button>Login</button>
<button>Cancel</button>
</div>
)
}
}
class App extends Component {
render() {
return (
<div className="App">
<Form />
</div>
);
}
}
export default App;
В данном примере компонент Form
жестко привязан к LoginFields
, однако можно сделать форму и более гибкой.
Как видно из примера, компонентный подход позволяет строить интерфейс из блоков любого размера, сложности
и гибкости. Так как класс является тоже типом данных, а компоненты React являются классами
у вас всегда есть возможность "пробросить" компонент в другой компонент как вы передаете коллбэки
в функции. Таким образом один компонент будет использовать другой, даже не зная с чем конкретно
он имеет дело. Этот подход позволяет добится простоты и гибкости строительства интерфейсов.
Для использования компонента в JSX достаточно тэга с именем класса компонента.
class App extends Component {
render() {
return (
<div className="App">
<h1>Hello World {Math.random()}</h1>
</div>
);
}
}
export default App;
Синтаксис JSX преобразовывается в выражения вызова функций создания элементов и компонентов React. Код выше аналогичен:
ReactDOM.render(React.createElement('div', {className: 'App'},
React.createElement('h1',null,`Hello World ${Math.random()}`)), document.getElementById('root'));
Вы можете проверить это в index.js
.
Преобразованием JSX в подобный вызов функций React (транспилированием) занимается транспилер babel,
запускаемый npm
при сборке проекта.
Таким образом
<div
, например) должно являться именем элемента или классом компонента, которые передается первым параметром в React.createElement
className="App"
) собираются в ассоциативный массив, который передается вторым параметром в React.createElement
. Очевидно, что коль это настройки
компонента, то они рано или поздно попадут в объект и будут влиять на отображение этого компонента.h1
) добавляются остальными параметрами React.createElement
. Их может быть сколько угодно.createElement(....createElement(...createElement(...
)if
или for
там неуместен)render
компонента просто возвращает объект, созданный транспиленным JSX-ом.return (
(нельзя переносить скобку на след. строку).render
должен вернуть только одно значение, а значит на верхнем уровне в JSX должен быть один тэг./
даже в непарных тэгах.С точки зрения React
компонент должен возвращать тот или иной элемент (обычно созданный JSX) и этого достаточно:
function Hello(){
return <h1>Hello</h1>
}
Однако обычно используются компоненты-классы:
class HelloClass extends Component {
render(){ //метод, который вызывает React для получения элемента для отрисовки
return ( //нельзя переносить; только один "тэг" верхнего уровня:
<div>
JSX here
</div>
);
}
}
Для передачи настроек в компоненты используются "атрибуты тэга" в JSX, которые
транспилятся в ассоциативный массив, передаваемый в объект компонента как
первый параметр компонента-функции или как this.props
(параметр конструктора) в объекте:
function Hello(props){
return <h1>Hello {props.name}!!</h1>
}
Hello.defaultProps = {name: 'NoName'};
class HelloClass extends Component {
constructor(props){
super(props)
console.log(props)
}
render(){ //метод, который вызывает React для получения элемента для отрисовки
return ( //нельзя переносить; только один "тэг" верхнего уровня:
<div>
{this.props.names.map((name,i) => <Hello name={name} key={i} />)}
</div>
);
}
static get defaultProps(){
return {names: ["N0NAME"]};
}
}
class App extends Component {
render() {
return (
<div className="App">
<Hello name="SuperMan" />
<HelloClass names={["Captain Obvious", "JSMan", "TrololoMan"]} />
<Hello />
<HelloClass />
</div>
);
}
}
export default App;
Props
позволяют передавать данные и настройки сверху вниз - от бОльших
компонентов к меньшим вложенным компонентам.
Для связи снизу вверх всегда можно передать callback в props
.
JSX может делать голову если вы хотите передать что-то кроме строки. Включайте JS в таком случае:
<Counter value={ 0 } stopped={ true } />
Статический геттер defaultProps
или же объект в функции-компоненте defaultProps
позволяет задать
значения props
по умолчанию.
Props нельзя менять изнутри компонента, однако можно обновлять снаружи. Изменение props
ведет к
запуску метода render
для обновления представления компонента.
Вы можете добавить сколько угодно вложенных элементов создав массив с ними и вставив его в JSX.
Обратите внимание на key
- этот идентификатор элемента из массива позволяет React понять когда нужно
перерисовывать отображение.
Кроме "внешних настроек", навязанных с помощью props
, есть внутреннее состояние компонента, под
названием state
, доступное только из компонента.
state
можно менять, но нельзя менять напрямую. Изменения нужно осуществлять через метод setState
.state
должен использоваться в render
при отображении.state
обычно ведет к автоматическому запуску render
для актуализации изменений в представлении.class MinimumInput extends Component {
constructor(props){
super(props)
this.state = { valid: false } //единственное место где можно напрямую писать в state.
this.check = this.check.bind(this)
}
check() {
if (this.input.value.length < this.props.min){
this.setState({valid: false})
}
else{
this.setState({valid: true})
}
}
render(){
let style = {backgroundColor: this.state.valid ? '' : 'red'};
return (
<input onChange={this.check} ref={ c => this.input = c} style={style}/>
);
}
}
class App extends Component {
render() {
return (
<div className="App">
<MinimumInput min={ 5 } />
</div>
);
}
}
export default App;
this
используется bind
в конструкторе;setState
render
- обычный метод, и вы можете сделать ту или иную логику на JS до return
. В данном случае цвет фона зависит от state
.ref
- встроенный props
, который вызывается при создании компонента. Таким образом можно добраться до значения input
в данном примере;setState
.При вызове setState
нет гарантии, что state
в этом объекте изменится синхронно (т. е. в тот же момент). React может обработать setState
и его отрисовку в компоненте
позже, т. е. асинхронно. По этой причине, если новое состояние state
зависит от предыдущего, в коде его вычисления нельзя отталкиваться от текущего state
, так как он может
быть еще не актуализированным предыдущим(и) setState
. В качестве решения этой проблемы в сам setState
передается функция, которая будет вызвана React в нужный момент времени
с актуальными значениями props
и state
:
class MyCheckBox extends Component {
constructor(props){
super(props)
this.state = { checked: this.props.checked } //единственное место где можно напрямую писать в state.
this.toggle = this.toggle.bind(this)
}
toggle() {
this.setState((prevState, props) => { //функция вызовется потом.
return {
checked: !prevState.checked
}
})
}
render(){
let style = {backgroundColor: this.state.checked ? 'green' : ''};
return (
<button onClick={this.toggle} style={style}>{this.props.text}</button>
);
}
static get defaultProps(){
return {checked: false };
}
}
class App extends Component {
render() {
return (
<div className="App">
<MyCheckBox text='Checkbox' />
</div>
);
}
}
class LifeCycle extends Component {
//first time methods:
constructor(props){ //once
super(props)
this.state = {counter: 0};
console.log('constructor', props, this.state)
}
componentWillMount(){ //once
console.log('componentWillMount')
this.interval = setInterval(()=>{
this.setState((prevState, props) => ({counter: prevState.counter +1}))
},1000)
}
render(){ //as many as component should update + once at first time
console.log('render')
return (
<div> life cycle: {this.state.counter}</div>
);
}
componentDidMount(){ //first time render success
console.log('componentDidMount')
}
componentWillUnmount(){ //end of life
console.log('componentWillUnmount')
}
//main loop methods:
shouldComponentUpdate(nextProps, nextState){ //every time when props or state changed
console.log('shouldComponentUpdate', nextProps, nextState)
return Math.random() > 0.7; //here we decide, is this update so major to re-render, or no
}
componentWillUpdate(nextProps, nextState){ //before every render (except first)
console.log('componentWillUpdate', nextProps, nextState)
}
componentDidUpdate(prevProps, prevState){ //after render
console.log('componentDidUpdate', prevProps, prevState)
}
componentWillReceiveProps(nextProps){ //when we receive new props
console.log('componentWillReceiveProps', nextProps)
}
}
Каждый компонент в процессе своей жизни проходит разные стадии. Существует две основных "ветви" этого пути:
bind
нужных методов, создания изначального state
.componentWillMount
. Вызывается однократно, тут можно создавать те или иные ресурсы для работы компонента.render
. Главный метод, вызывается хотя бы раз за жизнь компонента, а так же может быть вызван при изменении props или
state`.componentDidMount
. Вызывается однократно после первой отрисовки компонента. Сюда можно положить то, что требует полной готовности компонента.componentWillUnmount
. Вызывается однократно перед отключением компонента от Virtual DOM.state
или props
, оценка, стоят ли эти изменения перерисовки, подготовка, отрисовка, после отрисовки.
shouldComponentUpdate
. Вызывается каждый раз при приходе нового state
или props
. Метод должен вернуть true
если данные в новых props
или state
требуют отрисовки и false
в противном случае.componentWillUpdate
. Вызывается только если shouldComponentUpdate
сказал true
. Тут можно подготовить те или иные данные перед render
render
. ОтрисовкаcomponentDidUpdate
. Те или иные операции после render
.