React.js
Что такое React?
Небольшая библиотека для создания быстрых, отзывчивых, декларативных пользовательских интерфейсов
Почему React?
- Простота: небольшое API, все состоит из компонентов
- Скорость: виртуальный ДОМ, серверный рендеринг (React.NET)
- Реактивность: можно легко использовать с реактивными библиотеками (Bacon.js, Rx.Js)
- Виртуальный ДОМ: автоматически перерендеривает только то, что нужно
- JSX: Вместо того, чтобы вставлять код в разметку(как в шаблонах), мы вставляем разметку в код
- React Native: Интерфейсы для мобильных устройтв
Мы не склеиваем шаблоны - мы делаем композицию компонентов
- Компоненты React легко соединяются друг с другом
- Описание интерфейса и его логика тесно связаны и могут находиться в одном месте
- Вся мощь Javascript(ES6) в вашем распоряжении
Пример композиции
var apple = React.createElement("li", null, "Apple");
var banana = React.createElement("li", null, "Banana");
var list = React.createElement("ul", {}, apple, banana);
ReactDOM.render(list, document.getElementById("root"));
JSX
//No JSX
var HelloWorld = React.createClass({
render: function() {
return React.createElement("p", null,
"It is" + this.props.date.toTimeString());
}
});
//with JSX
var HelloWorld2 = React.createClass({
render: function() {
return <p>It is {this.props.date.toTimeString()}</p>;
});
JSX
- HTML транслируется в вызовы функций
- Декларативное описание интерфейса внутри JS кода
- Препроцессор преобразовывает JSX в JS на лету
Virtual DOM
- Можно не думать, где вы перерендерили больше, чем нужно
- С точки зрения кода вы перерендериваете все, но на самом деле - только то, что нужно
- Отображение всегда соответствует данным с сервера
Компоненты
- Компонент - главный строительный кирпичик React
- Представляет собой объект с методом render
- Внешний вид компонента в каждый момент времени определяется его State и Props
const HelloWorld3 = (props) => <p>It is {props.date.toTimeString()}</p>;
Можно создавать компоненты в чисто функциональном стиле* (Stateless):
var Component = React.createClass({
render: function() { return <p>Hello!</p> }
});
* Для увеличения производительности можно использовать PureRenderMixin
Идея реактивного программирования
z = x + y
Предположим, значение х или у изменилось
Императивный подход:
Оставить значение z без изменений
Реактивное программирование:
Пересчитать значение z в соответствии с актуальной информацией
Примеры: Excel, переменная State
State
State - поле обьекта Component (словарь)
-
setState (nextState, [callback])
-
replaceState (nextState, [callback])
-
forceUpdate([callback])
-
isMounted()
при изменении состояния компонент заново рендерится
- render
-
getInitialState
-
getDefaultProps
-
propTypes
-
mixins
-
statics
-
displayName
-
componentWillMount
-
componentDidMount
-
componentWillReceiveProps
-
shouldComponentUpdate
-
componentWillUpdate
-
componentDidUpdate
-
componentWillUnmount
Lifecycle
Specs
Props
var NameBox = React.createClass({
render: function() {
return <div>Hello, {this.props.FirstName} {this.props.Surname}</div>
}
});
var NamesList = React.createClass({
render: function() {
var Nodes = this.props.Names.map(function(name){
return <NameBox
key={name.Id}
FirstName={name.FirstName}
Surname={name.Surname} />
});
return (<div>{Nodes}</div>)
}
});
var names = [
{ Id:1, FirstName:"Nik", Surname:"Yurchenko" },
{ Id:2, FirstName:"Margaret", Surname:"Manachenko" },
{ Id:3, FirstName:"Volodymyr", Surname:"Bondariev" }
]
ReactDOM.render(
<NamesList Names={names}/>,
document.getElementById('example')
);
Props are immutable (readonly)
Props Validation
React.createClass({
propTypes: {
lArray: React.PropTypes.array,
Bool: React.PropTypes.bool,
Func: React.PropTypes.func,
Number: React.PropTypes.number,
Object: React.PropTypes.object,
String: React.PropTypes.string,
Symbol: React.PropTypes.symbol,
Renderable: React.PropTypes.node,
reactElement: React.PropTypes.element,
Message: React.PropTypes.instanceOf(Message),
Props Validation
Enum: React.PropTypes.oneOf(['News', 'Photos']),
Union: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Message)
]),
ArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
ObjectWithShape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number
}),
RequiredNum: React.PropTypes.number.isRequired,
Mixins
Mixin/Trait (примесь/типаж) вообще в ООП - класс, который позволяет вынести общие функции нескольких классов "за скобки" (интерфейс с реализацией)
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.forEach(clearInterval);
}
};
var TickTock = React.createClass({
mixins: [SetIntervalMixin], // Use the mixin
getInitialState: function() {
return {miliseconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, +this.props.interval);
},
tick: function() {
this.setState({
miliseconds: this.state.miliseconds + +this.props.interval
});
},
render: function() {
return (
<p>
Running for {this.state.miliseconds} miliseconds.
</p>
);
}
});
ReactDOM.render(
<TickTock interval="10"/>,
document.getElementById('example')
);
Top level API
React.createClass(specification)
React.createElement(type, [props], children... )
React.cloneElement(element, [props], children... )
React.createFactory(type )
React.isValidElement(* object)
ReactDOM.render(element, container, callback )
ReactDOM.unmountComponentAtNode(container)
ReactDOM.findDOMNode(component)
ReactDOMServer.renderToString(element)
ReactDOMServer.renderToStaticMarkup(element)
Практическая часть
Анонимный чат с лайками
Анонимный чат с лайками
Структура:
- CommentBox
- CommentList
- Comment
- CommentForm
Comment
var Comment = React.createClass({
getInitialState: function() {
return {likes: this.props.likes};
},
rawMarkup: function() {
var md = new Remarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
},
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={this.rawMarkup()} />
<h5>{this.state.likes}</h5>
<button onClick={this.props.likeHandler.bind(null, this)}>LIKE</button>
</div>
);
}
});
CommentList
var CommentList = React.createClass({
render: function() {
var that = this;
var commentNodes = this.props.data.map(function(comment) {
return (
<Comment
author={comment.author}
likes={comment.likes}
id={comment.id}
key={comment.id}
likeHandler={that.props.likeHandler}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
CommentForm
var CommentForm = React.createClass({
getInitialState: function() {
return {author: '', text: ''};
},
handleAuthorChange: function(e) {
this.setState({author: e.target.value});
},
handleTextChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
this.props.onCommentSubmit({author: author, text: text});
this.setState({author: '', text: ''});
},
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.author}
onChange={this.handleAuthorChange}
/>
<input
type="text"
placeholder="Say something..."
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Post" />
</form>
);
}
});
CommentBox
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
handleCommentSubmit: function(comment) {
var comments = this.state.data;
comment.id = Date.now();
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
this.setState({data: comments});
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
likeHandler: function(comment){
console.log(comment);
$.post("http://127.0.0.1:3000/api/like","id=" + comment.props.id);
comment.setState({likes: comment.state.likes + 1});
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} likeHandler={this.likeHandler} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
Html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React Tutorial</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.6.16/browser.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/remarkable/1.6.2/remarkable.min.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/babel">
var Comment = /* ... */
var CommentBox = /* ... */
var CommentList = /* ... */
var CommentForm = /* ... */
ReactDOM.render(
<CommentBox url="http://localhost:3000/api/comments" pollInterval={2000} />,
document.getElementById('content')
);
</script>
</body>
</html>
Ну вот и всё
Что можно почитать:
- http://www.reactivemanifesto.org/
- https://facebook.github.io/react/
- https://elm-lang.org/papers/concurrent-frp.pdf
React.js
By zelinskiy
React.js
- 1,175