React.js là một framework JavaScript cho phép chúng ta làm nhà phát triển đóng gói mã để làm cho nó dễ tái sử dụng hơn. Các đoạn mã được đóng gói này được gọi là components (thành phần). Chúng có thể giữ logic và state riêng mà không can thiệp vào những gì đang xảy ra trong Document Object Model (DOM).
Việc trừu tượng hóa các phần của trang web thành các thành phần nhỏ hơn để truyền dữ liệu xung quanh cho phép mã của bạn trở nên tái sử dụng và tuân thủ nguyên tắc DRY (Don't Repeat Yourself). Có hai loại thành phần chính mà bạn sẽ gặp trong React: Functional Components (Thành phần hàm) và Class Components (Thành phần lớp).
Tổng quan về React Components
Ở mức độ cao, các React component về cơ bản là các hàm JavaScript nhận props làm tham số và trả về các React elements mô tả những gì nên hiển thị trên màn hình:
import React from 'react';
import ReactDOM from 'react-dom';
const Greeting = (props) => {
return (
<div> Hello, {props.name} </div>
);
};
const element = <Greeting name="Yolanda" />;
ReactDOM.render(element, document.getElementById('root'));
React element trong trường hợp này là một <div> với một số văn bản bên trong. Văn bản đó sử dụng một đối tượng props đã được truyền vào component. Đối tượng props đó sẽ truyền dữ liệu xuống các component con từ component cha của chúng.
Trong ví dụ này, prop được truyền xuống là name. Element đại diện cho component cha.
Bất kỳ thuộc tính nào bạn truyền vào component <Greeting /> sẽ đưa nó vào đối tượng props và có sẵn để sử dụng bên trong Greeting. Điều này khiến Greeting siêu tái sử dụng vì chúng ta có thể truyền bất kỳ tên nào chúng ta muốn vào component.
Functional Components (Thành phần hàm)
Functional components, như đã đề cập trước đó, về cơ bản là các hàm JavaScript. Bạn có thể sử dụng cú pháp EcmaScript 5 (ES5) hoặc EcmaScript 6 (ES6) khi tạo React components. Theo quy tắc chung, React components phải được viết hoa chữ cái đầu để chỉ ra rằng chúng thực sự là components.
Ví dụ ES5 Functional Component (không dùng JSX)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Basic React Component</title>
</head>
<body>
<!-- App is inserted at the root node -->
<div id="root"></div>
<!-- React CDN -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<!-- React-DOM CDN -->
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- React Components can be rendered here -->
<script>
'use strict';
const element = React.createElement;
function Greeting() {
return element('h4', null, `Hello, World!`);
};
function App() {
return element(Greeting);
};
const domContainer = document.querySelector('#root');
ReactDOM.render(element(App), domContainer);
</script>
</body>
</html>
Đây là một ví dụ hoạt động giả định của một ES5 functional component khi chúng ta không sử dụng JSX, mà chỉ JavaScript thuần túy. Đừng quá lo lắng về những gì đang xảy ra trong HTML. Hãy tập trung vào logic JavaScript: chúng ta có một element được tạo ra, một hàm trả về cái gì đó và sau đó hàm đó được render vào một ReactDOM nào đó.
JSX là gì? JSX – viết tắt của JavaScript XML Extension – về cơ bản cho phép chúng ta viết HTML trong JavaScript để làm cho nó giống hơn với những gì chúng ta thường thấy trong HTML. Trong ví dụ mã ở trên, chúng ta hoàn toàn sử dụng JavaScript để tạo element HTML của mình và sau đó sử dụng phương thức React.createElement của React để chèn một <h4> vào DOM tại <div id="root">.
Nếu chúng ta muốn mở rộng ứng dụng của mình – làm cho ứng dụng lớn hơn nhiều – kiểu viết này sẽ nhanh chóng trở nên rườm rà. JSX được tạo ra về cơ bản như syntactic sugar (cú pháp đường) cho phương thức React.createElement và cho phép chúng ta mở rộng ứng dụng nhanh hơn nhiều.
ES5 Functional Component với JSX
import React from 'react';
import ReactDOM from 'react-dom';
function Animal(props) {
const element = <h1>My cat's name is {props.name}</h1>;
return element;
}
function App() {
return <Animal name="Marshmallow"/>;
}
const domContainer = document.querySelector('#root');
ReactDOM.render(<App/>, domContainer);
ES6 Functional Component với JSX
const Animal = (props) => {
const element = <h1>My cat's name is {props.name}</h1>;
return element;
}
const App = () => {
return <Animal name="Marshmallow"/>;
}
const domContainer = document.querySelector('#root');
ReactDOM.render(<App/>, domContainer);
Trước React v.16, functional components được gọi là các component stateless (vô trạng thái). Điều này có nghĩa là mục đích chính của component là trình bày (presentational) – trông đẹp trên trang. Thường xuyên, dữ liệu được truyền cho các functional components này để chúng có thể hiển thị thứ gì đó trên giao diện người dùng.
Với sự ra đời của React v.16, tất cả đã thay đổi. Chúng ta sẽ đi sâu vào điều đó trong chút nữa. Hiện tại, chỉ cần biết rằng functional components tồn tại để nhận một loại dữ liệu nào đó từ cha hoặc từ một đối tượng toàn cục để trình bày thứ gì đó cho client.
Class Components (Thành phần lớp)
Class components phức tạp hơn một chút so với "chỉ" một hàm JavaScript. Class components trong React mượn khái niệm class của ES6 JavaScript. Cấu trúc của một class trong JavaScript về cơ bản là một đối tượng có các thuộc tính (attributes) và phương thức (methods) liên kết với nó. Chúng ta sử dụng từ khóa this để truy cập một instance của đối tượng đó và tương tác với nó.
Trong React, về phần lớn, cấu trúc là giống nhau. React class components là một instance của một đối tượng và đối tượng này có những gì chúng ta gọi là state (trạng thái). Ở mức độ cao, state chỉ đơn giản là dữ liệu mà component giữ – hãy nghĩ đến nó như một cách khác để thiết lập các thuộc tính và gắn nó vào class. Là nhà phát triển, sau đó bạn có thể làm bất cứ điều gì bạn muốn với dữ liệu đó: trình bày trên màn hình, truyền nó xung quanh cho các component khác, sử dụng nó để làm logic khác, v.v.
Một ES6 class component luôn được viết hoa, giống như functional components – đó là quy tắc của React để transpiler biết đó là một component. Vì chúng ta đang kế thừa cấu trúc component từ chính React, chúng ta phải extends class Component của React. Bên trong khối mã đó là nơi chúng ta sẽ đặt state của mình:
import React from 'react';
import ReactDOM from 'react-dom';
import Name from './Name';
// Từ khóa 'this' cần được sử dụng khi nói về một instance của class.
// Nó sẽ đặt trước các phương thức và state khi tham chiếu đến nó trong phương thức render.
class Animal extends React.Component {
constructor(props) {
super(props);
this.state = {
species: ['fox', 'tiger', 'bear', 'kangaroo', 'fish', 'cat', 'dog', 'squirrel']
}
}
render() {
return (
<div>
{this.state.species.map(animal => {
return <Name animal={animal}/>
})}
</div>
)
}
}
ReactDOM.render(<Animal />, document.getElementById('root'));
State là một đối tượng đầy đủ các thuộc tính và giá trị. State của chúng ta ở đây có thuộc tính species và giá trị của nó là một mảng đầy động vật. Để tham chiếu hoặc tương tác với mảng này, chúng ta sử dụng this.state.species.
Mục đích của class component ở trên là truyền tên của động vật xuống component <Name />. Công việc của component <Name /> là làm gì đó với dữ liệu đó khi nó nhận được. State của Animal sẽ trở thành một phần của đối tượng props khi nó được truyền cho một functional component hoặc class component khác. Nó sẽ có thể được truy cập trong các component con miễn là nó tiếp tục được truyền xuống.
Chỉ cần nhớ rằng trong React, dữ liệu chảy xuống từ component cha đến component con chỉ một cấp. Bạn phải truyền nó xuống cấp khác nếu bạn cần dữ liệu trong component cháu của component cha.
Lifecycle Methods (Phương thức vòng đời)
Một tính năng khác của class components trong React là chúng ta có quyền truy cập và sử dụng các lifecycle methods để theo dõi state của mình. React lifecycles thường có ba giai đoạn: Chúng được tạo ra (Mounting), chúng sống (Update) và chúng chết (Unmounting). Có các phương thức để truy cập và/hoặc thay đổi state ở mỗi giai đoạn của lifecycle method:
- componentDidMount() – Đây là lifecycle method nơi chúng ta sẽ thực hiện các yêu cầu AJAX/network để khởi tạo state. Sử dụng
this.setState()để tải dữ liệu đã truy xuất vào state. - componentDidUpdate() – Mọi cập nhật state xảy ra ở đây sau khi người dùng đã tương tác với ứng dụng.
- componentWillUnmount() – Đây là hàm dọn dẹp xảy ra khi component unmount. Nó sẽ xử lý timers, AJAX requests, v.v.
Có nhiều lifecycle methods hơn thế – những liệt kê ở trên chỉ là những phương thức chính. Vui lòng xem tài liệu React để biết thêm thông tin về các phương thức này.
Cuối cùng, trái ngược với câu lệnh return của functional component, class components sử dụng phương thức render(). Phương thức này được gọi sau khi ReactDOM.render() truyền component Animal và React gọi constructor của nó. State sau đó được khởi tạo và sau đó phương thức render được gọi để thực sự đưa nội dung lên màn hình.
Functional Components và useState() (React Hooks)
Năm 2018, React giới thiệu khái niệm React Hooks. Hooks là một cách sạch sẽ và súc tích để sử dụng các lifecycle methods và state bên trong một Functional Component.
Hầu như mọi thứ đều rất giống với những gì chúng ta đã đề cập cho đến nay. Chúng ta có một số state, chúng ta cần làm gì đó với nó, và chúng ta cần truyền nó đi đâu đó. Các mục tiêu chính là giống nhau. Cú pháp gọn gàng hơn nhiều – nó chỉ đòi hỏi một chút thời gian để quen.
Hãy bắt đầu với mã chúng ta đã có trước đó:
import React from 'react';
import ReactDOM from 'react-dom';
const Animal = (props) => {
const element = <h1>My cat's name is {props.name}</h1>;
return element;
}
const App = () => {
const [ state, setState ] = useState('Marshmallow');
return <Animal name={state}/>;
}
const domContainer = document.querySelector('#root');
ReactDOM.render(<App/>, domContainer);
Chúng ta có hai components, một Animal và một App. Trông như App đang trả về Animal và truyền vào một prop tên là name với giá trị "Marshmallow".
Tin hay không, không có nhiều việc chúng ta phải làm để chuyển đổi này thành logic stateful. Hãy nhìn vào component <App>. Chúng ta sẽ làm theo các bước sau:
Bước 1: Import useState
import React, {useState} from 'react';
Dòng này sẽ import hook useState của chúng ta.
Bước 2: Khởi tạo state
const [ state, setState ] = useState('Marshmallow');
Dòng này khởi tạo state của chúng ta. state và setState ở đây là những từ tùy ý. Bạn có thể đặt tên chúng bất cứ thứ gì bạn muốn. Thông thường là đặt tên theo giá trị mà nó đại diện.
statelà state thực tế của chúng ta. State ban đầu nằm bên trong dấu ngoặc đơn tronguseState().setStatetương tự nhưthis.setState(). Đây là phương thức sẽ thay đổi state của chúng ta khi chúng ta đi qua ứng dụng.
Bước 3: Sử dụng state trong JSX
return <Animal name={state}/>
Thay thế "Marshmallow" bằng {state}. Khi chúng ta cần viết JavaScript trong JSX, chúng ta sử dụng dấu ngoặc nhọn. Dấu ngoặc nhọn ở đây cho phép chúng ta truyền biến của mình.
Một trong những tính năng xác định class components, React Lifecycle với các phương thức khác nhau của nó, được thu gọn thành một hook cơ bản bao đóng tất cả các phương thức! Hook useEffect() có thể mount, update và unmount React component mà nó nằm trong đó.
Kết luận
Trước React v. 16, functional components hoàn toàn được sử dụng như một view layer trình bày (presentational) không sử dụng state trừ khi là props được truyền xuống từ class components. Class components nắm giữ toàn bộ state của ứng dụng và truyền dữ liệu xung quanh. Class components sử dụng các lifecycle methods mount, update và unmount React component của chúng ta. Chúng ta cũng thấy rằng React Hooks, một pattern mới được phát hành bởi React trong phiên bản 16, cho phép functional components trở nên stateful và có phiên bản lifecycle methods riêng của nó.
Sẽ quan trọng để quen thuộc với tất cả các loại components khác nhau ngay cả khi React Hooks đang trở nên phổ biến hơn – legacy code có thể vẫn sử dụng class components trong một thời gian và vẫn cần được hiểu để bạn có thể làm việc với nó!