React

Chủ đề chính

  • Tổng quan về React
  • Declarative rendering / làm việc với state
  • Cú pháp JSX
  • Sử dụng component có sẵn
  • Tự tạo component
  • Truy vấn API trong component

Tổng quan về React.JS

React là gì?

1 trong 3 framework phổ biến nhất hiện nay (bên cạnh AngularVueJS)

Tính chất của framework JavaScript hiện đại

  • Declarative
  • Component

Declarative

  • Kiểu dữ liệu định nghĩa trạng thái (state) của cả ứng dụng
  • Tương tác của người dùng sẽ thay đổi data, khiến cho view được cập nhật tự động

Component

  • Luồng dữ liệu phân định bằng props và event
  • Dữ liệu truyền theo nhiều hướng: Từ cha đến con và có thể ngươc lại

Ví dụ: Kiểu data và luồng chạy của data trong ứng dụng todo-list

image/svg+xml TodoItem TodoList AddTodo TodoApp todos newTitle filterText TodoItem ...

Điều gì khiến React đặc biệt?

  • Cú pháp "lạ" dựa trên JavaScript
  • Thay đổi state rõ ràng thông qua setter
  • State object bất biến

Lịch sử ra đời

React cơ bản

Công cụ sửa code online

khuyến cáo: https://codesandbox.io

khác:

Ví dụ component tự tạo

import React, { useState } from 'react';

