Frontend build chậm quá thì chạy về khóc với Vite

■ Intro.

Thời gian này không biết anh chị em mình có theo dõi SEA Games 31 không nhỉ ^^ Hai ngày cuối tuần vừa rồi có ai "đi bão" không =))) Chưa bao giờ chiến thắng và tinh thần thể thao cao đẹp lại thổi mạnh mẽ trên khắp con phố như vậy! Quả là một kỳ thi đấu thành công cho đoàn thể thao Việt Nam, khép lại một mùa SEA Games quá trọn vẹn 🙌  🙌  🙌

Xin chúc mừng tất cả các tuyển thủ một lần nữa!!!

Bởi vậy mới mới nói:

- Chút thay đổi chiến thuật có thể viết nên cả một lịch sử;
  Và tỉ số thì chẳng thể nào được khẳng định cho đến phút 90 của trận đấu 😌😌

Ấy là câu chuyện về trái bóng bên sân cỏ. Còn với lập trình viên chúng ta thì sao 😸😸 Chút thay đổi trong code đôi khi cũng có thể làm thay đổi cả luồng logic rồi ấy nhỉ, và tính đúng đắn cũng chẳng thể nào khẳng định được cho tới khi chương trình đã chạy xong =)))

Vậy đó giờ anh chị em đã tham gia dự án nào có build time hệ  s l o w  m o t i o n  chưa? Phải chờ tận vài giây (hoặc nhiều hơn thế =))) để chờ hot-reload sau khi chỉnh sửa vài dòng mã chẳng hạn? Hoặc thậm chí có thể mất tới 20 - 45' để có thể deploy "xong" một tiến trình – dù chỉ là lần sửa một lỗi nho nhỏ? 🥲🥲

Phím với chiếc Buddy trong team về vấn đề này, mình được gợi ý từ khoá Vite – một build tool được cộng đồng ví von là Next Generation Frontend Tooling.

Nom "chất lượng" quá nhỉ, cơ mà tìm hiểu một mình thì "buồn ơi là buồn", đồng hành cùng mình trong bài viết này nhé!

Đầu tiên, chúng ta ngó lại Module Bundler một chút!

Bạn nào rành đoạn này rồi thì có thể chuyển qua Mục tiếp theo luôn nhaa ^^

■ Module Bundler (*)

Để bắt đầu xây dựng một website theo phong cách "kinh điển" đó giờ, tất cả những gì chúng ta cần là HTML, CSS và đôi dòng JavaScript.

Nâng cấp hơn xíu xiu, khi muốn sử dụng những thư viện ngoài (Bootstrap, lodash chẳng hạn), chúng ta sẽ download chúng về và nhúng vào HTML/CSS.

Điều này thì chẳng còn xa lạ gì đối với các anh chị em web developer rồi nhỉ 😸😸

Cơ mà nếu dự án có nhiều dependencies quá thì chúng ta quản lý như thế nào đây? Luôn phải manually download mãi hay sao? Kể cả khi chúng có các bản cập nhật mới?

Đó là lý do các Package Manager như Bower, NPM, Yarn… được sinh ra "cho đời bớt khổ" =))

Với NPM chẳng hạn, chúng ta sẽ có một package.json lưu thông tin dự án, một thư mục node_modules chứa tất cả các packages – cái dễ dàng được cài đặt thông qua dòng lệnh:

$ npm i PACKAGE_NAME

và rồi đính kèm chúng vào HTML/CSS:

http://node_modules/.../package_1.js
http://node_modules/.../package_2.js
...
http://main.js 

<!-- Mỗi script X được định nghĩa bởi 01 global variable
=>   X có thể được dùng ở bất kì script nào được-load-sau-nó -->

Việc phải đào sâu vào node_modules để tìm vị trí của từng package và lần lượt thêm thủ công gây ra bất tiện quá!!! (sad)

Để phá vỡ nguyên tắc này, CommonJS – dự án đưa ra các đặc tả về modules – đã cho phép JavaScript được import/export:

const lodash = require('lodash');
// FYI, NodeJS là một trong những implementation phần modules của CommonJS

Nhưng Browser không có quyền truy cập đến các file hệ thống, nên để dùng module theo cách này, chúng cần được load động, bằng cách đồng bộ (sẽ làm chậm quá trình thực thi) hoặc bất đồng bộ (có thể có vấn đề về thời điểm load).

Đó là chưa kể trong quá trình phát triển, chúng ta còn cài đặt thêm các pre-processor (như Pug, SCSS,..); sử dụng ES6; tích hợp TypeScript; hay là React với JSX; rồi thì áp dụng một số phương pháp performance optimization như Code-spliting, Lazy-loading theo modules… với mục đích nâng cao developer experience. Trong khi đó, browser lại chỉ cần HTML, CSSJavaScript thôiii!?!

Với tất cả các vấn đề kể trên, một chiếc Module bundler sẽ giúp chúng ta compile, transform, optimize, minify… rồi đóng gói các modules này lại, tạo ra kết quả cuối cùng tương thích với browser.

(*) Nội dung này được tóm lược trong bài viết Giải thích về Javascript thời hiện đại cho khủng long siêu-hayyy siêu-chi-tiếttt của anh Tạ Duy Anh (respect)x10 👍️ 👍️

Có thể kể tới một vài công cụ module bundler phổ biến mà bạn có thể đã từng nghe qua như: Webpack, Parcel, Rollup, Browserify này, và bây giờ là Vitemà chúng ta sẽ tìm hiểu ngay trong phần tiếp theoo!

■ Vite

Theo Trang chủ:

Vite is a build tool that aims to provide a faster and leaner development experience for modern web projects.

Tương tự các build tools phổ biến hiện nay, Vite có các chức năng cốt lõi như:

  • Project scaffolding thông qua các CLI generators
  • Hot Reload Module (HRM) – tự động reload modules trong quá trình dev
  • Plugin system cho phép config, custom thêm một số chức năng

🐾 Chút Fun fact, có thể bạn …thừa biết =)))

Vite được đọc là /vit/🔊, trong tiếng Pháp có nghĩa là "nhanh chóng".

Build tool này được phát triển bởi anh Evan You – cha đẻ của VueJS. Bảo sao mà cóV-prefix và phong cách viết document của VueVite "da same" thế (LOL).

Bên cạnh mảng Coding, anh này học cả Art, DesignUI/UX nữa nên cũng nghệ lắm anh chị em ạ (yaoming) ==’


Ban đầu, Vite được tạo ra để phục vụ cho "gà cưng" VueJS, về sau thì hỗ trợ thêm React, PreactSvelte nữa 😸😸 .

Điều khiến Vite nhận được sự đón nhận tích cực từ cộng đồng là về tốc độ nổi bật của nó. Để lý giải tại sao Vite lại "nhanh" như vậy? Chúng ta cùng tìm hiểu cách hoạt động của nó trong Development buildProduction build nhé!

■ How Vite is faster?

■ Unbundled dev build

Khi chúng ta bắt đầu khởi chạy ứng dụng, Vite phân loại các modules thành 02 categories:

  • Dependency modules:

    • Chủ yếu là các plain JavaScript hoặc các modules được import từ node_modules, không thay đổi thường xuyên trong quá trình phát triển.
    • Ví dụ: Một số libraries, packages như Antd, Bootstrap, etc.
    • Các modules này sẽ được pre-bundle bằng esbuild – một JavaScript bundler được viết bằng Golang, hứa hẹn nhanh hơn người anh Webpack khoảng từ 10–100x về mặt tốc độ.
  • Application modules:

    • Là các non-plain JavaScript – cần được chuyển đổi, biên dịch,… và thường được chỉnh sửa thường xuyên.
    • Ví dụ: Component.jsx, styles.scss, etc.
    • Dùng Native ES Modules (ESM).

Với tôn chỉ On-demand compilation, Vite không-gói-tất-cả các modules lại với nhau (điều này giảm được một khối lượng công việc đáng kể). Vite chỉ cung cấp cácmodules mà trình duyệt cần. Chúng có thể là Application modules kèm Dependency modules – cái mà đã được pre-compile bằng esbuild ở lần chạy đầu tiên.

Anh Evan You cũng đã chia sẻ về hiệu quả của Native ESM dev build trong show The Open Source Friday của Github, bạn xem thêm nhé ^^

■ Bundled prod build

Ngược lại với no-bundle dev build phía trên, Vite tiếp cận prod build vẫn theo hướng bundle thông qua một module bundler có tên là Rollup.

Hmmm…

Tới đây thì một vài câu hỏi được đặt ra…

(?) Dev build phía trên sử dụng ESM rồi sao Production build không dùng luôn?

Theo chia sẻ của anh Evan You, dù hiện tại, các ESM đã được hỗ trợ ở tất cả các trình duyệt chính, song, việc gom một bundle.js kèm các kĩ thuật performance optimization: tree-shaking, lazy-loading, chunk splitting,… vẫn mang lại hiệu suất tổng thể tốt hơn là hình thức unbundled – cái mà sẽ tạo ra nhiều extra HTTP requests hơn.

Okayy, câu tiếp theo =)))

(?) Bảo esbuild nhanh, thế tại sao sang Production build, Vite còn dùng thêm Rollup chi vậy?

Vẫn theo lời anh Evan You =)) Có một số tác vụ khó thực hiện hơn khi dùng esbuild như CSS code-splitting, tự động tối ưu các đoạn mã không đồng bộ,… nên dù thời gian prod build chậm hơn chút, nhưng sẽ có performance tốt hơn cho end user. Nom như là một sự đánh đổi 😹😹

Chi tiết về bài phỏng vấn này, bạn có thể đọc thêm tại đây nhé!

Giờ thì đặt Vite cạnh Webpack – một "tượng đài" module bundler được tin dùng và sử dụng rộng rãi – để so sánh chút nào!

■ Vite vs. Webpack

■ Operating mechanism

Ngay chính trên Trang chủ, Webpack đặt nhẹ một slogan:

Bundle your scripts; Bundle your images; Bundle your styles; Bundle your assets.

Quả là một người anh bundle "cả thế giới" (J4F) =))

Theo workflow, trước một browser request, Webpack sẽ thu thập, xử lý, gom góp, đóng gói các modules lại thành 01 bundle.js để sử dụng như hình mô tả dưới:

So sánh với Vite ở phía trên một chút:

Scope Webpack Vite
Supported modules ES Modules, CommonJS, AMD Modules ES Modules
Dev Server Bundled modules served via webpack-dev-server using Express.js ES-Modules served via Vite using Koa – a lightweight node web server
Production build Webpack Rollup

<br/>

Vậy còn về tốc độ thì sao?

■ Speed

Trong trận "battle" này, chúng ta sẽ so sánh một ứng dụng ReactJS lần lượt sử dụng ViteWebpack nhé!

FYI, nếu đã tiếp cận với ReactJS, hẳn bạn đã từng dùng qua Create-React-App (CRA) để khởi tạo dự án rồi nhỉ? CRA nhà mình sử dụng Webpack như 01 build tool nha. Bạn có thể ghé qua bài viết Điều React luôn giữ kín trong tim nếu muốn tìm hiểu thêm về nó.

Xem kết quả về tốc độ cho một sample app với 02 routes, 06 componentstrên cùng một máy tính:

Scope React with Webpack in CRA React with Vite
Server start duration 12s 298ms
Build duration 16.66s 9.11s

Thật ra, với ví dụ bé con phía trên, từ ms tới vài s, chúng ta vẫn có thể dễ dàng hài lòng và chấp nhận được. Song, nếu tính tỉ lệ %, giả sử quá trình build ứng dụng hiện tại của bạn đang là 30', giờ có thêm Vite, đời bỗng vui với thời gian chờ còn lại chỉ còn khoảng ~12-15'. Nghe vẻ ấn tượng quá nhỉ!

Bạn có thể xem thêm một ví dụ khác về Creation time, Project size, Build time, Build size của một anh Ấn tại video này 😸😸.

🐾 Đừng hiểu nhầm nhé !!!

Mình không cố "dìm" Webpack và "ca tụng" Vite đâu 😺😺

Chẳng phải tự nhiên mà Webpackđược tin dùng và sử dụng rộng rãi như vậy đúng không nào ^^ Bản thân nó đã và đang làm rất tốt rồi. Bắt đầu như một JavaScript-focused bundler, Webpack có các default configurations vô cùng đơn giản, cho phép chúng ta customize rất-rất-đa-dạng.

Chính vì có tính configurable linh hoạt như vậy, đa phần 90% các công cụ sử dụng Webpack đều phải config các conventions thường hay được sử dụng trong một hệ sinh thái nào đó (giả sử như việc compile Pug, SCSS, ES6… cho React trong Front-end chẳng hạn, chúng ta có Create React App).

Mục-đích-chính anh Evan You tạo ra Vite là xây dựng những default configurations tương ứng với các libraries, giúp chúng ta tập trung vào nghiệp vụ quan trọng hơn. Bên cạnh đó, vì không phải default configurationsnào cũng phù hợp nên Vite cũng cho phép chúng ta customize chút thông qua các plugins.

Thế nhưng Vite không-có-mục-tiêu-thay-thế-hoàn-toàn Webpack nha. Việc lựa chọn Vite, Webpack, Parcel hay bất kì một build tools nào cho dự án cũng tương tự như việc lựa chọn công nghệ để theo đuổi vậy. Không có công nghệ nào là tốt nhất, quan trọng hơn cả vẫn là sự phù hợp.

Quay lại bài viết của chúng ta, với ưu điểm về tốc độ của Vite như vậy, có anh chị em nào đã nghĩ tới việc đề xuất ý kiến migrate build tool của dự án hiện tại sang Vite trong buổi Daily sắp tới của team chưaaa ạ?

Trước khi đưa ra quyết định thì chúng ta cùng xem việc chuyển đổi như vậy có tốn nhiều công sức không nhé! Mình sẽ lấy ví dụ với ReactJS.

■ React with Vite

■ Init a React app with Vite

Để khởi tạo một dự án ReactJS sử dụng Vite như một build tool, chúng ta chạy dòng lệnh:

$ npm init vite PROJECT_NAME --template react

Xem qua chút folder structure thì có vài điểm khác biệt so với CRA:

Scope CRA boilerplate Vite boilerplate
Entry point index.js index.html
Dependencies react-scripts @vitejs/plugin-react
Bundler config Webpack Vite

Từ sự khác biệt này, chúng ta có các bước để migrate từ một dự án CRA đang phát triển qua dùng Vite như sau:

■ Migrate CRA to Vite

Đầu tiên, bỏ/thêm một số dependencies:

$ npm remove react-scripts
$ npm install --save-dev @vitejs/plugin-react vite 
// Vite không dùng react-scripts mà dùng chính plugin của nó

Config chút:

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default ({ mode }) => defineConfig({
    plugins: [react()],
    define: {
        "process.env.NODE_ENV": `"${mode}"`,
    }
});

Di chuyển index.html ra ngoài:

...
<link rel="icon" href="/favicon.ico" />             <!-- Bỏ "%PUBLIC_URL%" --> 
...
<div id="root"></div>
/src/index.js <!-- Đính thêm script --> 

Cập lại chút script với vite:

{
    ...
    "scripts": {
        "start": "vite",
        "build": "vite build"
    },
}

Ngoài ra, với các .env (nếu có):

// Change
REACT_APP_ENV = https://haodev.wordpress.com
// To
VITE_ENV = https://haodev.wordpress.com

Cuối cùng thì:

$ npm start 

và trải nghiệm thôiii! 😸😸

■ Sumup

Như vậy là chúng ta đã cùng nhau tìm hiểu qua về Vite rồi nè!!!

Mặc dù Webpack vẫn là một build tool được Next.js, Create React App,… ưa chuộng nhưng chúng ta cũng chẳng thể phủ nhận Vite – một ứng cử viên sáng giá trong tương lai với những ưu điểm vượt trội về tốc độ.

