Bắt đầu với Reactjs- Phần 2: Ví dụ về React virtual DOM và Giới thiệu về React Route

1. Ví dụ về React virtual DOM

Ở bài này mình sẽ xây dựng 1 ví dụ nhỏ về schedule cụ thể ta có các công việc CRUD các event.

1. Khởi tạo dự án:

create-react-app whats-next cd whats-next mkdir -p src/scenes mv src/App.js src/App.jsx rm src/App.css touch src/App.css rm src/logo.svg

Mình lười css nên sẽ dùng bootstrap

npm install react-bootstrap --save npm install [email protected] --save

thêm scss loader:

npm install sass-loader node-sass --save-dev Tại /src/App.jsx:

import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/css/bootstrap-theme.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App__header">
          <h2>What's Next</h2>
        </div>
        <div className="App__content">
        </div>
      </div>
    );
  }
}

export default App;

2. Index

Bắt đầu với trang index. mình sẽ giả xử 1 event thì có các thành phần:

{
    event: {
        id: 1,
        title: undefined,
        description: undefined,
        startAt: undefined,
        endAt: undefined,
        guest: {
            name: undefined,
        },
    }
}

Mình sẽ mock 1 vài event tại:

mkdir -p src/mock && touch src/mock/events.json

{
  "events": [
    {
      "id": 1,
      "title": "Luận bàn",
      "description": "Luận bàn với Lôi lão huynh",
      "startAt": "Sun Sep 01 2017 04:08:08 GMT+0700 (ICT)",
      "endAt": "Sun Sep 01 2017 12:08:08 GMT+0700 (ICT)",
      "guest": {
        "name": "Thiên lôi"
      }
    },
    {
      "id": 2,
      "title": "Đàm đạo",
      "description": "Luận về đan đạo cùng Thái Thượng lão quân",
      "startAt": "Sun Sep 02 2017 04:08:08 GMT+0700 (ICT)",
      "endAt": "Sun Sep 02 2017 12:08:08 GMT+0700 (ICT)",
      "guest": {
        "name": "Thái Thượng lão quân"
      }
    }   
  ]
}

Bây h ta sẽ thực sự bắt đầu với việc viết component show list event:

2.1. Component list event:

mkdir -p src/scenes/events && touch src/scenes/Events/index.jsx

Tất nhiên Events sẽ là 1 stateful component component này sẽ sẽ là biểu hiện cho Root node trong mô hình:

import React, { Component } from 'react';

class Events extends Component {
  render() {
    return (
      <div className="events">
        // #TODO show list events
      </div>
    );
  }
}

export default Events;

Để show 1 list events thì cách đơn giản mà mọi người vẫn hay làm rõ ràng đó là show 1 event và repeat nó. Ở đây chúng ta cũng sẽ làm như vậy, và tất nhiên cách show event trong 1 list nó sẽ khác với detail của event.

mkdir -p src/scenes/events/components/Event && touch src/scenes/events/components/Event/index.jsx

Vì đây chỉ là 1 thành phần của scenes events nên mình sẽ đặt nó vào danh sách những components của events. Đây là 1 biểu hiện của node con và root node. Và vì mình chỉ muốn component event này nhận và show event nên nó sẽ chỉ là stateless component.

import React from 'react';
import PropTypes from 'prop-types';

const Event = ({ event, removeEvent }) => (
  <div className="events__event col-md-4 well">
    <button className="btn btn-xs btn-default button--right"onClick={removeEvent}>
      x
    </button>
    <h3 className="text-center">{event.title}</h3>
    <div className="text-right">
      <i>{event.startAt}</i>
    </div>
  </div>
);

Event.propTypes = {
  event: PropTypes.object.isRequired,
};

export default Event;

update lại: /src/scenes/Events/index.jsx

import React, { Component } from 'react';
import Event from './components/Event';

import './style.css';

import mockData from './../../mock/events.json';

class Events extends Component {
  constructor(props) {
    super(props);
    this.state = { ...mockData };
    this.removeEvent = this.removeEvent.bind(this);
    this.findEventById = this.findEventById.bind(this);
  }

  findEventById(eventID) {
    const { events } = this.state;
    events.forEach((e, index) => {
      if (e.id === eventID) return index;
    });
    return -1;
  }

