React
React.js - это библиотека разработанная фирмой Facebook, которая отвечает за слой представления (View) для приложений построенных на архитектуре Flux.
Основным преимуществом данной библиотеки является VirtualDOM - виртуальный DOM. Т.к. перерисовка DOM одна из самых затратных операций, React реализует следующий алгоритм для решения этой проблемы: при каких либо изменениях интерфейса в результате действий пользователя, с начало создается виртуальный DOM, который сравнивается с текущим - и перерисовывается только та часть, которая действительно изменилась.
Это также является и своего рода недостатком, т.к. в памяти теперь нужно держать 2 дерева DOM (реальный и виртуальный). Также реализация построения виртуального DOM - требует затрат времени.
Основой библиотеки является Компонент. Главный метод компонента это render(). Создается следующим образом:
//ES5
var Comment = React.createClass({
render(){
return <div> ... </div>
}
});
//ES6
class Comment extends React.Component{
constructor(props){
super(props);
...
}
render(){
return <div>...</div>
}
}
//функциональный стиль (для stateless-компонентов)
const MyComponent = (props) => {
return <h1>...</h1>
}
Атрибуты компонента:
- key - уникальный ключ компонента, например если у нас есть компонент-родитель, который отрисовывает компоненты-дети через функция map - то необходимо чтобы у каждого компонента-ребёнка был свой уникальный ключ.
- ref - это специальное свойство, которое мы можем прикрепить к любому компоненту, который возвращается из render(). Это особое свойство позволяет обратиться к возвращаемому экземпляру(Помните: что вы возвращаете из render() не фактические экземпляры, а лишь описание экземпляра). Он всегда гарантирует правильный экземпляр, в любой момент времени.
Компонент содержит:
- props - это данные приходящие от родительского компонента или из стора, они readonly - поэтому мы не можем их изменять внутри компонента, только по средствам callback-ов
- state - это внутреннее состояние компонента, можно изменять с помощью
this.setState({...})
, использовать данный метод нужно с осторожностью, например если использовать внутри render - тогда при отрисовки компонента, react увидит что state изменился - перерисует - опять увидит что изменился - перерисует - ... Также его можно использовать только в 4 методах жизненого цикла компонента (см. ниже)this.setState((prevState, props) => {...}); // стоит избегать использования this.state внутри setState, т.к. this.state // не гарантирует что мы получим консистентнные данные. Поэтому лучше использовать // функцию с 2 аргументами prevState - пред.состояние и props - внешние данные.
propsType - это валидация props (number, string, boolean, function, any,...)
MyComponent.propsType = { id: React.propsTypes.number.isRequired, //тип число, обязательное присутствие name: React.propsTypes.string, // тип строка age: React.propsTypes.oneOfTypes({ //может быть как числом, так и строкой React.propsType.number, React.propsType.string }), object: React.propsType.share({ name: React.propsType.string age: React.propsType.number }), }
Про setState
- вызывает re-render компонента (исключение - если используется shouldComponentUpdate)
- обновление выполняется не моментально, react может отложить выполнения (оптимизация, частая ошибка - вызов this.state сразу после this.setState())
Вторым необязательным аргументом функции setState является функция callback, которая будет вызвана (гарантировано) только после того как компонент переотрисуется (re-rendered).
handleClick = () => {
this.setState({...}, () => {...});
}
Жизненый цикл компонента:
- Mount
- constructor (или getDefaultProps, getInitialState)
- componentWillMount (setState)
- render
- componentDidMount (setState)
- Update
- componentWillReceiveProps(setState)
- shouldComponentUpdate - компонент используется для оптимизации, можно реализовать свою логику проверки - изменилось ли действительно состояние хранилища или нет. Но по умолчанию производится неглубокое сравнение (return !shallowEqual(this.props, nextProps)) - аналогично можно унаследоваться от PureComponent - он будет проверять props и state из коробки
- componentWillUpdate
- render
- componentDidUpdate (setState)
- Unmount
- componentWillUnmount
Примечание: в eslint есть правило что нельзя вызывать setState в методе componentDidMount:
Я так понял это правило – чтобы избавится от лишних и бесполезных вызовов render-ов. Метод componentDidMount вызывается после первого вызова render(), а тут мы меняем состояние через setState – в результате будет снова вызван render() (такие методы жизненного цикла как shouldComponentUpdate, WillUpdate будут пропущены). А если у нас такое же поведения в child-компонентах – то они будут перерисовываться 4 раза. И чем глубже у нас иерархия компонентов – тем хуже.
VirtualDOM
если не используется shouldComponentUpdate или PureComponent - render будет вызван в любом случае, построится виртуальный DOM и если в верстке будет что-то изменено - вызовется перестроение реального DOM.
Для полноценной работы обычно подключают Redux.js - библиотека которая отвечает за слой Store архитектуры Flux. Для этого необходимо подключить библиотеку react-redux.js (см.React-Redux).
Объединение логики и представления это очень плохая идея. Поэтому компоненты в React обычно разделяют на Компонент-контейнер (умный) и Компонент-представления(глупый). Для лучшего понимания можно привести аналогию с паттерном MVC (Model - Controller - View). Компонент-представления это View - глуппый он потому, что не предстваляет откуда берутся данные. Компонент-контейнер это Controller.
Задачи | Компонент-представления | Компонент-контейнер |
---|---|---|
Цель | как компонент выглядит(разметка, стили) | Как это должно работать(получение данных, обновление) |
Знает от Redux | Нет | Да |
Считывание данных | props | подписан на Redux Store |
Изменения данных | через callback | отправляет dispatch |
Stateless-компоненты
В большинстве случаях рекомендуются использовать stateless-компоненты, это компонент который не имеет своего состояния this.state.
Нельзя использовать артибут ref с компонентом, постоенном на функции, т.к. функция не имеет экземпляра. Но можно использовать атрибут внутри этого компонента.
function CustomTextInput(props){
//textInput задекларирован здесь, т.к. обратный вызов ref ссылается на него
let textInput = null;
function handleClick(){
textInput.focus();
}
return(
<div>
<input
type="text"
ref={{(input) => inputText=input}} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}/>
</div>
);
}
React-Route
React-route - это маршрутизатор между разными url-ами.
ReactDOM.render({
<Router>
<Route path='/' component={Home} />
</Router>,
document.getElementById('root')
});
Route и Router это компоненты входят в библиотеку React-Route, они сами не отрисовывают DOM, а только реализуют маршрутизацию.
Расмотрим пример с вложенными маршрутами:
<Router>
<Route component={MainLayout}>
<Route component={SearchLayout}>
<Route path='user' component={UserList}/>
<Route path='widgets' component={WidgetList}/>
</Route>
</Route>
</Router>
Дополнительные атрибуты маршрута
<Router>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="accounts" component={Accounts}/>
<Route path="statements" component={Statements}/>
</Route>
</Router>
С помощью IndexRoute переходя по ссылке '/' будет render-ица App с компонентом Home, который будет доступен через this.props.children
.
Если необходимо сделать ссылки на маршруты, необъодимо использовать компонент Link
, пример:
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/users'>Users</Link></li>
<li><Link to='/widgets>Widgets</Link></li>
</ul>
Link to='/' className='btn'
в конечном итоге будет преобразован в a href='/' class='btn'
, сам компонент Link необходит для магии React-route
История браузера
Есть два подход остлеживания истории браузера:
- browserHistory
- hashHistory
Основное различие что при hashHistory нужно использовать доп.символ в url:exapmlpe.com/#/users
, а при browserHistory url пишется как обычный.
Есть один нюанс при работе с browserHistrory, если пользователь начинает работать с
example.com
и потом переходит на ссылкуexample.com/users
- все работает нормально. Но если пользователь начинает работу сразу сexample.com/users
- тогда мы получим ошибку 404, как на рисунке:
Чтобы решить эту проблему нужно настроить маршрутизацию на сервере решение
Two-way Binding Helper
В React данные движутся в одном направлении, от родителя к детям. Но иногда нам необходимо сразу узнать об изменениях, например когда мы используем form - для введения каких либо данных (например фильтр поиска или расширенный поиск). Для реализации two-way binding можно использовать LinkedStateMixin - это обертка надо setState и onChange. Давайте расмотрим простой пример:
const NoLink = React.createClass{
getInitialState: function(){
return {message: 'Hello'}
},
handleChange: function(event){
this.setState({
message: event.target.value
});
},
render: function(){
const {message} = this.state;
return <input type='text' value={massage} onChange={this.handleChange} />;
}
}
Это стандартная реализация компонента с полем ввода. Но с помощью LinkedStateMixin это можно сделать еще проше.
const WithLink = React.createClass{
//сначало подключим миксин
mixins: [LinkedStateMixin],
getInitialState: function(){
return {massage: 'Hello'};
},
render: function(){
const {message} = this.state
return <input type='text' valueLink={this.linkState('message')}
}
}
LinkedStateMixin добавляет метод linkState - синтаксический сахар. Также необходимо использовать valueLink за место value в тегах, а для тега checkbox - checkedkLink. Давайте расмотрим как реализуется valueLink, если бы мы его использовали без mixin-а:
var WithoutMixin = React.creatClass{
getInitialState: function(){
return {message: 'Hello'};
},
handleChange: function(event){
this.setState({message: event.target.value});
},
render: function(){
const valueLink = {
value: this.state.message,
requestChange: this.handleChange
};
return <input type='text' valueLink={valueLink}/>
}
}
На самом деле valueLink - это простой объект, который содержит value и requestChange
А теперь давайте расмотрим пример с использованием mixin, но без valueLink:
const WithoutValueLink = React.createClass{
//сначало подключим миксин
mixins: [LinkedStateMixin],
getInitialState: function(){
return {massage: 'Hello'};
},
render: function(){
const valueLink = this.linkState('message');
const handleChange = function(event){
valueLink.requestchange(event.target.value);
};
return <input type='text' value={valueLink.value) onChange={handleChange}}
}
Оптимизация
React медленный, React быстрый: оптимизация React-приложения на практике
Замичания
Очень плохая практика писать след код:
return(
<MyComponent
onChange={this.handleChange.bind(this)} // т.к. при каждом рендере будет создаваться новая функция
onClick={() => {this.handleClick()}} // тоже самое что и в пред примере
style={{color: "green"}} // и тут тоже самое
/>
);