Hy vọng rằng bài viết này có thể giúp ích được các bạn. Cảm ơn các bạn đã đọc bài chia sẻ này, ủng hộ 01 upvote để mình có thêm động lực cho những bài viết sắp tới nhé ❤


🐾 Dành cho các ace quan tâm tới Vietnam Mobile Day 2022

FYI, Vietnam Mobile Day là sự kiện thường niên do Top Dev tổ chức. Hai năm sau COVID-19, Vietnam Mobile Day 2022 với chủ đề WE CONNECT đã quay trở lại hình thức offline tại hai thành phố lớn:

  • Hà Nội: 10/06/2022 | CTM Palace
  • Hồ Chí Minh: 03/06/2022 | Melisa Center

Thông tin chi tiết về sự kiện, mình để tại đây nhé!

Mình cũng có một chiếc Early bird rùi ^^ Các anh chị em đăng ký tham gia sự kiện thì có thể sử dụng thêm 01 Voucher:

  • GIAM50K@VMD2022 để được giảm -50k (Số lượng: 05)
  • HAODEV@VMD2022 để được giảm -30k (Cập nhật: Mã này đã deactivated)

khi mua vé nhaaa ^^

Lưu ý chút là các mã không áp dụng đồng thời và số lượng giới hạn ạ ^^


Chúc các bạn tuần làm việc hiệu quả! Tiện ghé qua nhà mình chơi một chút rồi về!

■ Credits

Happy coding!

“Đu đỉnh” Bitcoin với Binance CLI nhà làm

Tháng Năm chào Hà Nội bằng một trận mưa đêm. Sáng nay, gió về, mát lạnh. Hà Nội như được gột rửa những bụi bặm và mệt mỏi, chỉ còn lại sự khoan khoái dễ chịu, có cả chút se lạnh giống mùa thu nữa 💦 💦

Sau những ngày mưa, hé cánh cửa đón làn gió mới, bật một bài nhạc dịu êm nhè nhẹ, mong rằng chúng mình luôn vui vẻ và yêu đời như Hà Nội những ngày này nhé!

■ Intro.

Đợt vừa rồi mình đăng ký tham gia Coding Camp – Phá Băng Web 3.0 Cùng Solana, dù mới đi được nửa chặng đường thôi cơ mà thấy bao nhiêu là điều hay ho muốn tìm hiểu >.<

Bài viết này thì chưa phải về Sol 😸 😸   Chẳng là gần đây, song song với những "cơn sóng lớn" của thị trường, xu hướng Run-to-earn, Play-to-earn, Shop-to-earn vẫn đang được mọi người khá quan tâm. Hay là anh chị em mình cũng "bon chen":

Learn-to-earn - Code in every universe (Developer Strange in the Multiverse of Madness)

điii ^^

Trong bài viết này, hãy cùng nhau tìm hiểu về cách tự tạo một Command-line interface (CLI) bằng JavaScript trên môi trường NodeJS để trade coin/token(s) trên sàn Binance nhé!

■ Target

Đầu tiên, ngó qua chút về các lệnh và chức năng của Binance CLI mà chúng ta sắp làm:

Mô tả:

Binance CLI là một CLI hỗ trợ các dịch vụ trên sàn Binance thông qua các dòng lệnh:

$ binance balance                     // Kiểm tra tài khoản

$ binance price                       // Xem giá các đồng coin/tokens
$ binance price --ass BTC --cur USDT  // Xem giá 1 đồng coin/token cụ thể (BTC/USDT)

$ binance order                       // Đặt lệnh mua/bán coin/token
$ ...

🚨   Disclaimer:

Bài viết này hướng tới các anh chị em đã và đang tìm hiểu về NodeJS, NPM , JavaScript; quan tâm cách tự xây dựng một Command-line interface (CLI).

Do đó, nghiệp vụ trading trên Binance chỉ là ví-dụ-về-mặt-chức-năng, không có mục đích l ừ a đ ả o, bán "tool" cho các anh chị em non-tech. Em (mình) xin phép từ-chối-trách-nhiệm nếu CLI này được sử dụng và có bất kì rủi ro nào về mặt tài chính ạ.

Được rồi, cùng bắt đầu thôiii ^^

■ Steps

Để hoàn thành Binance CLI, chúng ta sẽ đi theo các bước:

🔗   Init: Khởi tạo dự án

🔗   Config: Khai báo các lệnh

🔗   Features: Đi vào từng chức năng:

  • Get balance: Xử lý một lệnh cơ bản
  • Check price: Xử lý một lệnh kèm –flag
  • Place order: Xử lý một lệnh lấy giá trị đầu vào người dùng tương tác

🔗   Restructure – Expand: Tái cấu trúc; style, format lại cho xinhh ==’

Giờ thì đi vào bước đầu tiên! (go)(go)

■ Init

Sau khi cài đặt môi trường NodeJS (mình đang dùng v18.1.0), chúng ta tiến hành khởi tạo dự án:

$ mkdir binance  // tạo thư mục 🧊 binance-cli

$ cd binance     // di chuyển vào 🧊 binance-cli

$ npm init -y    // Tạo 📄 package.json - quản lý packages và thông tin dự án

Tạo 📋 index.js – chứa các script xử lý logic mà lát nữa chúng ta sẽ viết vào, cấu trúc thư mục hiện tại như sau:

🧊 binance
          |____ 📄 package.json
          |____ 📋 index.js

Tạm thời log thử dòng text trong này nha:

console.log('Make It Awesome!');

Tiếp theo, thêm 02 properties vào 📄 package.json:

{
    "type": "module",
    "bin": {
        "binance": "index.js"
    },
    ...
}

Trong đó:

  • "type": "module": Hỗ trợ việc sử dụng JavaScript modules
  • "bin": nhận vào một object có:
    • property: tên dùng để gõ lệnh qua Command line, trường hợp này là "binance"
    • value: địa chỉ trỏ tới script mà mình muốn liên kết với lệnh, trường hợp này là "index.js"

(Bạn có thể đọc thông tin chi tiết tại NPMJS Document nếu muốn tìm hiểu thêm nhé)

Với mong muốn dùng Binance CLIở bất cứ chỗ nào khi mở Terminal, chúng ta tiến hành cài đặt:

$ npm i -g . // lệnh này chạy trong thư mục 🧊  binance nha

Khi cài đặt một package, npm sẽ thêm bin[property](trong trường hợp này là "binance") vào $PATH variable trên máy tính, từ đó mà mình có thể dùng lệnh "binance" trên Terminal.

Kiểm tra thử xem ra cơm cháo gì chưa nào:

$ binance
// Expect: "Make It Awesome!"
// Result: Command not found

Hmm…

(?) Cài đặt thành công rồi? Sao vẫn "Command not found" nhỉ?

Cùng nhìn lại bức tranh tổng thể một chút nhé:

Khi chúng ta gõ một dòng lệnh trên Terminal – có thể là Bash shell hoặc Zsh shellscript tương ứng của dòng lệnh đó sẽ được một-trình-thông-dịch (interpreter) thực thi.

Vậy vấn đề phát sinh là: Làm saoTerminal biết trình thông dịch nào cần dùng để đọc script đó !?!

Đến đây thì Shebang line sinh ra cho Terminal "bớt khổ tâm" nè 😹😹))

Ngắn gọn thì Shebang linemột bộ ký tự được sử dụng để chỉ đạo hệ thống sử dụng trình thông dịch – có dạng:

#!interpreter_path [optional-arg]

Trong đó:

  • interpreter_path: đường dẫn đến trình thông dịch
  • [optional-arg]: các thiết lập nếu cần thiết, tùy thuộc vào interpreter

Có một số các shebang mặc định thông dụng thường dùng cho các ngôn ngữ, môi trường khác nhau. Trong trường hợp này, chúng ta cần NodeJS:

#!/usr/bin/env node               // Đặt lên trên cùng của file script
console.log('Make It Awesome!');

Giờ thì cài lại rồi kiểm tra nào:

$ binance
// Result: "Make It Awesome!"

Bingooo!!! 🙌🙌🙌

Để hoàn thành bước Init, chúng ta cài đặt thêm một số packages cần thiết:

$ npm i commander ccxt inquirer prompt-confirm chalk figlet

tương ứng với các mục đích dưới đây:

