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 (словарь)

  1. setState (nextState, [callback])

  2. replaceState (nextState, [callback])

  3. forceUpdate([callback])

  4. isMounted()

при изменении состояния компонент заново рендерится

  1. render
  2. getInitialState

  3. getDefaultProps

  4. propTypes

  5. mixins

  6. statics

  7. displayName

  1. componentWillMount

  2. componentDidMount

  3. componentWillReceiveProps

  4. shouldComponentUpdate

  5. componentWillUpdate

  6. componentDidUpdate

  7. 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