=
=

Bài 2: Xây dựng giao diện (UI) ứng dụng web với React

I. Cây DOM

DOM (Document Object Model) là tiêu chuẩn được định nghĩa bởi W3C (World Wide Web Consortium) cho phép truy cập và thay đổi nội dung, cấu trúc, định dạng của các tài liệu. W3C DOM gồm 3 phần:

Thông tin về DOM có thể tham khảo tại https://www.w3.org/DOM/. Bài này chỉ đề cập đến HTML DOM.

Các mức DOM (DOM levels)

DOM mức 0 xuất hiện trước năm 1998, được hỗ trợ chủ yếu khi truy cập các phần tử trên form, các liên kết hay hình ảnh. Năm 1998, khi DOM mức 1 xuất hiện thì DOM mức 0 được biết như là legacy DOM và các đoạn mã dùng cho DOM mức 0 được biên tập lại cho phù hợp. Ngày nay, mọi trình duyệt lớn đều hỗ trợ DOM mức 0.

DOM mức 1 được hỗ trợ bởi hầu hết các trình duyệt lớn. DOM mức 2 xuất hiện năm 2000 và DOM mức 3 xuất hiện năm 2004. Các trình duyệt hỗ trợ rất khác nhau các mức DOM. Việc truy cập các phần tử theo các mức DOM trên các trình duyệt khác nhau làm tốn nhiều công sức của người lập trình JavaScript. Thư viện jQuery ra đời đã giúp cho việc truy cập các phần tử DOM trở nên dễ dàng hơn.

Cây DOM

DOM thể hiện các tài liệu HTML dưới dạng cấu trúc cây.Ví dụ tài liệu HTML như sau:

<!doctype html>
<html>
 <head>
  <title>Hello World</title>
 </head>
 <body>
  <p>Here's some text.</p>
  <p>Here's more text.</p>
  <p>Link to the <a href="http://www.w3.org">W3</a></p>
 </body>
</html>

Cấu trúc DOM cho tài liệu này:

Các phần tử như html, head, body, v.v. cũng được gọi là các node (số nhiều là nodes). Các node mức thấp hơn một node đã cho gọi là các hậu duệ (descendant), các node mức kề dưới và có quan hệ với một node gọi là các node con và node đó được gọi là node cha (parent). Các node có cùng cha và cùng mức gọi là anh em (sibling).

Ví dụ từ cây DOM trên: các node head, body là con của html; head và body là anh em; title, p, v.v. là hậu duệ của html.

Để thao tác với các phần tử hay các node trên DOM, chúng ta dùng các phương thức JavaScript (hay jQuery) thông qua các DOM API.

Với React, chúng ta không tương tác với API DOM trực tiếp. Thay vào đó, chúng ta tương tác với DOM ảo (Virtual DOM) hoặc tập hợp các hướng dẫn mà React sẽ sử dụng để xây dựng giao diện người dùng và tương tác với trình duyệt.

DOM ảo được tạo thành từ các phần tử React (React Elements), về mặt khái niệm có vẻ giống với các phần tử HTML (HTML ELements), nhưng thực sự là các đối tượng JavaScript. Làm việc trực tiếp với các đối tượng JavaScript nhanh hơn nhiều so với API DOM. Chúng ta thực hiện các thay đổi đối với Đối tượng JavaScript, DOM ảo và React hiển thị những thay đổi đó cho chúng ta bằng cách sử dụng API DOM hiệu quả nhất có thể.

II. Thao tác với ReactDOM

Phần tử React (React Elements)

DOM của trình duyệt (hay HTML DOM) được tạo thành từ các phần tử DOM (các phần tử HTML như h1, div,...). Tương tự, React DOM tạo ra các phần tử React. Phần tử DOM và phần tử React có thể trông giống nhau, nhưng chúng thực sự khá khác nhau. Phần tử React là một mô tả về những gì phần tử DOM thực tế sẽ trông như thế nào. Nói cách khác, các phần tử React là các hướng dẫn để tạo các phần tử DOM của trình duyệt.

Để tạo các phần tử React chúng ta dùng phương thức createElement từ thư viện React. Ví dụ chúng ta muốn thể hiện đến trình duyệt phần tử HTML DOM

<h1>Xin chào.</h1>

Chúng ta sẽ tạo phần tử React

React.createElement("h1", null, "Xin chào.")

Hay chúng ta có thể viết:

React.createElement("h1", {}, "Xin chào.")

Tham số đầu tiên là phần tử HTML DOM, tham số thứ ba là nội dung và tham số thứ hai là thuộc tính của phần tử HTML DOM (tham số đầu tiên). Trong ví dụ trên vì h1 không chứa thuộc tính nào nên giá trị tham số thứ hai là null. Xét ví dụ chúng ta muốn thể hiện phần tử div như sau:

<div id = "root">Nội dung.</div>

Chúng ta sẽ tạo phần tử React

React.createElement("div", {id : "root"}, "Nội dung.")

Bây giờ chúng ta muốn thể hiện phần tử table

<table className="App">
 <tr>
   <th>STT</th>
   <th>Tên</th>
 </tr>
 <tr>
   <td>1</td>
   <td>Minh</td>
 </tr>
 <tr>
   <td>2</td>
   <td>Messi</td>
 </tr>
</table>

Lưu ý thuộc tính chúng ta dùng là className chứ không phải class. Có thể dùng hàm createElement

React.createElement("table",{className:"App"},
  React.createElement("tr",{},
    React.createElement("th",{},"STT"),
    React.createElement("th",{},"Tên")),
  React.createElement("tr",{},
    React.createElement("td",{},"1"),
    React.createElement("td",{},"Messi")),
  React.createElement("tr",{},
    React.createElement("td",{},"2"),
    React.createElement("td",{},"Minh")));

Tham khảo thêm về createElement tại react-tutorial

Hàm render

Mục đích của React chính là kết xuất (render) mã HTML đến các trang web bằng cách dùng hàm render() trong thư viện ReactDOM.

Hàm render nhận hai tham số là mã HTML (tham số thứ nhất) và phần tử HTML(tham số thứ hai). Mục đích của hàm render là kết xuất mã HTML từ tham số thứ nhất đến phần tử HTML trong tham số thứ hai.

Ví dụ: trong tập tin index.html của chúng ta có phần tử root

<body>
 <div id = "root"></div>
</body>

Bây giờ chúng ta muốn kết xuất đoạn mã sau đến phần tử root:

<h1>Xin chào đến với React!</h1>

Dùng React.createElement như sau:

const myfirstelement = React.createElement("h1", {}, " Xin chào đến với React! ");
ReactDOM.render(myfirstelement, document.getElementById('root'));

Bây giờ chúng ta muốn thể hiện bảng

<table className="App">
 <tr>
   <th>STT</th>
   <th>Tên</th>
 </tr>
 <tr>
   <td>1</td>
   <td>Minh</td>
 </tr>
 <tr>
   <td>2</td>
   <td>Messi</td>
 </tr>
</table>

Viết như sau:

const myTable = React.createElement("table",{className:"App"},
  React.createElement("tr",{},
    React.createElement("th",{},"STT"),
    React.createElement("th",{},"Tên")),
  React.createElement("tr",{},
    React.createElement("td",{},"1"),
    React.createElement("td",{},"Messi")),
  React.createElement("tr",{},
    React.createElement("td",{},"2"),
    React.createElement("td",{},"Minh")));
ReactDOM.render(myTable, document.getElementById('root'));

Kết quả thực thi ứng dụng

Bên cạnh sử dụng phương thức createElement chúng ta có thể sử dụng một giải pháp đơn giản và hiệu quả hơn là kết hợp HTML và JavaScript được gọi là JSX. Chủ đề JSX sẽ được trình bày chi tiết hơn trong mục tiếp theo của tài liệu nhưng chúng ta sẽ có một cái nhìn sơ lược về ứng dụng JSX trong React qua việc viết lại các ví dụ trên.

Ví dụ dùng hàm render như sau trong tập tin index.js:

ReactDOM.render(<h1>Xin chào đến với React!</h1>, document.getElementById('root'));

Trong JS, phần tử root được xác định dùng hàm getElementById.

Trong ví dụ trên, tham số thứ nhất của render là đoạn mã HTML đơn giản nhưng nếu đoạn mã HTML dài hơn, phức tạp hơn thì việc chuyển trực tiếp mã HTML là không khả thi. Một giải pháp là lưu trữ đoạn mã HTML trong một biến trước khi chuyển đến tham số thứ nhất hàm render:

const myfirstelement = <h1>Xin chào đến với React!</h1>;
ReactDOM.render(myfirstelement, document.getElementById('root'));

Bây giờ chúng ta sẽ thực hiện một ví dụ thứ hai bằng cách thêm một phần tử content đến tập tin index.html:

<body>
 <div id = "root"></div>
 <div id = "content"></div>
</body>

Trong tập tin index.js thêm nội dung như sau:

import React from 'react';
import ReactDOM from 'react-dom';
const myfirstelement = <h1>Xin chào đến với React!</h1>;
ReactDOM.render(myfirstelement, document.getElementById('root'));
const mycontent = (
 <table>
  <tr>
   <th>STT</th>
   <th>Tên</th>
  </tr>
  <tr>
   <th>1</th>
   <th>Minh</th>
  </tr>
  <tr>
   <th>2</th>
   <th>Messi</th>
  </tr>
 </table>
);
ReactDOM.render(mycontent, document.getElementById('content'));

Thực thi ứng dụng