function CounterApp() {
  const [count, setCount] = useState(0);

  return (
    <div>
      count: {count}
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

export default CounterApp;

Ví dụ component (2)

import React, { useState } from 'react';

function SlideshowApp() {
  const [img, setImg] = useState(0);
  return (
    <div>
      <button onClick={() => setImg(0)}>start</button>
      <button onClick={() => setImg(img - 1)}>prev</button>
      <img
        src={`https://picsum.photos/200?image=${img}`}
        alt="slideshow"
      />
      <button onClick={() => setImg(img + 1)}>next</button>
    </div>
  );
}

export default SlideshowApp;

Function component và class component

tùy vào lựa chọn:

  • tạo component dạng function
  • tạo component dạng class (là lựa chọn phổ biến trước khi react hooks ra đời)

Định nghĩa component

Để phân biệt component với các thẻ thông thường của HTML, tên component bắt đầu bằng chữ cái viết hoa

JSX cơ bản

JSX: JS + XML

JSX = cú pháp của React

  • < chuyển JS sang XML/HTML
  • { chuyển lại về JS

Gán dữ liệu

<div>Một năm có {365 * 24} giờ</div>

Gán nội dung

Task:

  • Hiển thị ngày giờ hiện tại
  • Hiển thị một trong 2 chữ "sấp" và "ngửa" trong một <div>

Gán nội dung

hiển thị ngày:

const dateString = new Date().toLocaleDateString();
<div>ngày hiện tại: {dateString}</div>

sấp hay ngửa:

<div>{Math.random() > 0.5 ? 'sấp' : 'ngửa'}</div>

Gán biến

chúng ta cũng có thể chuyển từ XML sang JS:

<a href={'https://en.wikipedia.org/wiki/' + articleName}>
  bài viết
</a>

Lưu ý: Không có kí tự nháy kép xung quanh giá trị của href

Gán event

const hello = () => {
  console.log('hello world');
  // ...
};
<button onClick={hello}>Say Hello</button>

Danh sách các browser event: https://www.w3schools.com/jsref/dom_obj_event.asp

Gán event

Lưu ý: Một event handler phải là function (chứ không phải việc gọi function)

Đúng:

<button onClick={alert}>Say something</button>

Không đúng:

<button onClick={alert('hello')}>Say Hello</button>

Đúng:

<button onClick={() => alert('hello')}>Say Hello</button>

Lặp lại phần tử

Nhiều phần tử có thể được thêm vào bằng mảng (array):

const elements = [
  <div>dota</div>,
  <div>lol</div>,
  <div>csgo</div>,
];
<h1>3 phần tử:</h1>
{ elements }

Lặp lại phần tử

Ví dụ: Hiển thị tất cả các tên phương thức của React trong element ul

const reactMethods = [];
for (let method in React) {
  reactMethods.push(<li>{method}</li>);
}
<div>
  React Methods:
  <ul>{reactMethods}</ul>
</div>

State

State

Component có thể có state nội tại

State có thể được tham chiếu trong template, giao diện sẽ được tự động cập nhật nếu state có thay đổi.

State hook

Trong function component, ta sử dụng state hook:

import { useState } from 'react';

State hook

useState có thể được gọi lại (nhiều lần) trong function component

  • useState nhận 1 tham số đầu vào: giá trị đầu tiên được khởi tạo
  • mỗi lần call useState sẽ trả về một mảng gồm 2 phần tử: state hiện tại và một hàm để set giá trị mới cho state
const App = () => {
  const [count, setCount] = useState(0);
  const [title, setTitle] = useState('React app');

  return ...
};

Ví dụ: nút đếm số

Chúng ta sẽ thêm một nút vào app. Ban đầu trên nút sẽ hiển thị số 0, sau mỗi click thì tăng lên 1.

Ví dụ: nút đếm số

const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <button
      onClick={() => {
        setCount(count + 1);
      }}
    >
      {count}
    </button>
  );
};

Ví dụ: Slideshow

Làm một trang slideshow hiển thị ảnh như dưới đây:

https://picsum.photos/200?image=10

  • có 2 nút previousnext
  • có 1 nút về trang đầu
  • chặn không cho số thứ tự bị âm

JavaScript cơ bản cho React

Chuẩn của JavaScript

JavaScript là ngôn ngữ đã được chuẩn hóa bởi tổ chức ECMAScript (ES)

Phiên bản JavaScript

ES5: Hỗ trợ bởi tất cả các trình duyệt, kể cả Internet Explorer

Từ 2015: chuẩn này được cập nhật vào tháng 6 mỗi năm (ES2015, ES2016, ...)

JS hiện đại được biên dịch về ES5 qua các công cụ như Babel, Webpack,...

Import và export

import và export có tên:

// mymodule.js
const foo = 1;
const bar = 2;
const baz = 3;

export { foo, bar, baz };
// index.js
import { foo, bar } from 'mymodule.js';

Import và export

Chỉ có thể có 1 default export

// mymodule.js
const foo = 1;
const bar = 2;
const baz = 3;

export { foo, bar, baz };

const main = 0;

export default main;
// index.js
import main, { foo, bar } from 'mymodule.js';

Import trong webpack

Các công cụ bundler như webpack có thể hơi khác so với chuẩn import Javascript thông thường:

  • Import có thể không cần đuôi .js
  • Nếu import vào folder, webpack sẽ tự tìm file index.js

Arrow function

  • Cú pháp ngắn gọn cho function vô danh
  • Giữ biến this nguyên trạng (không bị thay đổi scope)
const multiply = (a, b) => {
  return a * b;
};

const multiply = (a, b) => a * b;

Arrow function

nếu ta muốn return ra object trực tiếp: bọc nó trong ngoặc tròn

const getState = () => ({
  loggedIn: true,
  userName: 'user',
});

Template string

  • Cú pháp mới để tạo ra string
  • Phân tách bởi dấu backtick `
  • Có thể tạo ra string nhiều dòng và nội suy
const name = 'Mike';
const greeting = `Hello, ${name}!
                  This is ES2015!`;

Dấu chấm phẩy trong JavaScript

Dấu chẩm phẩy cuối câu lệnh trong JavaScript hoàn toàn không bắt buộc.

const a = 3
console.log(a)

được xem như là:

const a = 3;
console.log(a);

Dấu chấm phẩy trong JavaScript

Tuy nhiên cũng cần lưu ý một số trường hợp như

const Foo = () => {
  return
    <div>
      <h1>some content</h1>
    </div>;
};

sẽ bị coi là return sớm và có thể lỗi:

const Foo = () => {
  return;
  <div>
    <h1>some content</h1>
  </div>;
};

Destructuring

const [result, errors] = someComputation();

// đổi giá trị
let a = 1;
let b = 2;
[a, b] = [b, a];

Destructuring

const person = { name: 'John', age: 48 };
const { name, age } = person;

const TodoItem = ({ title, completed }) => (
  <div>
    {completed ? 'DONE: ' : 'TODO: '}
    {title}
  </div>
);

Spread syntax (array và object)

const squares = [1, 4, 9];
const moreSquares = [...squares, 16, 25];
// moreSquares: [1, 4, 9, 16, 25]
const person = {
  firstName: 'Joe',
  lastName: 'Doe',
  age: 31,
};
const newPerson = { ...person, email: '[email protected]', age: 32 };
// {firstName: 'Joe', lastName: 'Doe', email: '[email protected]', age: 32}

Map và filter

Là 2 phương thức trong functional programming

Map

  • Biến đổi mỗi phần tử trong mảng bằng function
  • Tạo ra một array mới, không biến đổi array cũ
const myNumbers = [1, 2, 3, 4];

const tripledNumbers = myNumbers.map((n) => 3 * n);
// [3, 6, 9, 12]

Filter

  • Chỉ giữ lại một số phần tử nhất định trong array
  • Sử dụng một function để kiểm tra phần tử có đáp ứng điều kiện nào đó hay không
  • Cũng tạo ra một array mới, không biến đổi array cũ
const myNumbers = [1, 2, 3, 4];

const isEven = (n) => n % 2 === 0;

const evenNumbers = myNumbers.filter(isEven);
// [2, 4]

Khởi tạo một project React

Làm việc với node.js và npm

  • node.js: JS-Runtime
    • Chạy server dev local
    • Unit test
  • npm: package manager: bộ quản lý các gói công cụ
    • Quản lý thư viện
    • Các package cài đặt đều nằm ở node_modules
    • Cài đặt trong file package.json

Khởi tạo một project React

Nhiều cách:

  • create-react-app (phổ biến nhất)
  • gatsby (bao gồm cả tool tạo ra site tĩnh)
  • next.js (bao gồm cả tool tạo ra site tĩnh và server-side rendering)

Khởi tạo một project React

Một số câu lệnh có thể dùng để tạo project có tên "todolist":

npx create-react-app todolist
npx create-react-app todolist --template typescript
npx create-react-app todolist --template cra-template-pwa-typescript
npx create-next-app todolist
npx create-next-app todolist --example with-typescript
npx gatsby new

xem thêm: https://reactjs.org/docs/create-a-new-react-app.html

Khởi tạo một project React

Có rất nhiều thứ có thể config trong quá trình tạo project:

  • Webpack và babel để build
  • Môi trường dev local
  • Unit test
  • CSS và SCSS
  • ...

Create-react-app: cấu trúc project mặc định

  • public/index.html, src/index.js: các file đầu vào
  • App.js, App.css: Component App gốc
  • node_modules: thư viện

Create-react-app: Môi trường dev local và build

Trong thư mục project:

  • npm run start (or npm start): Chạy server dev để có thể truy cập qua localhost
  • npm run build: tiến hành build (phục vụ cho quá trình dev)

Create-react-app: server dev

Ghi chú:

Đôi khi server dev sẽ báo lỗi mặc dù đã sửa. Có thể khắc phục bằng cách nhấn Ctrl + C và chạy lại.

Immutable state - Trạng thái bất biến

Immutable state

Immutability: Một trong những khái niệm quan trọng trong functional programming, được sử dụng trong React / Redux

Dữ liệu không được sửa đổi trực tiếp, mà sẽ có dữ liệu khác tạo ra, biến đổi từ dữ liệu cũ (có thể sẽ thay thế dữ liệu cũ)

Immutable state

Khi có object hoặc array, ta thường thử thay đổi chúng trực tiếp

và điều này là không nên, React sẽ không nhận ra thay đổi và sẽ không render lại view.

State nên được xem như một dạng dữ liệu bất biến (immutable).

Immutable state

Khi setState được gọi, React sẽ so sánh

  • Object mà state cũ trỏ tới
  • Object mà state mới trỏ tới

Nếu state cũ và state mới đều tham chiếu (refer) đến cùng một object (dù có thay đổi), component cũng sẽ không được render lại.

Immutable state

demo (xem thêm: https://codesandbox.io/s/exciting-dust-w7hni):

function App() {
  const [numbers, setNumbers] = useState([0, 1, 2]);
  return (
    <div>
      <div>{JSON.stringify(numbers)}</div>
      <button
        onClick={() => {
          // không hợp lệ - sửa state
          numbers.push(numbers.length);
          setNumbers(numbers);
        }}
      >
        add (mutate)
      </button>
      <button
        onClick={() => {
          // hợp lệ - thay state
          setNumbers([...numbers, numbers.length]);
        }}
      >
        add (replace)
      </button>
    </div>
  );
}

Immutable state

code dạng như sau sẽ không được chấp nhận khi thay đổi state và React sẽ không render lại:

todos[0].completed = true;
todos.push({ title: 'study', completed: false });

Quản lý dữ liệu mà không biến đổi data: Mảng

Dữ liệu ban đầu:

const names = ['Alice', 'Bob', 'Mallory'];

mutation: Cách này sẽ biến đổi mảng gốc

names.push('Dan');

không có mutation: Tạo ra array mới (spread syntax)

const newNames = [...names, 'Dan'];

Quản lý dữ liệu mà không biến đổi data: Object

Dữ liệu ban đầu:

const user = {
  name: 'john'
  email: '[email protected]'
}

mutation: cái này sẽ biến đổi object

user.email = '[email protected]';

không có mutation: tạo ra object mới (spread syntax)

const newUser = { ...user, email: '[email protected]' };

immer.js

immer.js là một thư viện giúp làm việc với dữ liệu bất biến (immutable)

và được Redux khuyến nghị sử dụng

immer.js

đoạn code này sẽ biến đổi mảng todos:

todos[0].completed = true;
todos.push({ title: 'study', completed: false });

tránh biến đổi trực tiếp, bằng cách dùng immer.js

import produce from 'immer';

const newTodos = produce(todos, (todosDraft) => {
  todosDraft[0].completed = true;
  todosDraft.push({ title: 'study', completed: false });
});

Input & form

Input

Trong React, thẻ <input> khá đặc biệt:

Thuộc tính (property, .value) có thể được thay đổi trực tiếp bởi người dùng.

Do đó sẽ có một số phương diện của UI state sẽ không được nhận bởi React state.

Input

Đây là cách ta điều khiển và theo dõi giá trị của input trong state:

const [inputText, setInputText] = useState('');

<input
  value={inputText}
  onChange={(event) => {
    setInputText(event.target.value);
  }}
/>

Form action

Hành vi mặc định của một form khi được submit: Gửi thẳng data lên server

Ta sẽ thay thế hành vi đó:

<form
  onSubmit={(event) => {
    event.preventDefault(); // Quan trọng
    // handle submit
  }}
>
  <input />
</form>

Form validation

Form validation thủ công:

const NewsletterRegistration = () => {
  const [email, setEmail] = useState('');
  const [emailEdited, setEmailEdited] = useState(false);
  const emailInvalid = !isEmail(email);
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        console.log(email);
      }}
    >
      <input
        type="email"
        name="email"
        value={email}
        onChange={(event) => setEmail(event.target.value)}
        onBlur={() => setEmailEdited(true)}
      />
      <button disabled={emailInvalid}>subscribe</button>
      {emailEdited && emailInvalid ? (
        <div>email không hợp lệ</div>
      ) : null}
    </form>
  );
};

const isEmail = (email) =>
  email.match(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i);

React developer tools

React developer tools

các tính năng:

  • Hiển thị cấu trúc component
  • Hiển thị state và props
  • Thay đổi state
  • Theo dõi và phân tích hiệu năng render của component

JSX chi tiết

JSX chi tiết

mục lục:

  • thuộc tính
  • lặp phần tử
  • if / else
  • if
  • khoảng trắng
  • comment
  • fragment
  • element hợp lệ
  • compilation

Tên thuộc tính

Một số thuộc tính có tên khác với HTML:

  • className (thay cho class)
  • onClick (thay cho onclick)
  • htmlFor (thay cho for)

Tên thuộc tính

Ví dụ: CSS class

<li
  className={
    isCompleted ? 'todoitem completed' : 'todoitem'
  }
>
  [...]
</li>

Có rất nhiều thư viện giúp cho việc tạo className động, ví dụ: classnames

Thuộc tính style

Trong JSX, thuộc tính style nhận đầu vào là object:

<div
  style={{
    backgroundColor: '#333',
    fontSize: getFontSize(),
  }}
/>

Lặp phần tử

Xây dựng một danh sách HTML (thẻ ul) từ data sau:

const initialTodos = [
  { id: 1, title: 'groceries', completed: false },
  { id: 2, title: 'cooking', completed: true },
  { id: 3, title: 'gardening', completed: false },
];

Lặp phần tử

Ta có thể sinh ra mảng các phần tử JSX bằng .map:

const TodoApp = () => {
  const [todos, setTodos] = useState(initialTodos);
  return (
    <ul>
      {todos.map((todo) => (
        <li>{todo.title}</li>
      ))}
    </ul>
  );
};

Lặp phần tử

Với đoạn code trên, console sẽ cảnh báo đỏ vì thiếu thuộc tính key, ta thêm vào như sau:

<ul>
  {todos.map((todo) => (
    <li key={todo.id}>{todo.title}</li>
  ))}
</ul>

if / else

<div>{Math.random() > 0.5 ? 'heads' : 'tails'}</div>

if / else

let face;
if (Math.random() > 0.5) {
  face = 'heads';
} else {
  face = 'tails';
}

return <div>{face}</div>;

if

<div>{state.hasError && state.errorMessage}</div>

Toán tử && trong JavaScript: Dùng để rút gọn biểu thức

true && 'my message'; // 'my message'

false && 'my message'; // false

Khoảng trắng

Các đoạn code HTML dưới đây là như nhau (hiển thị một dấu cách giữa các ảnh):

<img src="foo.png" /> <img src="bar.png" />
<img src="foo.png" />    <img src="bar.png" />
<img src="foo.png" />
<img src="bar.png" />

Khoảng trắng

quy tắc trong JSX:

  • Khoảng trắng giữa 2 element trên 1 dòng sẽ được tính như 1 khoảng trắng.

  • Khoảng trắng giữa 2 element trong 2 dòng khác nhau sẽ được bỏ qua

Một khoảng trắng:

<img src="foo.png" />     <img src="bar.png" />

Không có khoảng trắng:

<img src="foo.png" />
<img src="bar.png" />

Khoảng trắng

"ép" hiển thị khoảng trắng trong JSX:

<img src="foo.png" />{" "}
<img src="bar.png" />

Comment trong JSX

Comment có thể được viết dưới dạng JavaScript:

a = <div>Hello World!{/*this is a comment*/}</div>;

Fragment

Fragment cho phép return nhiều element trong một component mà không cần có thẻ nào khác bao ngoài (thường là div):

return (
  <>
    <td>Hello</td>
    <td>World</td>
  </>
);

hoặc

return (
  <React.Fragment>
    <td>Hello</td>
    <td>World</td>
  </React.Fragment>
);

element hợp lệ

  • string
  • number
  • component (ví dụ: <div>, <img>, <MyComponent>)
  • một mảng các element
  • null, undefined, true, false (sẽ không được render)

JSX compilation

const element = <a href="https://google.com">Google</a>;

được compile thành:

const element = React.createElement(
  'a',
  { href: 'https://google.com' },
  'Google'
);

JSX compilation

const element = (
  <MyComponent prop1={1} prop2={2}>
    <div>test 1</div>
    <div>test 2</div>
  </MyComponent>
);

được compile thành:

const element = React.createElement(
  MyComponent,
  { prop1: 1, prop2: 2 },
  React.createElement('div', null, 'test 1'),
  React.createElement('div', null, 'test 2')
);

Bài tập: todolist

Bài tập: todolist (danh sách đầu việc)

Tạo một app todolist với các chức năng sau:

  • Hiển thị các todo đã và chưa hoàn thành.
  • Chỉnh trạng thái đã / chưa hoàn thành của một todo.
  • Xoá một todo
  • Thêm todo từ một form nhập liệu đơn giản.

Component

Component

React app có thể được chia thành các component nhằm:

  • Tái sử dụng code
  • Bố trí code để quản lý

Component và state

Component cha có thể "truyền" state xuống component con.

Component con có thể kích hoạt event để component cha cập nhật.

Nên đặt state ở đâu?

Nếu nhiều component cần truy cập state giống nhau, thường ta sẽ đặt state ở component cha chung. Sau đó truyền vào các component con dưới dạng props. Xem thêm: React docs: lifting state up

Ví dụ: component và state trong TODO app:

image/svg+xml TodoItem TodoList AddTodo TodoApp todos newTitle filterText TodoItem ...

Ví dụ: prop và event trong TODO app:

image/svg+xml TodoItem TodoList AddTodo TodoApp todos newTitle filterText TodoItem ... todos onToggle onDelete onAdd todo onToggle onDelete todo onToggle onDelete

Định nghĩa component

Có 2 cách phổ biến:

  • Dưới dạng function (trước đây gọi là stateless component)
  • Dưới dạng class (phổ biến trước khi có Hooks)

Định nghĩa component

Để phân biệt component với các tag HTML khác, component phải được đặt tên chữ cái đầu viết hoa.

Thư viện Component

Thư viện Component

  • Material UI: React component theo chuẩn material design
  • React Native & React Native Web: Các thư viện này có các component dành cho mobile và web
  • React Bootstrap: Thiết kế theo framework bootstrap
  • AntDesign: Framework giao diện React của Trung Quốc
  • ...

Material-UI

Các component có sẵn theo chuẩn material design của Google.

Material-UI: cài đặt và sử dụng

https://material-ui.com

Xem thêm thông tin ở phần InstallationUsage trên trang chủ

Material-UI: bài tập

  • Thêm nút
  • Thêm Todo dưới dạng material design

AntDesign: cài đặt và sử dụng

https://ant.design/

Xem thêm thông tin ở phần Getting StartedComponents trên trang chủ

AntDesign: bài tập

  • Thêm nút
  • Thêm Todo sử dụng thư viện ant design

Component props

State và props

  • state = thuộc nội bộ component
  • props = các tham số được truyền vào từ component cha

Component props

ví dụ:

<ProgressBar value={0.75} color="green" />

Component props

Ví dụ khởi tạo component (TypeScript) với props:

type Props = { value: number; color?: string };

const ProgressBar = (props: Props) => {
  // ...
};

Component props

Ví dụ khởi tạo component với props object destructuring:

const ProgressBar = ({ value, color }: Props) => {
  // ...
};