NPM Package Description
commander Xây dựng command-line interfaces (CLI)
ccxt Hỗ trợ methods exchange coin/token trên các markets (`Binance, Huobi, AscendEX, etc)
inquirer, prompt-confirm Lấy thông tin đầu vào của người dùng khi tương tác trên CLI
chalk, figlet Cho phép style, format text thu hút hơn

Tiếp theo, chúng ta tiến hành config các command cho CLI nhé!

■ Config

Cú pháp khai báo một CLI command:

import { Command } from 'commander';
const program = new Command();

program
    .command('NAME')                         // Tên
    .description('DESCRIPTION')              // Mô tả chức năng
    .option('--opt <OPTION>', 'DESCRIPTION') // Các --option, có thể nhiều hơn 01 option
    .action(callbackHandler)                 // Thêm hàm thực thi
    .parse(process.argv);                    // Parse các arguments được truyền vào script

Áp dụng vào Binance CLI của chúng ta:

// BINANCE BALANCE
program
    .command('balance')
    .description('Check your own balances')
    .action(getMyBalance)      // console.log('getMyBalance')

// BINANCE PRICE
program
    .command('price')
    .description('Check the coin/token price(s)')
    .option(
        '--ass <ASSET: COIN/TOKEN>',
        'Set a specific coin/token'
    )
    .option(
        '--cur <CURRENCY>',
        'Set a specific base currency'
    )
    .action(getPriceInMarket)  // console.log('getPriceInMarket')

// BINANCE ORDER
program
    .command('order')
    .description('Place a buy/sell order')
    .action(placeAnOrder)     // console.log('placeAnOrder')

program.parse(process.argv);

Kiểm tra lại qua dòng lệnh:

$ binance balance // Result: getMyBalance
$ binance price   // Result: getPriceInMarket
$ binance order   // Result: placeAnOrder

rồi qua xử lý nghiệp vụ trên Binance nha! (go) (go)

■ Features

Để sử dụng Binance APIs cho việc kiểm tra ngân sách cá nhân và đặt lệnh cho coin/token(s) trên sàn, chúng ta cần có API KEYSecret KEY.

Bạn có thể lấy tại API Management sau khi đăng nhập vào Binance.

Song, trong quá trình phát triển, việc sử dụng API KEYSecret KEY tương ứng với tài khoản thật – thì quả là một bộ môn nguy hiểm, rất là rủi ro đúng không nào 🙀🙀 =)))

Chú CZ nhà Binance hiểu điều đó nên cũng tạo điều kiện lắm =))) bạn có thể lấy cặp API KEYSecret KEY này ứng với một tài khoản "ảo" trên Spot Test Network bằng cách:

Log In with GitHub > Generate HMAC_SHA256 Keys

Btw, thông tin ngân sách tài khoản trong chiếc gif demo phía trên bài viết, mình cũng dùng tài khoản ảo nha =)))

Keys đã có, tới đây thì có thể gọi các API endpoints tương ứng qua Binance APIhoặc sử dụng cctx package rồi.

Trong bài viết này, chúng ta dùng cctx:

import ccxt from 'ccxt';

const binanceExchange = new ccxt.binance({
  apiKey: YOUR_API_KEY,     // Thay cặp keys
  secret: YOUR_SECRET_KEY,  // của bạn vào đây nhé!
});

// Không cần setSandboxMode nếu bạn dùng cặp keys cho tài-khoản-thật trên Binance
binanceExchange.setSandboxMode(true); 

Với các callbackHanler cho từng chức năng tương ứng, chúng ta viết dạng:

const callbackHanler = async () => {
  try {
    const response = await binanceExchange.callFeatureMethod(); // (1)

    const result = transformFormat(response);                   // (2)
    console.log(result);

  } catch (err) {
    console.error(chalk.red.bold(`ERROR: ${err.message}`));
  };
};

Trong đó:

  • (1) Hàm binanceExchange.callFeatureMethod():
    • Tuỳ vào từng nghiệp vụ mà chúng ta sẽ gọi qua các method tương ứng, bạn có thể xem qua các methods tại CCTX document.
    • Trường hợp không muốn dùng thư viện cctx, đoạn này bạn gọi thẳng qua endpoint của Binance API cũng được luôn nhé, Binance Document được viết rất chi tiết tại đây.
  • (2) Hàm transformFormat():
    • Đầu vào: response, kết quả API trả về
    • Đầu ra: result, kết quả mong muốn log ra Terminal
    • Logic: biến đổi response thành result như ý, mình sẽ comment ngắn gọn mục đích bên cạnh, chi tiết về các xử lý JavaScript, bạn tham khảo trong source code ở cuối bài nhé!

Điểm qua 03 mục tiếp theo để đi vào từng chức năng chi tiết:

Get balance | Basic command

Lấy thông tin tài khoản nào:

const getMyBalance = async () => {
  try {
    const { info: { balances } } = await binanceExchange.fetchBalance();
    // Chỉ lấy những đồng có số lượng > 0
    const displayCryptos = transformBalance(balances);
    console.table(displayCryptos);

  } catch (err) { ... };
};

Check price | Command with –flag

Chúng ta có 2 lệnh xem giá:

$ binance price                       // Xem giá các đồng coin/tokens
$ binance price --ass BTC --cur USDT  // Xem giá 1 đồng coin/token cụ thể (BTC/USDT)

Hàm getPriceInMarket:

const getPriceInMarket = (options) => {
    console.log(options);
}

lấy giá trị các flag --ass, --cur thông qua param đầu vào:

const { ass, cur } = options;  

Vì vậy, logic chỗ này là:

const getPriceInMarket = ({
  ass,
  cur = 'USDT',
}) => {
  if (!ass) {
    fetchTickersAPI();               // Xem giá các đồng coin/tokens
    return;
  };
  
  const symbol = `${ass}/${cur}`;
  fetchASpecificTickerAPI(symbol);  // Xem giá 1 đồng coin/token cụ thể (BTC/USDT)
};

Tương tự như getMyBalance(), 02 hàm fetchTickersAPI(), fetchASpecificTickerAPI(symbol) lần lượt gọi qua binanceExchange.fetchTickers(), binanceExchange.fetchTicker(symbol) và hiển thị kết quả tương ứng ra Terminal nha ^^

Place order | Interactive Command

Để đặt lệnh mua/bán, luồng logic là sau command line:

$ binance order

chúng ta sẽ bắt đầu hỏi người dùng thông tin order: loại tiền crypto muốn đặt; lệnh mua hay lệnh bán; số lượng bao nhiêu,…. Sau đó xác nhận lại thông tin và tiến hành đặt lệnh thông qua các giá trị đầu vào.

Đầu tiên, khai báo một mảng các câu hỏi cần thiết:

const questions = [
    {
        name: 'asset',
        type: 'input',
        message: 'Which coin/token do you wanna place?(required)',
        validate: (asset) => !!asset,
        filter: (type) => type.toUpperCase(),
    },
        {
            name: 'currency',
            type: 'input',
            message: 'Which currency is based? (Default: USDT)',
            default: 'USDT',
        },
        {
             name: 'side',
             type: 'list',
             message: 'Which is order type?',
             choices: ['Buy', 'Sell'],
             default: 'Buy',
             filter: (type) => type.toLowerCase(),
        },
        {
              name: 'quantity',
              type: 'number',
              message: 'How quantity? (required float)',
              filter: (quantity) => (parseFloat(quantity) || ''),
              validate: (quantity) => (quantity > 0),
        },
];

Inquirerpackage hỗ trợ hầu hết các kiểu tương tác đầu vào kèm các hỗ trợ validate, transform,… đa dạng, bạn có thể đọc thêm cách dùng tại đây nhé.

Cùng viết hàm xử lý:

import inquirer from 'inquirer';
import Confirm from 'prompt-confirm';

const placeAnOrder = () => {
    inquirer
        .prompt(questions)
        .then((answers) => {
            const confirm = new Confirm('Are you sure to place this market order?');
            confirm.ask((isConfirm) => {
                    if (isConfirm) placeAnMarketOrderAPI(answers);
            });
        })
        .catch((err) => { ... });
};

Có đầu vào hợp lệ rồi thì còn chần chờ gì nữa, đặt lệnh thôi:

const placeAnMarketOrderAPI = async (answers) => {
    const { asset, currency, side, quantity } = answers;
    try {
        const orderResponse = await binanceExchange.createOrder(
            `${asset}/${currency}`,
            'market',
             side,
             quantity,
        );
        // ...
        console.info('SUCCESSFULLY! GOOD LUCK, "HODLER" :"> CHECK YOUR BALANCES AGAIN ^^');
  } catch (err) { ... };
};

Yayyy, tới đây thì đã cơ bản xong rồi nè, cơ mà có vài điều có thể cải thiện thêm chút, mình sẽ liệt kê ở mục dưới kèm các gợi ý mở rộng chức năng nhé!

■ Restructure – Expansion

Restructure:

  • Chỉnh sửa folder structure, file-splitting theo cách của bạn sao cho trông gọn gàng sạch sẽ hơn chút (có thể chia theo các module chức năng chẳng hạn ^^)
  • Thêm styles, format lại các text đầu ra cho xinh, bạn có thể thử với chalk nhé; font thì dùng figlet ==’
  • Source code của Binance CLI trong bài viết này, bạn tham khảo tại Github.

Expansion:

Ngoài 03 chức năng chính trong bài viết, các bạn có thể tạo mới hoặc phát triển thêm:

  • Kiểm tra thông tin số dư trong ví Spot, Funding, Earn,…
  • Xem các lệnh đang chờ khớp
  • Vẽ biếu đồ nến của 1 đồng nào đó
  • Đặt lệnh đa dạng hơn: Market, Stop-Loss, Stop-Limit,…

rồi chia sẻ thành phẩm phía dưới comments nha ^^

■ Sumup

Như vậy là chúng ta đã cùng nhau hoàn thành chiếc Binance CLI nhà làm rồi!!!

Có thể nói, song song với GUI, CLI cũng có rất nhiều cái hay và có thể phục vụ được đa dạng nhu cầu. Từ những tác vụ cho những công việc có thể kể tới như:

  • Tạo boilerplate mặc định cho dự án mới
  • Auto render ra test files kèm case mặc định cho các React components

cho đến những nhu cầu cá nhân: xem thời tiết, theo dõi lịch đấu bóng,.. Hoặc đơn giản chỉ là mong muốn biết thêm những behind the scenes từ khi một dòng lệnh được gõ cho tới lúc nó thực thi xong. Rõ ràng việc quan sát kỹ lưỡng và tìm hiểu một chút về cách hoạt động của các công cụ được sử dụng luôn là điều tốt đúng không nào ^^

Hy vọng rằng bài viết này có thể giúp ích được các bạn. Cảm ơn các bạn đã đọc bài chia sẻ này. Vừa hay 30/04 vừa qua là sinh nhật của mình =)), "mừng tuổi mới" 1 upvote để mình có thêm động lực cho những bài viết sắp tới nhé 🥰 🥰

Chúc các bạn ngày làm việc hiệu quả! Tiện ghé qua nhà mình chơi một chút rồi về!

■ Credits

Happy coding!

Chú Đen mang tiền về cho mẹ còn JSX mang gì về cho React?

Những chuỗi ngày cận Tết, quây quần bên gia đình, bên nồi bánh xanh đỏ lửa, nghi ngút khói mà thấy hạnh phúc vô cùng. Lại còn hì hục nướng củ khoai, bắp ngô, mặt đen nhẻm mà rộn ràng tiếng nói cười. 😽😽

Ngẫm lại thì, vèo cái đã hết một năm. Cảm ơn thật nhiều những người thân ở bên cạnh, những người anh chị em tuyệt vời luôn sát cánh và những trải nghiệm thú vị trong năm qua.

Chia tay năm cũ, chào đón năm Nhâm Dần với những thử thách và mục tiêu mới. Tết năm nay thì đúng vị rồi, rét chút và có tí mưa xuân nữa 💦💦

Chúc cho tất cả chúng ta bắt đầu một năm Nhâm Dần 2022 với nhiều điều thật mới mẻ, thật thành công, thật bình yên và thật vui tươi nhé ❤ ❤ ❤

◼ Đặt vấn đề

Từ ngay đầu tháng qua, chú Đen đã dặn dò các Đồng âm:

- Mang tiền về cho Mẹ, đừng mang ưu phiền về cho mẹ ~~

Chú Đen mang tiền về cho mẹ thì rõ ràng rồi. Hưởng ứng chia sẻ của chú, chị gái ngồi cạnh mang quà về cho mẹ, anh trai phía sau mang con dâu về cho mẹ, genZ em kề cạnh thì nhất quyết mang khẩu trang về cho mẹ, blabla… 😸😸

Quay trở lại với ReactJS của chúng ta, không biết JSX nhà mình mang lại điều gì cho chiếc thư viện này nhỉ? Điều gì thật sự diễn ra sau những đoạn mã JSX đó…(suynghiii)

Khởi động năm mới với chiếc chủ đề nhẹ nhàng này cùng mình nhé 😽😽))

■ Đối tượng

Bài viết chủ yếu hướng tới các bạn đã và đang tìm hiểu về ReactJS, nhưng trong quá trình tiếp cận JSX còn băn khoăn cũng như muốn có cái nhìn rõ nét hơn về nó.

Trong bài viết này, chúng ta sẽ cùng nhau điểm qua 03 phần:

🔗 Overview – Tổng quan JSX

🔗 Behind the scene – Những gì diễn ra phía sau JSX

🔗 Common issues – Một số vấn đề thường gặp với JSX

Đầu tiên, cùng nhau xem lại JSX một chút! 😺😺

■ Overview

Theo Trang chủ:

JSX is a syntax extension to JavaScript.

Là cú-pháp-mở-rộng của JavaScript, JSX cho phép chúng ta mô tả DOM tree cho ứng dụng dưới dạng một object.

Ngay chính JSX Spec của React team cũng đã đề cập:

(JSX)…to define a concise and familiar syntax for defining tree structures with attributes.

Và cũng chính vì quá đỗi quen thuộc như vậy, dẫn đến một-hiểu-lầm-khá-phổ-biến với các bạn tiếp cận với React rằng: JSXHTML.

Thoáng nhìn qua, cứ ngỡ một biến JavaScript lại được gán giá trị là một thẻ HTML 😹😹:

const blogTitle = <h1>Make It Awesome</h1>;

Một điều lưu ý, JSX không phải là kiểu JavaScript tương thích với trình duyệt. Nghiễm nhiên, trình duyệt của không thể hiểu được đoạn mã này 😵😵. Đó là lý do chúng ta cần có Babel.

Nói qua chút thì Babel là một JavaScript compiler/transpiler giúp chúng ta dịch đoạn JSX trên thành JavaScript thuần (có thể ghé đây nếu muốn tìm hiểu thêm nha) – đến đây thì quá là easy game cho trình duyệt rồi 😽😽

Quá trình chuyển đổi này diễn ra trong build process, do đó, trình duyệt sẽ không biết được sự có mặt của JSX ngay từ đầu, thứ nó nhận được là một object mô tả cấu trúc của ứng dụng 😸😸.

Hơn nữa, với một vài tính năng mới của ECMAScript 6, một số trình duyệt cũ hơn vẫn chưa hiểu được, do đó, vẫn cần có Babel compiler.

Giờ thì cùng xem thử Babel làm như nào để được object đó nhé (go).

■ Behind the scene

■ JSX to JS

Với component Blog:

const Blog = () => (<h1>Make It Awesome</h1>);

sẽ được dịch thành:

const Blog = () => React.createElement(
    'h1',
    null,
    'Make It Awesome'
);

Dĩ nhiên, khi ta viết thẳng React.createElement() mà không dùng JSX thì component Blog vẫn cho cùng một kết quả tương tự.

Song, với một đoạn mã nhỏ xíu như trên thì việc dùng JSX hay React.createElement() đều ổn hết.

Trải nghiệm với React.createElement() sẽ kém cạnh hơn khi componentslogic quá phức tạp hoặc các elements lồng nhau đa cấp. Trông khá khó đọc và khó bảo trì. Có lẽ JSX sẽ nâng cao chất lượng trải nghiệm của dev hơn – mang theo sức mạnh của HTMLJavaScript.

Để xem thêm cách chuyển đổi từ JSX sang React.createElement() như thế nào, bạn có thể ghé qua đây thử với các đoạn JSX cụ thể nha. 😉😉

■ React.createElement() API

Cú pháp của React.createElement() API:

React.createElement(type, [props], [...children]);

Cùng xem qua các tham số của API này:

  • type: có thể là HTML tag (div, h1, etc.), React fragment hoặc React component của element.
  • props: null hoặc một đối tượng chứa các thuộc tính của element.
  • children: là các HTML tags hoặc React components con của element; trường hợp có nhiều children, sử dụng array

React.createElement sẽ tạo ra một object đại diện cho element đó:

{   
    type: 'h1',   
    props: {     
        children: 'Make It Awesome',
        ...
    }
    ...
}

Chúng ta có thể xem chi tiết các property của object này bằng cách log ra cửa sổ Console:

const Blog = () => {
    const blogTitle = <h1>Make It Awesome</h1>;
    console.log(blogTitle);
    return blogTitle;
};

Open in ⇱Code Sandbox.

Kết quả cho ra một plain JavaScript object – được gọi là React element – mô tả chính xác những gì được hiển thị lên màn hình.

FYI, có thể nói, React elements – đại diện cho HTML elements (nằm trên Original DOM) – nằm trên Virtual DOM. React dựa vào các React elements để tạo HTML elements trên Virtual DOM; Sau đó đồng bộ hóa với Original DOM. Chi tiết về Original DOM, Virtual DOM, bạn có thể đọc qua bài viết này tại đây nhé!

Quay lại ví dụ của chúng ta, thêm chút propsevent:

const dropByMyBlog = () => { /* ... */ };

const blogTitle = (
    <h1 id="blogTitle" onClick={dropByMyBlog}>Make It Awesome</h1>
);

sẽ được dịch thành:

React.createElement(
    'h1',
    {
        id: "blogTitle", 
        onClick: function() { /* ... */ }
     },
     "Make It Awesome"
);

Bạn đoán xem: Object được tạo ra có dạng như thế nào? Thử và kiểm tra lại kết quả trên Code Sandbox nhé 😸😸

Giờ thì chúng ta cùng nhau đi qua một số vấn đề xoay quanh JSX (go)

■ Common issues

■ Adjacent JSX-s

Cập nhật chút component Blog:

const Blog = () => (
    <h1>Make It Awesome</h1>
    <p>URL: https://haodev.wordpress.com</p>
);

thì gặp ngay:

SyntaxError: Adjacent JSX elements must be wrapped in closing tag.

Lý do thì như thông báo rồi, cơ mà có bao giờ bạn tự hỏi tại sao lại như vậy!?! 🤔🤔

Điều này cũng dễ hiểu thôi. Ở phần trước, chúng ta đã biết JSX được dịch sang React.createElement(), hàm này tạo ra một object tương ứng, được trả về bởi component. Mà bản chất component cũng chỉ là một function. Function trong JavaScript thì chỉ trả về 1 giá trị thôi.

Do đó, component không thể nào mà trả về 02 đối tượng ngang nhau được:

const Blog = () => (
    React.createElement('h1', null, 'Make It Awesome'); React.createElement('p', null, 'URL: https://haodev.wordpress.com');
);

Để xử lý lỗi này, chúng ta có thể có 3 lựa chọn:

  • Div Tag
  • React Fragment
  • Array Converter

Hai cách đầu có lẽ cũng không xa lạ gì:

const Blog = () => (
    <div>
        <h1>Make It Awesome</h1>
        <p>URL: https://haodev.wordpress.com</p>
    <div>
);

hay

const Blog = () => (
    <>
        <h1>Make It Awesome</h1>
        <p>URL: https://haodev.wordpress.com</p>
    <>
);

Cách dùng mảng thì có lẽ hơi ít gặp một chút:

const Blog = () => (
    [<h1 key="blogTitle">Make It Awesome</h1>, <p key="blogURL">URL: https://haodev.wordpress.com</p>]
);

Sở dĩ chúng ta có thể làm như vậy vì trong JSX, một đoạn <p>{[1, 2, 3, 4]}</p> sẽ được chuyển thành <p>{1}{2}{3}{4}</p>, 1234 vẫn được in ra bình thường không vấn đề gì 😸😸.

Chú ý thêm unique key cho mỗi phần tử trong mảng để tránh Warning nha ^^

Việc tại sao cần dùng key, bạn có thể tìm hiểu thêm trong bài viết này.^^

Trong 03 hướng xử lý trên, mình hay dùng React.Fragment dạng shorthand để bọc các Adjacent JSX-s.

React.Fragment được thêm vào trong React version 16.2 để tránh việc chúng ta phải dùng Div tag trong các trường hợp không cần thiết (không có ý nghĩa trong DOM Structure hoặc chỉ có mục đích để tránh lỗi trên), một số khác cũng sẽ ảnh hưởng tới style ứng dụng (flexbox chẳng hạn). Với hướng Array Converter thì hơi khó nhìn và đòi hỏi việc kiểm soát key.

Chốt lại thì React.Fragment quá ư là ổn rồi đúng không nào ^^

Fragments group a list of children without adding extra nodes to the DOM.

■ Class vs. className

Chúng ta có thể thêm các thuộc tính vào các JSX elements:

<h1 id="blogTitle" className="blog-title">Make It Awesome</h1>

Đợt nọ, sau seminar React Fundamentals nội bộ, một anh giáo Laravel đố mình, tại sao chúng ta phải dùng className thay vì class!?!

Hmm…

Thử xem saooo…

Mạnh dạn sửa thành class="blog-title" nhận ngay chiếc

Warning: Invalid DOM property 'class'.

Tại sao nhỉ? (thinking)

Theo MDN:

The name className is used for class property instead of class because of conflicts with the class keyword in many languages which are used to manipulate the DOM.

Chính vì vậy, việc truy cập props.class dẫn đến Warning như trên 😸😸.

Ngoài ra, bạn có thể ghé Quora để tham khảo thêm câu trả lời siêu có tâm của chị Sophie Alpert – cựu React team manager tại Facebook nha, quá là uy tín luônn =))

■ import React

Khi làm việc với các components, bạn có bao giờ để ý đoạn import quen thuộc này:

import React from 'react';

Giả sử như component phía dưới chỉ trả về đoạn JSX đơn giản; và cũng không sử dụng các APIs khác trong thư viện 'react' như useState(), useEffect(), etc.

Giờ thì xóa dòng này điii =))

Thử đoán xem kết quả sẽ như thế nào nhé…

Hai trường hợp có thể xảy ra:

  • Hoặc vẫn chạy ngon lành
  • Hoặc thông báo lỗi trong build process.

Vậy, lý do thật sự cho điều này là gì?

Ở phần trên, chúng ta cũng đã biết JSX sẽ được compile ra React.createElement() rồi đúng không nào.

When compiled, it calls the React.createElement() function. So we need to have React in scope for JavaScript to know-what-to-do-with-the-compiled-code.

Khi chúng ta sử dụng JSX, Babel compiler sẽ chuyển chúng thành các lệnh gọi hàm React.createElement(). Do đó, chúng ta cần có React trong scope hoạt động của JSX (buộc cần import React from 'react';).

Cho tới bản React 17.0, Facebook team đã "collaborate" với Babel, JSX with new transform không còn yêu cầu điều này nữa. 😽😽

It is possible to write JSX without importing the React library at the top level or having React in scope.

// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime';

const Blog = () => _jsx('h1', { children: 'Make It Awesome' });

■ Kết

Yayyy… Như vậy là chúng ta đã cùng nhau tìm hiểu về JSX và vài điều thú vị diễn ra xung quanh nó rồi.

Hy vọng rằng bài viết này có thể giúp ích được các bạn đã và đang tiếp cận với ReactJS. Có thể nói rằng, JSX chẳng phải từ khóa khó tiếp cận với người mới, ấy thế nhưng, cùng nhau tìm hiểu thêm chút để giải thích Tại sao thế này, Tại sao thế kia? để nắm được bản chất thì cũng thú vị nhỉ ^^

Cảm ơn các bạn đã đọc bài chia sẻ này. Đầu xuân năm mới, lì xì mình 1 upvote để có thêm động lực cho những bài viết sắp tới nhé 😺😺

Và trong thời điểm hiện tại thì…

Mặc dù thời gian này (thời điểm mình publish bài viết, 01/02/2022), các tỉnh đã có các biện pháp phòng tránh, kiểm soát tình hình dịch bệnh; Việc tiêm vaccine Covid-19 cũng đã được triển khai, song, đang trong thời gian nghỉ Tết Âm lịch nên nhu cầu di chuyển của người dân là không thể tránh khỏi, chúng ta cũng chưa thể chủ quan, hãy tiếp tục tuân thủ quy tắc 5K được Bộ Y tế khuyến cáo:

#Coronavirus #5K #BoY Te
Khẩu trang - Khử khuẩn - Khoảng cách - Không tập trung - Khai báo y tế

để có thể giữ an toàn cho bản thân và mọi người xung quanh 😺😺

Một lần nữa, chúc các bạn kì nghỉ Tết ấm áp, đong đầy hạnh phúc bên gia đình nhé!

Năm mới ghé qua nhà mình "thưởng bánh uống trà" chút rồi về ^^

■ Credits

Happy coding!

useEffect() thì cũng đơn giản nhưng mà people make it complicated

Những ngày đầu đông gõ cửa, tiết trời Hà Nội sao mà đỏng đảnh đến thế. Sáng ra vẫn còn vấn vương những tia nắng ấm, làn gió mơn man mà chiều về đã mang theo cơn mưa nhẹ lất phất. Ấy thế mà Hà Nội vẫn có cho riêng mình sức hút lạ kì. Còn gì tuyệt vời hơn khi cùng homies ghé qua những con phố, nghe hương hoa sữa thoang thoảng, hay "cố tình" đi chậm lại chỉ để ngắm nhìn những gánh cúc họa mi một chút ❤

Chill như vậy, âu cũng là cái cớ để chúng ta cùng ngồi lại trao đổi về một chủ đề nữa trong ReactJS đón chào chiếc giao mùa nhỉ =))

◼ Đặt vấn đề

Vẫn là anh bạn mình nhắc tới trong bài viết Yêu React chẳng cần cớ, cần hiểu rõ setState() cơ !, sau hơn năm chinh chiến dự án thì React chẳng còn là cái gì đó "xoắn quẩy" với người anh nữa. Ngay ngày hôm qua, còn phím đố mình:

- Tại sao một vài trường hợp hàm trong useEffect() vẫn chạy lại trong khi dependency không hề thay đổi?

Hmm… Không biết có bạn nào gặp trường hợp này chưa?

Trong bài viết này, chúng ta cùng tìm hiểu về useEffect() để giúp mình tìm ra câu trả lời thỏa đáng để "đá bóng" lại ông anh nhé !!!

■ Đối tượng

Bài viết chủ yếu hướng tới các bạn đã nắm được các concepts cơ bản của ReactJS nhưng trong quá trình tìm hiểu còn băn khoăn về useEffect() cũng như muốn có cái nhìn rõ nét hơn về React API này ^^

Nhấp ngụm mật ong chanh cho ấm bụng rồi cùng bắt đầu thôiii! 🍯🍯

useEffect() API

■ Tổng quan

Đó giờ ReactJS đã hỗ trợ chúng ta khai báo một component theo 2 hướng cú pháp:

  • Thông qua Function
const FunctionComponent = () => <p>Function Component</p>;
  • Thông qua Class
class ClassComponent extends React.Component {
  render() {
    return <p>Class Component</p>;
  }
}

Ở thời điểm trước đó, Function Component chỉ được xem như một kiểu Presentation component/Stateless Component do chưa được React hỗ trợ về việc quản lý state, track life cycles so với Class Component.

Cho tới 2019, Function component mới thật sự bùng nổ và bắt đầu được sử dụng rộng rãi hơn khi có thêm sức mạnh của React Hooks – một nhóm các APIs được ReactJS trình làng trong version 16.8.

useEffect() là một trong số đó. API này hỗ trợ chúng ta giải quyết một số thách thức khi làm việc với life cycle trong component.

Giờ thì hãy đi vào cú pháp của useEffect()😃))

■ Cú pháp

useEffect(callback, dependency);

useEffect() là một hàm nhận vào 2 tham số:

  • Callback:

    • Là một hàm:
      • Không nhận tham số.
      • Bên trong chứa các effects cần được xử lý; Chúng sẽ được thực thi khi component được mount ở lần render-đầu-tiênđược-gọi-lại nếu một-trong-các-giá-trị trong mảng dependency thay đổi.
      • Luôn trả về hoặc một hàm khác (gọi là cleanup) hoặc undefined.
    • Bắt buộc truyền vào
  • Dependency:

    • Là một mảng gồm các dependencies, quyết định việc có gọi lại các effects trong callback hay không:

      • Nếu không truyền gì thì mặc định, các effects sẽ được gọi lại sau mỗi lần render.
      • Nếu truyền vào một mảng rỗng [] thì effects sẽ CHỈ chạy trong lần render đầu tiên.
    • Tùy chọn truyền vào hoặc không.

Đâu đó chúng ta đã bắt gặp:

useEffect(() => {
    getPostDetail(id);          // <-- effect
    
    return () => {
        cleanPostDetail();      // <-- cleanup
    }
}, [id]);                       // <-- dependency

Theo Trang chủ ReactJS:

The Effect Hook lets you perform side effects in function components.

Này là một số use cases về các side effects thực tế chúng ta thường gặp:

  • Thêm subscriber, event listener cho element.
  • Gọi dữ liệu từ API sau khi component được render trong giai đoạn mounting.
  • Thực thi một business logic/DOM update nào đó khi state hoặc props trong component thay đổi.
  • Tiến hành dọn dẹp, unsubscribe các event listeners trước đó đã sử dụng trước khi component unmount.

<br/>

Nói như vậy, liệu rằng life cycle methods trong Class component(CC) làm được gì thì useEffect() trong Function component(FC) cũng "cân" được hết hay sao nhỉ!?!

Cùng chuyển sang phần tiếp theo để đi tìm câu trả lời!

useEffect() vs. Lifecycle methods

Nếu như bạn đã quen với các life cycle methods trong Class component, có thể hiểu rằng useEffect() là sự kết hợp giữa 3 methodscomponentDidMount(), componentDidUpdate()componentWillUnmount() 😸😸))

Chi tiết được mô tả trong bảng sau:

Class Component Function Component
componentDidMount(){ effects() } useEffect(() => { effects() }, [])
componentDidUpdate(){ effects() } useEffect(() => { effects() }, dependencies)
componentWillUnmount(){ cleanup() } useEffect(() => () => { cleanup() }, [])

Open in CodeSandbox ▷▷▷

Nếu muốn tìm hiểu thêm về phần này, bạn có thể tham khảo trên Trang chủ React hoặc nghía qua chiếc slide trong một seminar giới thiệu chung về hooks mình làm cho team gần đây nhé ^^

Giờ thì điểm qua một số vấn đề thường gặp khi làm việc với useEffect()!

■ Các vấn đề thường gặp

Như đã đề cập ở trên, thay vì:

useEffect(callback);

thì với cú pháp

useEffect(callback, dependency);

chúng ta có thể kiểm soát những lần gọi lại callback không cần thiết của component.

Các giá trị được sử dụng bên trong useEffect() và nằm trong component nên được truyền vào dependency. Nếu chưa quen, thời gian đầu chúng ta có thể nhờ tới sự hỗ trợ của Lint Tools:

We provide the exhaustive-deps ESLint rule as a part of the eslint-plugin-react-hooks package to find components that don’t handle updates consistently.

Giờ thì xem qua một ví dụ:

useEffect(() => {
    getPostDetail(id);
}, [id]);  

Này thì dễ rồi nhỉ 😸😸 getPostDetail() sẽ chạy trong lần render đầu tiên và được gọi lại nếu như giá trị id thay đổi.

Hmm…

Dựa vào đâu để React phát hiện được "sự thay đổi" (change detection)?

React sẽ so sánh id hiện tại và id ở lần render ngay trước đó.

Vậy nếu nó là một Object hay Function thì sao? 🙂))

Trong JavaScript, chúng ta biết rằng khi so sánh:

// Primitive values
const prevURL = 'https://haodev.wordpress.com';
const currURL = 'https://haodev.wordpress.com';
prevURL === currURL    // TRUE

// Reference values
const prevBlog = { name: 'Make It Awesome' };
const currBlog = { name: 'Make It Awesome' };
prevBlog === currBlog  // FALSE

Ở một số trường hợp thực tế, Dependency có thể là 1 mảng các props, state, thậm chí là một function, như vậy thì sẽ không thể tránh khỏi việc trigger effects thừa thãi.

Cùng đi vào chi tiết nhé!

<br/>

Dependency chứa object

const ObjDependency = () => {
  const [vote, setVote] = useState({
    value: 0,
  });

  useEffect(() => {
    console.log("Component is invoked when vote changes");
  }, [vote]);

  return (
    <>
      <p>Vote value: {vote.value}.</p>
      <button onClick={() => setVote({ value: 0 })}>Set vote = 0</button>
    </>
  );
};

Lúc này, dù vote.value vẫn bằng 0 nhưng chuỗi Component is invoked when vote changes vẫn sẽ được log ra khi ta click vào button 😿😿

Để xem nào, chúng ta sẽ có một vài hướng tiếp cận để xử lý như sau:

  • Chỉ thêm những giá trị property thật sự cần thiết
useEffect(() => {
    console.log("Component is invoked when vote.value changes");
}, [vote.value]);

Truyền vote.value vào mảng dependency thay vì đưa cả object vote vào như trước.

Song, chẳng phải lúc nào value cũng tồn tại trong vote (optional property) hoặc nếu object đó có nhiều properties thì ta phải liệt kê hết vào hay sao? 🙃🙃))

Đến đây thì có thể tham khảo 03 cách tiếp theo:

  • Sử dụng JSON.stringify()
useEffect(() => {
    console.log("Component is invoked when JSON.stringify(vote) changes");
}, [JSON.stringify(vote)]);
  • Kết hợp useRef() and một số helpers hỗ trợ so sánh object
const useDeepCompareWithRef = (value) => {
    const ref = useRef();
    // Hoặc 1 helper deep comparison 2 objects thay vì lodash _.isEqual()
    if (!_.isEqual(value, ref.current)) {
        ref.current = value;
    }
    return ref.current;
};

useEffect(() => {
     console.log("Component is invoked when vote changes with useDeepCompareWithRef()");
}, [useDeepCompareWithRef(vote)]);  
  • Dùng use-deep-compare-effect

Tới đây, nếu object của chúng ta vẫn quá phức tạp để so sánh (thông thường thì không tới mức đó, hoặc chỉ đơn giản là bạn muốn kế thừa open source sẵn có 😸😸) thì có thể tham khảo package này.

Chỉ cần thay useEffect() bằng useDeepCompareEffect() là mọi thứ ổn thỏa, chẳng cần "xoắn quẩy" nữa:

import useDeepCompareEffect from 'use-deep-compare-effect';

useDeepCompareEffect(() => {
    console.log("Component is invoked when vote changes with useDeepCompareEffect()");
},[obj])

<br/>

Dependency chứa function

Xét một trường hợp dưới đây:

const FuncDependency = ({ data}) => {
    const doSomething = () => { console.log(data); };

    useEffect(() => {
        doSomething();
    }, []);
    // ...
};

doSomething() sử dụng props data, nhưng data lại không nằm trong dependency. Điều này dẫn tới việc khi ai đó cập nhật data, doSomething() sẽ không được gọi lại.

Theo Trang chủ React:

It is only safe to omit a function from the dependency list if nothing-in-it (or the functions called by it) references props, state, or values derived from them.

The recommended fix is to move that function inside of your effect.

Do đó, trong trường hợp này, chúng ta có thể định nghĩa doSomething bên trong useEffect() rồi gọi luôn.

Thực tế trong dự án, chúng ta thường tách các requests, logics, UIs, helpers thành các files độc lập để thuận tiện cho việc tái sử dụng và viết unit test hoặc hàm đó là một props nhận từ component cha 😸😸.

Cùng đi tới một ví dụ nữa, chúng ta có 02 components: ParentChild:

  • Parent là component cha của Child
  • Parent truyền 2 hàm updateAnyStatesupdateCounter xuống Child

như đoạn code dưới đây:

const Parent = () => {
    const [counter, setCounter] = useState(0);
    const [anotherState, setAnotherState] = useState(0);

    const doSTOnAnyChange = () => { console.log("doSTOnAnyChange runs on ANY changes") };
    const doSTOnCounterChange   = () => { console.log("doSTOnCounterChange should run on COUNTER changes") };

    return (
        <>
          <button onClick={() => setCounter(counter + 1)}>Update counter state</button>
          <button onClick={() => setAnotherState(anotherState + 1)}>Update a different state</button>
          <Child doSTOnAnyChange={doSTOnAnyChange} doSTOnCounterChange={doSTOnCounterChange} />
        </>
    );
}
const Child = ({ doSTOnAnyChange, doSTOnCounterChange }) => {
    useEffect(() => {
        doSTOnAnyChange();
    }, [doSTOnAnyChange]);

    useEffect(() => {
        doSTOnCounterChange();
    }, [doSTOnCounterChange]);

  return <p>Child</p>;
}

Cùng đoán xem điều gì sẽ xảy ra khi chúng ta click vào 2 buttons nào?

Luôn có 2 chuỗi

> doSTOnAnyChange runs on ANY changes
> doSTOnCounterChange should run on COUNTER changes

được log dưới cửa sổ Console, điều này nghĩa là, khi Parent re-renders, Child nhận thấy sự thay đổi của cả updateAnyStates, updateCounter.

Thực tế thì hàm doSTOnCounterChange – đúng như tên gọi của nó – chỉ cần chạy lại khi có sự thay đổi của state couter thôi.

Tới đây thì useCallback() được sinh ra cho "đời bớt khổ", hạn chế những lần chạy không cần thiết 😸😸

Chỉ cần thay đổi một chút khi khai báo hàm doSTOnCounterChange:

const doSTOnCounterChange = useCallback(() => {
    console.log("doSTOnCounterChange should run on COUNTER changes");
}, [counter]);

Tương tự với cú pháp của useEffect(), useCallback() cũng nhận vào 2 tham số: callbackdependency.

useCallback() will return a memoized version of the callback that only changes-its-identity if any of the dependencies has changed, ensuring we don’t create a new instance of the function every time the parent re-renders.

Với useCallback(), chẳng cần phải lo nghĩ doSTOnCounterChange bị tạo lại mỗi lần Parent re-renders nữa 😸😸

Thử chạy lại và cảm nhận nhé 😽😽

Source code trong các ví dụ ở các mục trên, bạn có thể vào đây tham khảo ^^

Xong rồi thì cùng đi tiếp 02 lưu ý nhỏ xíu xiu nữa nào!

<br/>

■ Infinite loop

Một vòng lặp vô hạn (infinite loop) có thể được tạo ra và dẫn đến các lỗi không mong muốn trong một vài trường hợp chúng ta trigger một vài sự kiện làm component re-renders (props hoặc state thay đổi) bên trong useEffect().

Quan sát ví dụ dưới đây:

const InfiniteLoop = () => {
    const [value, setValue] = useState("");
    const [count, setCount] = useState(-1);

    useEffect(() => {
        setCount(count + 1);
        console.log("Infinite Loop is created & go on ...");
    });
 
  return (
    <input type="text" value={value} onChange={({ target }) => setValue(target.value)} />
  );
};

Khi component thay đổi giá trị trường input ⇒ kích hoạt sự kiện onChangesetValue() được gọi ⇒ Component được re-rendercallback trong useEffect() được gọi lại ⇒ setCount chạy ⇒ component lại re-rendercallback trong useEffect() được gọi lại ⇒ setCount chạy ⇒ …. 😵😵

Cứ như vậy, một vòng lặp vô hạn được tạo ra.

Hướng giải quyết thì có thể chọn cách thêm dependency vào params thứ 2 của hook này:

useEffect(() => setCount(count + 1), [value]);

Do đó, trong quá trình làm việc, chúng ta cần hiểu rõ cơ chế hoạt động của useEffect()ReactJS lifecycle để có thể nắm rõ được luồng chạy của ứng dụng ^^

<br/>

■ Parent Effect vs. Child Effect

Giờ thì chúng ta có 02 components:

const ParentComponent = () => {
    useEffect(() => { console.log('Parent Component') });
    return <ChildComponent />;
}

function ChildComponent() {
    useEffect(() => { console.log('Child Component') });  
}

Khi ParentComponent được render, chuỗi Child Component sẽ được log ra trước Parent Component.

Hmm… Qua đây thì cần lưu ý gì không nhỉ?

Giả sử chúng ta cần làm chức năng Tự động thanh toán. Đoạn code xử lý này được viết trong component con sau mỗi lần render. Trong khi đó, thông tin hóa đơn (tổng chi phí, thông tin giảm giá, tổng thanh toán hay các chi tiết bắt buộc khác) lại được xử lý trong effect của component cha!?!

Như vậy thì có gì đó "chưa ổn" rồi, thanh toán không thành công 😹😹

Thông qua ví dụ này, điều mình muốn nhấn mạnh là, ngoài việc nắm rõ được thứ tự lifecycle-trong-1-component, chúng ta cũng cần lưu ý một chút về tương-quan-lifecycle-giữa-các-components để có thể xây dựng một cấu trúc components phù hợp nhaaa ❤

■ Kết

Như vậy là chúng ta đã cùng nhau điểm qua cơ chế hoạt động của useEffect() và một số trường hợp thú vị xung quanh nó rồi.

Hy vọng rằng bài viết này có thể giúp ích được các bạn đang tiếp cận với ReactJS, từ đó có thể hiểu về luồng của ứng dụng và kiểm soát được một số lỗi liên quan tốt hơn.

Cảm ơn các bạn đã đọc bài chia sẻ này. Tặng mình 1 upvote để có thêm động lực cho những bài viết sắp tới nhé 😺😺

Và trong thời điểm hiện tại thì…

Mặc dù thời gian này (thời điểm mình publish bài viết, 01/12/2021), Hà Nội đã nới lỏng giãn cách xã hội và việc tiêm vaccine Covid-19 cũng đã được triển khai, song, chúng ta cũng chưa thể chủ quan, hãy tiếp tục tuân thủ quy tắc 5K được Bộ Y tế khuyến cáo:

#Coronavirus #5K #BoY Te
Khẩu trang - Khử khuẩn - Khoảng cách - Không tập trung - Khai báo y tế

để có thể giữ an toàn cho bản thân và mọi người xung quanh 😺😺

Chúc các bạn ngày làm việc hiệu quả! Tiện ghé qua nhà mình chơi một chút rồi về!

■ Credits

Happy coding!

React tìm thì Enzyme trốn, React trốn Enzyme vẫn tìm

■ Intro.

Đợt này mình làm việc tại nhà, ở quê giờ cũng đang vụ mùa. Sau một tối chạy mưa té khói, trời nhỏ cho vài giọt như bình xịt hoa thì xong đâu lại vào đấy. Có khi nay còn nóng "rực rỡ" hơn :joy::joy:

Lại nói đến mưa mùa Hạ, chẳng mong thì nó ào ào như vũ bão. Mà đợi hoài thì mất tăm mất tích luôn, nói như Đen Vâu bây giờ thì:

- Anh đi tìm thì mưa lại trốn, anh đi trốn thì mưa chẳng tìm...

Quay trở lại với câu chuyện lập trình của chúng ta, coding & unit testing cũng như cặp kẻ trốn - người tìm trong mỗi giai đoạn phát triển phần mềm vậy.

Đó giờ các vấn đề xoay quanh coding chiếm spotlight nhiều rồi, hôm nay cùng mình tìm hiểu về chủ đề unit testing – cụ thể là Áp dụng Enzyme để thực hiện unit test trong dự án ReactJS nhé :smile::smile:))

■ Target

Bài viết này gồm 02 phần chính:

  • Giới thiệu về Enzyme.
  • Áp dụng Enzyme trong dự án ReactJS.

Do vậy, những bạn đã có kiến thức cơ bản về React và đang muốn tìm hiểu về unit test trong ReactJS components là một trong những đối tượng chính của bài viết nhaa ^^

Bây giờ thì hãy bắt đầu thôiii !

■ Enzyme

Theo Trang chủ:

> Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components‘ output.

Enzyme – thư viện được phát triển bởi nhà Airbnb – thông qua đa dạng các hàm từ render các components cho tới tìm kiếm hay tương tác sự kiện với các elements đã giúp cho việc thực hiện unit test trở nên dễ dàng hơn.

- Ngại gì vết bẩn vì đã có Enzyme  {
    test("CASE_1", () =&gt; { /* ... */ });
    test("CASE_2", () =&gt; { /* ... */ });
    // ...
});

describe("NAME_2", () =&gt; { /* ... */ });
// ...

Để làm rõ điều này, cùng bắt tay vào ví dụ thực hành dưới đây ^^

■ Practices

■ Init

Khởi tạo một dự án ReactJS thông qua Create React App:

npx create-react-app PROJECT_NAME

Ta được cấu trúc thư mục như sau:

■ PROJECT_NAME
└──────────── ■ src
│            ├──────────────── 📋 App.js
│            ├──────────────── 📋 App.test.js
│            ├──────────────── 📋 setupTests.js
│            ├──────────────── ...
├──────────── 📋 package.json
│...

Tiếp theo, tiến hành cài đặt các devDependencies cần thiết:

yarn add --dev enzyme enzyme-adapter-react-16

Để chạy lệnh test, chúng ta sử dụng câu lệnh:

yarn test

Tahdaahh!!

Kết quả tương tự như vầyy: PASS

■ Coding – Config

Trên cơ sở tập trung giới thiệu về Enzyme (bỏ qua câu chuyện "code first - test last" hay "code last - test first"), chúng ta xét một ví dụ về Counter đơn giản:

function App() {
    const [counter, setcounter] = useState(0);
    
    return (
        <div>
            <h1>This is counter app</h1>
            <div id="counter-value">{counter}</div>
             setcounter(counter + 1)}&gt;
                Increment
            
             setcounter(counter - 1)}&gt;
                 Decrement
            
        </div>
    );
}

Trước khi vào phần tiến hành viết test cho component này, hãy thêm config:

import "@testing-library/jest-dom/extend-expect";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

configure({
    adapter: new Adapter(
});

Được rồi, bắt đầu test nhé ^^

■ UI Testing

Chúng ta tiến hành test mặt UI(User Interface) trước như kiểm tra text value, status trên User Interface, etc.

Check text element

import { configure, shallow } from "enzyme";

describe("Counter Testing", () =&gt; {
    test("render the title of counter", () =&gt; { 
        const wrapper = shallow();
        expect(wrapper.find("h1").text()).toContain("This is counter app");
    });
});

Lưu ý một chút,shallow() là một hàm trong Enzyme – nhận params là một ReactElement và trả về là một wrapper instance around-rendered-output.

Hàm .find() là một trong số medthod của instance này:

wrapper.find("h1").text()

Tương tự, chúng ta tiến hành test button:

describe("Counter Testing", () =&gt; {
    // ...
    test("render a button with text of `increment`", () =&gt; { 
        const wrapper = shallow();
        expect(wrapper.find("#increment-btn").text()).toBe("Increment");
    });
    // test("render a button with text of `increment`", ...)
});

■ Logic Testing

Giả sử chưa kể các specs phát sinh, chúng ta có 03 cases cơ bản:

  • Case 1: Giá trị ban đầu của count là 0.
  • Case 2: Khi nhấn vào Increace thì giá trị count tăng lên 1 đơn vị.
  • Case 3: Khi nhấn vào Decreace thì giá trị count giảm xuống 1 đơn vị.

Case 1:

describe("Counter Testing", () =&gt; {
    // ...
    test("render the initial value of state in a div", () =&gt; {
        const wrapper = shallow();
        expect(wrapper.find("#counter-value").text()).toBe("0");
    });
});

Case 2:

describe("Counter Testing", () =&gt; {
    // ...
    test("render the click event of increace button and decrement counter value", () =&gt; {
        const wrapper = shallow();
        wrapper.find("#increment-btn").simulate("click");
        expect(wrapper.find("#counter-value").text()).toBe("1");
    });
});

Case 3:

describe("Counter Testing", () =&gt; {
    // ...
    test("render the click event of decrement button and decrement counter value", () =&gt; {
        const wrapper = shallow();
        wrapper.find("#increment-btn").simulate("click");
        expect(wrapper.find("#counter-value").text()).toBe("1");
        
        wrapper.find("#decrement-btn").simulate("click");
        expect(wrapper.find("#counter-value").text()).toBe("0");
    });
});

Như đã được mô tả trong ví dụ, find(), .text(), .simulate(),… là một số hàm được sử dụng phổ biến trong Enzyme. Ngoài ra, bạn có thể tham khảo thêm tại đây.

Challenge

Dựa vào đa dạng các utilities kể trên, mình có một challenge dành cho các bạn:

Specs thay đổi:
- Giá trị nhỏ nhất `count` có thể nhận là `0`.
- Khi `count = 0` thì `Decreace` sẽ bị `disabled`.

Hãy tham khảo phía trên rồi tự mình xử lý logic & hoàn thành đoạn unit test này nhaaa ^^

■ Refactor

beforeEach() || afterEach()

Trong mỗi test(CASE_i), dòng:

const wrapper = shallow();

luôn bị lặp đi lặp lại :sob::sob:

Để xử lý vấn đề này, bên Enzyme có hàm beforeEach() với cách dùng rất đơn giản:

describe("Counter Testing", () =&gt; {
    let wrapper;
    beforeEach(() =&gt; {
        wrapper = shallow();
    });
    
    // test(CASE_i) deleted the line `const wrapper = shallow();`
});

Tương tự với afterEach():

describe("Counter Testing", () =&gt; {
    // beforeEach
    
    afterEach(() =&gt; {
        wrapper.unmount();
    });
  
    // test(CASE_i)
});

shallow() vs. mount()

Một ứng dụng thì có thể có nhiều modules kèm vô số các components. Do đó, trong ví dụ trên, chúng ta tách Counter component khỏi App:

const App = () =&gt; (
    <div>
        
    </div>
);

const Counter = () =&gt; { /* title, count và 2 button về counter như trên */ };

Đến đây thì thấy bị báo FAIL test. Vào ngay App.test.js đổi .shadow() thành .mount() thì báo PASS test như cũ.

Như vậy, shallow() liệu có khác so với mount()?

Có thể nhận ra ngay điểm khác biệt giữa hai hàm này khi log luôn nè:

shallowWrapper = shallow();
console.log(shallowWrapper.debug());
// RESPONSE: shallowWrapper
<div>
  
<div>

Còn với mount() thì:

mountWrapper = mount();
console.log(mountWrapper.debug());
// RESPONSE: mountWrapper
<div>
  
    <div>
      <h1>This is counter app</h1>
      <div id="counter-value">{counter}</div>
      Increment
      Decrement
    <div>
  
<div>

Ngoài mount(), shadow(), chúng ta còn 1 hàm nữa là render(). Một bảng mô tả sự khác biệt giữa chúng:

Utils mount() render() shadow()
Render Full DOM static HTMLs chỉ "render" ra một component đang test mà không bao gồm các component con, tạo sự tách biệt việc test trên từng component-độc-lập
Common use Integration Test Integration Test Unit Test

Trên đây là một số thông tin cơ bản về Enzyme cũng như cách sử dụng. Để tìm hiểu & trải nghiệm thêm những Unit Test Challenges phức tạp & thú vị, bạn có thể đọc thêm tại đây nhé!

■ Sumup

Yayyy, vậy là chúng ta đã cùng nhau tìm hiểu về Enzyme và thử viết một chút unit test trong React component rồi nè 🎉🎉

Bên cạnh một số thư việntesting phổ biến khác như Jest, Enzyme với sức mạnh của mình cũng có thể xem như một trong những mảnh ghép quan trọng trong hệ sinh thái React.

Mình cảm ơn các bạn vì đã đọc bài viết này và hy vọng rằng nó có thể mang lại được giá trị nào đó.

Tặng mình 1 upvote để có thêm động lực cho những bài viết sắp tới nha ❤

Và trong thời điểm hiện tại thì…

Hãy cùng nhau thực hiện quy tắc 5K được Bộ Y tế khuyến cáo:

#Coronavirus #5K #BoY Te
Khẩu trang - Khử khuẩn - Khoảng cách - Không tập trung - Khai báo y tế

để có thể giữ an toàn cho bản thân và mọi người xung quanh nhé 😺😺

Chúc các bạn cuối tuần vui vẻ ^^

■ Credits

Happy coding !

Sài Gòn thì đau lòng nhưng nhúng TypeScript vào ReactJS thì chưa chắc

■ Intro.

Đợt đầu tháng, mình có trao đổi với người anh về việc triển khai TypeScript trong dự án. Là một người đã có trải nghiệm nhiều với ngôn ngữ lập trình này, anh thao thao:

- Ai rồi cũng phải dùng TypeScript thôi mà (J4F) =))

rồi spoil cho một vài điểm nổi bật của nó. Cá nhân mình tiếp cận với TypeScript trong Angular một thời gian nên cũng hiếu kỳ, không biết sang ReactJS thì nó sẽ "dư lào" 🙂 :smile:))

Để kiểm chứng nhận định trên của người anh, hãy đồng hành cùng mình trong bài viết này nhé!

■ Target

Để tìm hiểu về việc nhúng TypeScript vào dự án ReactJS, chúng ta tập trung chủ yếu về:

  • Lý do chọn TypeScript.
  • Cấu hình dự án ReactJS kết hợp với TypeScript.
  • Một số ví dụ cơ bản thường gặp.

Do đó, bài viết sẽ hướng tới các bạn đã nắm qua cơ bản về ReactJS và một chút xíu xiu về TypeScript rồi nha các homies ^^

Nhấp một ngụm Espresso và bắt đầu nào!

■ Why TypeScript?

Theo Trang chủ:

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

TypeScript được phát triển bởi Microsoft và cộng đồng mã nguồn mở.

"Superset" ở đây có thể được hiểu là:

TypeScript = JavaScript + some amazing stuffs <3

Hãy cùng ngó qua Biểu đồ xu hướng phát triển của một số ngôn ngữ lập trình phổ biến một chút:

Có thể nói TypeScript đang có xu hướng "re-trending" trong những năm trở lại đây :smile::smile:))

Ngoài sở hữu những đặc điểm của JavaScript, điều TypeScript làm mình ấn tượng hơn cả là về việc hỗ trợ static typing. Thành thật mà nói, đợt đầu mình cảm thấy "không thoải mái" lắm vì đã quen với dynamic typing bên JavaScript rồi, nghĩ bụng ngôn ngữ này thật là khó tínhh quá đi mà >.< (J4F)

Tới giờ thì suy nghĩ khác chút, chúng ta có thể quản lý dữ liệu và luồng dữ liệu chặt chẽ hơn nhờ có TypeScript.

Cùng nhau cấu hình một dự án nhỏ rồi xem TypeScript đã thuyết phục mình như thế nào!

■ How to config?

Ngay từ cuối 2018, React team đã ra mắt bản Create React App 2.1 có hỗ trợ TypeScript đầy đủ.

Để bắt đầu init một dự án ReactJS mới với Create React App tích hợp TypeScript, chúng ta chạy lệnh:

npx create-react-app PROJECT_NAME --template typescript
// OR
yarn create react-app PROJECT_NAME --template typescript

Trường hợp đã có dự án ReactJS trước đó rồi thì thể cài đặt thêm TypeScript thông qua:

npm install --save typescript @types/node @types/react @types/react-dom @types/jest
// OR
yarn add typescript @types/node @types/react @types/react-dom @types/jest

sau đó đổi một số files đuôi .js/.jsx thành .ts/.tsx.

Tiếp theo chỉ cần Restart server & enjoyyy 🎉🎉

Notes:

Một số tuỳ chọn biên dịch trong tsconfig.json:

{
    "compilerOptions": {
        "jsx": "react",
        "module": "commonjs",
        "noImplicitAny": true,
        "outDir": "./build/",
        "preserveConstEnums": true,
        "removeComments": true,
        "sourceMap": true,
        "target": "es5",
        // ...
     },
     "include": [
        "src/components/index.tsx",
        // ...
     ],
}

Để tìm hiểu thêm, bạn có thể xem chi tiết tại đây.

Phần tiếp theo, chúng ta sẽ đi vào một số ví dụ khi sử dụng TypeScript trongReactJS nhé!

■ How to implement?

■ Component

Với Functional Component:

// Declare
import React, { FC } from 'react';

type PostProps = {
    link: string,           // Required props
    hasSupportMe?: boolean, // Optional props
};

const Post1 = ({ link, hasSupportMe }: PostProps) => ( /* ... */ );
const Post2: FC<PostProps> = ({ link, hasSupportMe }) => ( /* ... */ );

// Use
<Post1 link="https://haodev.wordpress.com" hasSupportMe={true} />
<Post2 link="https://haodev.wordpress.com" hasSupportMe={false} />

Thoạt nhìn thì cách khai báo Post1Post2 khá giống nhau nhưng cách khai báo Post2 cho phép mình có thể lấy thêm các options khác nữa, cụ thể:

const Post2: FC<PostProps> = ({ link, hasSupportMe, children }) => ( /* ... */ );

Với Class component:

// interface PostProps, PostState

class Post3 extends React.Component<PostProps, PostState> {
    constructor(props: PostProps) {
         super(props)
    }
    
    return ( /* ... */ );
};

Để khai báo default props cho component:

// Function component
const Post1 = ({
    link = "https://haodev.wordpress.com",
    hasSupportMe,
 }: PostProps) => ( /* ... */ );


// Class component
class Post3 extends React.Component<PProps, PState> {

    static defaultProps = {
         link: 'https://haodev.wordpress.com'
    }
    
}

Notes: Prop-types

Nói đến việc kiểm tra kiểu dữ liệu của props, phía ReactJS cũng có một built-inprop-types hỗ trợ điều này.

PostI.propTypes = {
    link: PropTypes.string,
    hasSupportMe: PropTypes.boolean.isRequired,
};

PostI.defaultProps = {
    link: "https://haodev.wordpress.com",
    hasSupportMe: true,
};

Chú ý một chút, hasSupportMe được PropTypes khai báo vừa required và vừa có default props, thì React component sẽ hiểu đây chính là Optional props chứ không phải Required props.

Dan Abramov từng có một nhận xét:

React has its own, built-in way of type checking called prop types. Together with TypeScript this provides a full, end-to-end type-checking experience: Compiler and run-time.

Chúng ta hoàn toàn có thể kết hợp sử dụng props-types trong dự án ReactJS tích hợp TypeScript. Chi tiết bạn có thể đọc thêm tại đây.

Hmmm…

Sao lại là "Sử dụng kết hợp"? Chẳng phải chúng đều dùng để check type hay sao?

Nếu đã có prop-types, tại sao vẫn cần dùng Typescript?

Có một số lý do dưới đây:

  • Phân biệt Compiler timeRun time. Về bản chất thì TypeScript cần biên dịch sang JavaScript, trong quá trình biên dịch, nếu việc type check không được đảm bảo, IDEs và ứng dụng sẽ báo lỗi hoặc gợi ý sửa ngay khi chúng ta mắc phải.
  • Chỉ có thể sử dụng prop-types cho các components. Mà trong ứng dụng thì còn muôn vàn các helpers, constants, hooks, common functions không sử dụng React.

Do đó, chúng ta vẫn rất cần anh bạn TypeScript trong trường hợp này ^^

Xuống phần tiếp theo xem TypeScript ứng dụng trong Xử lý sự kiệnStyle - CSS như thế nào nhé!

■ Events

const Post = () => {

    function handleChangeInput(event: SyntheticEvent) { /* ... */ }
   
    function handleClickBtn(event: MouseEvent) { /* ... */ }
  
    return (
        <>
            <input type="text" onChange={handleChangeInput} />
            <button onClick={handleClickBtn}>Comment</button>
        </>
     );
};

Ngoài SyntheticEvent, MouseEvent như ví dụ phía trên, chúng ta có thể tìm hiểu thêm các sự kiện khác như ChangeEvent, ClipboardEvent, DragEvent, FocusEvent, FormEvent, KeyboardEvent, PointerEvent, TouchEvent, etc.

Giả sử chỉ muốn tracking một hoặc một số kiểu element nhất định:

// Chỉ áp dụng cho các HTMLButton Elements
handleVote(event: MouseEvent<HTMLButtonElement>) { /* ... */  }

 // Chỉ áp dụng cho các HTMLButton hoặc HTMLAnchorElement Elements
handleVote(event: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) { /* ... */  }

■ Styles – CSS

Có nhiều cách để áp dụng CSS trong một dự án ReactJS. Do đó việc ứng dụng TypeScript cũng rất đa dạng. Ví dụ với một số phương cách thường gặp:

Với Inline Style

// Declare
import CSS from 'csstype';

const h1Styles: CSS.Properties = {
    fontSize: '1.5rem',
};

// Use
<h1 style={h1Styles}>Make It Awesome</h1>

Với Styled Components

// Declare
type FlexProps = {
  direction?: 'row' | 'column',
}

const Flex = styled.div<FlexProps>`
    display: flex;
    flex-direction: ${props => props.direction};
`;

// Use
const el = <Flex direction="row"></Flex>

Trên đây là một số ví dụ về ứng dụng TypeScript vào ReactJS :smile::smile:))

Hiển nhiên, một dự án ReactJS không dừng lại ở 01 "chiếc" thư viện ReactJS mà là cả một hệ sinh thái ReactJS với React-Router, Redux, Lodash, Axios, etc. Chưa có gì đáng ngại bởi vì phần lớn chúng đã đều hỗ trợ Typescript đầy đủ (có thể research với từ khoá TECH_NAME + TypeScript). Đôi khi sẽ có một vài thư viện nho nhỏ chưa hỗ trợ Typescript nhưng chúng đều không đáng kể và ít sử dụng.

Eslint cho Typescript cũng gần như không khác gì nhiều; Các IDEs như VSCode, WebStorm, Sublime Text,… cũng hỗ trợ rất tốt ^^

■ Sumup

Yayyy, vậy là chúng ta đã cùng nhau tìm hiểu xong từ lý do chọn TypeScript; thực hành cấu hình cho tới xem qua một số ví dụ cơ bản về việc nhúng TypeScript vào ReactJS rồi nè 🎉🎉

Một điều nữa mình muốn chia sẻ, TypeScript không-sinh-ra-với-mục-đích-thay-thế JavaScript. Nó là một-lựa-chọn.

Với sức mạnh nổi bật của TypeScript, việc tích hợp vào ReactJS sẽ phù hợp với các dự án dài hạn hoặc phục vụ cho việc phát triển một thư viện. Song, nếu chưa chọn dùng TypeScript cũng chẳng sao, TypeScript thực sự tốt nhưng JavaScript không gặp bất lợi gì quá lớn cả, nó vẫn đáng để chúng ta tin tưởng và yêu thích ^^

Các bạn thấy như thế nào, hãy chia sẻ ý kiến của mình phía dưới comment nhé!

Mình cảm ơn các bạn vì đã đọc bài viết này và hy vọng rằng nó có thể mang lại được giá trị nào đó.

Ủng hộ mình để có thêm động lực cho những bài viết sắp tới nha ❤

Và trong thời điểm hiện tại thì…

Hãy cùng nhau thực hiện quy tắc 5K được Bộ Y tế khuyến cáo:

#Coronavirus #5K #BoY Te
Khẩu trang - Khử khuẩn - Khoảng cách - Không tập trung - Khai báo y tế

để có thể giữ an toàn cho bản thân và mọi người xung quanh nhé 😺😺

Chúc các bạn cuối tuần vui vẻ ^^

■ Credits

Happy coding !

Muộn rồi mà sao còn chẳng dùng Apollo Client

■ Mở đầu

Đối với các bạn đã và đang tìm hiểu về ReactJS, có lẽ không dưới dăm ba lần nghe qua những cái tên như Redux, MobX, Context API hay gần nhất là Recoil khi có ai đó nhắc đến từ khóa state management. Mỗi công nghệ đều có cho mình những ưu nhược điểm riêng.

Do đó, câu chuyện lựa chọn công nghệ áp dụng cho một dự án luôn luôn được cân nhắc, cần phù hợp với cả bài toán công nghệ cũng như thế mạnh của team nữa. Chẳng của riêng ai, gần nhất là dự án hiện tại mình tham gia, quyết định dùng Apollo Client cho ứng dụng ReactJS kết hợp GraphQL.

Và đây cũng chính là chủ đề của bài viết này, hãy cùng mình tìm hiểu nhé!

■ Apollo Client là gì?

Theo Official document:

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL.

Là một trong những thư viện giải pháp đối với việc state management của một ứng dụng, Apollo Client cho phép chúng ta gửi requests tới GraphQL server thông qua các QueriesMutations.

Bây giờ thì bật máy tính lên và khởi tạo một dự án ReactJS với Apollo Client nào…

■ Khởi tạo

Sau khi cài đặt tất cả những dependencies:

npm install @apollo/client graphql
// OR
yarn add @apollo/client graphql

chúng ta bắt đầu tiến hành khai báo:

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
   uri: GRAPHQL_SERVER_URL,
   cache: new InMemoryCache()
});

Xong rồi, sau đó thì kết nối Apollo Client với ứng dụng ReactJS:

import { ApolloProvider } from '@apollo/client/react';

render(
   <ApolloProvider client={client}>
      <App />
   </ApolloProvider>,
   document.getElementById('root'),
);

Đoạn code này có làm bạn thoáng nghĩ ngay tới cú pháp khởi tạo và truyền store xuống “ trong Redux không? 😀😀

Tương tự như Context.Provider API phía React hay Provider API phía thư viện react-redux, ApolloProvider API gói gọn toàn ứng dụng vào trong context, cho phép chúng ta có thể truy cập client ở bất kì components nào nằm trong “.

Hmmm…

Giống nhau hết như vậy mà chỉ khác cú pháp với tên thôi thì chuyển sang dùng liệu-có-đáng !?!

Cùng nhau tìm hiểu các phần tiếp theo để có thể trả lời cho nghi vấn này nhé!

■ Concepts

Nhìn lại các thư viện cung cấp giải pháp state management một chút, cho dù là Redux, MobX, Context API, Recoil hay một thư viện nào bạn tự viết thì chúng đều sẽ có 03 vai trò chính:

  • Storage (Lưu trữ).
  • Updation (Xử lý thao tác: thêm, xóa, sửa, etc.).
  • Reactivity (Lắng nghe thay đổi và cập nhật).

đồng thời luôn giữ cho state của ứng dụng luôn là a single "source of truth".

Giả sử, chúng ta phân loại global states của một ứng dụng ra làm 02 kiểu:

  • Local state:

    • Là một số biến trạng thái ở phía client.
    • VD: isAdmin, isSignIn, flag, device, etc.
  • Remote state:

    • Là một hoặc các bộ dữ liệu được server trả về, hoặc liên quan tới các giá trị trên server.
    • VD: products, users, etc.

Redux sẽ quản lý 2 kiểu states này giống nhau, với Apollo Client thì khác chút:

  • Cache cho Remote state.
  • Reactive Variables cho Local state (được lưu ở ngoài Cache).

Giờ chúng ta sẽ đi vào trường hợp Remote state trước!

■ Remote state

Common APIs

Apollo Client cung cấp đa dạng APIs hỗ trợ thao tác với dữ liệu. Trong phạm vi bài viết này, cùng tìm hiểu 2 hooks phổ biến: useQuery()useMutation() nhé. 😄😄

🔗 useQuery

Giả sử, với câu truy vấn

import { gql } from '@apollo/client';

const EXCHANGE_RATES = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      currency
    }
  }
`;

thì thông qua useQuery:

import { useQuery, gql } from '@apollo/client';

function ExchangeRates() {
  const { loading, error, data } = useQuery(EXCHANGE_RATES);

  if (loading) { /* ... */ };
  if (error) { /* ... */ };
  return ( /* data binding */ );
}

Qua API useQuery(), việc tracking dữ liệu trả về (data), lỗi (error) cũng như trạng thái request(loading) trở nên dễ dàng hơn bao giờ hết.

Và một điều tuyệt vời trên Trang chủ có đề cập nữa là:

Apollo Client automatically caches this data when it comes back from the server, so you won’t see a loading indicator if you run the same query again.

Source code ví dụ phía trên tại đây.

Hãy tham khảo và tự thực hiện cho mình một project nhỏ để có thể hiểu rõ mọi thứ hơn nhé 😉😉))