Lưu ý rằng, đoạn mã HTML (table) chúng ta thêm vào viết trên nhiều dòng nên phải đặt trong cặp ngoặc.

III. Thao tác với React Component

Component là gì

Component là các đoạn mã độc lập và có thể được sử dụng lại. Khái niệm componet giống khái niệm hàm trong các ngôn ngữ lập trình (bao hàm cả JS) và có giá trị về là các phần tử HTML.

Tên của một React component luôn bắt đầu bằng chữ cái in hoa, ví dụ App, Car. Component có hai kiểu: Kiểu Function (như App ở trên) và kiểu Class.

Trong bài 1 chúng ta đã làm quen với tập tin App.js có mã như sau:

import logo from './logo.svg';
import './App.css';
function App() {
  return (
     <div className="App">
        <h1>Xin chào các bạn đến với React!</h1>
     </div>
  );
}
export default App;

Hàm App() là một component kiểu Function.

Tạo một component kiểu Class

Cú pháp giống định nghĩa một class trong JavaScript:

class Tên_component extends React.Component{
  render(){
     return...
  }
}

Một component được tạo bắt đầu bằng từ khóa class theo sau là tên component (chú ý là luôn bắt đầu bằng chữ cái in hoa). Một component kiểu Class thừa kế từ lớp React.Component với lệnh extends React.Component để có thể được phép sử dụng các hàm trong React.Component. Component kiểu Class luôn yêu cầu một hàm render để trả về các phần tử HTML.

Ví dụ tạo một componnet Car như sau:

class Car extends React.Component {
  render() {
    return <h2>Hi, I am a Car!</h2>;
  }
}

Sử dụng component theo cú pháp tương tự một phần tử HTML:

<Tên_component />

Ví dụ chúng ta có thể sử dụng component Car trong hàm ReactDOM.render như sau:

ReactDOM.render(
<Car />, document.getElementById('root'));
Tạo một component kiểu Function

Cú pháp giống định nghĩa hàm trong JavaScript:

function Tên_component() {
    return...
}

Một component được tạo bắt đầu bằng từ khóa function theo sau là tên component (chú ý là luôn bắt đầu bằng chữ cái in hoa) đi kèm với cặp ngoặc tròn (như hàm). Một component kiểu Function trả về trực tiếp phần tử HTML mà không dùng hàm render().

Ví dụ viết lại component Car kiểu Function như sau:

function Car() {
  return <h2>Hi, I am also a Car!</h2>;
}

Tương tự kiểu Class, sử dụng component theo cú pháp tương tự một phần tử HTML:

<Tên_component />
Ví dụ chúng ta có thể sử dụng component Car trong hàm ReactDOM.render như sau:
ReactDOM.render(
  <Car />, document.getElementById('root'));
Các component lồng nhau

Chúng ta có thể gọi một component trong một component khác. Ví dụ tạo component tên Garage như sau:

class Garage extends React.Component {
    render() {
      return (
        <div>
          <h1>Who lives in my Garage?</h1>
          <Car />
        </div>
      );
    }
}

Component Garage trả về các phần tử HTML và component Car.

Các component trong tập tin

Để quản lý hiệu quả, các component có thể được tạo trong các tập tin có phần mở rộng .js khác nhau như Car.js hay Garage.js từ ví dụ trên.

Tuy nhiên, các component có thể được tạo trong một tập tin có phần mở rộng .js. Một tập tin .js chứa các component có nội dung cơ bản như sau:

import React from 'react';
import ReactDOM from 'react-dom';

Component_1
export Component_2
...

export default Component_1;

Kết thúc tập tin là dòng mã export default và một component bất kỳ. Để sử dụng các component, trong tập tin js (ví dụ index.js) chúng ta phải import các component dùng lệnh:

import Component_1 from './tập tin chứa Component_1';
import { Component_2 } from './tập tin chứa Component_2';
...

Để ý rằng, Component_1 dùng export default nên import bình thường, Component_2 khi import phải đặt trong cặp "{}".

Ví dụ chúng ta thay đổi nội dung App.js trong bài 1 như sau:

import React from 'react';
import ReactDOM from 'react-dom';
export function Car() {
  return <h2>Hi, I am also a Car!</h2>;
}
class Garage extends React.Component {
  render() {
    return (
      <div>
        <h1>Who lives in my Garage?</h1>
      </div>
    );
  }
}
export default Garage;

Trong tập tin index.js chúng ta viết lại:

import React from 'react';
import ReactDOM from 'react-dom';
import Garage from './App.js'
import { Car } from './App.js'
ReactDOM.render(
  <Garage />, document.getElementById('root'));
ReactDOM.render(
  <Car />, document.getElementById('content'));
Thuộc tính component và hàm constructor