  removeEvent(eventID) {
    const { events } = this.state;
    events.splice(this.findEventById(eventID), 1)
    this.setState({
      events,
    });
  }

  render() {  
    const { events } = this.state;
    return (
      <div className="events col-md-12">
        <div className="row text-right">
          <small>total event: {events.length}</small>
        </div>
        <div className="row">
          {
            events.map(event => 
              <Event 
                key={event.id} 
                event={event}
                removeEvent={this.removeEvent}  
              />
            )
          }
        </div>
      </div>
    );
  }
}

export default Events;

Hoàn thành page index với việc render index tại App.jsx

//import 
import Events from './scenes/Events';

// render
<div className="App__content">
  <Events />
</div>

Events sẽ là nơi chứa các thông tin về các event cũng như chiệu trách nhiệm xử lý chúng. Còn Event sẽ chỉ là view render. Chịu trách nhiệm show event mà thôi. đây là 1 ví dụ nhỏ, khá tiêu biểu cho sự khác biệt của statefull và stateless. source code: https://github.com/phantien133/react-demo-whats-next/tree/index 3. Show Bây h chúng ta sẽ tạo trang show details của event. Như đã nói trước đó. mình sẽ xem component Events là root node trong ví dụ của mình. Vì vậy mình sẽ chỉnh sửa lại ở đây 1 ít để components có thể bao gồm luôn cả scene details. Tạo thêm 1 component ListEvent là 1 trong các scene của Events

mkdir -p src/scenes/Events/scenes/ListEvent && touch src/scenes/Events/scenes/ListEvent/index.jsx

import React from 'react';
import PropTypes from 'prop-types';

import Event from './../../components/Event';

const ListEvent = ({ events }) => {
  return (
    <div className="row">
      {
        events.map(event => 
          <Event 
            key={event.id} 
            event={event}
            removeEvent={this.removeEvent}  
          />
        )
      }
    </div>
  );
};

Event.propTypes = {
  events: PropTypes.array.isRequired,
  removeEvent: PropTypes.func.isRequired,
};


export default ListEvent;

Tạo page Event details

mkdir -p src/scenes/Events/scenes/EventDetails && touch src/scenes/Events/scenes/EventDetail/index.jsx

import React from 'react';

import './style.css';

const EventDetails = ({ event, remove }) => {
  const details = object => Object.keys(object).map(key => 
    object[key] instanceof Object ? (
      <div className="well" key={key} >
        {key} : {details(object[key])}
      </div>
    ) : (
      <div className="event__detail well" key={key} >
        <span>{key}</span>
        <span>{object[key]}</span>
      </div>
    )
  );
  const removeEvent = () => remove(event.id);
  return (
    <div className="row event">
      <div className="col-md-2" />
      <div className="col-md-8">
        <h2 className="text-center">{event.title}</h2>
        <div className="row">
          { details(event) }
        </div>
        <button className="btn btn-danger button--right" onClick={removeEvent}>
          Delete
        </button>
      </div>
      <div className="col-md-2" />
   </div>
  );
};

export default EventDetails;

Bây h chúng ta sẽ link các thành phần lại:

import React, { Component } from 'react';

import ListEvent from './scenes/ListEvent';
import EventDetails from './scenes/EventDetails';

import './style.css';

import mockData from './../../mock/events.json';

class Events extends Component {
  constructor(props) {
    super(props);
    this.state = { 
      ...mockData,
      showEvent: undefined,
    };
    this.removeEvent = this.removeEvent.bind(this);
    this.indexOfEvent = this.indexOfEvent.bind(this);
    this.showEvent = this.showEvent.bind(this);
  }

  indexOfEvent(eventId) {
    const { events } = this.state;
    events.forEach((e, index) => {
      if (e.id === eventId) return index;
    });
    return -1;
  }

  removeEvent(eventId) {
    const { events } = this.state;
    events.splice(this.indexOfEvent(eventId), 1)
    this.setState({
      events,
      showEvent: undefined,
    });
  }

  showEvent(event) {
    this.setState({
      showEvent: event,
    })
  }

  render() {  
    const { events, showEvent } = this.state;
    return (
      <div className="events col-md-12">
        <div className="row text-right">
          <small>Total event: {events.length}</small>
        </div>
        { 
          showEvent === undefined ? (
            <ListEvent events={events} removeEvent={this.removeEvent} showDetails={this.showEvent} />
          ) : (
            <EventDetails event={showEvent} remove={this.removeEvent} />
          )
        }
        
      </div>
    );
  }
}