🔗 useMutation

Một ví dụ về useMutation() trong Apollo Client:

function Votes() {
  const [upVote, { count }] = useMutation(UP_VOTE);

  return (
      <button
          type="submit"
          onClick={() => upVote({
            variables: { unit: 1 }
          })}
      >
          Upvote to supporting me
      </button>
  );
}

Một điểm chú ý:

Apollo Client automatically caches your data and normalizes new data in query responses and after mutation.

Notes:

Khi giới thiệu về useQuery()useMutation(), cuối ví dụ mình có nhắc qua một chút tới cache. Vậy thì nó là gì vậy? 🙄🙄

Tạm thời chúng ta có thể hiểu là khi lấy dữ liệu từ GraphQL server, nó sẽ được lưu vào cache.

Cache có dạng tương tự:

{
  "Currency:1": {
    __typename: "Currency",
    id: 1,
    ...
  },
  "Currency:2": {
    __typename: "Currency",
    id: 2,
    ...
  },
  "ROOT_QUERY": {
    __typename: "Query",
    currencies: {…}
    …
  }
  ...
}

Chi tiết về cơ chế hoạt động của cache, mình sẽ đề cập ở một bài viết riêng, bạn cũng có thể tham khảo thêm tại đây.

Mutations dùng để cập nhật dữ liệu. Một mutation được chạy thành công sẽ làm thay đổi dữ liệu trên server. Nghĩa là dữ liệu trong cache phía client chưa-chắc-giống dữ liệu trên cơ sở dữ liệu phía server. Điều này còn tuỳ thuộc vào tuỳ chọn fetchPolicies được config trong ứng dụng. Những trường hợp nào thì cần cập nhật cache sau khi thực thi xong một mutation? Chúng ta sẽ bàn luận về chủ đề này trong một bài viết khác.