Một component sẽ chứa các thuộc tính, ví dụ Car chứa các thuộc tính như color, brand, model,...Nếu một component có một hàm constructor thì hàm này sẽ được gọi khi component khởi tạo.

Hàm constructor là nơi khởi tạo các thuộc tính của component và trong hàm này chúng ta có thể dùng lệnh gọi hàm super() để thừa kế component cha.

Các thuộc tính của component có thể được gọi thông qua đối tượng state hay đối tượng props. Các thuộc tính này sẽ được tìm hiểu trong bài kế tiếp.

Ví dụ hàm constructor trong component Car:

class Car extends React.Component {
    constructor() {
      super();
    }
    render() {
      return <h2>I am a Car!</h2>;
    }
}

Bằng cách gọi hàm super(), component Car sẽ thực thi hàm constructor và có thể truy cập các hàm từ component cha (trong trường hợp này là React.Component).

IV. Thao tác với JSX (JavaScript Extension)

JSX là gì

JSX là viết tắt của JavaScript XML. JSX là một mở rộng của JavaScript phiên bản ES6 cho phép chúng ta viết mã HTML trực tiếp trong mã JavaScript một cách dễ dàng. Nếu không sử dụng JSX, React cũng cho phép chúng ta viết mã HTML bằng cách dùng hàm createElement. Trong ví dụ sau đây chúng ta dùng JSX:

const myfirstelement = <h1>Xin chào đến với React!</h1>;
ReactDOM.render(myfirstelement, document.getElementById('root'));

Viết lại dùng hàm createElement:

const myfirstelement = React.createElement('h1', {}, 'Xin chào đến với React!');
ReactDOM.render(myfirstelement, document.getElementById('root'));

Các tham số hàm createElement:

Từ hai ví dụ trên, có thể thấy việc dùng createElement phức tạp hơn sử dụng JSX.

Cú pháp JSX
Biểu thức trong JSX

Biểu thức có thể là một biến, một thuộc tính hay bất cứ biểu thức nào hợp lệ trong JavaScript. Trong JSX, biểu thức phải được đặt trong cặp ngoặc {}.

Ví dụ:

const ketqua = <h1>5 + 3 = {5 + 3}</h1>;
ReactDOM.render(ketqua, document.getElementById('root'));

Trong đó 5 + 3 = là một chuỗi thông thường và 5 + 3 trong {} là một biểu thức số học. Khi thưc thi, kết quả:

5 + 3 = 8

Biểu thức trong JSX không hỗ trợ lệnh if. Chúng ta phải thực hiện lệnh if bên ngoài JSX:

const x = 5;
let text = "Goodbye";
if (x < 10) {
    text = "Hello";
}
const mycondition = <h1>{text}</h1>;

Tuy nhiên, trong trường hợp đơn giản, JSX cho phép chúng ta sử dụng if dưới dạng các biểu thức cấp ba (dùng toán tử ? và :):

const mycondition = <h1>{(x) < 10 ? "Hello" : "Goodbye" }</h1>;
Đoạn mã HTML

Xét ví dụ minh họa khi thêm một đoạn mã dài và viết trên nhiều hàng:

const mycontent = (
 <table>
  <tr>
   <th>STT</th>
   <th>Tên</th>
  </tr>
  <tr>
   <th>1</th>
   <th>Minh</th>
  </tr>
  <tr>
   <th>2</th>
   <th>Messi</th>
  </tr>
 </table>
);

Có vài chú ý với các đoạn mã HTML:

Để rõ hơn chúng ta xét ví dụ khác sau:

<h1>Tôi là tiêu đề.</h1>
<p>Đoạn văn bản 1.</p>
<p>Đoạn văn bản 2.</p>

Chúng ta viết:

const mycode = (
  <h1>Tôi là tiêu đề.</h1>
  <p>Đoạn văn bản 1.</p>
  <p>Đoạn văn bản 2.</p>
);

Sẽ báo lỗi sau trong Visual Studio Code:

Trong trường hợp này thiếu một phần tử cao nhất theo quy tắc trong XML. Xử lý chúng ta chỉ việc thêm phần tử <div>

const mycode = (
  <div>
    <h1>Tôi là tiêu đề.</h1>
    <p>Đoạn văn bản 1.</p>
    <p>Đoạn văn bản 2.</p>
  </div>
);

Cũng theo luật XML, đoạn mã HTML phải luôn có thẻ kết thúc như </div> hay </table> hay tự kết thúc như <input.../>. React sẽ phát sinh lỗi nếu chúng ta chưa có thẻ kết thúc.

const myinput = <input type="text" / >;

Sử dụng thuộc tính className thay vì class. Nguyên nhân vì class trùng với từ khóa class trong JavaScript. Như vậy, thay vì viết:

const myelement = <h1 class = "myclass">Hello World</h1>;
chúng ta sẽ viết thành:
const myelement = <h1 className = "myclass">Hello World</h1>;