export default Events;
import React from 'react';
import PropTypes from 'prop-types';

import Event from './../../components/Event';

const ListEvent = ({ events, removeEvent, showDetails }) => {
  return (
    <div className="row">
      {
        events.map(event => 
          <Event 
            key={event.id} 
            event={event}
            removeEvent={removeEvent}  
            showDetails= {showDetails}
          />
        )
      }
    </div>
  );
};

ListEvent.propTypes = {
  events: PropTypes.array.isRequired,
  showDetails: PropTypes.func.isRequired,
};


export default ListEvent;
import React from 'react';
import PropTypes from 'prop-types';

const Event = ({ event, removeEvent, showDetails }) => (
  <div className="events__event col-md-4 well">
    <button className="btn btn-xs btn-info" onClick={() => showDetails(event)}>
      Details
    </button>
    <button className="btn btn-xs btn-default button--right" onClick={() => removeEvent(event.id)}>
      x
    </button>
    <h3 className="text-center">{event.title}</h3>
    <div className="text-right">
      <i>{event.startAt}</i>
    </div>
  </div>
);

Event.propTypes = {
  event: PropTypes.object.isRequired,
  removeEvent: PropTypes.func.isRequired,
  showDetails: PropTypes.func.isRequired,
};

export default Event;

kết quả các bạn có thể xem lại đây: https://github.com/phantien133/react-demo-whats-next/tree/show

Kết luận:

  • Data được giữ và xử lý tại 1 node cố định, để các node con của nó nhận data và xử lý thì phải được cho phép truy cập trừ node cha.
  • Khá mất thời gian để giải quyết các vấn đề đơn giản.

Trải qua ví dụ này, có thể bạn sẽ thấy React tại sao lại rắc rối như vậy? Tốn quá nhiều thời gian cho các vấn đề đơn giản mà với JQuery có lẽ là simple nhưng React thì cũng là cả 1 vấn đề. Nhưng không, đây là 1 ví dụ của mình về sử dụng React cơ bản và chưa có tận dụng sức mạnh của nó để giải quyết các vấn đề khó. Có rất nhiều lý do để chọn React và cụ thể thì bạn có thể hỏi anh Google. Vì đã có quá nhiều người liệt kê các lý do này rồi Vậy cơ bản chúng ta đã hoàn thành 1 ví dụ để hiểu hơn về virtual DOM. Mình cũng vừa bắt đầu với React, và có gì sai sót, mong mọi người chỉ bảo.

2. Giới thiệu về React Route, Redux:

Tất nhiên 1 thành phần gần như là tất yếu nếu chúng ta muốn xây dựng website với React. Nó hỗ trợ chúng ta rất nhiều về xây dựng cấu trúc trang web, lưu trữ state... install và cách sử dụng: + https://github.com/ReactTraining/react-router + https://reacttraining.com/react-router/ Như với ví dụ ở trên. Chúng ta sẽ khá mất thời gian để xử lý làm lúc nào sẽ hiển thị gì. nhưng với react route, rõ ràng việc này trở nên hết sức đơn giản. Nhưng lại phát sinh 1 vấn đề đó là nếu chia nhỏ các scene của chúng ta ra với react route thì: + Đâu là nơi chúng ta lưu trữ data? + Đâu là nơi chứa các logic xử lý? + Root node của chúng ta sẽ là gì? Việc này với mình thì sẽ có có 2 hướng để chúng ta giải quyết: 1. Nơi mà chúng ta install và config sử dụng react-route sẽ là root node và bao gồm logic xử lý. Nhưng đương nhiên cách này sẽ rất dài dòng và để lấy ra data + func từ prop của Route thì khá là rắc rối. 2. Chúng ta sẽ dùng thêm 1 thư viện nữa đó là Redux(Redux là một thư viện giúp bạn quản lí trạng thái (state) của application. Nó được thiết kế dựa trên Flux, nhưng giảm bớt những đau khổ thường gặp phải khi viết một ứng dụng Flux). Mình sẽ có 1 bài nói rõ về Redux cũng như cách xử dụng react-route.


All Rights Reserved