Trên đây là tổng quan về useQueryuseMutations. Ngoài chức năng cơ bản trên, chúng còn cung cấp rất nhiều options đa dạng:

useQuery(
  variables: { /* key: value */ },
  skip: /* Boolean value */,
  fetchPolicy: 'cache-first' // Default
)

useMutation(
  onError() { /* handle on error */ }
  onCompleted() { /* handle on complete */ }
  refetchQueries() { /* ... */ }
  ...
)

Local-only fields

Local only fields is a way we can define client side fields on the GQL type that doesn’t need to come from the server.

Ngoài một trong các trường đã được định nghĩa trong GraphQL server's schema, response của một Apollo Client queries có thể được định nghĩa thêm một số local-only fields khác nếu cần thiết.

Cùng xét một trường hợp cụ thể nhé.

Mình có query lấy thông tin chi tiết một sản phẩm:

const GET_PRODUCT_DETAIL = gql`
  query product($id: ID!) {
    product(id: $id) {
       id
       name
       price
    }
  }
`;

const { data, error, loading } = useQuery(GET_PRODUCT_DETAIL);

Trong ProductDetail, mình muốn hiển thị trạng thái của product có đang tồn tại trong giỏ hàng hay không (giả sử kiểm tra trong localStorage).

Có phải bạn định xử lý như thế này:

const ProductDetail = () => {
  const [isInCart, setIsInCart] = useState(false);
  // useQuery: GET_PRODUCT_DETAIL here

  useEffect(() => {
    if (data &amp;&amp; localStorage.getItem('CART').includes(data.id)) {
     setIsInCart(!isInCart);
    }
  }, [data])
  
  // return here
};

Với Apollo Client, thử tiếp cận theo hướng dùng local-only fields xem sao nhé:

const cache = new InMemoryCache({
  typePolicies: {
    Product: {
      fields: {
        isInCart: {
          read(_, { variables }) {
            return localStorage.getItem('CART').includes(
               variables.id
            );
          }
       }
    }
  }
});

Thêm isInCart vào query kèm @client directive:

const GET_PRODUCT_DETAIL = gql`
  query product($id: ID!) {
    product(id: $id) {
       ...
       isInCart @client
    }
  }
`;

Như vậy, tương tự như selectors phía Redux, chúng ta đã có thể truy cập trực tiếp product.isInCart thông qua việc tạo local-only fields rồi. 😄😄

Tiếp theo cùng mình tìm hiểu về local state nào…

Local state

Reactive Variables

Như đã đề cập ở phần Concept, với Local state Apollo Client cho phép chúng ta tạo ra các custom values – được gọi là Reactive Variables.

Reactive Variables được lưu ở bên ngoài cache nhưng các components trong ứng dụng vẫn có thể dùng chung, thao tác truy cập và thay đổi được.

import { makeVar } from '@apollo/client';

const isLoginVar = makeVar(false);

const client = new ApolloClient({
   uri: GRAPHQL_SERVER_URL,
   cache: new InMemoryCache({
     Query: {
       fields: {
         isLogin: {
            read() {
              return isLoginVar();
            }
         }
       }
     }
   })
});

Trên đây, isLogin đang được khai báo như một reactive variables.

Các components có thể truy cập hoặc thay đổi giá trị isLogin như sau:

import React from 'react';
import { useReactiveVar } from '@apollo/client';

const LoginFrm = () => {
  const isLogin = useReactiveVar(isLoginVar);
  
  const handleClick = () => {
    isLoginVar(!isLogin);
  };
  
  return (
    
      {isLogin ? 'Logout' : 'Login'}
    
  );
}

Thật là thuận tiện phải không nào ^^

Notes:

Một số lưu ý với reactive variables:

  • Modifying a reactive variable triggers:

    • An update of every active query that depends on that variable
    • An update of state associated with variable values returned from useReactiveVar().
  • Reactive variables don’t update the cache.

■ Apollo Client vs. Redux

Ở góc nhìn cá nhân, điều mình cảm thấy tiện khi dùng Apollo Client trong dự án có lẽ là dễ dàng tracking trạng thái, kết quả của request:

const { data, error, loading } = useQuery(GET_PRODUCTS);

Với 1 request này, anh bạn Redux cần có 03 actions khác nhau:

  • GET_PRODUCTS_REQUEST
  • GET_PRODUCTS_SUCCESS
  • GET_PRODUCTS_FAIL

kèm các reducers tương ứng để xử lý.

Song, Redux giúp chúng ta dễ control request thread của ứng dụng hơn. 😀😀

Một bảng so sánh nhẹ giữa Apollo Client vs. Redux:

N/A Redux Apollo Client
Storage Plain JS object Normalized cache & outer-cache data
Updation Action & Reducers Apollo Client APIs
Reactivity Connect (Auto) broadcast to Queries
Debugger Redux DevTools Apollo Client Devtools

Ý kiến của các bạn như thế nào, hãy cùng chia sẻ phía dưới comments nha!

■ Kết

Yeahhh, vậy là chúng ta đã cùng nhau tìm hiểu về Apollo Client từ Concepts, Demos cho tới câu chuyện quản lý state của nó so với thư viện khác rồi nè 🎉🎉

Mình cảm ơn các bạn vì đã đọc bài viết này và hy vọng rằng nó có thể mang lại được giá trị nào đó.

Ủng hộ mình để có thêm động lực cho những bài viết sắp tới nha ❤

Và trong thời điểm hiện tại thì…

Hãy cùng nhau thực hiện quy tắc 5K được Bộ Y tế khuyến cáo:

#Coronavirus #5K #BoY Te
Khẩu trang - Khử khuẩn - Khoảng cách - Không tập trung - Khai báo y tế

để có thể giữ an toàn cho bản thân và mọi người xung quanh nhé 😺😺

Chúc các bạn cuối tuần vui vẻ ^^

■ Credits

Happy coding !

Điều React luôn giữ kín trong tim

■ Mở đầu

Ngồi viết bài khi đang nghĩ vu vơ chuyện con gà hay quả trứng có trước, mình phân vân chưa biết sẽ chọn chủ đề gì để chúng ta có thể cùng nhau bàn luận. Nhớ có lần một người anh của mình từng chia sẻ:

- Viết code thì dễ thôi, viết code để tối ưu mới khó !

Cơ mà trước khi muốn tối ưu thì phải hiểu nó đã. Thì vẫn là câu chuyện xoay quanh anh bạn ReactJS, nhưng sẽ quay về thuở sơ khai khởi tạo dự án bằng Create React App, tìm hiểu về:

React - Behind the scenes,
Điều React luôn giữ kín trong tim :v 

nhé, có được không nào 🙂))

Trong bài viết này, với folder structure được tạo bởi Create React App (v4.0.1), chúng mình sẽ cùng nhau tìm hiểu về 02 vấn đề sau:

  • Liên kết ngầm giữa index.jsindex.html?
  • index.html "trắng trơn" trên browser?

Bắt đầu thôi!

Đầu tiên, hãy cùng nhau xem lại Create React App một chút!

Bạn nào rành đoạn này rồi thì có thể chuyển qua Mục tiếp theo luôn nhaa ^^

■ Creater React App

Định nghĩa

Theo Official document:

Create React App is an officially supported way to create single-page React applications with no configuration.

Đúng là như vậy, Create React App giúp chúng ta tất cả các bước từ A – Z để khởi tạo một ứng dụng ReactJS: từ setup cho tới config; từ thiết lập Babel dịch JSX cho tới cấu hình webpack, đóng gói các tài nguyên, etc.

Điều duy nhất chúng ta cần làm có lẽ là:

- Run command & enjoy !!!

Quả là sinh ra cho đời bớt khổ mà 🙂🙂))

Cấu trúc thư mục

Đây là vị trí các file index.html, index.js mình đề cập đến trong phạm vi bài viết này:

🧊 application
├──────────── package.json
├──────────── public
│            ├──── 📄 index.html
│            ├──── ...
└──────────── src
│            ├──── 📋 index.js
│            ├──── ...
├── ...

Notes:

Nếu bạn chưa từng sử dụng Create React App hay chưa có trải nghiệm nào với ReactJS, hãy thử làm theo hướng dẫn và vọc vạch một chút để nắm được vai trò chính của các files này trước khi đọc tiếp nhé. Chi tiết có thể tham khảo tại đây.

Bây giờ thì chúng ta lần lượt đi tìm câu trả lời cho 02 vấn đề phía trên nào!

■ Liên kết ngầm: index.jsindex.html?

Chúng ta có index.html chứa div#root:

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

còn index.js thực hiện render():

const root= document.getElementById("root");
ReactDOM.render(<App />, root);

vậy thì App sẽ được load vào div#root.

Ơ mà khoannn !?! Có gì đó sai sai thì phải… 😕😕

Chúng ta biết rằng một trong những cách sử dụng Javascript trong HTML là khai báo qua thẻ script trong html:

<scrip-t src="index.js"></script>
// hoặc
<script>
    $blogUrl = document.getElementById('haodev.wordpress.com');
</script>

Ấy thế mà trong index.html > body chẳng có khai báo gì cả ngoài div#root.

Mà chưa kể khi chạy ứng dụng và mở trên trình duyệt, View source code đoạn script đó lại xuất hiện?

Như vậy, phải chăng có một liên kết ngầm nào đó giữa index.htmlindex.js?

Câu trả lời đó là nhờ html-webpack-plugin.

Html Webpack Plugin

Plugin này config public/index.htmltemplate được đọc. Plugin này sẽ thêm đoạn script (bundle.js, chunk.js) vào template (trước đó webpack đã cung cấp cho plugin một đường dẫn khả dụng).

Nói có sách mách có chứng chứ nhỉ 😀))

Vào ngay node_modules/react-scripts/config/webpack.config.js xem lại:

plugins: [
    // Generates an `index.html` file with the <script> injected.
    new HtmlWebpackPlugin(
        Object.assign(
            {},
            {
                inject: true,
                template: paths.appHtml,
            },
         )
    )
]

Chèn script vào file index.html khi npm start.

Quá trình npm start thì xem tại node_modules/.bin:

switch (script) {
    case 'start': {
        const result = spawn.sync(
        'node',
        [require.resolve('../scripts/' + script)].concat(args),
        // ...
    );
}

Vào node_modules/react-scripts/config/paths.js xem html-webpack-plugin đã làm gì với template: paths.appHtml:

module.exports = {
    appHtml: resolveApp('public/index.html'),
    appIndexJs: resolveModule(resolveApp, 'src/index'),
};

Đó là lúc index.html bắt đầu được resolve rồi, script đã được chèn vào template của chúng ta ^^

■ index.html "trắng trơn" trên browser?

Một vấn đề nữa mình muốn chúng ta tìm hiểu, đó là sau khi phát triển ứng dụng, chạy npm run build, miệng nhấp ngụm trà xanh, đầu chắc cú rằng mọi thứ sẽ được compile ra như ý rồi.

Lúc giờ mới chột dạ vì mở trên trình duyệt file build là 1 trang trắng tinh 😞😞.

Để tìm ra nguyên nhân, chạy lệnh npm run eject để xem những gì được thực hiện behind the scenes sau những câu lệnh có-vẻ-là-ngắn-gọnnpm start/ run build/run test – trong 🧊 application/scripts.

Quá trình chạy lệnh npm start sử dụng webpack-dev-server:

// 🧊 application/scripts/start
const WebpackDevServer = require('webpack-dev-server');

Được rồi, bây giờ vào webpack.config.js xem thì thấy đoạn này:

const publicPath = isEnvProduction ? paths.servedPath : isEnvDevelopment && '/';
// "homepage" can be set to "." to enable relative asset paths

Đó chính là lý do, khi chúng ta mở trực tiếp index.html trên trình duyệt thì nhận được "một trang trắng tinh" bởi vì Webpack đang cố tải các static files từ 🧊 application chứ không phải từ 🧊 application/build.

Để khắc phục điều này, chúng ta vào package.json thêm dòng:

{
    // ...
    "homepage":".",
    // ...
}

là ngon ngay 🙂🙂))

Chạy lại npm run build và mở index.html trên trình duyệt để xem kết quả nhé ❤

■ Kết

Create React App mặc dù không quá mới mẻ với các bạn đã và đang làm việc với React, nhưng cũng chẳng thể phủ nhận việc nó được xem như một công cụ vô cùng hữu hiệu, nhanh gọn và tiết kiệm thời gian cài đặt của chúng ta 😀😀.

Song song với loạt ưu điểm đó, còn muôn vàn những behind the scenes mà chẳng riêng 02 vấn đề kể trên mình mong muốn chúng ta chia sẻ với nhau. Rõ ràng việc quan sát kỹ lưỡng và tìm hiểu một chút về cách hoạt động của các công cụ được sử dụng luôn là điều tốt đúng không nào ^^.

Mình cảm ơn các bạn vì đã đọc bài viết này và hy vọng rằng nó có thể mang lại được giá trị nào đó ^^ Ủng hộ mình để có thêm động lực cho những bài viết sắp tới nha ❤

Chúc các bạn cuối tuần vui vẻ ^^

■ Credits

Happy coding !

Vui Trung Thu – React props không mờ lu 🌙

■ Intro

Sáng nay gió mùa Đông Bắc về, một buổi mai đầy sương thu và trên không có những đám mây bàng bạc, mình lại nao nức những kỷ niệm về những ngày đầu mới tìm hiểu React 💦

Tiết trời dịu dàng như vậy thì có lý do gì mà chẳng thả một chiếc bài viết nhẹ nhàng về React nhỉ ^^

Trong bài viết này, chúng mình cùng nhau tìm hiểu về React Props theo một lăng kính mới nhé !

■ Đối tượng

Chủ đề không đao to búa lớn nên hoàn toàn phù hợp với các bạn mới học React hoặc các bạn đã và đang làm việc với React rồi muốn tìm hiểu sâu thêm về nó nha các homies.

Trước tiên hãy điểm nhanh qua Props một chút nào 😋))

■ Props

Mục đích

Theo concept của React, ứng dụng của chúng ta sẽ được phân tách ra thành các component. Mỗi component lại có tính độc lập và riêng biệt. Do đó, các state của component đó cũng sẽ không được truy cập bởi các components còn lại 😕😕 !?!

Như vậy thì làm sao để các component trong một ứng dụng có thể giao tiếp được với nhau đây?

Đó là chính là lý do props được React đưa vào 🎉

  • Component có thể truyền values, states, methods xuống các component con với vai trò như một props.
  • Các component con có thể sử dụng chúng (One-way data flow - flow down).

Cú pháp

Cách một ReactJS component truyền dữ liệu xuống một ReactJS component khác (giả sử là HComponent component) như sau:

<HComponent url="https://haodev.wordpress.com" />

Khi đó thì HComponent nhận props trong argument:

// Class component receives props:
class HComponent extends Component {
    constructor(props) {};
    render() {
        return <>{this.props.url}</>
    }
}

// Function component receives props:
function HComponent(props) {
    return <div>{props.url} Cat</div>
}

■ Render props vs. Function props

Render props

Props của chúng ta có thể nhận đa dạng các kiểu dữ liệu như: Objects, Arrays, Booleans, Strings, Numbers và thậm chí đó là một Function 😺😺.

Theo Official document:

A render prop is a function prop that a component uses to know what to render.

Quan sát ví dụ về Render props nhé:

// Pass
<HComponent1 tags={() => [ "haodev", "haole" ]} />
<HComponent1 keys={()=><a href="https://haodev.wordpress.com/">Make It Awesome</a>} />

// Receive
{this.props.tags()}
{this.props.keys()}

Function props

Xem qua ví dụ về Render props, nếu bạn thoáng có suy nghĩ:

- Xùy !! Khác gì `<button onClick={this.props.handleClickBtn} />` đâu. Văn vở ><

thì chưa phải đâu nhé !

Chúng ta cần phân biệt một chút 2 khái niệm này:

  • Các props nhận giá trị là một function được gọi là Function props.

  • Render props – một kiểu của Function props – có mục đích để tái sử dụng, xử lý để component biết những gì cần render.

Còn this.props.handleClickBtn phía trên thì chỉ được thực thi khi có sự kiện click vào button thôi ^^

■ Default props

Truyền props xuống component con, component con nhận props để xử lý. Mọi thứ vẫn đang rất là ổn áp như chúng ta mong đợi 😺😺.

Thế nhưng giả sử vì lý do nào đó parent component không truyền giá trị của url props xuống thì sao?

Lúc này giá trị của nó sẽ là undefined thay vì giá trị hợp lệ cần truyền xuống. Nếu trong HComponent vẫn thao tác xử lý sẽ dẫn tới Runtime errors.

Thế là dở rồi =)))

Để xử lý vấn đề này, chúng ta có thể check url props có tồn tại hay không để xử lý logic cũng như có fallback value tương ứng trong phần JSX:

<h3>{this.props.url || 'Please directly contact to get my blog url.' }</h3>

Nghe cũng hợp lý đó nhỉ? Mình cũng hay chọn cách xử lý này.

Ngoài ra, 1 cách nữa mà mình cảm thấy "xịn" hơn, "ngon" hơn muốn giới thiệu với các bạn – đó là defaultProps 😺😺.

Overview

React cung cấp cho chúng ta defaultProps giúp xử lý vấn đề này:

defaultProps is a property in React component used to set default values for the props argument.

defaultProps có thể được xem như một property của component. Tương tự như biến, giá trị mặc định props có thể được ghi đè nếu như component truyền props và được sử dụng nếu ngược lại.

Nó được áp dụng cả trong class componnent cũng như function component, đến đây thì vẫn đề chỉ còn là cú pháp thôi 😸😸.

Class component

class ReactComp extends React.Component {}
ReactComp.defaultProps = { }
    
// OR

class ReactComp extends React.Component {
    static defaultProps = { };
}

Với ví dụ phía trên thì:

class HComponent extends Component {
    /* ... */ 
};
HComponent.defaultProps = {
    url: 'https://haodev.wordpress.com'
};

// OR

class HComponent extends Component {
    static defaultProps = {
        url: 'https://haodev.wordpress.com'
    }
    /* ... */
};

Function component

function HComponent(props) {};

HComponent.defaultProps = {};

Tương ứng với ví dụ trên sẽ là:

function HComponent(props) { /* ... */ }

HComponent.defaultProps = {
    url: 'https://haodev.wordpress.com'  
}

Nhận xét

Dù cùng giải quyết được một vấn đề nhưng cá nhân mình nghĩ, có vẻ cách dùng defaultProps ngon lành cành đào hơn:

  • Phân tách được logictemplate.
  • Phần JSX sẽ không còn bị rối với như cách xử lý với || nữa.

Bạn thấy sao chứ mình ưng bụng ghê ấy ^^

Tiếp theo, chúng mình xem qua phần mở rộng khi React tích hợp Typescript xử lý vấn đề này như thế nào nhé !

Mở rộng với React sử dụng TypeScript

Xây dựng một Interface:

type url = "https://haodev.wordpress.com";
export interface HProps {
    url: url
};

Sau đó áp vào component:

// CLASS COMPONENT
class HComponent extends Component<HProps> {
    static defaultProps = {
        url: "https://haodev.wordpress.com"
    };
    /* ... */
};


// FUNCTION COMPONENT
function HComponent(props: HProps) { /* ... */ };
CatComponent.defaultProps = {
    url: "https://haodev.wordpress.com"
}

Nom có vẻ ổn quá nhỉ ^^

■ Kết

Vậy là nãy giờ chúng ta đã cùng nhau tìm hiểu về PropsRender propsDefault props rồi 🎉🎉

Cảm ơn các bạn đã đọc bài chia sẻ này và hy vọng rằng bài viết này có thể giúp ích được các bạn đang tiếp cận với ReactJS cũng như đang tìm hiểu về chủ đề này.

Ủng hộ mình để có thêm động lực cho những bài viết sắp tới nhé 😺😺 !

Chúc các bạn mùa Trung Thu vui vẻ bên gia đình ! Tiện ghé qua nhà mình chơi một chút rồi về !

■ Credits

Happy coding !

Custom hook with ReactBoi

Intro.

Với các bạn đã và đang tìm hiểu về ReactJS, đó giờ hẳn đã từng nghe qua từ khóa React hooks, hay cụ thể hơn là Hooks trong React function components 😺😺.

Đây là một features mới trong phiên bản ReactJS 16.8 với ý tưởng chính là cho phép chúng ta quản lý state, tác động vào lifecycles của các component-không-được-định-nghĩa-bằng-class.

Với chủ đề này thì hôm nay chúng ta sẽ cũng nhau bàn luận về React Custom hooks nhé ^^

Prerequisite

Bài viết chủ yếu hướng tới các bạn đã và đang tiếp cận với ReactJS, đã có kiến thức cơ bản về React Hook nhưng còn băn khoăn về React custom hooks cũng như muốn có cái nhìn rõ nét hơn về em nó ^^

Cùng bắt đầu thôi !

What

Theo Official Document:

Hooks – a new addition in React 16.8 – let you use state and other React features without writing a class.

Như mình đã spoil ở phần giới thiệu, Hooks là một tính năng được các ae dev cho là tuyệt vời trong phiên bản 16.8 của ReactJS.

Có lẽ đầu tiên chúng ta phải kể tới các built-in hooks được xây dựng sẵn hỗ trợ xử lý một số tác vụ rất hiệu quả như useState(), useEffect(), etc. Nếu chưa nhớ lắm thì bạn có thể tạt qua một vòng tại đây rồi quay lại đọc tiếp 😛😛

Ngoài các built-in hooks kể trên, chúng ta có thể tự tạo ra Custom hooks theo chức năng mình mong muốn. Custom hooks cho phép:

  • Kết hợp một hoặc nhiều các built-in hooks trong cùng một custom hooks.
  • Tái sử dụng lại logic giữa các component tương tự nhau.

Hiểu là vậy đã, chúng ta tìm hiểu xem Custom hooks có gì hay ho nhé !

Why

Problem

Một trong những concepts nổi bật ở ReactJS chúng ta không thể không nhắc tới đó là việc phân chia views phức tạp thành hệ thống các component nhỏ-gọn, độc-lập & tái-sử-dụng.

Song, đôi khi việc tách một số components "to to" ra chưa phải là một giải pháp hay do chúng có quan hệ logic mật thiết với nhau.

Như là anh Dan Abramovco-founder của Redux và Create React App – từng chia sẻ:

React doesn’t let them separate concerns.

Nghe trừu tượng nhỉ ?

Ví dụ cụ thể như các componentanimation-phụ-thuộc-vào-logic; form-xử-lý-nhiều-trường-đa-dạng, etc. Ouh chắc phải xử lý nhiều lắm đây, mà chưa kể lặp code nữa 😿😿.

Mới nghe thôi đã thấy hơi hơi…ngài ngại rồi, chỉ muốn về quê trồng rau nuôi cá ngay luôn thì Custom hook lúc này được tạo ra để giúp cho đời bớt khổ nè 😀 😀

Size

Khi tạo thêm React Hooks vào source code của version trước, kích thước của thư viện ReactJS tăng lên (dĩ nhiên rồi =))), song điều đáng nói là chỉ tăng vỏn vẹn ~1.5kB (min+gzip).

Việc khai báo component bằng Class hay Function chẳng khác nhau là mấy, song, với cùng một cách xử lý logic, bỏ qua các sai số tự nhiên, trong React conferences, anh Dan Abramov cũng chia sẻ rằng việc sử dụng Hooks có thể làm giảm kích thước file bundle.js (không đáng kể và chỉ giảm-một-chút thôi).

Sau bài này bạn thử tự kiểm chứng xem đúng không nhé ^^

How

Sau khi tìm hiểu nó là cái gì, nó dùng làm gì thì phải biết làm như thế nào chứ nhỉ ^^ Đâu nói suông vậy được 😸😸.

Cùng thử tạo một custom hooks đơn giản:

function useCustomHooks() {
  let blogName = 'Make It Awesome';
  let blogUrl = 'https://haodev.wordpress.com';
  return [blogName, blogUrl];
}

Chẳng khác gì mình đang khai báo một Javascript function đâu đúng không nào ^^

Có chăng điều chúng ta cần lưu ý một chút về Naming rules của một custom hooks là cần phải có prefix use- trước tên hàm.

Ví dụ như là useTimeSet(), useCheckbox(), etc.

Khai báo xong xuôi, giờ component MyBlog muốn sử dụng hook này thì làm như sau:

function MyBlog() {
  const [blogName, blogUrl] = useCustomHooks();
  return (<h3>{`${ blogName } - ${ blogUrl }`}</h3>)
}

Tiếp theo, chúng ta cùng xem qua một ví dụ để hiểu rõ hơn !

Example

Giả sử trong Users module, mình có component User có chức năng hiển thị một user lấy từ API (/users) dựa vào id.

Ta viết theo kiểu functional component sẽ kiểu như này:

function User({ id }){
    const [user, setUser] = useState();
    const [error, setError] = useState();

    useEffect(() => {
        fetch(`/api/users/${id}`).then(response => {
            response.json().then(user => setUser(user));
        })
        .catch(error => setError(error);
    },[]);

    return (<>{ /* Bind users here */ }</>);
};

Vài ngày sau mình có thêm Products module, component Product cũng có chức năng hiển thị một product lấy từ API (/products) dựa vào id. Thế là nhanh trí sang User component copy, paste sang Product, đổi tên component, thế là xong ! IQ vô cực chưa (J4F) 😸😸)))

Thực ra thì điều này chẳng sai, song, sau khi tìm hiểu về React Hook rồi, mình có thêm một cách khác để tránh lặp code ở đây.

Mình sẽ tách đoạn xử lý ra làm một Custom hook:

function useItemById(endpoint, id){
    const [item, setItem] = useState();
    const [error, setError] = useState();
 
    useEffect(() => {
        fetch(`/api/${endpoint}/${id}`).then(response => {
            response.json().then(item => setUser(item));
        })
        .catch(error => setError(error);
    },[]);

    return [item, error];
}

Và sau đó chỉ cần qua User để refactor lại cho ngắn gọn:

function User({ id }){
    const [user, error] = useItemById('user', id);
   
    return return (<>{ /* Bind users here */ }</>);
};

Với Product cũng tương tự như vậy nhé. Các component bây giờ trông gọn gàng hơn rất nhiều. Sau này có thêm n module có chức năng tương tự thì chỉ cần gọi lại custom hook useItemById() thôi đúng không nào ^^

Lúc cần hook có, lúc khó hook lo !

Tiếp theo, mình cùng lên bàn cân để hiểu rõ hơn về Hooks cũng như Custom hooks nhé !

Comparison

Hooks vs. Class

React hooks không hẳn là một cách khai báo component đối lập với React Class. Chúng đều hỗ trợ mình xử lý các tác vụ trong component. Trong một dự án, hoàn toàn có thể sử dụng đồng thời React hooks đối với Function componentsetState(), lifecycle methods đối với Class component 😺😺.

Mở rộng hơn một chút, ý tưởng về Hooks không hẳn chỉ có trong ReactJS. Chỉ trong vòng vài ngày đầu sau thời điểm React hooks được giới thiệu trong React Conference diễn ra, nhiều developer communities đã implement Hooks API cả cho Vue, Web Components hay thậm chí là cả plain JS functions.

React Hook vs. Service & Helpers

Service, Helpers, Add-ons, Utils hay khái niệm nào đó tương tự có thể được sử dụng ở bất kì đâu, cả trong React hook luôn.

React hook thì chỉ-có-thể-hoạt-động ở trong React component. Nó có thể định nghĩa state, truy cập vào context của component – điều mà những anh bạn kia lại chưa làm được 🎉🎉.

React Hook vs. HOC?

Như đã đề cập ở phía trên, React hooks PHẢI được sử dụng trong function component.. Giả sử bây giờ muốn tái sử dụng logic giữa các components thì phải làm sao?

Ngày React hooks chưa được giới thiệu, một trong những giải pháp là chúng ta có thể dùng HOC (Higher Order Component).

Ví dụ như withRouter() trong React Router chẳng hạn:

class A /* declaration */
    const { match, location, history } = this.props;
    /* render*/
export default withRouter(A);

Giả sử mình cần biến { match } thôi chẳng hạn, thế nhưng props truyền xuống sẽ có nhiều thành phần dư thừa 😕😕. Phân tích ý này vậy thôi chứ mình cũng thích HOC lắm ^^ Bạn nào biết thêm thông tin thì chia sẻ phía dưới comments nhé 😺😺

JS Function vs. React Hook?

Đọc nãy giờ, có giây phút nào bạn nảy ra trong đầu ý nghĩ:

-  Xùyyy,  Văn vở =))) Nó có khác gì hàm trong Javascript đâu (2tat) :">

Chính xác ! Hiểu đơn giản thì hook cũng là một function đó.

Trong JS function, chúng ta có các built-in function (như parseInt(), Math.pow(), etc.). Tương tự, hooks trong ReactJS cũng vậy, tồn tại tận 10 built-in hooks cho phép chúng ta quản lý state và xử lý lifectycles.

Chậm lại một chút, chúng ta hãy cùng ngẫm lại về Code Reuse.

Có rất nhiều cách để tái sử dụng code về logic trong ReactJS. Chẳng nói đâu cho xa xôi, ngay chính mình đây, mình hay viết chúng thành các pure functions. Chừng nào cần thì vào component đó gọi ra sử dụng luôn 😺😺.

Cách di chuyển code logic tốn ít công sức nhất, dành sức mà đi chill =)))

Khi tìm hiểu về Custom hooks thì mình mới nhận ra một vài đặc điểm về cách làm này. Là những hàm thông thường trong Javascript, chúng không có states. Thông thường thì điều này chẳng sao cả, vẫn rất ngon lành cho đến khi mình muốn tính window size, cập nhật state, tạo animation phụ thuộc vào state trong component đó, etc. 😕😕

Hooks với những đặc quyền sử dụng các React features (state, useState(), etc.) sẽ khắc phục được hạn chế này, cho phép chúng ta tương tác với state, lifecycle, context, etc. Một điểm lưu ý rằng Hooks phải được gọi trong một function-component-đã-được-rende-ra-giao-diện nhé !

Conclusion

Vậy là chúng ta đã cùng nhau điểm qua Custom hook, so sánh nó với một vài anh bạn tương tự như lifecycle methods, JS functions, services, etc để hiểu hơn rồi ^^

Theo ý kiến cá nhân của mình, Hook có phần "được lòng" hơn một chút so với class 😸😸. Dù chúng đều cho phép quản lý state, xử lý lifecycles, song, sự linh hoạt trong việc tạo ra các custom hook để tái sử dụng cũng như tách logic làm mình cảm thấy rất là "bánh cuốn".

Có chăng đây sẽ là cách được ưa chuộng trong tương lai nhỉ ^^ Chia sẻ ý kiến của bản thân về nó cho mình biết dưới comments nhé ^^

Cảm ơn các bạn đã đọc bài chia sẻ này và hy vọng rằng bài viết này có thể giúp ích được các bạn đang tiếp cận với ReactJS cũng như đang tìm hiểu về Custom hooks. Ủng hộ mình để có thêm động lực cho những bài viết sắp tới nhé 😺😺 !*

Chúc các bạn cuối tuần vui vẻ ! Tiện ghé qua nhà mình chơi một chút rồi về !

Credits

Happy coding !