[{"content":"Thay vì các tutorial với các ví dụ basic nhàm chán, nay mình sẽ chuyển sang hướng dẫn lập trình bằng một cái thú vị hơn, đó là viết script auto cho game.\nGame mà dùng JavaScript thì dễ nhất tất nhiên là dạng web base rồi. Mình sẽ hướng dẫn với game này (tranh thủ kiếm ref trá hình 😂): https://t.me/seed_coin_bot/app?startapp=1369475670.\nGame này là một mini app trên Telegram nên chỉ cần có tài khoản Telegram là chơi được, không cần cài cắm thêm gì cả (đỡ lo bị hack).\nVà để có thể chạy script thì cần chạy game trên trình duyệt (browser) hoặc Telegram Desktop App (bật chế độ Webview Inspecting).\nThao tác chạy tool rất đơn giản: Bật DevTools lên, vào tab Console, paste code vào và bấm enter. Phần còn lại tùy vào code của bạn 😁.\nGiới thiệu sơ qua về game Game này cũng thuộc dạng game play to earn trên Telegram, nhưng thay vì chỉ thao tác đơn giản như các game tap to earn khác thì game này có tính giải trí cao hơn, chiến thuật hơn (cày sau có thể đuổi kịp hoặc vượt cày trước).\nNgoài ra trong game có hệ thống chợ (marketplace), nên có thể mua bán kiếm lời hoặc thực hành mua đỉnh bán đáy hay các chiến thuật tài chính khác (om hàng, xả hàng, đẩy giá, \u0026hellip;).\nMục đích của script Với những người chuyên về MMO thì có nhiều tool để auto chơi game này nhưng chủ yếu các tool đó là auto các thao tác cơ bản và áp dụng để farm nhiều account.\nMình chỉ chơi 1 account cho vui, và cũng ngại cài tool vì sợ bị hack, nên các thao tác auto chỉ tự viết code và run trên Console cho an toàn. Dùng của nhà trồng được sẽ yên tâm hơn, nhân tiện ôn lại kiến thức luôn.\nScript sẽ có tác dụng như nào tùy vào bạn muốn auto những thao tác gì. Giờ mình sẽ hướng dẫn các bạn thử auto một vài thao tác cơ bản vận dụng các kiến thức trong JavaScript, còn lại thì tùy vào sự sáng tạo của mỗi người.\nThực hành Bước 1: Phân tích Đầu tiên cần phải xem trong game có thao tác nào lặp đi lặp lại để có thể auto được hoặc dùng script sẽ hiệu quả hơn làm thủ công. Với game này thì mình sẽ auto thao tác mua item trong Marketplace để có thể mua được nhiều item giá thấp (sau đó bán lại với giá cắt cổ). Như vậy sẽ không phải ngồi canh nhìn mỏi mắt và bấm mỏi tay nữa, tỉ lệ mua trúng cao hơn (vì nhanh hơn bấm tay) và cũng đỡ bị nhầm lẫn hơn (không cần nhớ giá).\nĐể cho đơn giản thì sẽ bắt đầu từ màn hình Marketplace. Thao tác mua item bao gồm:\n Chọn category (ví dụ chỉ mua rare worm), bật filter (thường là sắp xếp giá từ thấp đến cao). Check giá vài item đầu tiên, nếu gặp item giá thấp hơn so với dự định thì bấm mua. Sau khi bấm mua cần thêm một bước bấm confirm để xác nhận mua. Reload lại danh sách để load item mới (hoặc chuyển sang category khác). Quay trở lại bước thứ 2.  Như vậy mình có thể thực hiện bước thứ 2 đến 5 trong 1 vòng lặp, có thể cho chạy n lần hoặc trong n phút tùy vào code logic.\nBước 2: Chạy thử cơ bản Trước khi thực hiện lặp thì phải code thử và đảm bảo các bước chạy ổn, sau đó mới đưa các bước lặp vào trong vòng lặp.\nBước 1 không cần lặp đi lặp lại nên tạm bỏ qua, mình sẽ làm từ bước 2.\nChúng ta dùng DevTools để xem cấu trúc DOM của giao diện trước, sau đó phân tích và chọn ra các selector phù hợp.\nĐể cho nhanh chúng ta có thể inspect thẳng vào một element, sau đó chuột phải và chọn Copy JS Path để lấy ra code selector của element đó.\nPaste thử vào console xem có select được item hay không. Nếu select được thì selector này có thể dùng được (mặc dù hơi dài).\nThử lấy ra text bên trong element bằng thuộc tính .innerText.\n1 2  const itemPriceElement = document.querySelector(...); // Lấy selector từ DevTools const itemPrice = parseFloat(itemPriceElement?.innerText);   Ok vậy là chúng ta đã lấy ra được giá tiền của item đầu tiên. Làm tương tự các bạn có thể lấy ra được giá của các item tiếp theo. Chú ý là selector do DevTools chọn ra sẽ rất dài và có thể không tối ưu, các bạn có thể tự tối ưu lại, miễn sao cho select đúng item mình cần.\nGiờ đến bước so sánh, cần đặt ra 1 giá mà bạn sẽ mua khi gặp item với mức giá thấp hơn, có thể tạo 1 hằng số, ví dụ:\n1  const MAX_PRICE = 1;   Sau đó so sánh giá trị của giá tìm được với MAX_PRICE, nếu nhỏ hơn hoặc bằng thì thực hiện thao tác mua.\nĐể thực hiện thao tác mua thì chúng ta cần chọn được nút Buy, lấy selector thì làm tương tự như trên. Sau đó dispatch một event click để giả lập thao tác người dùng bấm nút. Ở đây mình dùng method click() cho đơn giản, các bạn có thể dùng dispatchEvent nếu muốn tùy chỉnh giống người dùng thật hơn.\n1 2 3 4  const btnBuyElement = document.querySelector(...); // Lấy selector từ DevTools if (itemPrice \u0026lt;= MAX_PRICE) {  btnBuyElement.click(); }   Chạy thử code trên console nếu modal confirm xuất hiện là ok.\nTiếp theo làm tương tự chúng ta dispatch event click vào nút confirm (Yep!) là xong thao tác mua cơ bản.\nBước 3: Đưa vào vòng lặp Cái này thì tùy vào bạn muốn chạy vòng lặp theo số lần hay theo thời gian. Theo cách nào cũng được nhưng cần chú ý:\n Các thao tác này sẽ thực hiện call API, và sẽ mất thời gian chứ không thực hiện ngay lập tức. Khi màn hình đang loading thì không nên thao tác gì cả.  Vậy nên chúng ta sẽ cần phải có thời gian chờ, thời gian delay giữa các thao tác. Ví dụ như sau khi bấm nút Buy xong thì phải chờ Confirm modal xuất hiện đã rồi mới tìm và bấm nút confirm.\nĐể đơn giản thì chúng ta có thể dùng hàm setTimeout() để chờ. Và viết dưới dạng Promise cho dễ xử lý code logic:\n1 2 3  async function delay(x) {  return new Promise((resolve) =\u0026gt; setTimeout(resolve, x * 1000)); }   Sau đó chúng ta có thể viết code dạng như này:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  const COUNT = 100; // Ví dụ chạy 100 lần  for (let i = 0; i \u0026lt; COUNT; i++) {  try {  await reloadPage();  await delay(1); // Chờ 1 giây để hiển thị danh sách item   await buyItem();  await delay(3); // Chờ 3 giây để load confirm modal   await confirmBuy();  await delay(2); // Chờ 2 giây để api gọi xong (có thể thành công hoặc thất bại)  } catch (error) {  console.log(error);  return;  } }   Các bạn tự implement các hàm chi tiết nhé.\nBước 4: Tối ưu code Còn nhiều thứ phải tối ưu như xử lý ngoại lệ, thêm random giả lập như người chơi thật (để đỡ bị ban nếu game check quá kỹ), \u0026hellip; Ở đây mình sẽ hướng dẫn tối ưu phần setTimeout để luyện skill JavaScript async.\nVấn đề gặp phải: Dùng setTimeout thì chỉ đặt 1 khoảng thời gian cảm tính, ví dụ delay 1-2 giây. Nhưng nếu mạng lag chẳng hạn thì sẽ không xử lý được hoặc delay lâu quá thì tốn thời gian.\nThay vì thế mình sẽ dùng setInterval với thời gian cực nhỏ để liên tục kiểm tra DOM xem đã xuất hiện element mong muốn hay chưa. Ví dụ với thao tác confirm thì sẽ chờ cho đến khi thấy element button confirm, sau đó mới thực hiện thao tác bấm confirm:\n1 2 3 4 5 6 7 8 9 10 11  function checkAndClickConfirmButton() {  const interval = setInterval(() =\u0026gt; {  const confirmButton = document.querySelector(...);   // Nếu tìm thấy nút confirm thì click và xóa bỏ interval  if (confirmButton) {  confirmButton.click();  clearInterval(interval);  }  }, 200); // Mỗi 200 mili giây kiểm tra một lần }   Như vậy dù mạng nhanh hay chậm thì script của chúng ta vẫn chạy ổn.\nTuy nhiên nếu dùng setInterval dạng callback như trên mà cho vào vòng lặp thì sẽ rất khó xử lý nên chúng ta viết lại dưới dạng Promise như setTimeout ở trên:\n1 2 3 4 5 6 7 8 9 10 11 12 13  async function checkAndClickConfirmButton() {  return new Promise((resolve) =\u0026gt; { // Trả về kết quả là một Promise  const interval = setInterval(() =\u0026gt; {  const confirmButton = document.querySelector(...);   if (confirmButton) {  confirmButton.click();  clearInterval(interval);  resolve(); // Hoàn thành Promise  }  }, 200);  }); }   Các bạn có thể bổ sung thêm logic reject nếu chờ quá lâu mà không thấy nút confirm (mạng lag, web bị treo, \u0026hellip;) và nhớ có try catch đầy đủ khi gọi hàm.\nCode sau khi tối ưu sẽ tương tự như sau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  const COUNT = 100; // Ví dụ chạy 100 lần  for (let i = 0; i \u0026lt; COUNT; i++) {  try {  await reloadPage();  await waitListItemLoaded(); // Chờ cho đến khi hiển thị danh sách item   await buyItem();  await checkAndClickConfirmButton(); // Chờ confirm modal hiển thị và bấm vào nút confirm   await waitNotify(); // Chờ đến khi hiển thị thông báo mua thành công hoặc thất bại  } catch (error) {  console.log(error);  return;  } }   Các bạn có thể tham khảo thêm 2 bài viết sau cũng về JavaScript async nếu rảnh:\n Xử lý bất đồng bộ trong JavaScript Phần 1 Xử lý bất đồng bộ trong JavaScript Phần 2  Tổng kết Vậy là chúng ta đã tạo được 1 script đơn giản để giúp tự động hóa việc mua item trong game nhàn hơn, dễ thành công và chính xác hơn.\nChúng ta cũng có thể áp dụng kỹ thuật trên để viết script auto cho các thao tác khác hoặc một số game khá nổi tiếng và gameplay đơn giản khác như: Mayor, Blum, MemeFi, \u0026hellip;\nChúc các bạn vừa chơi game ra tiền vừa nắm vững hơn kiến thức JavaScript (đừng quên bấm vào các link game ở trên để ủng hộ tác giả 1 ref).\n","permalink":"https://robinhuy.github.io/blog/luyen-javascript-bang-cach-viet-tool-auto/","summary":"Thay vì các tutorial với các ví dụ basic nhàm chán, nay mình sẽ chuyển sang hướng dẫn lập trình bằng một cái thú vị hơn, đó là viết script auto cho game.\nGame mà dùng JavaScript thì dễ nhất tất nhiên là dạng web base rồi. Mình sẽ hướng dẫn với game này (tranh thủ kiếm ref trá hình 😂): https://t.me/seed_coin_bot/app?startapp=1369475670.\nGame này là một mini app trên Telegram nên chỉ cần có tài khoản Telegram là chơi được, không cần cài cắm thêm gì cả (đỡ lo bị hack).","title":"Luyện JavaScript bằng cách viết script auto cho game"},{"content":"Bài viết chia sẻ một số kinh nghiệm về làm dự án start up công nghệ với vai trò là một lập trình viên (Dev). Còn nếu bạn muốn xem chia sẻ kinh nghiệm về làm các dự án maintain cho lập trình viên thì xem ở đây nhé.\nVề đặc điểm chung thì các dự án startup thường là:\n Các dự án mới, nhiều khi ý tưởng chưa rõ ràng, requirement chưa đầy đủ. Có thể được lựa chọn công nghệ. Cũng có thể phải áp dụng nhiều công nghệ mới mà mình không biết, không quen thuộc. Bạn là lập trình viên nhưng có thể sẽ phải kiêm nhiệm thêm nhiều nhiệm vụ khác như BA, Design, Tester, \u0026hellip; 😂 Thời gian là vàng, cần tung ra sản phẩm càng nhanh càng tốt.  Dựa vào những đặc điểm trên thì có thể rút ra một số kinh nghiệm dành cho các lập trình viên:\n1. Cần hiểu rõ sản phẩm Nhiều khi chính PO/PM (Product Owner/Project Manager) hay BA (Business Analyst) cũng chưa rõ hết được chi tiết các chức năng của sản phẩm, mình cần chức năng gì, người dùng cần chức năng gì, \u0026hellip; Do đó, với vai trò của người trực tiếp làm ra sản phẩm (lập trình viên), chúng ta càng phải cố gắng tìm hiểu kỹ về sản phẩm. Nếu không thì khi làm sẽ như kiểu làm mò và không vừa ý của khách hàng, dẫn đến sẽ phải sửa đi sửa lại tốn nhiều thời gian.\nGiai đoạn đầu của startup hãy chủ động hỏi càng nhiều càng tốt, không ngại bị chê và không sợ bị mắng. Ngoài ra trong quá trình làm sản phẩm chúng ta cũng có thể đưa ra những góp ý hữu ích cho sản phẩm để sản phẩm đi đúng hướng.\n2. Cần có một code base tốt Code base tốt sẽ giúp phát triển dự án nhanh hơn và dễ mở rộng hơn. Nếu bạn không có kinh nghiệm dựng code base hoặc không có kinh nghiệm làm nhiều dự án thì hãy để các Leader/Senior Dev dựng code base trước, rồi sau đó mới tham gia phát triển tính năng dựa trên code base đó.\nNếu không có code base tốt mà vừa làm vừa sửa, vừa bổ sung, thì sẽ tốn rất nhiều thời gian. Nhưng cũng đừng quá sa đà vào việc dựng code base, chỉ cần đủ dùng và có khả năng mở rộng, customize tốt là được.\nTrong trường hợp bạn có sẵn code base rồi thì trước khi start project cũng nên update lại, nâng cấp các package lên bản mới hơn để tránh sau này khi maintain đỡ khổ.\n3. Tìm Kiếm và Tận Dụng Tài Nguyên Vì là startup nên thời gian là vàng. Hãy cố gắng tận dụng tất cả các công cụ, công nghệ giúp tăng năng suất và hiệu quả công việc. Nếu một chức năng mà có thư viện sẵn thì hãy dùng thư viện luôn, đừng tự viết cho tốn thời gian. Cũng đừng ngại áp dụng các kỹ thuật mới, miễn là nó giúp bạn giải quyết được vấn đề.\nKhi làm startup sẽ có một điểm lợi là bạn thường sẽ được chủ động trong việc lựa chọn công nghệ. Cứ cái nào mới, phổ biến, cộng đồng sử dụng lớn thì mình chọn.\n4. Linh hoạt và thích ứng nhanh Để làm các dự án startup thì bạn phải linh hoạt, vì mọi thứ có thể thay đổi rất nhanh. Requirement có thể thay đổi liên tục từng ngày, hoặc là requirement không rõ ràng chi tiết mà cần bạn phải tự \u0026ldquo;sáng tạo\u0026rdquo; dựa trên kinh nghiệm.\nNhiều khi mình vừa code xong 1 tính năng, hôm sau khách hàng lại muốn sửa sang kiểu khác, rồi hôm sau nữa lại sửa về như cũ \u0026hellip; Do đó tất cả các thay đổi trong code nên được quản lý chặt chẽ bằng git, có tạo branch và commit message cẩn thận rõ ràng để có thể sẵn sàng rollback bất cứ lúc nào.\n5. Chủ động trao đổi, làm việc nhóm Hiếm có dự án startup nào mà bạn phải làm một mình cả. Thường thì khi làm startup bạn sẽ làm việc với một nhóm nhỏ. Vậy hãy tận dụng lợi thế nhóm nhỏ để phối hợp với nhau làm việc hiệu quả hơn.\nHãy chủ động trong việc trao đổi với team về task, requirement, review code, \u0026hellip; Nếu bạn là dev thì không chỉ trao đổi với dev mà với cả Design, BA, PM, PO, \u0026hellip; Thỏa thuận với họ để đưa ra được phương án tốt thay vì chỉ làm theo kiểu \u0026ldquo;hoàn thành tốt việc được giao\u0026rdquo;. Ví dụ bạn gặp một task mà theo bạn là chưa cần thiết ở thời điểm hiện tại thì hãy trao đổi với Leader/PM để ưu tiên làm task khác cần thiết hơn thay vì được giao task nào thì cắm đầu vào làm task đó.\n6. Biết quản lý thời gian và chủ động trong công việc Khi làm việc trong môi trường startup, áp lực và khối lượng công việc có thể rất lớn. Bạn phải chạy đua với thời gian nên có thể OT sẽ xảy ra thường xuyên. Hãy biết cách quản lý thời gian và duy trì cân bằng giữa công việc và cuộc sống cá nhân. Cố gắng estimate task càng chính xác càng tốt, và thỏa thuận với Leader/PM để hạn chế OT.Sẵn sàng OT khi dự án cần nhưng không sẵn sàng OT đến chết 😂.\nBạn cũng cần chủ động tối đa trong công việc, đừng chỉ trông chờ làm đúng những task được giao. Khi hết task bạn có thể chủ động đề xuất task mới hoặc thời gian trống có thể tranh thủ review lại code cũ, optimize code, \u0026hellip; Dù sao thì nếu sau có phát sinh bug thì chính bạn là người phải fix.\n7. Phát triển sản phẩm theo từng giai đoạn nhỏ Thường thì các dự án startup hay sử dụng phương pháp phát triển Agile để chia dự án thành các giai đoạn nhỏ và có thể điều chỉnh dễ dàng. Nếu bạn chưa biết phương pháp này thì nên tìm hiểu trước khi tham gia các dự án startup.\nCó một số dự án startup mình thấy PO/PM bị sa đà vào làm các tính năng mà quên mất tinh thần Minimum Viable Product. Rồi sau đó một thời gian lại cuống cuồng chuyển qua tập trung làm phần demo, có thể bỏ dở các tính năng đang phát triển. Lúc này nếu bạn là một dev có kinh nghiệm hãy trao đổi trực tiếp với PO/PM để họ đưa ra quyết định hợp lý hơn.\nVới các dự án startup mà mình tham gia thì thời gian đầu nên tập trung vào việc phát triển các tính năng cốt lõi trước, bỏ bớt các tính năng phụ và tập trung vào mục đích demo sản phẩm cho khách hàng. Ví dụ như data có thể mockup, hoặc các API có thể tạm thời bỏ qua phân trang, tìm kiếm, \u0026hellip; Hãy tập trung làm giao diện trông đẹp đẹp 1 chút, dữ liệu mockup càng giống thật càng tốt để cho khách hàng dùng thử và phản hồi, rồi sau đó mình lại điều chỉnh dần dần thay vì cố gắng làm thật chỉn chu ngay từ đầu.\n8. Sử dụng AI Tất nhiên rồi, dùng AI sẽ giúp bạn tiết kiệm được rất nhiều thời gian, đặc biệt là công đoạn mockup dữ liệu. Dùng AI sẽ giúp bạn tạo dữ liệu mockup như thật mà đỡ tốn công, chỉ cần cung cấp dữ liệu mẫu và đặt yêu cầu chuẩn là được.\nCác AI mà mình đang dùng thì cũng giống như bài viết trước, liệt kê lại:\n ChatGPT, Claude, Gemini: Hỏi đáp, tra cứu trên web. Codeium, Cody AI: VS Code extension, hỗ trợ gợi ý code hoặc hỏi đáp ngay trên VS Code.    Để một dự án startup thành công thì cần rất nhiều yếu tố, vai trò khác nhau. Lập trình viên chỉ là 1 trong các yếu tố đó, nhưng hy vọng những kinh nghiệm trên sẽ giúp bạn đóng góp hiệu quả trong vai trò này.\nHappy coding 😎\n","permalink":"https://robinhuy.github.io/blog/chia-se-kinh-nghiem-lam-du-an-startup/","summary":"Bài viết chia sẻ một số kinh nghiệm về làm dự án start up công nghệ với vai trò là một lập trình viên (Dev). Còn nếu bạn muốn xem chia sẻ kinh nghiệm về làm các dự án maintain cho lập trình viên thì xem ở đây nhé.\nVề đặc điểm chung thì các dự án startup thường là:\n Các dự án mới, nhiều khi ý tưởng chưa rõ ràng, requirement chưa đầy đủ.","title":"Chia sẻ kinh nghiệm làm dự án startup"},{"content":"Đã là lập trình viên thì chắc bạn sẽ không thể tránh khỏi phải làm các dự án maintain (bảo trì dự án, fix bug, \u0026hellip;), chỉ là sớm hay muộn 😅\nThực tế thì mình hay làm sản phẩm mới hơn, kinh nghiệm làm dự án maintain cũng không nhiều. Nhưng trong quá trình làm các dự án kiểu này mình cũng có đúc kết được một vài kinh nghiệm, liệt kê ra đây để cho anh chị em nào mới join các dự án dạng này đỡ bỡ ngỡ.\nVề đặc điểm chung thì các dự án maintain thường là:\n Các dự án đang hoạt động, có thể đã chạy được 1 thời gian khá lâu (thường là khoảng vài năm). Với tốc độ phát triển công nghệ như hiện nay thì thường các dự án kiểu này sẽ bị out date, sử dụng những công nghệ hơi cũ (thậm chí có cả công nghệ đã bị khai tử). Đa số công việc của người bảo trì là fix bug hoặc cải thiện hiệu suất, cũng có thêm tính năng mới nhưng ít. Thường là fix bug của người khác, nhưng cũng có khi là fix bug của chính mình 😂 Nếu là dự án lớn thì các quy trình từ tiếp nhận yêu cầu đến trao đổi, fix bug, release, \u0026hellip; sẽ khá rườm rà. Có những dự án không có tài liệu đầy đủ. Nếu người bảo trì không phải người phát triển dự án từ đầu thì sẽ không nắm hết được chức năng, business logic, \u0026hellip;  Dựa vào những đặc điểm trên thì có thể rút ra một số kinh nghiệm:\n1. Sửa code ít nhất Code nhiều thì lỗi nhiều, code ít thì lỗi ít. Cho nên cố gắng sửa code ít nhất có thể, vừa dễ review, mà lỡ có sai lầm thì cũng dễ revert.\nTuy nhiên cũng cần chú ý nếu bạn sửa vào 1 function mà được gọi ở nhiều chỗ khác thì phải thật cẩn thận. Nên search trong toàn bộ project xem function đó được gọi ở những đâu, nếu sửa sẽ gây ảnh hưởng gì. Hoặc một cách khác an toàn hơn đó là duplicate function đó ra một function mới để tránh ảnh hưởng đến các chỗ khác.\n2. Code theo convention có sẵn, structure có sẵn Để đảm bảo tính nhất quán thì hãy code theo structure, convention có sẵn của project, dù nó có vẻ không được tối ưu theo góc nhìn của bạn. Tính nhất quán sẽ giúp code dễ đọc và dễ bảo trì hơn.\nNếu project có file README thì làm theo file README. Nếu không có file README thì nên dành thời gian xem qua 1 lượt code có sẵn và code theo. Người ta đặt tên file theo PascalCase thì mình cũng đặt PascalCase chứ đừng camelCase theo thói quen (cái này áp dụng cho cả các project mới chứ không chỉ các project maintain).\nTrong trường hợp nếu thấy có chỗ bất hợp lý và cần cải tiến thì phải trao đổi và thống nhất với leader và các thành viên khác chứ không tự làm theo ý mình.\n3. Hạn chế format code, đặc biệt là code HTML Với những người bị OCD thì nhìn thấy code không format sẽ cực kỳ khó chịu. Tuy nhiên nếu format code thì sẽ vi phạm quy tắc số 1, vì nó sẽ làm code bị sửa đổi nhiều, gây khó khăn cho reviewer và khó revert code. Chưa kể trong 1 số trường hợp (hiếm) format code cũng có thể gây ra lỗi. Đặc biệt với code HTML, khi format code sẽ dẫn đến code thay đổi rất nhiều, và có thể có 1 số lỗi phát sinh do liên quan đến khoảng trắng (ví dụ như ký tự xuống dòng, hoặc là dùng thẻ pre, character entities, \u0026hellip;).\nDo đó nếu format code hãy tuân thủ theo rule của project, và chỉ nên format các đoạn code liên quan đến code mình sửa, tránh format toàn bộ file.\nNote: Nếu là một dự án mới code từ đầu thì nên format toàn bộ file, thậm chí toàn bộ project luôn, để code vừa sạch đẹp vừa dễ đọc và đỡ khổ cho maintainer.\n4. Tận dụng những thứ có sẵn Trước khi code một đoạn logic nào mới thì hãy thử tìm 1 lượt trong project xem đã có code sẵn hay chưa. Thường thì project sẽ có các thư mục dạng helpers, ultils, functions, \u0026hellip; chứa các code logic chung được dùng ở nhiều nơi trong project. Hoặc bạn cũng có thể xem code ở các màn hình có chức năng tương tự với chức năng mình đang làm, vì nó có thể có sẵn các code logic tương tự rồi.\nNếu tìm thấy code có sẵn thì có thể tận dụng luôn, đỡ tốn công viết lại mất thời gian và bị dư thừa code. Và những code này đã dược dùng ở các màn hình khác rồi thì có thể yên tâm mà sử dụng. Nếu chẳng may code đó mà có lỗi thì những chỗ khác đang dùng nó cũng sẽ lỗi, đằng nào cũng phải fix 😂.\nTrong trường hợp bạn tìm thấy 1 function có sẵn, nhưng nó chỉ đáp ứng được 80-90% yêu cầu của bạn, cần phải custom lại. Thì có thể có 2 phương án:\n Nếu code đơn giản, thì chỉ cần bổ sung thêm tham số, xử lý trường hợp nếu gọi hàm mà không truyền tham số này (default value). Sau đó search trong toàn bộ project những chỗ đang dùng để test lại cho chắc. Nếu code phức tạp thì clone ra một function mới cho an toàn, để ngay bên cạnh function cũ, chấp nhận duplicate code, miễn là giải quyết được vấn đề.  5. Không optimize code nếu không cần thiết Trừ khi bug của bạn là bug yêu cầu optimize, nếu không thì không nên optimize lại những đoạn code đang chạy mà không bị lỗi. Có thể nó hơi chậm 1 tí, nhưng không ảnh hưởng mấy và người dùng không phàn nàn thì tốt nhất nên để nguyên. Kẻo lại vừa tốn công, lại vừa \u0026ldquo;chữa lợn lành thành lợn què\u0026rdquo;.\n6. Kiểm thử kỹ trước khi tạo pull request để merge code Dĩ nhiên là trước khi tạo pull request phải test lại xem còn bug không. Nhưng đừng chỉ test các happy case (trường hợp được mô tả trong bug), mà hãy test thêm các trường hợp mà bạn có thể nghĩ ra. Những dev có kinh nghiệm sẽ có cái nhìn rộng hơn và phát hiện được nhiều trường hợp gây bug hơn, nhiều edge case hơn. Mà sớm muộn đằng nào cũng phải fix, mình tìm ra và fix được thì sẽ tốt hơn là đợi tester hoặc client phát hiện ra rồi bắt mình fix 😎\n7. Tránh tiện tay fix bug liên quan Bug liên quan ở đây không phải là edge case của bug mình đang fix, mà có thể là bug trong cùng một màn hình, hoặc trong cùng một chuỗi thao tác của user. Ví dụ như mình fix bug ở màn hình Create và phát hiện thấy bug ở màn hình Edit.\nTrường hợp này nên báo lại với Project Manager hoặc Leader chứ không tiện tay fix luôn vào bug đang fix. Vì nó có thể làm tăng phạm vi của bug, gây khó khăn trong việc quản lý, và giống như mình đang \u0026ldquo;mua thêm việc\u0026rdquo;.\n8. Cần đọc hiểu code logic, business logic trước khi sửa (càng kỹ càng tốt) Cái này thì chắc chắn rồi. Tuy nhiên mình vẫn gặp một số trường hợp có dev chỉ fix cho nó chạy được (đúng với test case), chứ không hiểu hết toàn bộ code logic liên quan, dẫn đến phát sinh thêm bug khác.\nCó những bug giải pháp fix chỉ 1 dòng code. Nhưng để có 1 dòng code đó bạn phải đọc hàng ngàn dòng code, phải debug hàng trăm lần để hiểu được nguyên nhân gốc gây ra lỗi (chứ không chỉ nguyên nhân trực tiếp). Cái này cần phải kiên trì, và phải giải thích được cho PM/Leader, không lại mang tiếng là ngồi chơi 😂\n9. Chỗ nào không hiểu thì phải hỏi Muốn biết phải hỏi, muốn giỏi phải học. Hỏi tất cả những bên liên quan nếu có thể (BA, QA, design, dev cũ, dev mới, \u0026hellip;). Có nhiều dự án sẽ không đủ tài liệu (thậm chí có dự án không có luôn), nên nhiều chỗ mình sẽ không hiểu (thường là về nghiệp vụ). Mà đã không hiểu thì không nên sửa, vì có thể sửa xong chỗ này nó lại tạo ra bug chỗ khác.\nNgoài ra, trong quá trình fix bug sẽ có thể gặp bug khó, hoặc code logic khó hiểu. Lúc này hãy mạnh dạn nhờ sự trợ giúp của đồng nghiệp (cùng hoặc kể cả khác dự án). Có thể họ đã từng fix bug tương tự, hoặc họ giỏi hơn mình, đưa ra được hướng giải quyết tốt, như vậy sẽ tiết kiệm được nhiều thời gian. Tuy nhiên không phải cái gì cũng hỏi, phải tự bỏ công ra nghiên cứu trước, để tránh làm mất thời gian của người khác.\n10. Sử dụng AI Hiện nay có rất nhiều AI hỗ trợ cho việc lập trình. Hãy tận dụng chúng để giúp công việc trở nên dễ dàng hơn. Từ việc search tài liệu, hỏi giải pháp cho đến thực hiện những thao tác lặp đi lặp lại, \u0026hellip; thì AI đều có thể làm rất tốt, giúp tiết kiệm rất nhiều công sức. Bạn cũng có thể sử dụng AI để verify code xem có bug tiềm tàng không, hoặc thậm chí là tối ưu code. Tất nhiên AI không phải lúc nào cũng đúng nên cần tự kiểm tra lại thật kỹ, nhiều khi AI cũng bịa code như thật 😂\nMột số AI bạn có thể dùng free (và nâng cấp lên bản trả phí xịn hơn) như:\n ChatGPT, Claude, Gemini: Hỏi đáp, tra cứu trên web. Codeium, Cody AI: VS Code extension, hỗ trợ gợi ý code hoặc hỏi đáp ngay trên VS Code.    Hy vọng những kinh nghiệm ở trên sẽ giúp bạn phần nào trong công việc.\nHappy coding 😎\n","permalink":"https://robinhuy.github.io/blog/chia-se-kinh-nghiem-lam-du-an-maintain/","summary":"Đã là lập trình viên thì chắc bạn sẽ không thể tránh khỏi phải làm các dự án maintain (bảo trì dự án, fix bug, \u0026hellip;), chỉ là sớm hay muộn 😅\nThực tế thì mình hay làm sản phẩm mới hơn, kinh nghiệm làm dự án maintain cũng không nhiều. Nhưng trong quá trình làm các dự án kiểu này mình cũng có đúc kết được một vài kinh nghiệm, liệt kê ra đây để cho anh chị em nào mới join các dự án dạng này đỡ bỡ ngỡ.","title":"Chia sẻ kinh nghiệm làm dự án maintain"},{"content":"Tiếp tục với Xử lý bất đồng bộ trong JavaScript Phần 1.\nPhần này mình sẽ chuyển qua demo với Promise, Async await, \u0026hellip; cho nó quen thuộc.\nLý thuyết Lý thuyết thì các bạn có thể tra cứu tại đây Promises, async/await.\nHoặc tiện hơn thì các bạn có thể hỏi ChatGPT:\nVí dụ Thay vì dùng các ví dụ như của ChatGPT, thì mình sẽ vẫn sử dụng các ví dụ như ở phần trước, chỉ thay Callback bằng Promise và Async/await.\nĐể giả lập thời gian thực thi của 1 hàm thì mình vẫn dùng hàm delay như phần trước:\n1 2 3 4  function delay(x) {  const start = new Date().getTime();  while (new Date().getTime() - start \u0026lt; x * 1000) {} }   Vẫn là các hàm mô tả hành động trong quy trình luộc rau như sau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  function soCheRau() {  delay(3);  console.log(\u0026#39;Sơ chế rau.\u0026#39;); }  function dunSoiNuoc() {  delay(4);  console.log(\u0026#39;Đun sôi nước.\u0026#39;); }  function luocRau() {  delay(5);  console.log(\u0026#39;Luộc rau.\u0026#39;); }  function votRau() {  delay(3);  console.log(\u0026#39;Vớt rau, để nguội.\u0026#39;); }   Nếu để các hàm chạy tuần tự thì tổng thời gian sẽ là xấp xỉ 15 giây. Mục tiêu là sửa lại sao cho tổng thời gian rút gọn lại chỉ còn khoảng 12 giây, mà trình tự chạy các hàm vẫn phải hợp lý (ví dụ sơ chế rau xong thì mới luộc).\nĐể rút gọn bớt thời gian chạy chương trình thì chúng ta sẽ phải thực hiện nhiều hàm cùng một lúc. Ở đây ta thấy có thể vừa \u0026ldquo;sơ chế rau\u0026rdquo; vừa \u0026ldquo;đun sôi nước\u0026rdquo; được, nên ta sẽ thực hiện 2 việc này cùng lúc để tiết kiệm thời gian.\n1 2 3 4 5 6 7 8 9 10  console.time(\u0026#39;run\u0026#39;);  Promise.all([soCheRau(), dunSoiNuoc()])  .then(() =\u0026gt; {  luocRau();  votRau();  })  .then(() =\u0026gt; {  console.timeEnd(\u0026#39;run\u0026#39;);  });   Đoạn code ở trên dùng hàm Promise.all() để chạy đồng thời 2 hàm soCheRau() và dunSoiNuoc(). Sau khi cả 2 hàm cùng chạy xong thì mới chạy tiếp 2 hàm luocRau() và votRau(). Bây giờ copy code trên ném vào Console của Browser để chạy thử, và kết quả là:\nVẫn tốn ~15 giây như cũ, chả tối ưu được tí nào, đúng là đời không như mơ 😂.\nVậy theo các bạn lỗi sai nằm ở đâu?\n  Thử suy nghĩ trong 5 phút trước khi đọc tiếp nhé!   Nếu chưa nghĩ ra thì các bạn có thể tra lại docs hoặc hỏi ChatGPT. Vấn đề ở đây là do các hàm ở trên của mình đều là các hàm sync, chứ không phải async nên sẽ không áp dụng Promise.all() được, vì Promise.all() cần truyền vào các Promise (kết quả của các hàm async). Vậy chúng ta cần phải viết lại các hàm trên thành các hàm async, ví dụ:\n1 2 3 4 5 6 7 8  function soCheRau() {  return new Promise((resolve) =\u0026gt; {  setTimeout(() =\u0026gt; {  console.log(\u0026#39;Sơ chế rau.\u0026#39;);  resolve();  }, 3000);  }); }   Các bạn cũng có thể viết lại hàm delay thành async để tái sử dụng cho dễ. Ví dụ hàm delay phiên bản async:\n1 2 3  function delay(x) {  return new Promise(resolve =\u0026gt; setTimeout(resolve, x * 1000)); }   Sau khi viết lại các hàm thành async thì cũng cần sửa lại code Promise.all() ở trên một chút:\n1 2 3 4 5 6 7 8 9 10 11 12  console.time(\u0026#39;run\u0026#39;);  Promise.all([soCheRau(), dunSoiNuoc()])  .then(() =\u0026gt; {  return luocRau();  })  .then(() =\u0026gt; {  return votRau();  })  .then(() =\u0026gt; {  console.timeEnd(\u0026#39;run\u0026#39;);  });   Ở đây mình dùng Promise Chaining để đảm bảo luocRau() và votRau() chạy tuần tự (nếu các bạn đã sửa nó thành async). Còn nếu 2 hàm luocRau() và votRau() vẫn là sync thì các bạn dùng luôn code cũ không cần Promise Chaining.\nChú ý khi dùng Promise Chaining thì mỗi một lần .then các bạn phải đảm bảo có return để trả về kết quả, nếu không rất dễ xảy ra lỗi. Mình đã từng fix nhiều bug do quên return dẫn đến sai logic code.\nXem lại kết quả sau khi tối ưu:\nOk vậy là kết quả đã giống như phần 1, nhưng code dễ đọc và dễ hiểu hơn.\nCác bạn cũng có thể viết theo kiểu Async/await thì code sẽ ngắn gọn hơn nữa:\n1 2 3 4 5 6 7  console.time(\u0026#39;run\u0026#39;);  await Promise.all([soCheRau(), dunSoiNuoc()]); await luocRau(); await votRau();  console.timeEnd(\u0026#39;run\u0026#39;);   Chú ý là để dùng await thì bạn phải gọi trong 1 hàm có khai báo keyword async, nên trong thực tế ta thường viết như sau:\n1 2 3 4 5 6 7 8 9 10 11  async function start() {  console.time(\u0026#39;run\u0026#39;);   await Promise.all([soCheRau(), dunSoiNuoc()]);  await luocRau();  await votRau();   console.timeEnd(\u0026#39;run\u0026#39;); }  start();   Vậy là xong ví dụ đơn giản về Promise, Async/await. Bây giờ các bạn thử làm tiếp bài tập ở phần trước nhưng dùng Promise, Async/await thay cho Callback.\n Viết lại chương trình mô tả quy trình luộc rau ở trên nhưng bước sơ chế rau sẽ tách ra thành vặt rau và rửa rau. Khi đó chúng ta cần cho vatRau() + ruaRau() + dunSoiNuoc() chạy cùng lúc, nhưng vẫn phải đảm bảo vatRau() chạy xong rồi mới đến ruaRau().\n Xử lý lỗi Trong quá trình chạy phần mềm kiểu gì cũng sẽ có lỗi. Chúng ta cần xử lý lỗi để không gây ảnh hưởng đến trải nghiệm người dùng.\nVẫn ví dụ trên, giả sử như đang rửa rau thì bị mất nước, chúng ta sẽ xử lý như nào?\n1 2 3 4 5  async function ruaRau() {  console.log(\u0026#39;Bắt đầu rửa rau.\u0026#39;);  await delay(2);  throw Error(\u0026#39;Mất nước!\u0026#39;); }   Nếu không có xử lý lỗi thì khi chạy đến hàm ruaRau() sẽ bị throw ra Error và có thể làm code logic phía sau không chạy tiếp gây ra lỗi chương trình.\nVậy chúng ta cần phải xử lý lỗi để nó không gây lỗi chương trình. Có thể là khi gặp lỗi thì thông báo lỗi cho user và dừng chương trình lại hoặc bỏ qua lỗi chạy tiếp code logic phía sau (rau bẩn tí vẫn ăn được 😂).\nĐể bắt lỗi thì nếu bạn dùng Promise chúng ta có .catch(), còn nếu dùng Async/await thì dùng try\u0026hellip;catch. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11  async function soCheRau() {  await vatRau();   try {  await ruaRau();  } catch (error) {  console.log(error);  }   console.log(\u0026#39;Sơ chế rau xong.\u0026#39;); }   Như code ở trên thì đã có try catch cho bước rửa rau nên nếu bước đó có lỗi thì chương trình vẫn chạy tiếp được. Chúng ta có thể return để code không chạy tiếp, hoặc trả về kết quả true/false để dùng ở các chỗ khác, \u0026hellip; Hoặc cẩn thận hơn thì có thể try catch toàn bộ cả 2 lệnh await và khi có error thì hiển thị 1 thông báo chung chung đại loại như \u0026ldquo;Có lỗi trong quá trình sơ chế rau!\u0026rdquo;.\nMột số bài học rút ra Trên thực tế thì còn nhiều trường hợp xử lý phức tạp hơn, lúc này chúng ta sẽ phải dùng đến các hàm hỗ trợ của Promise như .allSettled(), .any(), .race(), \u0026hellip; Tuy nhiên bài viết quá dài rồi nên mình sẽ chỉ tổng hợp lại một số bài học liên quan tới các ví dụ trên:\n Không phải cứ dùng keyword async thì hàm đó là async, nó chỉ là khai báo hàm và có thể dùng await bên trong hàm. Nếu một hàm trả về kết quả là một Promise thì hàm đó là async. Có thể await một Promise như một hàm async. Khi dùng Promise, Async/await thì phải chú ý các hàm đang thao tác là sync hay async. Dùng Promise Chaining nhớ đừng quên return. Luôn xử lý lỗi khi dùng Promise hoặc Async/await để đảm bảo chương trình không bị lỗi không mong muốn.  Nếu các bạn muốn tìm hiểu kỹ hơn thì có thể nghiên cứu thêm docs (các link trong bài) hoặc hỏi ChatGPT, \u0026hellip; hoặc comment xuống dưới bài viết để trao đổi thêm.\nHappy Coding 😁 .\n","permalink":"https://robinhuy.github.io/blog/xu-ly-bat-dong-bo-trong-javascript-phan-2/","summary":"Tiếp tục với Xử lý bất đồng bộ trong JavaScript Phần 1.\nPhần này mình sẽ chuyển qua demo với Promise, Async await, \u0026hellip; cho nó quen thuộc.\nLý thuyết Lý thuyết thì các bạn có thể tra cứu tại đây Promises, async/await.\nHoặc tiện hơn thì các bạn có thể hỏi ChatGPT:\nVí dụ Thay vì dùng các ví dụ như của ChatGPT, thì mình sẽ vẫn sử dụng các ví dụ như ở phần trước, chỉ thay Callback bằng Promise và Async/await.","title":"Xử lý bất đồng bộ trong JavaScript Phần 2"},{"content":"Lập trình bất đồng bộ (asynchronous programming) là một trong những vấn đề cơ bản trong JavaScript, nhưng không phải ai cũng hiểu cặn kẽ, đa số chỉ làm theo thói quen. Mình đã làm một số dự án maintain và thấy rất nhiều lỗi khi xử lý bất đồng bộ khiến cho chương trình bị chạy chậm hoặc bị sai logic. Lúc mới phát triển dự án thì sẽ chưa thấy ảnh hưởng gì nhưng sau khi dữ liệu đủ lớn thì sẽ gây giật, lag hoặc một số bug tiềm ẩn.\nMình viết bài viết này để chia sẻ lại những hiểu biết cũng như kinh nghiệm của mình trong việc xử lý bất đồng bộ, hy vọng sẽ giúp các bạn tránh được các lỗi kể trên.\nLý thuyết Trước tiên chúng ta sẽ ôn lại 1 chút lý thuyết cơ bản về bất đồng bộ trong JavaScript.\nThông thường khi viết ứng dụng, ta sẽ thực thi các hàm một cách tuần tự, từ trên xuống dưới như sau:\n1 2 3  func1(); func2(); func3();   Đây gọi là xử lý đồng bộ (synchronous, viết tắt là sync). Code kiểu này đơn giản và dễ đọc, nhưng trong nhiều trường hợp, nếu viết như vậy lại làm chương trình chạy chậm đi.\nChúng ta có thể viết cách khác để cho các hàm này chạy song song (parallel), không theo thứ tự từ trên xuống nữa và chạy luôn cùng lúc. Như vậy tốc độ chương trình sẽ nhanh hơn, các hàm sẽ không cần phải đợi nhau nữa (blocking). Đây được gọi là hàm chạy bất đồng bộ (asynchronous, viết tắt là async).\nLập trình bất đồng bộ sẽ khó hơn vì có thể khi đọc code thì thấy thứ tự gọi hàm là func1, func2, func3 nhưng vì các hàm này chạy cùng 1 lúc nên có thể func3 lại chạy xong trước làm ảnh hưởng đến luồng logic.\nTrong JavaScript sẽ có các hàm có sẵn là chạy sync hoặc async. Ví dụ các hàm xử lý chuỗi, số, \u0026hellip; là sync: toUpperCase(), substr(), \u0026hellip; Các hàm viết theo dạng callback thì là async: setTimeout(), fetch(), \u0026hellip;\nHoặc như trong NodeJS các bạn sẽ thấy cùng 1 tác vụ nhưng lại có đến 2 hàm như để ghi file chúng ta có fs.writeFile (async) và fs.writeFileSync (sync). Hàm async sẽ được khuyến khích hơn vì nó không làm chương trình bị block như sync. Ví dụ trường hợp thao tác đọc ghi file bị lỗi hoặc quá lâu thì chương trình sẽ bị block, phải chờ quá trình này hoàn tất mới thực hiện được các tác vụ phía sau.\nOk, lý thuyết chỉ tìm hiểu đến đây thôi, còn về lý thuyết sâu hơn như blocking, non blocking, event loop, \u0026hellip; các bạn hãy tự tìm hiểu thêm nhé. Vì lý thuyết nhiều quá thì sẽ dễ gây buồn ngủ nên chúng ta chuyển qua phần ví dụ thực hành luôn.\nVí dụ Dưới đây sẽ là một số hàm sẽ được sử dụng trong các ví dụ:\n  setTimeout: Là một hàm built in chạy bất đồng bộ, dùng để trì hoãn (delay) việc thực thi hàm sau 1 khoảng thời gian.\n  Hàm này thì là hàm tự chế để thay thế cho setTimeout ở trên nhưng là chạy theo kiểu đồng bộ, có tác dụng chờ x giây để giả lập thời gian thực thi của 1 hàm.\n  1 2 3 4  function delay(x) {  const start = new Date().getTime();  while (new Date().getTime() - start \u0026lt; x * 1000) {} }    console time(): Để tính thời gian chạy chương trình.  Ok, bây giờ sẽ đến phần ví dụ thực tế. Mình sẽ viết một chương trình mô tả quy trình luộc rau, vì chắc AI ai cũng biết luộc rau rồi 😂.\nTa sẽ có các hàm mô tả hành động như sau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  function soCheRau() {  delay(3);  console.log(\u0026#39;Sơ chế rau.\u0026#39;); }  function dunSoiNuoc() {  delay(4);  console.log(\u0026#39;Đun sôi nước.\u0026#39;); }  function luocRau() {  delay(5);  console.log(\u0026#39;Luộc rau.\u0026#39;); }  function votRau() {  delay(3);  console.log(\u0026#39;Vớt rau, để nguội.\u0026#39;); }   Mỗi hàm mình giả lập chạy mất vài giây (còn trong thực tế thì sẽ là 5-10 phút cho 1 công đoạn).\nThực thi chương trình:\n1 2 3 4 5 6  console.time(\u0026#39;Total time\u0026#39;); soCheRau(); dunSoiNuoc(); luocRau(); votRau(); console.timeEnd(\u0026#39;Total time\u0026#39;);   Vậy tổng thời gian sẽ là xấp xỉ 15 giây (3+4+5+3). Các bạn có thể copy các đoạn code ở trên chạy thử ở Console của Browser để thử nghiệm.\nTrong thực tế nếu bạn luộc rau theo cách trên thì khả năng cao là sẽ bị bố mẹ mắng vì không biết cách sắp xếp thời gian 😅.\nNhư vậy để tối ưu thời gian, mình sẽ chuyển qua cách làm đồng thời nhiều việc cùng 1 lúc. Viết lại chương trình trên theo cách asynchronous như sau (dùng setTimeout và hàm callback):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31  function soCheRau(callback) {  setTimeout(() =\u0026gt; {  console.log(\u0026#39;Sơ chế rau.\u0026#39;);   if (callback) callback();  }, 3000); }  function dunSoiNuoc(callback) {  setTimeout(() =\u0026gt; {  console.log(\u0026#39;Đun sôi nước.\u0026#39;);   if (callback) callback();  }, 4000); }  function luocRau(callback) {  setTimeout(() =\u0026gt; {  console.log(\u0026#39;Luộc rau.\u0026#39;);   if (callback) callback();  }, 5000); }  function votRau(callback) {  setTimeout(() =\u0026gt; {  console.log(\u0026#39;Vớt rau, để nguội.\u0026#39;);   if (callback) callback();  }, 3000); }   Các hàm trên được viết lại theo kiểu callback, tức là cho phép truyền vào tham số là 1 hàm, và sẽ gọi lại (callback) hàm đó sau khi thực hiện xong code logic nào đó. Ở trên mình đặt luôn tên hàm là callback, còn thực tế các bạn có thể đặt tùy theo ngữ cảnh.\nBây giờ các hàm này đã chạy bất đồng bộ, nên nếu chúng ta gọi như này:\n1 2 3 4 5 6  console.time(\u0026#39;run\u0026#39;); soCheRau(); dunSoiNuoc(); luocRau(); votRau(); console.timeEnd(\u0026#39;run\u0026#39;);   Kết quả sẽ ra như này: console.timeEnd còn chạy trước các console.log khác và các bước lung tung không theo đúng trình tự, vớt rau trước cả khi nước sôi 😂.\nĐó là vì các hàm này chạy đồng thời cùng lúc, không phụ thuộc lẫn nhau, dẫn đến kết quả không như ý muốn. Trong thực tế chúng ta có thể tối ưu quá trình luộc rau bằng cách vừa sơ chế rau vừa đun sôi nước, nhưng cũng phải chờ nước sôi thì mới luộc rau và luộc rau xong thì mới vớt rau.\nDo đó mình sẽ viết lại chương trình để gọi đồng thời 2 hàm soCheRau() và dunSoiNuoc() cùng lúc. Sau đó khi cả 2 hàm đã chạy xong thì lại gọi tuần tự các hàm luocRau() và votRau():\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  console.time(\u0026#39;run\u0026#39;);  // Thêm một biến đếm để kiểm tra số hàm callback đã thực hiện let count = 0;  // Hàm này để kiểm tra khi có 2 hàm callback đã thực hiện (soCheRau() và dunSoiNuoc()) function checkCallback() {  count++;   // Nếu đã có 2 hàm callback được gọi thì tiếp tục thực hiện các hàm tiếp theo  if (count === 2) {  // Gọi hàm luocRau() và truyền callback là hàm votRau() để khi luộc rau xong thì mới vớt rau  luocRau(() =\u0026gt; {  votRau(() =\u0026gt; {  // Vớt rau xong thì kết thúc chương trình  console.timeEnd(\u0026#39;run\u0026#39;);  });  });  } }  // Thực thi 2 hàm soCheRau và dunSoiNuoc cùng lúc, với callback là hàm checkCallBack ở trên soCheRau(checkCallback); dunSoiNuoc(checkCallback);   Kết quả:\nNhư vậy các hàm vẫn chạy đúng trình tự mong muốn và chúng ta đã rút ngắn tổng thời gian xuống còn khoảng 12 giây, tiết kiệm được 3 giây.\nTrong thực tế nếu số lượng các hàm lớn thì hiệu suất (performance) sẽ được cải thiện đáng kể so với cách gọi tuần tự. Ví dụ như trong một màn hình cần gọi rất nhiều API, nếu cứ viết theo thói quen dùng async await và cứ await lần lượt từng lệnh call API thì đó lại chính là lập trình theo kiểu đồng bộ làm trang web load rất lâu.\nLập trình bất đồng bộ cũng sẽ có nhược điểm là khó xử lý hơn, đặc biệt nếu chúng ta chỉ dùng callback như ví dụ ở trên, sẽ dẫn đến callback hell khiến code vừa khó đọc vừa khó bảo trì. Các bạn thử tăng độ khó của ví dụ trên lên sẽ thấy code khó hơn và callback hell rõ ràng hơn:\n Viết lại chương trình mô tả quy trình luộc rau ở trên nhưng bước sơ chế rau sẽ tách ra thành vặt rau và rửa rau. Khi đó chúng ta cần cho vatRau() + ruaRau() + dunSoiNuoc() chạy cùng lúc, nhưng vẫn phải đảm bảo vatRau() chạy xong rồi mới đến ruaRau().\n   Callback Hell   Giờ đến lúc các bạn nên tự thực hành để hiểu rõ hơn về lập trình bất đồng bộ với callback trong JavaScript. Phần tiếp theo mình sẽ hướng dẫn tiếp về lập trình bất đồng bộ với Promise, async await và các cách \u0026ldquo;bắt lỗi\u0026rdquo; (handling error) để chương trình chạy chuẩn hơn.\nSee you again!\nTiếp theo: Xử lý bất đồng bộ trong JavaScript Phần 2.\n","permalink":"https://robinhuy.github.io/blog/xu-ly-bat-dong-bo-trong-javascript-phan-1/","summary":"Lập trình bất đồng bộ (asynchronous programming) là một trong những vấn đề cơ bản trong JavaScript, nhưng không phải ai cũng hiểu cặn kẽ, đa số chỉ làm theo thói quen. Mình đã làm một số dự án maintain và thấy rất nhiều lỗi khi xử lý bất đồng bộ khiến cho chương trình bị chạy chậm hoặc bị sai logic. Lúc mới phát triển dự án thì sẽ chưa thấy ảnh hưởng gì nhưng sau khi dữ liệu đủ lớn thì sẽ gây giật, lag hoặc một số bug tiềm ẩn.","title":"Xử lý bất đồng bộ trong JavaScript Phần 1"},{"content":"Để đúng với tinh thần là một blog cá nhân, không phải blog chuyên về công nghệ (lập trình), hôm nay mình sẽ chia sẻ với các bạn một số kinh nghiệm cá nhân khi đi du lịch tự túc tại Huế (mình thích đi tự túc để chủ động hơn và không bị chạy xô quá).\nDi chuyển Đến Huế các bạn có thể đi máy bay, xe khách hoặc tàu hỏa. Mình chọn đi tàu hỏa để trải nghiệm, xuất phát từ Hà Nội đi ban đêm thì trưa hôm sau đến nơi, buổi sáng có thể ngồi trên tàu ngắm cảnh. Nếu đi tàu thì nên mua vé giường nằm khoang 4, bạn sẽ bao cả 1 buồng 4 giường, riêng tư và thoải mái hơn ghế mềm, trong buồng còn có 1 bàn để bày đồ đạc. Còn giường nằm khoang 6 (rẻ hơn) thì mình thấy hơi chật, giường tầng 3 thì cao, không phù hợp với những người sợ độ cao và không gian hẹp.   Giường nằm khoang 4   Vé tàu thì mình đặt qua app Momo, trên app sẽ có tìm kiếm chuyến tàu phù hợp, chọn chỗ và thanh toán online. Nếu đi khoang giường nằm thì các bạn nên đặt vé sớm nếu không các vé giường nằm sẽ hay hết sớm hơn hoặc là bị đặt hết giường tầng 1 dẫn đến nếu đi theo team thì các bạn sẽ không được ở chung buồng. Chọn tàu cũng nên xem thời gian vì sẽ có tàu đi nhanh có tàu đi chậm, tàu đi nhanh thì thời gian gần xấp xỉ với đi xe khách.   Đặt vé tàu hỏa trên Momo   Di chuyển các nơi ở Huế thì các bạn có thể đi grab, taxi (có thể nhờ lễ tân khách sạn gọi giúp), xích lô, hoặc thuê xe máy tự đi.\nNơi ở Tiếp đến là tìm nơi lưu trú. Mình tìm khách sạn qua booking.com, chọn khách sạn 3 sao thôi vì mình cũng không cần quá tiện nghi xịn sò, chỉ cần có chỗ để đồ, chỗ ngủ nghỉ sạch sẽ và có điều hòa. Cái này tùy ngân sách và các địa điểm mà các bạn định đi mà chọn cho mình một khách sạn phù hợp. Đợt đi Huế này mình chọn khách sạn Jade Hotel (ở đường Hùng Vương, ngay trung tâm) và khá là ưng bởi chi phí rẻ, nhân viên rất nhiệt tình dễ mến, sẵn sàng hỗ trợ và giải đáp mọi thắc mắc của bạn. Khách sạn nằm ở trung tâm rất tiện đi lại, nhiều chỗ có thể đi bộ được như: Chè Hẻm, Phố đi bộ, cầu Trường Tiền, \u0026hellip; Nhược điểm là khách sạn không có thang máy, bù lại nếu bạn có hành lý gì thì nhân viên sẽ giúp bạn mang lên/xuống phòng. Nếu trong đoàn có người lớn tuổi thì có thể liên hệ trước với khách sạn để được ưu tiên ở phòng tầng thấp.   Khách sạn Jade Hotel Huế   Địa điểm vui chơi, tham quan Một số địa điểm vui chơi tham quan các bạn có thể tham khảo:\n Hoàng thành Huế (Đại Nội). Cái này hầu như ai đi Huế lần đầu cũng nên đi, giá vé 200k/ng. Các bạn nên đi vào sáng sớm vì khu này rất rộng, nếu đi thong thả chụp ảnh tự sướng các kiểu có khi mất cả buổi sáng, hơn nữa buổi trưa ở Huế thì trời nắng nóng dã man luôn 😅. Lăng vua (lăng Gia Long, Khải Định, Minh Mạng, \u0026hellip;), giá vé khá đắt (150k/ng, diện tích nhỏ hơn nhiều so với Đại Nội), nên các bạn có thể mua combo (nếu đi cùng ngày) hoặc chọn 1-2 lăng nào đó để đi thôi (có thể xem review trước trên youtube). Cầu Trường Tiền, chợ Đông Ba (khu chợ ngoài trời, có mái che, nên tránh đi buổi trưa nắng nóng). Chùa Thiên Mụ. Chỗ này nên đi, vừa free vừa đẹp và cũng khá rộng. Làng Hương. Chỗ này hầu như không có gì, đến đây chụp ảnh tự sướng là chính. Ở đây không phải mua vé nhưng chụp ảnh ở quán nào sẽ trả chút phí cho chủ quán (~50k, hoặc thuê quần áo ở đây để chụp ảnh thì không phải trả phí). Các bạn cũng có thể thuê quần áo cổ trang ở ngoài mặc cả ngày và đi hết 1 lượt các địa điểm tham quan luôn sẽ rẻ hơn. Ở Đại Nội cũng có cho thuê quần áo cả ngày nhưng mình thấy đắt hơn hẳn. Đồi Vọng Cảnh. Cái này cũng free, ở ngay gần làng Hương nên nếu đi thì đi cả 2 luôn. Nếu lên trên này vào lúc hoàng hôn hoặc bình mình thì sẽ rất đẹp, tha hồ chụp ảnh sống ảo. Phố đi bộ Nguyễn Đình Chiểu và phố đi bộ tây. Nên đi vào buổi tối, phố đi bộ dọc theo bờ sông mát mẻ và có thể có các tiết mục hát hò như phố đi bộ Hồ Gươm, còn phố đi bộ tây thì na ná như Tạ Hiện.   Hoàng thành Huế     Nếu đi dài ngày, bạn có thể ra biển chơi. Theo mình tìm hiểu từ trước thì có biển Thuận An hoặc vịnh Lăng Cô. Vịnh Lăng Cô nghe đồn đẹp hơn, nước trong hơn, \u0026hellip; nhưng đi xa hơn, vì thế nên mình chọn đi biển Thuận An. Biển Thuận An cách trung tâm thành phố khoảng 15km, nên đi taxi chỉ mất khoảng 20-30ph. Các bạn có thể đi vào buổi chiều, đến chụp ảnh, tắm biển, ăn uống rồi tối về lại khách sạn..   Biển Thuận An - Huế   Điểm đặc biệt ở đây đó là khi đến nơi, taxi dừng lại ngay trước cửa 1 nhà hàng, thấy mọi người đã đang ăn uống ở đó khá đông (mới khoảng tầm 3-4 giờ chiều). Trong khi mình còn chưa kịp định thần thì nhân viên quán đã chạy ra xếp bàn, giúp bê đồ, mang nước, xô đá các thứ ra như kiểu chuẩn bị dọn bàn ăn sẵn luôn vậy. Thôi chết quả này bị lừa vào nhà hàng này chắc bị chém ngọt rồi 😂, phải gọi bác taxi và nhân viên ra hỏi lại xem thế nào. Hóa ra nếu các bạn ăn ở đây thì có thể gửi đồ và tắm tráng ở đây luôn (không mất phí). Gửi đồ đi tắm biển chụp ảnh các kiểu xong rồi quay lại tắm tráng và ăn ở đây luôn. Hải sản còn sống ở bể, có niêm yết giá, các bạn chọn và cân rồi họ mang đi chế biến. Đồ ăn ở đây mình thấy cũng rẻ nhưng không quá xuất sắc (tùy khẩu vị mỗi người và trù nghệ của đầu bếp). Sau khi ăn uống xong thì lại lên xe về khách sạn (bác taxi chở đi sẽ chờ ở đó luôn, cũng không thu thêm phí).\nĂn uống Về đồ ăn thì đồ ăn ở đây ngon (tùy khẩu vị từng người) và rất rẻ, các bạn có thể hỏi lễ tân khách sạn hoặc các bác tài xế (nếu đi taxi) các chỗ ăn uống bình dân, ví dụ như mình ăn cơm hến 10k/bát, trà tắc 15k/cốc (cốc to bằng cái xô), \u0026hellip; Còn nếu không thì cứ lên mạng search các địa điểm nổi tiếng để đi (nhưng các chỗ nổi tiếng cho khách du lịch thì có thể đắt hơn mà chưa chắc đã đúng chuẩn Huế).\nMột số địa điểm các bạn có thể thử (mình toàn đi những quán bình dân vô danh nên không gợi ý được nhiều):\n Bánh mì Trường Tiền O Tho, thích hợp để ăn sáng, sau đó đi bộ cầu Trường Tiền và chợ Đông Ba là chuẩn bài. Bánh Khoái Hạnh. Thực ra mình cũng chưa ăn thử ở đây, vì quán đông quá và hết hàng sớm nên chắc là cũng ngon 😄. Chè Hẻm. Cái này ở ngay sát khách sạn nên mình có thể đi bộ qua được, có rất nhiều loại chè ngon và rẻ, nhưng buổi tối thì quá đông người nên ngồi khá chật và nóng (có thể mua mang về cũng được). Chè Mợ Tôn Đích, cũng dành cho các tín đồ của chè, có rất nhiều loại chè cho các bạn lựa chọn, đặc biệt có món chè heo quay với nhân là \u0026hellip; thịt heo quay 😳. \u0026hellip;   Bài viết mình sẽ chỉ dùng ảnh minh họa thôi, còn lại để các bạn tự trải nghiệm. Chúc các bạn có những chuyến đi vui vẻ!\n","permalink":"https://robinhuy.github.io/blog/kinh-nghiem-du-lich-tu-tuc-hue-2023/","summary":"Để đúng với tinh thần là một blog cá nhân, không phải blog chuyên về công nghệ (lập trình), hôm nay mình sẽ chia sẻ với các bạn một số kinh nghiệm cá nhân khi đi du lịch tự túc tại Huế (mình thích đi tự túc để chủ động hơn và không bị chạy xô quá).\nDi chuyển Đến Huế các bạn có thể đi máy bay, xe khách hoặc tàu hỏa.","title":"Kinh nghiệm du lịch tự túc Huế 2023"},{"content":"Hiện có rất nhiều website và ứng dụng di động cho phép những người thường (không phải designer hay họa sĩ) cũng có thể vẽ ra những bức tranh đẹp, sử dụng được cho mục đích cá nhân (và cả thương mại). Để vẽ tranh lúc này chỉ cần có ý tưởng và biết Tiếng Anh để mô tả chính xác ý tưởng của mình.\nTham khảo bài viết này để nâng trình Tiếng Anh của mình nhé.\nDưới đây mình sẽ giới thiệu 1 số website mà có cho phép người dùng sử dụng miễn phí theo thứ tự từ dễ dùng đến khó dùng (với ứng dụng di động thì các bạn chờ bài viết khác kẻo bài này dài quá).\n1. DeepAI Trang web này cho phép bạn tạo ảnh mà không cần tài khoản luôn, nhưng sẽ có hạn chế hơn so với người dùng trả phí.\nCác bạn chỉ cần truy cập website https://deepai.org/machine-learning-model/text2img, nhập mô tả cho ảnh bạn muốn tạo ở mục Create an image from text prompt, chọn một phong cách bên dưới (trừ những cái bị khóa là dành cho member trả phí), sau đó bấm Generate.\nNgồi chờ khoảng 1 phút, AI sẽ tạo ngẫu nhiên cho bạn 1 bức ảnh.\nNếu vừa ý, bạn có thể bấm vào Download để tải ảnh về xài, hoặc bấm vào Enhance để phóng to ảnh lên. Nếu không thì nhập lại mô tả và Generate lại cho đến khi vừa ý.\nNgoài các chức năng free, bạn có thể đăng nhập và đăng ký các gói PRO để có nhiều chức năng hơn như: Private style, Private image generation, API access (cho developer), \u0026hellip;\n2. Stable Diffusion Cách dùng tương tự DeepAI, tuy nhiên không có chọn style và mỗi lần tạo ảnh sẽ tạo ra 1 bộ 4 ảnh.\nNgoài ra, bạn có thể dùng trang này miễn phí và không cần đăng nhập.\n3. DALL-E Một sản phẩm khác của OpenAI, dùng chung tài khoản với ChatGPT, nên nếu bạn chưa có tài khoản thì có thể tham khảo bài viết này để đăng ký.\nSau khi đăng ký xong, đăng nhập vào trang web https://openai.com/dall-e-2 để tạo ảnh.\nCách tạo ảnh cũng tương tự 2 website trên, nhập mô tả rồi bấm Generate và chờ AI tạo ảnh. Kết quả mỗi lần tạo là 1 bộ 4 ảnh tương tự nhau, sau đó chúng ta có thể chọn 1 ảnh trong đó và tiếp tục tạo ảnh dựa trên ảnh gốc đã chọn (tạo ra các biến thể khác).\nChú ý đây là dịch vụ có trả phí, tuy nhiên lần đầu đăng ký chúng ta sẽ có 50 credit để sử dụng trong vòng 1 tháng (tương đương với 50 lần tạo ảnh hoặc chỉnh sửa ảnh). Sau đó mỗi tháng chúng ta sẽ có thêm 15 credit hạn sử dụng 1 tháng (hết hạn tự biến mất). Nếu bạn muốn tạo nhiều ảnh hơn thì có thể nạp tiền vào tài khoản để mua thêm credit (hạn 12 tháng).\n4. Midjourney Trùm cuối hy vọng sẽ không làm các bạn thất vọng. Đây là một trong những AI tạo ra image rất đẹp, các bạn có thể xem những ảnh được cộng đồng tạo ra mới nhất ở đây: Midjourney Community Showcase\nĐể sử dụng AI này các bạn cần đăng ký tài khoản Discord trước, sau đó join vào server của Midjourney để tạo ảnh bằng cách tương tác với bot Midjourney trên Discord. Chú ý với tài khoản mới thì chúng ta sẽ có miễn phí 25 lần request, sau đó sẽ phải nạp tiền vào để sử dụng tiếp (hoặc bạn có thể tạo tài khoản mới để có thêm 25 lần miễn phí).\n  Trang chủ của midjourney   Truy cập trang chủ của midjourney chọn Join the Beta hoặc vào thẳng Midjourney Discord server\nSau khi join được vào Midjourney Discord server, tìm đến các channel dành cho Newbie. Chúng ta sẽ tạo ảnh ở đây, và cũng có thể xem ảnh do người khác tạo để tham khảo các ý tưởng (keyword) và sản phẩm của họ.\n  Newbies channel   Ở phần khung chat chúng ta sẽ gõ lệnh để tạo ảnh. Gõ /imagine prompt để bắt đầu câu lệnh (hoặc chọn từ gợi ý câu lệnh khi đang gõ). Trong khung prompt, nhập mô tả cho ảnh mà bạn muốn tạo, sau đó bấm enter và chờ AI tạo ảnh.\n  Lệnh tạo ảnh /imagine prompt   Sẽ mất khoảng 1-2 phút, sau đó chúng ta sẽ có kết quả là 1 bộ 4 ảnh có kích thước 512x512 pixels.\n  Ảnh do AI tạo ra   Từ 4 ảnh trên chúng ta có thể tiếp tục \u0026ldquo;nâng cấp\u0026rdquo; nó lên bằng các lựa chọn ngay bên dưới, bao gồm:\n Upscale (U1, U2, U3, U4 tương ứng với 4 ảnh): Tăng kích thước cho ảnh và bổ sung thêm một số chi tiết cho ảnh. Create Variantions (V1, V2, V3, V4 tương ứng với 4 ảnh): Tạo biến thể cho ảnh được chọn trong 4 ảnh, ảnh được tạo ra sẽ tương tự với ảnh đã chọn nhưng được vẽ khác đi 1 chút. Từ 1 ảnh được chọn AI sẽ tạo ra cho chúng ta 4 ảnh khác tương tự, và chúng ta có thể tiếp tục chọn 1 ảnh trong đó để tạo thêm các biến thể khác cho đến khi vừa ý. Reroll (biểu tượng refresh ở bên phải): Chạy lại lệnh tạo ảnh ban đầu để tạo ra 4 ảnh mới hoàn toàn.  Chú ý mỗi thao tác upscale, create variantions, reroll đều được tính là 1 request, và với tài khoản mới tạo chỉ có miễn phí 25 request nên hãy dùng tiết kiệm trước khi bỏ tiền mua subscription 😂\n  Nâng cấp ảnh   Sau khi tạo được ảnh ưng ý và upscale nó lên, chúng ta sẽ có 1 số lựa chọn sau:\n Make Variations: Tiếp tục tạo variantions từ ảnh đã upscale. Beta/Light Upscale Redo: Thực hiện lại lệnh upscale nhưng dùng một upscalers modal khác (thêm ít hoặc nhiều chỉnh sửa hoặc không chỉnh sửa chỉ tăng kích thước, \u0026hellip;). Xem thêm mô tả chi tiết tại đây. Web: Mở ảnh trên website midjourney.com, trong mục Gallery ở tài khoản của bạn (nơi xem lại toàn bộ những ảnh mình đã tạo). Thả tương tác (mặt cười) để đánh giá ảnh vừa tạo. Mỗi ngày, top 1000 người đánh giá (đánh giá ảnh của mình và của người khác) sẽ nhận được 1 giờ sử dụng miễn phí.    Sản phẩm cuối cùng   Tham khảo thêm tài liệu hướng dẫn bằng Tiếng Anh:\n Midjourney Discord Midjourney quick start  ","permalink":"https://robinhuy.github.io/blog/mot-so-trang-web-cho-phep-tao-anh-bang-cach-go-text/","summary":"Hiện có rất nhiều website và ứng dụng di động cho phép những người thường (không phải designer hay họa sĩ) cũng có thể vẽ ra những bức tranh đẹp, sử dụng được cho mục đích cá nhân (và cả thương mại). Để vẽ tranh lúc này chỉ cần có ý tưởng và biết Tiếng Anh để mô tả chính xác ý tưởng của mình.\nTham khảo bài viết này để nâng trình Tiếng Anh của mình nhé.","title":"Một số trang web cho phép tạo ảnh bằng cách gõ text"},{"content":"Trong Vue 3 có hai Reactivity API mà dễ làm newbie gây nhầm lẫn khi sử dụng đó là ref và reactive. Bài viết này mình sẽ hướng dẫn cách sử dụng 2 API trên, kèm một số so sánh với Vue 2 cho những ai mới chuyển từ Vue 2 lên Vue 3.\nRef Ví dụ đơn giản khi thay đổi một reactive state bằng Vue 2:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  \u0026lt;script\u0026gt; export default {  data() {  return {  count: 0,  };  },   methods: {  increaseCount() {  this.count++;  },  }, }; \u0026lt;/script\u0026gt;  \u0026lt;template\u0026gt;  \u0026lt;h1\u0026gt;Count: {{ count }}\u0026lt;/h1\u0026gt;  \u0026lt;button @click=\u0026#34;increaseCount\u0026#34;\u0026gt;Increase Count\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt;   Chức năng tương tự nhưng sử dụng ref() trong Vue 3:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  \u0026lt;script setup\u0026gt; import {ref} from \u0026#34;vue\u0026#34;;  // Tạo 1 reactive state count = 0 (count ở đây là một Proxy object chứ không phải number) const count = ref(0);  const increaseCount = () =\u0026gt; {  // Tăng giá trị của count bằng cách cập nhật giá trị thuộc tính value  count.value++; }; \u0026lt;/script\u0026gt;  \u0026lt;template\u0026gt;  \u0026lt;h1\u0026gt;Count: {{ count }}\u0026lt;/h1\u0026gt;  \u0026lt;button @click=\u0026#34;increaseCount\u0026#34;\u0026gt;Increase Count\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt;   Để hiểu rõ hơn về nguyên lý hoạt động của ref(), các bạn nên tìm hiểu thêm về Proxy trong Javascript.\nMột số chú ý về ref():\n  Chúng ta có thể lưu dữ liệu gì vào ref object cũng được.\n  Ref object là mutable, khi cần thay đổi giá trị thì có thể thay đổi trực tiếp thuộc tính value của nó. Tuy nhiên khi dùng ref object ở template thì chúng ta không cần .value vì nó được tự động unwrap.\n  Reactive Trong đa số trường hợp, chúng ta chỉ cần dùng ref() là đủ. Vậy dùng reactive() để làm gì?\nreactive() hoạt động tương tự ref() nhưng nó chỉ nhận tham số là object, không nhận các kiểu dữ liệu primitives (number, string, boolean). Và chúng ta thay đổi giá trị của reactive object bằng cách thay đổi các thuộc tính của nó (thay vì thay đổi thuộc tính value như ref). Ví dụ ở trên viết lại bằng reactive():\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  \u0026lt;script setup\u0026gt; import {reactive} from \u0026#34;vue\u0026#34;;  // Tạo 1 reactive state có thuộc tính count = 0 const state = reactive({ count: 0 });  const increaseCount = () =\u0026gt; {  // Tăng giá trị của thuộc tính count  state.count++; }; \u0026lt;/script\u0026gt;  \u0026lt;template\u0026gt;  \u0026lt;h1\u0026gt;Count: {{ state.count }}\u0026lt;/h1\u0026gt;  \u0026lt;button @click=\u0026#34;increaseCount\u0026#34;\u0026gt;Increase Count\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt;   Về bản chất ref() là một hàm wrap lại reactive (bên trong ref() sử dụng reactive()), nên trong đa số trường hợp chúng ta có thể sử dụng hầu hết ref() cho đồng bộ và đỡ phải nhớ nhiều, chỉ cần chú ý khi thay đổi giá trị của ref object phải thông qua thuộc tính value. Bạn cũng có thể dùng reactive khi muốn tạo 1 state tập trung để đỡ phải tạo nhiều biến, ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  // Dùng ref() const isLoading = ref(false); const isError = ref(false); const user = ref({  name: \u0026#34;Robin\u0026#34;,  role: \u0026#34;Admin\u0026#34;, });  // Dùng reactive() const state = reactive({  isLoading: false,  isError: false,  user: {  name: \u0026#34;Robin\u0026#34;,  role: \u0026#34;Admin\u0026#34;,  }, });   Chú ý khi dùng reactive chúng ta chỉ được truyền vào một object và khi update thì sẽ update các thuộc tính của object đó, chứ không dùng phép gán trực tiếp vào reactive object. Ví dụ như sau là sai:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  \u0026lt;script setup\u0026gt; import {reactive} from \u0026#34;vue\u0026#34;;  // Tạo 1 reactive object user const user = reactive({ name: \u0026#34;Robin\u0026#34;, role: \u0026#34;Admin\u0026#34; });  const updateUser = () =\u0026gt; {  // Ví dụ dữ liệu mới lấy từ form, api, ... sau đó update trực tiếp bằng phép gán   // Code sai  user = { name: \u0026#34;Huy\u0026#34;, role: \u0026#34;Staff\u0026#34; }; }; \u0026lt;/script\u0026gt;  \u0026lt;template\u0026gt;  \u0026lt;h1\u0026gt;User name: {{ user.name }}\u0026lt;/h1\u0026gt;  \u0026lt;h1\u0026gt;User role: {{ user.role }}\u0026lt;/h1\u0026gt;   \u0026lt;button @click=\u0026#34;updateUser\u0026#34;\u0026gt;Update\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt;   Ở dòng 20 ví dụ trên là code sai do chúng ta gán giá trị cho biến user thành một object mới. Nếu ở lúc khai báo dùng const thì sẽ báo lỗi luôn, còn nếu dùng let thì code đúng cú pháp nhưng khi bấm nút thì giao diện không update do biến user không còn là reactive object nữa, chỉ là một object bình thường. Có thể sửa lại bằng cách cập nhật từng thuộc tính một:\n1 2  user.name = \u0026#34;Huy\u0026#34;; user.role = \u0026#34;Staff\u0026#34;;   Hoặc sử dụng ref():\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  \u0026lt;script setup\u0026gt; import {ref} from \u0026#34;vue\u0026#34;;  // Tạo 1 ref object user const user = ref({ name: \u0026#34;Robin\u0026#34;, role: \u0026#34;Admin\u0026#34; });  const updateUser = () =\u0026gt; {  // Cập nhật ref object qua thuộc tính value  user.value = { name: \u0026#34;Huy\u0026#34;, role: \u0026#34;Staff\u0026#34; }; }; \u0026lt;/script\u0026gt;  \u0026lt;template\u0026gt;  \u0026lt;h1\u0026gt;User name: {{ user.name }}\u0026lt;/h1\u0026gt;  \u0026lt;h1\u0026gt;User role: {{ user.role }}\u0026lt;/h1\u0026gt;   \u0026lt;button @click=\u0026#34;updateUser\u0026#34;\u0026gt;Update\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt;   ","permalink":"https://robinhuy.github.io/blog/ref-va-reactive-trong-vue-3/","summary":"Trong Vue 3 có hai Reactivity API mà dễ làm newbie gây nhầm lẫn khi sử dụng đó là ref và reactive. Bài viết này mình sẽ hướng dẫn cách sử dụng 2 API trên, kèm một số so sánh với Vue 2 cho những ai mới chuyển từ Vue 2 lên Vue 3.\nRef Ví dụ đơn giản khi thay đổi một reactive state bằng Vue 2:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  \u0026lt;script\u0026gt; export default {  data() {  return {  count: 0,  };  },   methods: {  increaseCount() {  this.","title":"Ref và Reactive trong Vue 3"},{"content":"Dạo này ChatGPT đang rất hot, được thần thành hóa lên quá khiến nhiều người lo sợ nó sẽ \u0026ldquo;cướp\u0026rdquo; mất công việc của mình. Vậy ChatGPT cụ thể là gì, dùng như nào?\n  ChatGPT thập niên 90 😂   Bài viết này mình sẽ hướng dẫn các bạn tự tạo tài khoản cho riêng mình và trải nghiệm thử ChatGPT, một công cụ khá hữu ích nếu bạn sử dụng đúng cách. Trên mạng có chia sẻ một số tài khoản miễn phí nhưng sẽ hay bị lỗi do có nhiều người truy cập. Các bạn nên tự tạo tài khoản email chính chủ, vì tài khoản này còn dùng được nhiều dịch vụ khác của OpenAI (mình sẽ hướng dẫn thêm ở các bài viết khác).\nTạo tài khoản ChatGPT Tóm tắt các bước:\n Đăng ký tài khoản ChatGPT (dùng VPN). Thuê 1 số điện thoại nước ngoài để nhận SMS OTP. Nhập OTP để xác thực số điện thoại. Đăng nhập và sử dụng: https://chat.openai.com.  Hiện tại ChatGPT không cho phép tạo tài khoản ở Việt Nam, do đó để tạo được tài khoản thì chúng ta sẽ cần đổi địa chỉ IP (chỉ cần đổi khi tạo tài khoản). Các bạn có thể dùng các extension VPN free, ví dụ trên Chrome thì mình dùng Extension VeePN. Đơn giản chỉ việc cài extension lên và chọn location, sau đó bật lên để đổi địa chỉ IP sang location vừa chọn.\n  Ví dụ đổi location sang Netherlands   Sau khi bật VPN thì chúng ta sẽ truy cập vào https://platform.openai.com để tạo tài khoản (dùng email của bạn để tạo tài khoản chứ không dùng chức năng login với Google nhé).\nTạo tài khoản xong, bạn vào email để lấy link kích hoạt. Bấm vào link kích hoạt ở email sẽ hiện ra trang nhập tên, nhập tên xong thì cần xác thực số điện thoại. Đến bước này chúng ta sẽ cần một số điện thoại nước ngoài để nhận OTP. Có nhiều dịch vụ cho thuê số điện thoại hoặc nhận SMS online. Ở đây mình dùng dịch vụ của https://sms-activate.org vì giá rẻ (chỉ khoảng 3k VNĐ 1 số điện thoại) và dễ sử dụng (nếu đọc bài viết này 😂).\nCác bạn đăng ký tài khoản trên https://sms-activate.org, sau đó vào nạp tiền (nạp mức tối thiểu là được). Trang này hỗ trợ khá nhiều phương thức thanh toán (có cả crypto), mình thì sử dụng thẻ Visa và thanh toán qua cổng Stripe.\n  Chọn Top up balance để nạp tiền vào tài khoản     Chọn phương thức thanh toán phù hợp để nạp tiền   Sau khi nạp tiền xong, chúng ta sẽ thuê một số điện thoại để nhận OTP. Các bạn chỉ cần nạp một số tiền bằng mức tối thiểu của phương thức thanh toán mà mình chọn là đủ, hoặc nạp hẳn 1$ đăng ký tài khoản cho bạn bè luôn. Chọn dịch vụ OpenAI bên trái và chọn một quốc gia để thuê số điện thoại. Chú ý quốc gia nào có 0 pcs tức là hết số điện thoại, còn số bên cạnh là giá tiền, trung bình vào khoảng 10 Rub 1 số điện thoại (khoảng 3000 VNĐ, bằng giá tiền 1 cốc trà đá 😁).\n  Chọn dịch vụ OpenAI     Thuê số điện thoại bằng cách bấm vào giỏ hàng bên cạnh quốc gia đó   Sau khi thuê xong 1 số điện thoại, chúng ta sẽ có 20 phút để nhận OTP. Nếu trong thời gian này chờ lâu mà không nhận được OTP thì có thể cancel (chữ X ở góc phải) để thuê số khác (không mất tiền).\n  Copy số điện thoại vừa thuê để xác thực tài khoản ChatGPT   Quay lại trang đăng ký tài khoản ChatGPT và nhập số điện thoại đã thuê để lấy OTP.\n  Nhập số điện thoại đã thuê để nhận OTP (chú ý bỏ mã quốc gia ở đầu)   Chờ OTP gửi về thì copy và paste vào phần Enter code là hoàn tất việc đăng ký.\n  Nhập mã OTP nhận được qua số điện thoại để xác thực   Đăng ký xong, phần mục đích sử dụng các bạn chọn I'm exploring personal use hoặc truy cập link này để sử dụng https://chat.openai.com (lúc này có thể tắt VPN đi cho mạng đỡ chậm).\nSử dụng ChatGPT Sử dụng ChatGPT rất đơn giản, bạn chỉ cần nhập câu hỏi hoặc yêu cầu vào khung chat và chờ bot phản hồi. Có thể sử dụng Tiếng Anh hoặc Tiếng Việt đều được, nhưng Tiếng Anh sẽ ra kết quả chính xác hơn.\nNgoài ra bạn có thể lưu lại các đoạn chat này theo chủ đề, đặt tên cho nó để sau này xem lại (giống như lưu tài liệu vậy).\nChatGPT cũng có thể sử dụng như Google Search, khi search trên Google thì kết quả trả về sẽ mới hơn nhưng cũng nhiều hơn, dùng ChatGPT sẽ ra kết quả sau khi được chọn lọc nên trong một số trường hợp sẽ nhanh hơn search Google.\nMột số extension giúp bạn sử dụng ChatGPT tiện hơn:\n ChatGPT for Google: Tích hợp ChatGPT vào Google search, khi search ngoài hiển thị kết quả từ Google còn hiển thị cả kết quả từ ChatGPT ở bên cạnh. Promptheus - Converse with ChatGPT: Thêm tính năng sử dụng giọng nói để chat thay vì gõ text. ChatGPT PDF: Export lịch sử ChatGPT thành file PNG, PDF hoặc tạo link chia sẻ (extension này cài bằng source code vì chưa có trên store).  ","permalink":"https://robinhuy.github.io/blog/huong-dan-tao-tai-khoan-va-su-dung-chatgpt-chi-voi-1-coc-tra-da/","summary":"Dạo này ChatGPT đang rất hot, được thần thành hóa lên quá khiến nhiều người lo sợ nó sẽ \u0026ldquo;cướp\u0026rdquo; mất công việc của mình. Vậy ChatGPT cụ thể là gì, dùng như nào?\n  ChatGPT thập niên 90 😂   Bài viết này mình sẽ hướng dẫn các bạn tự tạo tài khoản cho riêng mình và trải nghiệm thử ChatGPT, một công cụ khá hữu ích nếu bạn sử dụng đúng cách.","title":"Hướng dẫn tạo tài khoản và sử dụng ChatGPT chỉ với 1 cốc trà đá"},{"content":"Nếu bạn có điều kiện tài chính thì nên kiếm nguồn tài liệu hoặc khóa học nào mất phí nhưng chất lượng để có thể thông thạo Tiếng Anh càng nhanh càng tốt. Giỏi Tiếng Anh sẽ mang lại cho bạn rất nhiều lợi ích so với chi phí bỏ ra.\nOk, trước khi đọc tiếp, bạn thử bỏ ra 5 phút suy nghĩ để trả lời câu hỏi sau:\n Cuộc đời bạn sẽ thay đổi như nào nếu bạn thành thạo Tiếng Anh?\n (câu trên mình mượn từ https://speakenglishwithvanessa.com, một nguồn học Tiếng Anh có vẻ tốt, nhưng mình chưa có thời gian học thử).\nTrả lời được câu hỏi trên tức là bạn đã tìm được mục tiêu để học Tiếng Anh, mục tiêu rõ ràng và đủ lớn thì mới có động lực để học, vì học Tiếng Anh quan trọng nhất là \u0026ldquo;chăm\u0026rdquo;.\nQuay lại với tiêu đề bài viết \u0026ldquo;Chia sẻ cách học Tiếng Anh cho người nghèo\u0026rdquo;, người nghèo ở đây là những người hiện tại không có đủ tài chính để đăng ký các khóa học đắt tiền chất lượng cao, hoặc là chưa tìm được chỗ học đủ tin tưởng, sợ lãng phí thời gian và tiền bạc.\nVậy các bạn có thể thử học theo cách của mình: Học qua ứng dụng di động (1 ứng dụng miễn phí + 1 ứng dụng có phí nhưng thấp) kết hợp với sử dụng Google. Chú ý là học với chi phí thấp thì công sức và thời gian các bạn phải bỏ ra nhiều hơn, mà thời gian chính là tiền bạc. Và không có công thức nào hiệu quả tuyệt đối, áp dụng cho tất cả mọi người, nếu bạn học theo cách này không thấy hiệu quả thì hãy tìm một cách học khác.\n1. Elsa Speak Ứng dụng đầu tiên mình muốn giới thiệu đến các bạn là Elsa Speak. Vì mục đích trước mắt của mình là học Tiếng Anh giao tiếp để phục vụ cho công việc (giao tiếp với khách hàng nước ngoài), cũng như sở thích cá nhân (đi du lịch nước ngoài).\nĐây là một ứng dụng có thu phí, nhưng thấp hơn nhiều so với bỏ tiền ra đi học ở một trung tâm hoặc học 1:1 với giáo viên bản xứ. Đặc biệt ứng dụng này là của Việt Nam, nên người Việt sẽ được ưu đãi rất lớn khi đăng ký các gói học. Các bạn có thể đăng ký ở website để được hỗ trợ (qua email hoặc sđt) và ưu đãi lớn nhất (có thể lên đến 85%): https://elsaspeak.vn/?id=707.\nĐăng ký qua website trên sẽ có cả gói vĩnh viễn (trên app không có, kích hoạt ở website), tuy nhiên mình khuyến khích các bạn đăng ký gói theo tháng hoặc theo năm (tùy theo mục tiêu cụ thể), vì khi đó sẽ có áp lực để học nhanh hơn (kẻo phí tiền). Chỉ đăng ký gói vĩnh viễn nếu có đủ quyết tâm, nếu không bạn sẽ bỏ xó (vì nó không bao giờ hết hạn).\nƯu điểm:\n Nội dung phong phú, tính năng phân tích giọng nói độ chính xác cao =\u0026gt; giúp phát âm chuẩn hơn (âm cuối, nối âm, nuốt âm, \u0026hellip;). Học mọi lúc mọi nơi =\u0026gt; Cứ có mạng internet và thời gian rảnh là học được, học 5-10ph cũng được không cần phải học theo buổi kéo dài 1-2 tiếng. Ứng dụng thiết kế theo dạng gamification (giống như chơi game), giúp dễ có hứng thú và duy trì việc học lâu hơn. Có nhiều chức năng cập nhật liên tục, giúp bạn tự thiết kế được cách học phù hợp cho riêng mình (hiện tại mình đã học mấy tháng rồi mà cũng chưa dùng được hết toàn bộ tính năng của app 😅).  Nhược điểm:\n Chỉ tập trung vào Listening và Speaking, không tập trung vào ngữ pháp =\u0026gt; Cứ nói cho người nghe hiểu là được, chưa cần quan tâm ngữ pháp. Không có người hướng dẫn trực tiếp, giải đáp thắc mắc ngay lập tức (ví dụ về ngữ pháp, thành ngữ, thuật ngữ, \u0026hellip;) =\u0026gt; Khắc phục bằng cách đi hỏi hoặc tự tìm hiểu qua Google search (tự mò ra sẽ nhớ lâu hơn). Không nói chuyện trực tiếp với người thật =\u0026gt; khó luyện phản xạ như khi nói chuyện trực tiếp, nên sau khi học ở trên app này để có kiến thức cơ bản thì bạn sẽ cần chủ động luyện tập nói chuyện trực tiếp (với bạn bè, đồng nghiệp, khách hàng, \u0026hellip;).  Một số mẹo khi học:\n Trong ứng dụng có 1 tính năng rất hay đó là bảng xếp hạng. Mỗi khi hoàn thành 1 bài học bạn sẽ được cộng điểm và dùng để tính vào bảng xếp hạng =\u0026gt; học càng nhiều thì xếp hạng càng cao, xếp hạng cao thì có thể lên \u0026ldquo;giải đấu\u0026rdquo; cao hơn, xếp hạng thấp thì bị đẩy xuống \u0026ldquo;giải đấu\u0026rdquo; thấp hơn. Mục tiêu nhỏ mình đặt ra đó là leo lên \u0026ldquo;giải đấu\u0026rdquo; cao nhất.  Nếu học liên tục đều đặn các ngày thì bạn sẽ đạt được streak và được cộng điểm =\u0026gt; cố gắng đạt được streak đồng thời sẽ tạo được thói quen học hàng ngày, thời gian ít cũng được nhưng đều đặn. Một khi đã tạo được thói quen, bạn sẽ không cần phải duy trì động lực nữa (duy trì thói quen sẽ dễ hơn, ví dụ như thói quen đánh răng mỗi sáng). Bình thường mỗi ngày mình sẽ học khoảng 15 phút (do lười dần), tối thiểu là xong phần \u0026ldquo;Luyện tập bài học Hàng ngày\u0026rdquo; và một vài bài ở \u0026ldquo;Học theo Chủ đề\u0026rdquo;.  Với các bài tập phát âm nên để chế độ nâng cao và cố gắng tập đi tập lại cho đến khi đạt yêu cầu (độ chính xác hiển thị màu xanh lá, thường \u0026gt;= 80%).  Với các bài tập dạng hội thoại, nên tập nghe hiểu bằng cách không nhìn vào script khi đến lượt computer nói.   2. Duolingo Đối với nhiều người thì ứng dụng này khá là khủng bố, vì 1 khi đã học mà bỏ giữa chừng nó sẽ tìm mọi cách để notify bạn (thậm chí chửi bạn 😂).\nỨng dụng này có thể dùng FREE (chịu khó xem quảng cáo) hoặc thu phí (học thoải mái hơn, không cần xem quảng cáo). Nếu bạn tải app qua link này thì sẽ được dùng bản VIP trong vài tuần (tùy từng thời điểm chạy chương trình mời bạn bè).\nƯu điểm và nhược điểm khá giống với Elsa Speak, tuy nhiên app này nhận diện giọng nói không tốt bằng Elsa, và app thiên về học từ vựng + ngữ pháp hơn là nghe nói.\nMột số mẹo khi học:\n Trong ứng dụng này cũng có bảng xếp hạng, hoạt động tương tự Elsa Speak, bạn có thể đặt mục tiêu đạt \u0026ldquo;giải đấu\u0026rdquo; hạng cao nhất. Nếu học liên tục đều đặn các ngày thì bạn sẽ đạt được streak, và trong Duolingo bạn có thể xem streak cao nhất của mình, lấy đó làm động lực duy trì việc học.  Nên mời thêm bạn bè, theo dõi lẫn nhau, học cùng nhau thì sẽ có thêm động lực (xem được tiến trình học của bạn bè và có nhiệm vụ chung với bạn bè).  Với các bài tập dịch sang Tiếng Anh, hãy dùng tính năng dịch giọng nói thành chữ để luyện phát âm cho chuẩn.   3. Google Dùng Google search sẽ giúp bạn tự học thêm về ngữ pháp, nghe hiểu và đọc tài liệu Tiếng Anh. Xem nhiều video Tiếng Anh, đọc nhiều tài liệu Tiếng Anh cũng giúp bạn nâng cao trình độ Tiếng Anh.\nTrong khi học Tiếng Anh từ 2 ứng dụng trên, sẽ có những trường hợp bạn không hiểu (về ngữ pháp, thành ngữ, \u0026hellip;) thì lúc này chúng ta sẽ search Google để tìm giải thích và ví dụ liên quan (có thể search bằng Tiếng Việt cũng được).\nMẹo: Trong quá trình sử dụng Tiếng Anh bạn có thể dùng Google translate để dịch từ Tiếng Việt sang Tiếng Anh, nhưng nó có thể bị sai ngữ pháp. Lúc này hãy copy đoạn Tiếng Anh đó và search lại bằng Google (copy từng câu ngắn sẽ chính xác hơn). Dựa vào kết quả tìm kiếm chúng ta có thể biết được đoạn đó có đúng ngữ pháp hay không (câu đó xuất hiện trong kết quả nào không, hoặc Google sẽ gợi ý một câu khác chuẩn ngữ pháp hơn).\n4. Một số nguồn giúp học Tiếng Anh trên mạng  Từ điển Tiếng Anh (tra từ điển nên xem kỹ cả phần phát âm và ví dụ sử dụng): Cambridge Dictionary, Glosbe Dictionary, OZDIC. Youtube: Channel SpeakEnglishWithVanessa, Channel mmmEnglish, Channel Learn English with Papa Teach Me. Học những kiến thức thực tế cực kì đơn giản bằng tiếng Anh: BBC. Các diễn đàn, game online dùng Tiếng Anh. Nếu học Tiếng Anh giao tiếp thì cách tốt nhất là nói chuyện trực tiếp với người nước ngoài (đừng sợ sai, nó cũng giống như mình nói chuyện với người nước ngoài nói Tiếng Việt thôi). Nếu không có bạn bè là người nước ngoài (trên mạng) thì nói chuyện với bạn bè, đồng nghiệp bằng Tiếng Anh cũng là 1 cách luyện phản xạ rất tốt. Viết blog, dịch lại các bài Tiếng Anh về chủ đề bạn yêu thích cũng là 1 cách học Tiếng Anh hiệu quả. Ví dụ ban đầu mình học Tiếng Anh bằng cách dịch các bài blog về công nghệ như này. Khi mới đầu dịch thì để cho nhanh có thể dùng thêm extension Google Translate (cái này cho phép bôi đen 1 từ và dịch trực tiếp ngay trên trang web).   Nếu bạn có nguồn tài liệu, cách học Tiếng Anh hiệu quả muốn chia sẻ thì để lại comment ở dưới nhé. Thanks for sharing!\n","permalink":"https://robinhuy.github.io/blog/chia-se-cach-hoc-tieng-anh-cho-nguoi-ngheo/","summary":"Nếu bạn có điều kiện tài chính thì nên kiếm nguồn tài liệu hoặc khóa học nào mất phí nhưng chất lượng để có thể thông thạo Tiếng Anh càng nhanh càng tốt. Giỏi Tiếng Anh sẽ mang lại cho bạn rất nhiều lợi ích so với chi phí bỏ ra.\nOk, trước khi đọc tiếp, bạn thử bỏ ra 5 phút suy nghĩ để trả lời câu hỏi sau:","title":"Chia sẻ cách học Tiếng Anh cho người nghèo"},{"content":"Với những ai mới học chơi đàn (Guitar Acoustic) thì có cần học cách tự thay dây không? Dây đàn đứt thì làm thế nào?\nTheo ý kiến của mình thì không cần học cách tự thay dây, mang đàn ra tiệm chọn dây rồi người ta thay luôn cho mình là xong.\nThế thì mình viết bài này để làm gì? Để cho những người lười như mình, lười mang đàn ra tiệm (nếu như tiệm ở xa), hoặc chỉ đơn giản là để biết, thêm 1 kỹ năng thì cũng không thiệt đúng không?\nOk, giờ lôi đàn ra thực hành nào.\nBước 1: Chuẩn bị Để thay dây đàn trước tiên chúng ta cần phải chuẩn bị đầy đủ công cụ, bao gồm:\n Dây đàn (bắt buộc phải có, không có thì lấy gì mà thay). Dụng cụ thay dây đàn. Hướng dẫn thay dây đàn (chính là bài viết này, cho những ai lười xem video hướng dẫn).  Chọn dây đàn Có nhiều loại dây đàn, tùy vào ví tiền mà các bạn có thể chọn loại phù hợp. Hiện mình thấy phổ biến thì có 3 loại sau:\n Alice (hàng Trung Quốc): Giá từ 50-200k. Mình dùng thử vài lần thì thấy cũng được nhưng nhanh hỏng. D\u0026rsquo;Addario (hàng Mỹ): Giá từ 110-270k. Dây này có lẽ là được sử dụng nhiều nhất vì giá không quá cao, hàng Mỹ nghe cũng có vẻ uy tín hơn hàng Tàu (nhưng mình chưa dùng bao giờ). Elixir (hàng Mỹ, nhưng có nhiều hàng nhái của Trung Quốc): Giá từ 320-410k. Giá đắt nhưng dùng bền (kể cả tay hay bị ra mồ hôi). Âm thanh chắc cũng hay hơn 2 loại kia, nhưng mình tai trâu và không nghe nhiều để phân biệt nên không dám đảm bảo 😅.  Một chú ý nữa khi mua dây thường sẽ có 2 loại để chọn (phân biệt theo kích cỡ) là loại cỡ 11 và 12 (0.011 inch và 0.012 inch). Thì mình chọn cỡ 11 vì nó là trung bình phổ biến nhất, dây dày hơn thì đánh vang hơn (và đau tay hơn).\nDây thì mình hay mua dây Elixir trên Shopee (đắt nhưng chắc nửa năm, có khi cả năm mới thay 1 lần). Mình mua của shop này vì thấy nhiều người mua và rating cũng cao: https://shope.ee/6AC1r3MwfA.\nHoặc vẫn shop đó nhưng bán dây lẻ (ví dụ bạn bị đứt 1 dây và chỉ muốn thay dây đó thôi cho đỡ tốn kém): https://shope.ee/2fc9gjgNXj, chú ý là mua dây lẻ thì cộng vào sẽ thấy đắt hơn là mua cả bộ, và dây hay đứt nhất thì đắt nhất (thường là dây 1).\nChọn công cụ thay dây Bạn có thể không cần công cụ gì cũng thay dây được, nhưng tối thiểu phải có cái kìm để còn cắt dây thừa. Nếu có công cụ thì sẽ tiện hơn và thay dây nhanh hơn, nhàn hơn.\nCó nhiều loại công cụ để giúp tháo chốt, quay dây, cắt dây, \u0026hellip; Các bạn có thể mua ngoài tiệm hoặc mua online. Mình thì tiện mua dây thì mua luôn cái công cụ này, 3 trong 1 cho tiện (cũng ở shop trên luôn): https://shope.ee/AUL11oUeVE.\nBước 2: Tháo dây cũ Trước khi thay dây mới thì tất nhiên chúng ta phải tháo dây cũ ra đã. Để tháo dây thì vặn khóa đàn cho dây lỏng ra rồi rút dây là được, có thể quay tay không cần tool hoặc dùng cái quay tay mua ở trên cho nhanh.\nSau khi rút dây ra khỏi khóa đàn thì tháo các chốt ở phần ngựa đàn để tháo nốt đầu dây còn lại. Có thể dùng tay, dùng kìm hoặc vẫn dùng cái tool bên trên.\nNếu như cái chốt chặt quá thì có thể luồn tay vào bên trong thùng đàn để đẩy chốt từ dưới lên cho đỡ bị trầy xước đàn.\nSau khi đẩy chốt lên thì chúng ta rút hết dây ra và vệ sinh lại đàn 1 lượt (bình thường khó vệ sinh vì vướng dây đàn).\nBước 3: Lắp dây mới Ở đây mình demo với dây Elixir, các dây khác thì cũng tương tự.\nSau khi mở túi ra chúng ta sẽ thấy có 6 túi con đựng 6 dây với các kích thước khác nhau, có đánh số theo kích cỡ. Chúng ta sẽ thay lần lượt các dây theo thứ tự từ bé đến lớn (hoặc ngược lại nếu bạn thích, miễn là tuần tự).\nĐầu tiên chúng ta sẽ cố định dây vào ngựa đàn trước. Chỉ cần nhét dây vào lỗ, sau đó ấn chốt vào để cố định đây là xong.\nTiếp đến kéo dây dọc xuống phía cần đàn, chú ý dây sẽ phải đi qua các khe tương ứng trên lược đàn.\nChỉnh lại khóa đàn sao cho lỗ thẳng theo chiều dọc để dễ luồn dây vào.\nSau khi luồn dây vào lỗ chốt điều chỉnh, kéo căng dây, đảm bảo dây đi qua khe cố định, sau đó chúng ta sẽ đo dây theo công thức sau để cắt bớt phần thừa đi cho dễ. Nếu bạn chưa thay dây bao giờ, thì có thể lấy dây đàn cũ ra thực hành thử trước.\n Với dây 1-3 là dây mỏng, chúng ta có thể để phần thừa nhiều, phần này sẽ dùng để quấn quanh chốt điều chỉnh. Vì là dây mỏng nên chúng ta có thể quấn 5-7 vòng quanh khóa đàn mà các vòng không bị đè lên nhau. Với dây 4-6 là dây dày, chúng ta chỉ quấn khoảng 3-4 vòng là đủ, quấn nhiều hơn thì không đủ chỗ và các vòng sẽ phải chồng lên nhau nhìn không được đẹp lắm. Đường kính của chốt cố định vào khoảng 12.5mm (số do mình tự đo nên chỉ tương đối thôi 😅). Vậy nếu muốn quấn 2 vòng thì để phần thừa khoảng 25mm, cứ thế mà nhân lên với số vòng mong muốn. Khoảng cách giữa 2 chốt là ~40mm, vậy để cho đơn giản cứ lấy khoảng cách giữa 2 chốt làm mốc, với dây 1-3 thì lấy ~2 lần khoảng cách giữa 2 chốt, dây 4-6 thì lấy ~1 lần khoảng cách giữa 2 chốt (nếu muốn đẹp hơn thì bạn cần tính kỹ hơn).  Tiếp theo kéo dây đàn lùi lại đến điểm chúng ta đã đo và đánh dấu ở trên và bắt đầu vặn khóa. Chúng ta sẽ vặn khóa sao cho dây ở phía bên trong chốt. Khi vặn khóa dây sẽ từ từ quấn quanh chốt, chú ý để cho đẹp thì kéo căng dây và tránh cho các vòng dây chồng lên nhau (vòng dây ở phía dưới dây đàn thì sẽ đẹp hơn). Các vòng đầu nên vặn bằng tay và nắn, điều chỉnh cho đẹp, các vòng sau thì dùng tool để quay cho nhanh. Vặn khóa cho đến khi dây căng lên, không còn bị trượt ra khỏi khe nữa là được (không nên vặn căng quá kẻo đứt dây, sẽ chỉnh lại dây đàn sau).\nLàm tương tự lần lượt cho đến hết 6 dây. Trong lúc làm nếu bị vướng thì có thể cắt bớt phần thừa của dây (đằng nào cuối cùng cũng cắt).\nSau khi đã cố định hết 6 dây, chúng ta sẽ cắt bớt phần dây còn thừa đi, có thể cắt thừa khoảng 3-4mm và lấy kìm bấm cho phần thừa hướng xuống dưới để tránh bị cọ vào gây xước tay.\nBước cuối cùng là chỉnh lại dây đàn sao cho chuẩn. Chú ý dây mới thì thời gian đầu bạn sẽ phải chỉnh nhiều hơn.\n- - -\nThế là xong, chúng ta đã tự thay được dây đàn, không cần phải mang đàn ra tiệm nữa.\nChúc các bạn tập đàn vui vẻ 🎸\n","permalink":"https://robinhuy.github.io/blog/cach-tu-thay-day-dan-guitar-acoustic-tai-nha-tu-a-z-ai-cung-lam-duoc/","summary":"Với những ai mới học chơi đàn (Guitar Acoustic) thì có cần học cách tự thay dây không? Dây đàn đứt thì làm thế nào?\nTheo ý kiến của mình thì không cần học cách tự thay dây, mang đàn ra tiệm chọn dây rồi người ta thay luôn cho mình là xong.\nThế thì mình viết bài này để làm gì? Để cho những người lười như mình, lười mang đàn ra tiệm (nếu như tiệm ở xa), hoặc chỉ đơn giản là để biết, thêm 1 kỹ năng thì cũng không thiệt đúng không?","title":"Cách tự thay dây đàn Guitar Acoustic tại nhà từ a-Z, ai cũng làm được"},{"content":"Khi code các dự án bằng JavaScript (hoặc NodeJS) thì có nhiều tool để debug, nhưng mình vẫn hay debug theo kiểu nông dân đó là dùng console.log. Những ai có cùng sở thích như vậy thì có thể tham khảo bài viết này để có thể log một cách pro hơn\nCó nhiều loại log ngoài console.log Console là một object, và nó có nhiều phương thức khác nhau. Trong đó .log() là phổ biến nhất.\nTham khảo các phương thức của object console ở đây: https://developer.mozilla.org/en-US/docs/Web/API/console.\nKhi code ở browser thì có thể dùng 1 số loại log sau, để khi hiển thị có thể lọc theo ý muốn: console.log(), console.info(), console.warn(), console.error(). Các phương thức này cách dùng giống nhau nhưng hiển thị khác nhau.\n  Lọc hiển thị các loại logs     Một số loại logs và cách hiển thị trên Console   Hoặc khi hiển thị dữ liệu là mảng các object thì có thể hiển thị dưới dạng bảng bằng console.table (có thể điều chỉnh được độ rộng các cột khi có nhiều thuộc tính).\n  Log sử dụng console.log()     Log sử dụng console.table()   Với NodeJS thì nếu xem log trên Terminal sẽ thấy giống nhau (trừ console.table). Có thể kết hợp với một số tool khác để lọc log, remove log, \u0026hellip; ví dụ như dùng console.log() để debug nhanh và console.info() để thông báo lên terminal (running server at port \u0026hellip;).\nConsole.log với CSS Cách này chỉ áp dụng trên trình duyệt.\nThay vì chỉ console.log() như bình thường thì chúng ta có thể thêm chút CSS vào cho nổi bật (nhất là trong trường hợp có nhiều log do nhiều người viết mà chưa xóa 😅).\nVí dụ:\n1 2  const style = \u0026#34;color: red; font-size: 30px;\u0026#34;; console.log(\u0026#34;%c\u0026#34; + \u0026#34;Hello World\u0026#34;, style);   Cách này được áp dụng như ở Facebook, bật developer tools lên sẽ thấy.\nConsole.log với mã màu Cách này áp dụng được cho cả trình duyệt lẫn Terminal, đó là sử dụng mã màu (ANSI escape code) để log ra chữ có màu.\nVí dụ ký hiệu đặc biệt của chữ màu đỏ là \\x1b[31m, kết thúc màu là \\x1b[0m (reset). Vậy đoạn log sau sẽ in ra chữ Hello World có 2 màu xanh và đỏ:\n1 2 3 4  const textRed = \u0026#34;\\x1b[31m\u0026#34;; const textGreen = \u0026#34;\\x1b[32m\u0026#34;; const reset = \u0026#34;\\x1b[0m\u0026#34;; console.log(textRed + \u0026#34;Hello\u0026#34; + reset + \u0026#34; \u0026#34; + textGreen + \u0026#34;World\u0026#34; + reset);   Ngoài ra còn có mã màu nền đỏ là \\x1b[41m, vậy đoạn log sau sẽ in ra chữ xanh nền đỏ:\n1 2 3 4  const textGreen = \u0026#34;\\x1b[32m\u0026#34;; const bgRed = \u0026#34;\\x1b[41m\u0026#34;; const reset = \u0026#34;\\x1b[0m\u0026#34;; console.log(textGreen + bgRed + \u0026#34;Hello World\u0026#34; + reset);   Có thể dùng tool sau để chọn màu cho nhanh: https://console-colors.vercel.app.\nLog nhanh hơn với User Snippets của VS Code VS Code cho phép người dùng tự tạo các snippets tùy theo ngôn ngữ để code cho nhanh. Tận dụng chức năng này chúng ta có thể viết sẵn các snippets log khác nhau để không cần mất công gõ dài dòng hoặc nhớ mã màu (nếu muốn log có màu). Lúc này chỉ cần gõ 1 vài ký tự là VS Code sẽ có gợi ý luôn.\nĐể tạo snippets thì vào mục File \u0026gt; Preferences \u0026gt; Configure User Snippets. Sau đó chọn ngôn ngữ áp dụng snippet, ví dụ javascript.json (JavaScript).\nSau đó dựa theo gợi ý có sẵn trong file này để cấu hình. Ví dụ:\n1 2 3 4 5 6 7  {  \u0026#34;Print to console\u0026#34;: {  \u0026#34;prefix\u0026#34;: \u0026#34;cl\u0026#34;,  \u0026#34;body\u0026#34;: [\u0026#34;console.log(\u0026#39;--- ${1:DATA} ---\u0026#39;, ${2:\u0026#39;\u0026#39;});\u0026#34;, \u0026#34;$0\u0026#34;],  \u0026#34;description\u0026#34;: \u0026#34;Log output to console\u0026#34;  } }   Như vậy khi code chỉ cần gõ cl là sẽ có gợi ý, bấm enter thì sẽ hiển thị ra đoạn log có kèm các vị trí tab stops (điểm dừng khi bấm tab, có bôi đen sẵn) và vị trí con trỏ chuột cuối cùng sau khi gõ lệnh ($0):\nTrên đây là 1 ví dụ snippet log đơn giản, các bạn có thể tự tùy biến màu mè theo ý thích cho nó trông nguy hiểm hơn khi debug.\nHappy coding 😎\n Tham khảo:\n It’s 2022, Please Don’t Just Use “console.log” Anymore. How to change node.js's console font color?. Everything you never wanted to know about ANSI escape codes. Snippets in Visual Studio Code.  ","permalink":"https://robinhuy.github.io/blog/debug-bang-console-log-theo-cach-pro-hon/","summary":"Khi code các dự án bằng JavaScript (hoặc NodeJS) thì có nhiều tool để debug, nhưng mình vẫn hay debug theo kiểu nông dân đó là dùng console.log. Những ai có cùng sở thích như vậy thì có thể tham khảo bài viết này để có thể log một cách pro hơn\nCó nhiều loại log ngoài console.log Console là một object, và nó có nhiều phương thức khác nhau. Trong đó .","title":"Debug bằng console.log theo cách PRO hơn"},{"content":"Trước khi đẩy app lên Store, chúng ta sẽ cần tạo App Launcher Icon (icon của ứng dụng trên máy của người dùng). App code bằng React Native sẽ có thể đẩy lên cả Google Play (Android) và App Store (iOS), do đó mình sẽ hướng dẫn cách tạo App Launcher Icon cho cả 2 hệ hiều hành trên.\nĐể tạo bộ icon cho các thiết bị với kích thước khác nhau (dùng cho cả trên store) thì chúng ta cần chuẩn bị sẵn 1 icon gốc với kích thước 1024x1024 pixels.\nSau đó dùng trang web sau để generate ra các bộ icons cho Android và iOS: https://easyappicon.com. Trang web này cho phép tạo ra cả icon cho iOS theo cả cách cổ điển lẫn hiện đại (bo góc).\n  Tạo App icon trên website easyappicon.com   1. App Launcher Icon cho iOS Bước 1 Tạo ra bộ icon với các kích thước khác nhau cho các loại thiết bị iOS. Có thể sử dụng website ở trên hoặc một số tool thay thế sau:\n App Icon Set Creator (trên App Store). Website https://makeappicon.com, upload ảnh lên (JPG hoặc PNG) và nhập email để nhận bộ icon trên cả iOS lẫn Android.  Bước 2 Bật XCode lên, mở project React Native (file [project] .xcworkspace trong thư mục ios).\nTìm đến thư mục Images.xcassets, sau đó kéo thả bộ icon đã được tạo từ bước 1 (thư mục AppIcon.appiconset) hoặc bấm vào biểu tượng dấu cộng ở góc dưới bên trái, chọn import.\nNếu không dùng XCode có thể copy thư mục AppIcon.appiconset vào trong thư mục /ios/[tên-app]/Images.xcassets\nXong, build lại app để thấy kết quả.\n 1 phút quảng cáo: Icon ở trên là mình tự chế cho app game Master Mind X viết bằng React Native, anh chị em chơi thử rồi cho xin góp ý ở comment nhé, review ủng hộ 5 sao thì càng tốt 😆\n 2. App Launcher Icon cho Android Bước 1 Tương tự như bên iOS, chúng ta cũng cần tạo ra bộ icon với các kích thước khác nhau cho các loại thiết bị Android. Có thể sử dụng website ở trên hoặc một số tool thay thế sau:\n Website Android Assets Studio. Website https://makeappicon.com.  Bước 2 Giải nén bộ icon vừa download về, trong đó có thư mục res chứa các thư mục dạng: mipmap-hdpi, mipmap-mdpi, mipmap-xhdpi, \u0026hellip;, trong mỗi thư mục lại chứa file ic_launcher (tên mặc định). Copy (ghi đè) toàn bộ vào trong thư mục android \u0026gt; app \u0026gt; src \u0026gt; main \u0026gt; res.\nChú ý là có nhiều thiết bị Android sử dụng icon dạng hình tròn, nên chúng ta tạo thêm 1 bộ icon dạng tròn (Circle) và cũng copy như trên, tên icon mặc định sẽ là ic_launcher_round.\n  Tên icon đã được khai báo sẵn trong file AndroidManifest.xml     Import Icons   Chú ý: Theo khuyến nghị của Google thì nên thiết kế icon cho Android theo dạng hình vuông đầy đủ (không cần bo tròn), không đổ bóng, vì khi đẩy app lên Store thì Google sẽ tự áp dụng các kiểu hiệu ứng đó cho đồng nhất. Do đó nếu tạo icon từ trang Android Assets Studio thì nên bỏ hết cấu hình phần Effect (để None).\nTham khảo thêm: https://developer.android.com/google-play/resources/icon-design-specifications.\n","permalink":"https://robinhuy.github.io/blog/tao-app-launcher-icon-cho-react-native-app-android-ios/","summary":"Trước khi đẩy app lên Store, chúng ta sẽ cần tạo App Launcher Icon (icon của ứng dụng trên máy của người dùng). App code bằng React Native sẽ có thể đẩy lên cả Google Play (Android) và App Store (iOS), do đó mình sẽ hướng dẫn cách tạo App Launcher Icon cho cả 2 hệ hiều hành trên.\nĐể tạo bộ icon cho các thiết bị với kích thước khác nhau (dùng cho cả trên store) thì chúng ta cần chuẩn bị sẵn 1 icon gốc với kích thước 1024x1024 pixels.","title":"Tạo App Launcher icon cho React Native app (Android + iOS)"},{"content":"Vào một ngày đẹp trời, bỗng dưng mình nảy ra ý định làm một trang blog cá nhân thay vì viết Blog trên các nền tảng có sẵn như viblo.asia, techmaster.vn, \u0026hellip; Tất nhiên các bài viết mới vẫn sẽ đăng lên các nền tảng này để kiếm người đọc chứ blog này ma nó đọc 😅).\nÝ tưởng có rồi, nhưng thực hiện như nào, sử dụng công nghệ nào, chi phí như nào? Khá nhiều câu hỏi đau đầu và khó lựa chọn. Vậy cần đặt ra một số tiêu chí:\n Ưu tiên số một là chi phí, càng rẻ càng tốt, miễn phí thì còn tốt hơn nữa. Sử dụng công nghệ nào cũng được miễn là cài đặt nhanh, dễ dùng, dễ tùy biến. Blog có thể lượng truy cập ít (thậm chí không có ma nào xem), nhưng tốc độ truy cập vẫn phải nhanh, PageSpeed Insights điểm càng cao càng tốt.  Sau một hồi search Google với 3 tiêu chí trên (chủ yếu là tiêu chí miễn phí) thì mình chọn ra được giải pháp như sau:\n Sử dụng Static Site Generator, chơi web tĩnh thì tốc độ sẽ nhanh và điểm PageSpeed Insights sẽ cao. Cụ thể mình dùng tool Hugo. Hosting ở đâu? Tất nhiên là Github Page rồi, free và tốc độ cao. Các bạn cũng có thể dùng một số hosting free khác như: Netlify, Firebase, Vercel, \u0026hellip;  OK. Let\u0026rsquo;s get started!\nCài đặt và sử dụng Hugo Vào trang chủ của Hugo rồi làm theo hướng dẫn cài đặt tùy theo hệ điều hành mà bạn đang sử dụng thôi: https://gohugo.io/getting-started/installing.\nSau khi cài xong thì bật terminal lên và gõ lệnh sau để tạo một project web tĩnh (ví dụ huydq.dev):\nhugo new site huydq.dev Cấu trúc project tạo bởi Hugo như sau:\nTrong đó chúng ta chỉ cần chú ý đến mấy thư mục và file chính:\n content: Nơi viết nội dung cho website, là các file markdown, mỗi file tương ứng 1 trang trong website. theme: Chứa các theme có sẵn tải trên mạng về để làm giao diện cho website. config.toml: File cấu hình cho website như tên website, sử dụng theme gì, \u0026hellip; Có thể đổi sang định dạng yml hoặc yaml nếu không quen với toml.  Tiếp đến chúng ta vào trang này và chọn 1 cái theme ưng ý để cài. Có thể cài bằng cách download file về và ném vào trong thư mục themes hoặc là dùng git submodule để clone qua Github, ví dụ cài theme ananke qua Github:\ncd huydq.dev git init git submodule add https://github.com/theNewDynamic/gohugo-theme-ananke.git themes/ananke Cài xong theme thì cần khai báo sử dụng ở trong file config, ví dụ:\nbaseURL = \u0026#34;https://huydq.dev/\u0026#34; title = \u0026#34;HuyDQ\u0026#39;s Blog\u0026#34; theme = \u0026#34;ananke\u0026#34; Trong này cũng cho phép khai báo cấu hình cho theme, cái này là tùy từng theme nên dùng theme nào thì xem ở hướng dẫn của theme đó.\nCấu hình xong theme thì chúng ta có thể bắt đầu viết blog bằng cách gõ lệnh sau để tạo ra một file markdown trong thư mục content (my-first-post.md):\nhugo new posts/my-first-post.md File mới tạo sẽ trông dạng như sau:\n--- title: \u0026#34;My First Post\u0026#34; date: 2019-03-26T08:47:11+01:00 draft: true --- Trong đó có cấu hình tên bài viết (title), ngày xuất bản (date), bản nháp hay đã sẵn sàng xuất bản (draft). Nội dung bài viết thì viết bằng cú pháp markdown, viết sau phần dấu gạch ngang ---. Bài viết nào có đánh dấu draft: true thì sẽ không được build.\nChạy thử website trên local bằng lệnh hugo server, truy cập http://localhost:1313 để xem kết quả. Đường dẫn của trang sẽ tương ứng với đường dẫn file http://localhost:1313/posts/my-first-post. Khi đã thấy ưng ý thì build ra static files (HTML CSS JS) bằng lệnh hugo. Website sẽ được build vào trong thư mục public và chỉ cần đẩy file trong thư mục này lên 1 hosting hỗ trợ static web là xong.\nCấu hình Github Pages Để sử dụng Github Pages hosting static web thì chúng ta tạo 1 repository trùng với tên miền free của Github Pages theo dạng [username].github.io, ví dụ username github của mình là robinhuy vậy mình sẽ tạo 1 repository là robinhuy.github.io (đây cũng chính là tên miền free của Github Pages).\nChúng ta có thể build website bằng Hugo, sau đó copy code web tĩnh ở trong thư mục public vào trong repository này và push code lên là xong.\nTuy nhiên để quản lý cả source code thì chúng ta sẽ đẩy toàn bộ lên Github. Nếu sử dụng Github Actions để build và deploy project (sang branch gh-pages) thì có thể ignore thư mục build đi (cấu hình trong .gitignore).\nTham khảo cấu hình Github Actions của mình (file cấu hình .github/workflows/gh_pages.yml):\nname: Deploy Hugo site to Pages on: push: branches: [\u0026#34;main\u0026#34;] workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: \u0026#34;pages\u0026#34; cancel-in-progress: true defaults: run: shell: bash jobs: build: runs-on: ubuntu-latest env: HUGO_VERSION: 0.99.0 steps: - name: Install Hugo CLI run: | wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.deb \\ \u0026amp;\u0026amp; sudo dpkg -i ${{ runner.temp }}/hugo.deb - name: Checkout uses: actions/checkout@v3 with: submodules: recursive - name: Setup Pages id: pages uses: actions/configure-pages@v1 - name: Build with Hugo run: | hugo \\ --minify \\ --baseURL ${{ steps.pages.outputs.base_url }} - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: path: ./public deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 Chỉ đơn giản vậy thôi, mỗi lần chúng ta push code lên branch main thì nó sẽ tự động build và đẩy code trong thư mục public sang branch gh-pages và website của chúng ta sẽ được cập nhật theo.\nChốt lại các thao tác khi cần viết bài mới sẽ là:\n Tạo 1 file mới trong thư mục content, cấu hình nội dung trang và viết bài theo cú pháp markdown. Dùng lệnh hugo server để chạy website local (có sẵn live reload để tiện preview). Hoặc nếu muốn trải nghiệm viết bài như một CMS thì các bạn có thể cài thêm một số phần mềm theo hướng dẫn sau: https://gohugo.io/tools/frontends. Commit code và push lên branch main.  Phần cấu hình website, cấu hình theme, \u0026hellip; thì các bạn tự tìm hiểu nốt trên trang chủ của Hugo và tài liệu hướng dẫn của theme mà bạn chọn nhé. Chúc các bạn viết Blog vui vẻ 😬\n","permalink":"https://robinhuy.github.io/blog/cach-tao-mot-trang-blog-ca-nhan-mien-phi-danh-cho-dev/","summary":"Vào một ngày đẹp trời, bỗng dưng mình nảy ra ý định làm một trang blog cá nhân thay vì viết Blog trên các nền tảng có sẵn như viblo.asia, techmaster.vn, \u0026hellip; Tất nhiên các bài viết mới vẫn sẽ đăng lên các nền tảng này để kiếm người đọc chứ blog này ma nó đọc 😅).\nÝ tưởng có rồi, nhưng thực hiện như nào, sử dụng công nghệ nào, chi phí như nào?","title":"Cách tạo một trang blog cá nhân miễn phí dành cho dev"},{"content":"Để cho các App React hoạt động mượt mà hơn, đẹp hơn, trải nghiệm người dùng tốt hơn, \u0026hellip; thì nên có thêm các hiệu ứng animation, transition.\nBài viết này mình sẽ hướng dẫn các bạn sử dụng thư viện React Transition Group để tạo hiệu ứng transition một cách nhanh chóng.\nĐể cho tiện thì mình sẽ demo code trên stackblitz.com. Trong ví dụ sẽ sử dụng cả React Router v6 để cấu hình multiple page, và có hiệu ứng transition giữa các page. Dưới đây là danh sách các dependencies sử dụng trong ví dụ demo:\nCấu hình React Router (v6) React Transition Group cung cấp cho chúng ta 4 Component để hỗ trợ cho việc tạo transition, do đó mình sẽ tạo ra 4 page để demo, và có transition giữa các page.\nTạo ra 4 function Component rỗng đại diện cho mỗi page là Home.js, Page1.js, Page2.js, Page3.js. Ví dụ Component Home:\n1 2 3 4 5 6 7  import React from \u0026#39;react\u0026#39;;  export default function Home() {  return (  \u0026lt;h1\u0026gt;Home Page\u0026lt;/h1\u0026gt;  ) }   Sau đó cấu hình Router cho website ở App.js:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37  import React from \u0026#39;react\u0026#39;; import { BrowserRouter, Link, Routes, Route } from \u0026#39;react-router-dom\u0026#39;;  import Home from \u0026#39;./pages/Home\u0026#39;; import Page1 from \u0026#39;./pages/page1/Page1\u0026#39;; import Page2 from \u0026#39;./pages/page2/Page2\u0026#39;; import Page3 from \u0026#39;./pages/page3/Page3\u0026#39;;  export default function App() {  return (  \u0026lt;BrowserRouter\u0026gt;  {/* Tạo menu */}  \u0026lt;nav  style={{  borderBottom: \u0026#39;solid 1px\u0026#39;,  padding: \u0026#39;1rem 0\u0026#39;,  }}  \u0026gt;  \u0026lt;Link to=\u0026#34;/\u0026#34;\u0026gt;Home\u0026lt;/Link\u0026gt;  {\u0026#39; | \u0026#39;}  \u0026lt;Link to=\u0026#34;/page1\u0026#34;\u0026gt;Page 1\u0026lt;/Link\u0026gt;  {\u0026#39; | \u0026#39;}  \u0026lt;Link to=\u0026#34;/page2\u0026#34;\u0026gt;Page 2\u0026lt;/Link\u0026gt;  {\u0026#39; | \u0026#39;}  \u0026lt;Link to=\u0026#34;/page3\u0026#34;\u0026gt;Page 3\u0026lt;/Link\u0026gt;  \u0026lt;/nav\u0026gt;   {/* Cấu hình Route */}  \u0026lt;Routes\u0026gt;  \u0026lt;Route path=\u0026#34;/\u0026#34; element={\u0026lt;Home /\u0026gt;} /\u0026gt;  \u0026lt;Route path=\u0026#34;/page1\u0026#34; element={\u0026lt;Page1 /\u0026gt;} /\u0026gt;  \u0026lt;Route path=\u0026#34;/page2\u0026#34; element={\u0026lt;Page2 /\u0026gt;} /\u0026gt;  \u0026lt;Route path=\u0026#34;/page3\u0026#34; element={\u0026lt;Page3 /\u0026gt;} /\u0026gt;  \u0026lt;/Routes\u0026gt;  \u0026lt;/BrowserRouter\u0026gt;  ); }   Sau khi cấu hình xong chúng ta có 1 website đơn giản gồm 4 trang, và có menu để chuyển trang.\nTiếp theo mình sẽ demo các Component mà React Transition Group cung cấp.\nTransition Component Dùng để tạo transition cho một Component khi nó thay đổi trạng thái (thường là chuyển đổi giữa mount và unmount).\nVí dụ sau sẽ tạo hiệu ứng transition khi Component xuất hiện (enter) và biến mất (exit):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51  import React, { useState } from \u0026#39;react\u0026#39;; import { Transition } from \u0026#39;react-transition-group\u0026#39;;  // Tạo một biến lưu thời gian chạy transition const duration = 1000; // 1000ms = 1s  // Có 4 trạng thái chính của một Transition // =\u0026gt; Tạo ra một object để style cho các trạng thái này const transitionStyles = {  entering: { opacity: 1 },  entered: { opacity: 1 },  exiting: { opacity: 0 },  exited: { opacity: 0 }, };  export default function Home() {  // Tạo state để ẩn hiện Component  const [isShow, setShow] = useState(false);   return (  \u0026lt;div\u0026gt;  \u0026lt;h1\u0026gt;Home\u0026lt;/h1\u0026gt;   {/* Sử dụng component Transition để tạo hiệu ứng transition */}  \u0026lt;Transition in={isShow} timeout={duration}\u0026gt;  {/* Nội dung bên trong là 1 hàm với tham số là state của Transition (4 state) */}  {(state) =\u0026gt; (  {/* Component sẽ hiển thị (hoặc biến mất) dựa vào state isShow */}  {/* Sử dụng inline style để tạo style transition */}  \u0026lt;div  style={{  transition: `opacity ${duration}ms ease-in-out`,  opacity: 0,  ...transitionStyles[state],  }}  \u0026gt;  Component content  \u0026lt;/div\u0026gt;  )}  \u0026lt;/Transition\u0026gt;   \u0026lt;br /\u0026gt;   {/* Bấm nút để hiển thị Component */}  \u0026lt;button onClick={() =\u0026gt; setShow(true)}\u0026gt;Show\u0026lt;/button\u0026gt;   {/* Bấm nút để ẩn Component */}  \u0026lt;button onClick={() =\u0026gt; setShow(false)}\u0026gt;Hide\u0026lt;/button\u0026gt;  \u0026lt;/div\u0026gt;  ); }   CSSTransition Component Sử dụng CSS để tạo Transition. Compnent này tương tự Component Transition và kế thừa các thuộc tính của Component Transition.\nVí dụ sau tạo hiệu ứng tương tự như ví dụ trước, nhưng sử dụng CSS ở một file riêng:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66  import React, { useState } from \u0026#39;react\u0026#39;; import { CSSTransition } from \u0026#39;react-transition-group\u0026#39;; // Nhúng CSS từ file vào Component import \u0026#39;./style.css\u0026#39;;  export default function Page1() {  // Tạo state để ẩn hiện Component  const [isShow, setShow] = useState(false);   return (  \u0026lt;div\u0026gt;  \u0026lt;h1\u0026gt;Page 1\u0026lt;/h1\u0026gt;   {/* Sử dụng component CSSTransition để tạo hiệu ứng transition */}  {/* Chú ý classNames my-node sẽ được sử dụng ở file CSS để style */}  \u0026lt;CSSTransition in={isShow} timeout={1000} classNames=\u0026#34;my-node\u0026#34;\u0026gt;  \u0026lt;div className=\u0026#34;content\u0026#34;\u0026gt;Component content\u0026lt;/div\u0026gt;  \u0026lt;/CSSTransition\u0026gt;   \u0026lt;br /\u0026gt;   \u0026lt;button onClick={() =\u0026gt; setShow(true)}\u0026gt;Show\u0026lt;/button\u0026gt;  \u0026lt;button onClick={() =\u0026gt; setShow(false)}\u0026gt;Hide\u0026lt;/button\u0026gt;  \u0026lt;/div\u0026gt;  ); }``` ```CSS /* Thêm CSS để ban đầu ẩn luôn Component */ .content {  opacity: 0; }  /* Sử dụng class my-node và kèm thêm các suffix để style */  /* -enter: Component bắt đầu xuất hiện */ .my-node-enter {  opacity: 0; }  /* -enter-active: Component đang xuất hiện */ .my-node-enter-active {  opacity: 1;  transition: opacity 1000ms; }  /* -enter-done: Component kết thúc hiệu ứng xuất hiện */ .my-node-enter-done {  opacity: 1; }  /* -exit: Component bắt đầu biến mất */ .my-node-exit {  opacity: 1; }  /* -exit-active: Component đang biến mất */ .my-node-exit-active {  opacity: 0;  transition: opacity 1000ms; }  /* -exit-done: Component kết thúc hiệu ứng biến mất */ .my-node-exit-done {  opacity: 0; }   SwitchTransition Component Sử dụng khi muốn điều khiển việc render Component theo state với 2 chế độ in-out và out-in (dùng kết hợp với Transition hoặc CSSTransition).\nVí dụ sau sẽ tạo transition khi thay đổi trạng thái của Component, nội dung của Component thay đổi kèm hiệu ứng transition:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33  import React, { useState } from \u0026#39;react\u0026#39;; import { SwitchTransition, CSSTransition } from \u0026#39;react-transition-group\u0026#39;; import \u0026#39;./style.css\u0026#39;;  export default function Home() {  const [state, setState] = useState(false);   return (  \u0026lt;div\u0026gt;  \u0026lt;h1\u0026gt;Page 2\u0026lt;/h1\u0026gt;   {/* Thử thay mode=\u0026#34;in-out\u0026#34; để xem hiệu ứng transition khác nhau */}  \u0026lt;SwitchTransition mode=\u0026#34;out-in\u0026#34;\u0026gt;  \u0026lt;CSSTransition  // Dùng key để phân biệt các trạng thái  key={state ? \u0026#39;out\u0026#39; : \u0026#39;in\u0026#39;}   // Sử dụng event transitionend để đánh dấu kết thúc transition  addEndListener={(node, done) =\u0026gt;  node.addEventListener(\u0026#39;transitionend\u0026#39;, done)  }   // Tạo hiệu ứng fade transition theo class \u0026#34;fade\u0026#34;  classNames=\u0026#34;fade\u0026#34;  \u0026gt;  \u0026lt;button onClick={() =\u0026gt; setState((state) =\u0026gt; !state)}\u0026gt;  {state ? \u0026#39;Goodbye!\u0026#39; : \u0026#39;Hello!\u0026#39;}  \u0026lt;/button\u0026gt;  \u0026lt;/CSSTransition\u0026gt;  \u0026lt;/SwitchTransition\u0026gt;  \u0026lt;/div\u0026gt;  ); }   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  .fade-enter {  opacity: 0; } .fade-exit {  opacity: 1; } .fade-enter-active {  opacity: 1; } .fade-exit-active {  opacity: 0; } .fade-enter-active, .fade-exit-active {  transition: opacity 500ms; }   TransitionGroup Component Sử dụng để tạo hiệu ứng transition cho 1 danh sách (list) các Component. Ví dụ demo mình lấy luôn trên docs của thư viện nhưng tối giản đi một chút:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52  import React, { useState } from \u0026#39;react\u0026#39;; import { CSSTransition, TransitionGroup } from \u0026#39;react-transition-group\u0026#39;; import { nanoid } from \u0026#39;nanoid\u0026#39;; import \u0026#39;./style.css\u0026#39;;  export default function Page3() {  // Tạo ra một list, sử dụng nanoid() để sinh unique id cho item  const [items, setItems] = useState([  { id: nanoid(), text: \u0026#39;Buy eggs\u0026#39; },  { id: nanoid(), text: \u0026#39;Pay bills\u0026#39; },  { id: nanoid(), text: \u0026#39;Invite friends over\u0026#39; },  { id: nanoid(), text: \u0026#39;Fix the TV\u0026#39; },  ]);   return (  \u0026lt;div\u0026gt;  \u0026lt;h1\u0026gt;Page 3\u0026lt;/h1\u0026gt;   \u0026lt;TransitionGroup className=\u0026#34;todo-list\u0026#34;\u0026gt;  {/* Render list, mỗi item trong list bọc trong Component CSSTransition */}  {items.map(({ id, text }) =\u0026gt; (  // Lấy id của item làm key cho CSSTransition  \u0026lt;CSSTransition key={id} timeout={500} classNames=\u0026#34;item\u0026#34;\u0026gt;  \u0026lt;div className=\u0026#34;container\u0026#34;\u0026gt;  {text}  \u0026lt;button  className=\u0026#34;btn-remove\u0026#34;  onClick={() =\u0026gt;  setItems((items) =\u0026gt; items.filter((item) =\u0026gt; item.id !== id))  }  \u0026gt;  \u0026amp;times;  \u0026lt;/button\u0026gt;  \u0026lt;/div\u0026gt;  \u0026lt;/CSSTransition\u0026gt;  ))}  \u0026lt;/TransitionGroup\u0026gt;   \u0026lt;button  className=\u0026#34;btn-add\u0026#34;  onClick={() =\u0026gt; {  const text = prompt(\u0026#39;Enter some text\u0026#39;);  if (text) {  setItems((items) =\u0026gt; [...items, { id: nanoid(), text }]);  }  }}  \u0026gt;  Add Item  \u0026lt;/button\u0026gt;  \u0026lt;/div\u0026gt;  ); }   1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26  /* Style */ .container {  margin: 15px 0; } .btn-remove {  margin-left: 0.5rem; } .btn-add {  margin-top: 0.5rem; }  /* Transition */ .item-enter {  opacity: 0; } .item-enter-active {  opacity: 1;  transition: opacity 500ms ease-in; } .item-exit {  opacity: 1; } .item-exit-active {  opacity: 0;  transition: opacity 500ms ease-in; }   Transition giữa các Page Để tạo transition giữa các page ta có thể dùng Component TransitionGroup. Tuy nhiên cần có key phân biệt giữa các Component được render. Mình sử dụng đường dẫn để làm key, do đó cần sử dụng thêm hook useLocation của React Router để lấy ra được đường dẫn.\nHook useLocation chỉ sử dụng được khi nằm trong Component BrowserRouter nên chúng ta phải tạo thêm 1 Component con để tạo transition. Tạo thêm một Component là RoutesWithTransition:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27  import React from \u0026#39;react\u0026#39;; import { Routes, Route, useLocation } from \u0026#39;react-router-dom\u0026#39;; import { TransitionGroup, CSSTransition } from \u0026#39;react-transition-group\u0026#39;; import Home from \u0026#39;./pages/Home\u0026#39;; import Page1 from \u0026#39;./pages/page1/Page1\u0026#39;; import Page2 from \u0026#39;./pages/page2/Page2\u0026#39;; import Page3 from \u0026#39;./pages/page3/Page3\u0026#39;; import \u0026#39;./style.css\u0026#39;;  export default function RoutesWithTransition() {  // Lấy ra location dùng hook useLocation  const location = useLocation();   return (  \u0026lt;TransitionGroup\u0026gt;  {/* Dùng location.pathname làm key */}  \u0026lt;CSSTransition key={location.pathname} classNames=\u0026#34;slide\u0026#34; timeout={300}\u0026gt;  \u0026lt;Routes\u0026gt;  \u0026lt;Route path=\u0026#34;/\u0026#34; element={\u0026lt;Home /\u0026gt;} /\u0026gt;  \u0026lt;Route path=\u0026#34;/page1\u0026#34; element={\u0026lt;Page1 /\u0026gt;} /\u0026gt;  \u0026lt;Route path=\u0026#34;/page2\u0026#34; element={\u0026lt;Page2 /\u0026gt;} /\u0026gt;  \u0026lt;Route path=\u0026#34;/page3\u0026#34; element={\u0026lt;Page3 /\u0026gt;} /\u0026gt;  \u0026lt;/Routes\u0026gt;  \u0026lt;/CSSTransition\u0026gt;  \u0026lt;/TransitionGroup\u0026gt;  ); }   Sau đó đổi lại phần cấu hình routes ở App.js sử dụng Component trên:\n1 2 3 4 5 6 7 8  ...  \u0026lt;BrowserRouter\u0026gt;  \u0026lt;nav\u0026gt;...\u0026lt;/nav\u0026gt;   {/* Config routes */}  \u0026lt;RoutesWithTransition /\u0026gt;  \u0026lt;/BrowserRouter\u0026gt; ...   Tham khảo toàn bộ code mẫu tại đây: https://stackblitz.com/edit/react-transition-group-react-router-v6?file=src/App.js.\nHappy coding 😎\n","permalink":"https://robinhuy.github.io/blog/tao-hieu-ung-transition-cho-react-app-voi-react-transition-group/","summary":"Để cho các App React hoạt động mượt mà hơn, đẹp hơn, trải nghiệm người dùng tốt hơn, \u0026hellip; thì nên có thêm các hiệu ứng animation, transition.\nBài viết này mình sẽ hướng dẫn các bạn sử dụng thư viện React Transition Group để tạo hiệu ứng transition một cách nhanh chóng.\nĐể cho tiện thì mình sẽ demo code trên stackblitz.com. Trong ví dụ sẽ sử dụng cả React Router v6 để cấu hình multiple page, và có hiệu ứng transition giữa các page.","title":"Tạo hiệu ứng transition cho React App với React Transition Group"},{"content":"RTK Query là một addon trong bộ thư viện Redux Toolkit. Nó giúp chúng ta thực hiện data fetching một cách đơn giản hơn thay vì sử dụng createAsyncThunk để thực hiện async action. Chú ý RTK Query là dùng để query (kết nối API), chứ không phải dùng để code async trong Redux thay cho createAsyncThunk.\nNếu bạn chưa từng sử dụng Redux Toolkit thì có thể xem bài hướng dẫn này trước: Hướng dẫn sử dụng React Router và Redux Toolkit.\nCòn nếu chưa biết cách dùng createAsyncThunk thì xem bài hướng dẫn này: Hướng dẫn sử dụng createAsyncThunk trong Redux Toolkit.\nChúng ta sẽ tiếp tục sử dụng ví dụ này để demo: https://stackblitz.com/edit/react-router-redux-toolkit-fetch-api?file=src/App.js. Trong code mẫu này có sử dụng createAsyncThunk và fetch API để kết nối đến API. Chúng ta sẽ thay thế phần kết nối API bằng RTK Query.\nĐầu tiên chúng ta tạo 1 file mới, tương tự như tạo một slice, và file này sẽ dùng để khai báo các lệnh gọi API. Ví dụ trong thư mục store tạo thêm file api.js với nội dung như sau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14  import { createApi, fetchBaseQuery } from \u0026#34;@reduxjs/toolkit/query/react\u0026#34;;  export const api = createApi({  // Tương tự tên Slice khi tạo Slice thông thường  reducerPath: \u0026#34;api\u0026#34;,   // Cấu hình chung cho tất cả request  baseQuery: fetchBaseQuery({  baseUrl: \u0026#34;https://fake-rest-api-nodejs.herokuapp.com/\u0026#34;,  }),   // Các endpoints (lệnh gọi request)  endpoints: (builder) =\u0026gt; ({}), });   Nhúng API này vào trong store như một Slice, sửa file store/index.js:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  import { configureStore } from \u0026#34;@reduxjs/toolkit\u0026#34;; import { api } from \u0026#34;./api\u0026#34;; import userReducer from \u0026#34;./userSlice\u0026#34;;  export const store = configureStore({  reducer: {  // Tạo thêm slice từ api  [api.reducerPath]: api.reducer,   // Slice thông thường  user: userReducer,  },   // Thêm cấu hình middleware để dùng được các chức năng của RTK Query như caching, invalidation, polling, ...  middleware: (getDefaultMiddleware) =\u0026gt;  getDefaultMiddleware().concat(api.middleware), });   Sau khi cấu hình xong, chúng ta có thể thêm các endpoint để thực hiện request đến API. Endpoint trong RTK Query phân làm 2 loại:\n Query: Dùng để lấy dữ liệu (có thể lưu cache). Mutation: Dùng để cập nhật dữ liệu (validate cache).  Với request login thì mình sẽ dùng loại mutation, sửa lại phần endpoints của file store/api.js:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  export const api = createApi({  ...  endpoints: (builder) =\u0026gt; ({  // Tạo 1 request dạng mutation  login: builder.mutation({  query: (credentials) =\u0026gt; ({  url: `login`,  method: \u0026#39;POST\u0026#39;,  body: credentials,  }),  }),   getUsers: builder.query({  query: () =\u0026gt; `users`,  }),  }),  ... }  // Export ra ngoài thành các hooks để sử dụng theo cú pháp use + endpoints (login) + endpoints type (mutation) export const { useLoginMutation } = api;   Sửa lại trang login, sử dụng mutation ở trên để gọi API:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  ... // Import hook để sử dụng import { useLoginMutation } from \u0026#39;../store/api\u0026#39;;  export default function Login() {  // Sử dụng hook useLoginMutation sẽ trả về method login dùng để gọi request  // kèm theo 1 số trạng thái như loading, dữ liệu hoặc lỗi trả về khi gọi request  const [login, { isLoading, data, error }] = useLoginMutation();  ...  return (  \u0026lt;\u0026gt;  ...  {/* Gọi login method lấy từ hook useLoginMutation() ở trên */}  {/* Có thể sử dụng biến isLoading để hiển thị trạng thái loading thay cho state */}  \u0026lt;button onClick={() =\u0026gt; login({ email, password })} disabled={isLoading}\u0026gt;  Login  \u0026lt;/button\u0026gt;  \u0026lt;/\u0026gt;  ); }   Như vậy việc gọi API sẽ trở nên dễ dàng hơn. Bạn cũng có thể sử dụng biến error để hiển thị báo lỗi trên giao diện.\nTrong trường hợp cần lưu dữ liệu vào trong store, ví dụ cập nhật state ở Slice khác thì làm tương tự như khi dùng createAsyncThunk. Sửa lại file store/userSlice.js để thêm logic lưu thông tin user sau khi user đăng nhập thành công:\n1 2 3 4 5 6 7   extraReducers: (builder) =\u0026gt; {  // Xử lý logic khi endpoint login được fulfilled  builder.addMatcher(api.endpoints.login.matchFulfilled, (state, action) =\u0026gt; {  // Lưu thông tin user vào state  state.currentUser = action.payload;  });  },   Hoặc RTK Query cũng hỗ trợ lấy state từ Slice khác. Ví dụ sau khi login thành công thì các request đến private API cần có gửi thêm token. Chúng ta có thể lấy token từ trong userSlice (state currentUser).\nSửa lại hàm fetchBaseQuery() ở store/api.js để cho phép các request đều gửi kèm token nếu có:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17   baseQuery: fetchBaseQuery({  baseUrl: \u0026#39;https://fake-rest-api-nodejs.herokuapp.com/\u0026#39;,   // Xử lý header trước khi gửi request  prepareHeaders: (headers, { getState }) =\u0026gt; {  // getState() giúp lấy ra toàn bộ state trong store  // getState().user lấy ra state trong userSlice  const token = getState().user.currentUser?.token;   // Nếu có token thì thêm vào headers  if (token) {  headers.set(\u0026#39;Authorization\u0026#39;, `Bearer ${token}`);  }   return headers;  },  }),   Kiểm tra thử bằng cách tạo thêm 1 endpoint nữa để lấy ra danh sách user. Endpoint này là private và nếu không có token sẽ trả về lỗi 401. Bổ sung thêm endpoint getUsers vào file store/api.js:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15  export const api = createApi({  ...  endpoints: (builder) =\u0026gt; ({  login: builder.mutation(...),   // Thêm endpoint dạng query  getUsers: builder.query({  query: () =\u0026gt; `users`,  }),  }),  ... }  // Export các hooks ra ngoài để sử dụng export const { useLoginMutation, useGetUsersQuery } = api;   Sửa lại nội dung trang Dashboard (sau khi login thành công) để hiển thị thông tin users lấy từ API:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29  ... import { useGetUsersQuery } from \u0026#39;../store/api\u0026#39;;  export default function Dashboard() {  // Sử dụng hook useGetUsersQuery để gọi API  const { data, isLoading, error } = useGetUsersQuery();   ...   return (  \u0026lt;\u0026gt;  ...   \u0026lt;h2\u0026gt;User List\u0026lt;/h2\u0026gt;   {isLoading ? (  \u0026#39;fetching data ...\u0026#39;  ) : (  \u0026lt;ul\u0026gt;  {data?.map((item) =\u0026gt; (  \u0026lt;li key={item.id}\u0026gt;  {item.firstName} {item.lastName}  \u0026lt;/li\u0026gt;  ))}  \u0026lt;/ul\u0026gt;  )}  \u0026lt;/\u0026gt;  ); }   Vậy là chúng ta đã làm xong một ứng dụng nhỏ sử dụng RTK Query cho việc fetching data. Addon này còn nhiều chức năng nữa như caching, prefetching, polling, code splitting, \u0026hellip; các bạn hãy đọc thêm document trên trang chủ của thư viện để áp dụng vào dự án.\nTham khảo toàn bộ code mẫu tại đây: https://stackblitz.com/edit/react-router-redux-toolkit-fetch-api-2c64iz?file=src/App.js.\nHappy coding 😎\n","permalink":"https://robinhuy.github.io/blog/huong-dan-su-dung-rtk-query-trong-redux-toolkit/","summary":"RTK Query là một addon trong bộ thư viện Redux Toolkit. Nó giúp chúng ta thực hiện data fetching một cách đơn giản hơn thay vì sử dụng createAsyncThunk để thực hiện async action. Chú ý RTK Query là dùng để query (kết nối API), chứ không phải dùng để code async trong Redux thay cho createAsyncThunk.\nNếu bạn chưa từng sử dụng Redux Toolkit thì có thể xem bài hướng dẫn này trước: Hướng dẫn sử dụng React Router và Redux Toolkit.","title":"Hướng dẫn sử dụng RTK Query trong Redux Toolkit"},{"content":"Nếu bạn chưa từng sử dụng Redux Toolkit thì có thể xem bài hướng dẫn này trước: Hướng dẫn sử dụng React Router và Redux Toolkit.\nBài viết này mình sẽ hướng dẫn cách thực hiện các async logic (xử lý bất đồng bộ) trong Redux Toolkit, cụ thể là sử dụng createAsyncThunk để kết nối với API login.\nVí dụ demo chúng ta có thể lấy luôn từ ví dụ trước rồi cải tiến thêm: https://stackblitz.com/edit/react-router-redux-toolkit?file=src/index.js.\nĐầu tiên là bổ sung giao diện để có thêm ô nhập mật khẩu vì khi gọi API sẽ cần có email và password.\nBây giờ khi người dùng bấm nút Login, chúng ta sẽ gọi vào API để thực hiện login. Để gọi API thì chúng ta dùng luôn hàm fetch() có sẵn. Lệnh fetch() là một lệnh async, nên sẽ không viết code logic vào trong action để cập nhật state như trước, mà chúng ta sẽ dùng hàm createAsyncThunk để tạo ra các async action.\nVí dụ tạo một async action (và export luôn ra ngoài để sử dụng ở Component):\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30  export const login = createAsyncThunk(  // Tên action  \u0026#34;user/login\u0026#34;,   // Code async logic, tham số đầu tiên data là dữ liệu truyền vào khi gọi action  async (data, { rejectWithValue }) =\u0026gt; {  // Gọi lên API backend  const response = await fetch(  \u0026#34;https://fake-rest-api-nodejs.herokuapp.com/login\u0026#34;,  {  method: \u0026#34;POST\u0026#34;,  headers: {  \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;,  },  body: JSON.stringify(data),  }  );   // Convert dữ liệu ra json  const jsonData = await response.json();   // Nếu bị lỗi thì reject  if (response.status \u0026lt; 200 || response.status \u0026gt;= 300) {  return rejectWithValue(jsonData);  }   // Còn không thì trả về dữ liệu  return jsonData;  } );   Ví dụ trên sẽ tạo 1 request đến API backend: https://fake-rest-api-nodejs.herokuapp.com/login, API này sẽ trả về thông tin user nếu đăng nhập thành công. Các bạn có thể tự tạo một API riêng bằng cách dùng tool này: https://github.com/robinhuy/fake-rest-api-nodejs (xem hướng dẫn Tiếng Việt tại đây).\nTiếp theo chúng ta sẽ tạo ra các extra Reducers dùng để xử lý các trạng thái của async action ở trên:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47  // Tạo login action (async) export const login = createAsyncThunk(\u0026#34;user/login\u0026#34;, {  // Code như trên });  export const userSlice = createSlice({  name: \u0026#34;user\u0026#34;,   // Thêm 1 số state như trạng thái loading, báo lỗi và thông tin user đang đăng nhập  initialState: {  isLoading: false,  errorMessage: \u0026#34;\u0026#34;,  currentUser: null,  },   // Các action bình thường (sync action)  reducers: {  // Logout không gọi API mà chỉ đơn giản là cập nhật state  logout: (state) =\u0026gt; {  state.currentUser = null;  state.errorMessage = \u0026#34;\u0026#34;;  },  },   // Code logic xử lý async action  extraReducers: (builder) =\u0026gt; {  // Bắt đầu thực hiện action login (Promise pending)  builder.addCase(login.pending, (state) =\u0026gt; {  // Bật trạng thái loading  state.isLoading = true;  });   // Khi thực hiện action login thành công (Promise fulfilled)  builder.addCase(login.fulfilled, (state, action) =\u0026gt; {  // Tắt trạng thái loading, lưu thông tin user vào store  state.isLoading = false;  state.currentUser = action.payload;  });   // Khi thực hiện action login thất bại (Promise rejected)  builder.addCase(login.rejected, (state, action) =\u0026gt; {  // Tắt trạng thái loading, lưu thông báo lỗi vào store  state.isLoading = false;  state.errorMessage = action.payload.message;  });  }, });   Sau khi code xong action thì chúng ta dispatch như bình thường. Ví dụ truyền vào 1 object có chứa email và password:\n1  \u0026lt;button onClick={() =\u0026gt; dispatch(login({ email, password }))}\u0026gt;Login\u0026lt;/button\u0026gt;   Sửa lại thêm một số logic khi có thêm các state như isLoading, currentUser, errorMessage, cái này thì các bạn hãy thử tự làm vì cách lấy state ra vẫn như cũ.\nTham khảo toàn bộ code mẫu tại đây: https://stackblitz.com/edit/react-router-redux-toolkit-fetch-api?file=src/App.js.\nHappy coding 😎\n","permalink":"https://robinhuy.github.io/blog/huong-dan-su-dung-createasyncthunk-trong-redux-toolkit/","summary":"Nếu bạn chưa từng sử dụng Redux Toolkit thì có thể xem bài hướng dẫn này trước: Hướng dẫn sử dụng React Router và Redux Toolkit.\nBài viết này mình sẽ hướng dẫn cách thực hiện các async logic (xử lý bất đồng bộ) trong Redux Toolkit, cụ thể là sử dụng createAsyncThunk để kết nối với API login.\nVí dụ demo chúng ta có thể lấy luôn từ ví dụ trước rồi cải tiến thêm: https://stackblitz.","title":"Hướng dẫn sử dụng createAsyncThunk trong Redux Toolkit"},{"content":"Trong một project sẽ có những đoạn code có thể tái sử dụng ở nhiều nơi, thường đặt ở trong các thư mục đại loại như: helper, utils, \u0026hellip; Vậy nếu những đoạn code đó lại được tái sử dụng ở nhiều project thì sao?\nCó nhiều phương pháp áp dụng như: Git Submodules, Monorepos, Bit, \u0026hellip; hay tạo hẳn thư viện riêng và đẩy lên cloud.\nTrong bài viết này mình sẽ hướng dẫn các bạn cách sử dụng Git Submodules cho việc tái sử dụng code.\nGit Submodules cho phép tạo một (hoặc nhiều) repository bên trong repository hiện tại. Những đoạn code tái sử dụng cho nhiều project thì có thể đặt ra một repository riêng, sau đó nhúng vào trong các project cần sử dụng theo dạng Sub module.\nVí dụ mình có một project my-project, có cấu trúc như sau:\nTrong đó mình muốn code nằm trong thư mục src/library có thể tái sử dụng được ở nhiều project, vậy mình sẽ tạo 1 repository mới là my-library, và nhúng repo này vào trong my-project. Ở các project khác muốn dùng chung code thì cũng nhúng my-library vào qua submodules.\nĐể tạo submodule cho repository my-project ta dùng lệnh sau:\n1  git submodule add https://github.com/robinhuy/my-library   Lệnh này sẽ tạo ra một thư mục mới là my-library và một file mới là .gitmodules trong project my-project. Thư mục này chính là repository my-library luôn, chúng ta có thể cd vào và thực hiện các lệnh fetch, pull, push, \u0026hellip; như bình thường. Còn file .gitmodules sẽ chứa thông tin về submodule vừa tạo.\nNgoài ra, để cho cấu trúc thư mục được đẹp, khi tạo submodule chúng ta có thể đổi lại tên thư mục, hay cho vào trong một thư mục con cũng được. Ví dụ mình tạo submodule là folder src/library thay vì my-library thì dùng lệnh sau:\n1  git submodule add https://github.com/robinhuy/my-library src/library   Trường hợp tạo nhầm thì các bạn có thể xóa submodule đi bằng cách chạy lệnh sau:\n1 2  git rm -rf my-library rm -rf .git/modules/my-library   với my-library là thư mục chứa submodule (được khai báo trong file .gitmodules).\nCòn nếu project của bạn đã được cấu hình Git submodule từ trước, thì khi clone project về bạn chạy thêm lệnh sau để nó clone luôn cả submodule:\n1  git submodule update --init --recursive   Chú ý mọi chỉnh sửa trong submodule muốn cập nhật vào repository chính thì cần tạo thêm commit. Như vậy khi có thay đổi từ repository my-library và muốn cập nhật vào trong my-project thì cần:\n Vào trong thư mục src/library để pull code mới nhất về (hoặc có thể chỉnh sửa trong này rồi push code lên). Commit thay đổi ở my-project.  Như vậy dùng Git submodules sẽ giúp chúng ta chia sẻ code giữa các project một cách dễ dàng. Tuy nhiên nhược điểm của nó là việc đồng bộ code giữa các project không diễn ra tự động mà phải chủ động update ở từng project.\n","permalink":"https://robinhuy.github.io/blog/tai-su-dung-code-cho-nhieu-project-voi-git-submodules/","summary":"Trong một project sẽ có những đoạn code có thể tái sử dụng ở nhiều nơi, thường đặt ở trong các thư mục đại loại như: helper, utils, \u0026hellip; Vậy nếu những đoạn code đó lại được tái sử dụng ở nhiều project thì sao?\nCó nhiều phương pháp áp dụng như: Git Submodules, Monorepos, Bit, \u0026hellip; hay tạo hẳn thư viện riêng và đẩy lên cloud.\nTrong bài viết này mình sẽ hướng dẫn các bạn cách sử dụng Git Submodules cho việc tái sử dụng code.","title":"Tái sử dụng code cho nhiều project với Git Submodules"},{"content":"Nếu bạn là một Web Frontend Developer thì chắc sẽ không xa lạ gì với Webpack, một công cụ bundle code mạnh mẽ. Webpack thường được tích hợp sẵn trong các thư viện như React, Angular, Vue, … và có nhiều người thậm chí còn không biết đến sự tồn tại cũng như tác dụng của nó 😅\nBài viết này mình sẽ hướng dẫn các bạn cách tối ưu một static website (web tĩnh chỉ gồm HTML CSS JS) bằng Webpack. Chú ý static web này thuộc dạng multiple pages chứ không phải single page, và có thể áp dụng cho các website động có kiến trúc dạng Monolithic (dùng view template engine để render).\nVí dụ cấu trúc thư mục một static website thường có dạng như sau:\nSau khi cắt HTML CSS từ bản thiết kế xong, trước khi đẩy code lên production, chúng ta cần thêm một bước tối ưu như: Minify (obfuscate) code, nén ảnh, đánh version file CSS, JS để revalidate cache, …\nChúng ta có thể dùng các tool như Grunt, Gulp, … Còn trong bài này mình sẽ dùng Webpack với những tính năng ưu việt hơn, giúp lập trình viên có thể lười hơn.\nCode tham khảo sau khi cấu hình xong các bạn có thể xem luôn tại đây (xem xong nhớ Star để ủng hộ tác giả): https://github.com/robinhuy/webpack-static-pages-template.\nGiờ chúng ta sẽ thử cấu hình từ đầu để hiểu được cơ bản cách Webpack hoạt động và có thể tùy biến theo từng trường hợp cụ thể.\n1. Khởi tạo project Khởi tạo project với lệnh npm init và nhập vào các thông số như: Tên project, author, …\nCài webpack và webpack-cli với môi trường dev:\n1  npm install webpack webpack-cli --save-dev   hoặc dùng yarn:\n1  yarn add webpack webpack-cli --dev   Sau khi cài xong chúng ta sẽ thấy có file package.json chứa các thông tin về project vừa tạo.\nSửa lại file package.json, bỏ phần main đi và thêm option private với giá trị là true. Cái này để tránh lỗi về sau khi build production.\n2. Bundle Code Thử nghiệm chức năng đầu tiên của Webpack đó là Bundle code, cho phép chúng ta gộp các file js lại thành 1 file (bundle) và inject vào trong một file HTML.\nĐầu tiên tạo thư mục src để chứa source code, trong thư mục src tạo 1 file là index.js với nội dung tùy ý. Ví dụ mình tạo file index.js chỉ với 1 dòng console.log(\u0026lsquo;index.js\u0026rsquo;):\nRồi giờ chạy lệnh sau để bundle code (sử dụng webpack cài trong project):\n1  npx webpack   Sau khi chạy xong lệnh trên thành công, sẽ có 1 thư mục tự động tạo ra là dist và bên trong có 1 file là main.js. Xem thử nội dung thì thấy giống hệt file index.js mà chúng ta vừa tạo.\nNhư vậy là chúng ta vừa bundle file index.js thành main.js. Nhưng file index.js đơn giản quá chưa thấy có gì. Mình sẽ sửa lại cho nó phức tạp hơn một chút bằng cách nhúng jQuery vào để sửa DOM. Với các static website thì mình thấy dùng jQuery vẫn ổn, không cần thiết phải chạy theo các trending framework làm gì.\nĐể sử dụng jQuery chúng ta có thể chèn link CDN hoặc vào trang chủ để down file js về rồi nhúng vào file HTML. Tuy nhiên khi dùng webpack chúng ta có thể cài jQuery qua npm và import vào file js khác để sử dụng.\nCài jQuery:\n1  npm install jquery   hoặc\n1  yarn add jquery   Sau khi cài xong, jQuery sẽ được tải vào trong thư mục node_modules và được khai báo trong mục dependencies của file package.json.\nSửa lại file index.js như sau:\n1 2 3  import $ from \u0026#39;jquery\u0026#39;;  $(\u0026#39;body\u0026#39;).html(\u0026#39;\u0026lt;h1\u0026gt;Hello Webpack\u0026lt;/h2\u0026gt;\u0026#39;);   Giờ để cho dễ test, các bạn tạo thêm cho mình 1 file index.html ở trong thư mục dist, sau đó nhúng file main.js vào trong file index.html\nRồi chạy lại lệnh build: npx webpack.\nSau khi build thành công thì bật file HTML lên các bạn sẽ thấy nó hiển thị ra màn hình dòng chữ “Hello Webpack”. Như vậy là chúng ta đã bundle jQuery + index.js thành file main.js và nhúng vào file index.html. Và nếu bạn bật file main.js lên xem thì sẽ thấy là nó đã được minify + obfuscated.\nCác bạn có thể thử viết thêm function, import code từ nhiều file khác nhau và bundle lại thành một file như trên.\n3. Sử dụng file cấu hình Ở ví dụ trên chúng ta chạy webpack với cấu hình mặc định. Để có thể cấu hình được nhiều hơn, tùy chỉnh dễ hơn thì chúng ta tạo thêm 1 file cấu hình và chạy webpack theo cấu hình của file đó.\nỞ thư mục gốc tạo 1 file là webpack.config.js với nội dung như sau:\n1 2 3 4 5 6 7 8 9  const path = require(\u0026#39;path\u0026#39;);  module.exports = {  entry: \u0026#39;./src/index.js\u0026#39;,  output: {  filename: \u0026#39;main.js\u0026#39;,  path: path.resolve(__dirname, \u0026#39;dist\u0026#39;),  }, };   Đây chính là cấu hình mặc định, chạy thử với lệnh:\n1  npx webpack --config webpack.config.js   bạn sẽ thấy kết quả y hệt như cũ.\nBây giờ thử sửa file config, ví dụ sửa phần output \u0026gt; filename thành bundle.js,xóa hết file .js trong thư mục dist đi và build lại, bạn sẽ thấy sau khi build thì file bundle của chúng ta đã đổi thành bundle.js như trong file cấu hình.\nFile cấu hình của webpack chỉ đơn giản là export ra một object có chứa các options mà webpack cung cấp và giá trị do chúng ta thiết lập, options nào không khai báo thì webpack sẽ sử dụng giá trị mặc định. Ví dụ một file cấu hình:\n4. Sử dụng Plugin Sử dụng plugin để bổ sung thêm tính năng mong muốn, có thể dùng những plugin có sẵn hoặc tự viết. Ví dụ một số Plugin hay dùng: HtmlWebpackPlugin, MiniCssExtractPlugin, ImageMinimizerWebpackPlugin, ProvidePlugin.\nVới những plugin có sẵn trên npm thì đầu tiên chúng ta cần cài thư viện trước:\n1  npm install html-webpack-plugin --save-dev   hoặc\n1  yarn add html-webpack-plugin --dev   Sau khi cài xong thì khai báo plugin sử dụng ở file config với options plugins, giá trị của nó là 1 mảng các plugin. Ví dụ cài plugin html-webpack-plugin để cho phép tạo file html mẫu và tùy chỉnh inject script theo cách mình muốn.\n1 2 3 4 5 6 7 8 9 10 11 12  const HtmlWebpackPlugin = require(\u0026#39;html-webpack-plugin\u0026#39;);  module.exports = {  entry: \u0026#39;index.js\u0026#39;,   output: {  path: `${__dirname}/dist`,  clean: true,  },   plugins: [new HtmlWebpackPlugin()], };   Bây giờ thay vì tạo sẵn file html ở trong thư mục dist, thì chúng ta chỉ cần chạy lệnh build, webpack sẽ tự tạo ra 1 file html và nhúng file bundle vào luôn. Chú ý option output clean: true là để tự động xóa hết các file cũ trong thư mục dist trước khi build.\n5. Cache Busting Thông thường trình duyệt sẽ tự động cache các file tĩnh như CSS, JS để tăng performance (trừ trường hợp phía server cấu hình không cho phép cache file). Thỉnh thoảng bạn sẽ gặp trường hợp sửa file CSS nhưng người dùng không thấy có gì thay đổi. Muốn áp dụng style mới thì phải tự clear cache trên trình duyệt (ít người dùng biết và tự làm việc này).\nVậy để tối ưu performance, vẫn cho phép cache file với thời gian dài, thì chúng ta áp dụng cache busting. Có thể là dùng query strings hoặc đổi tên file để browser nhận biết là có thay đổi và tải file mới về. Để đảm bảo chính xác thì mình chọn cách đổi tên file, mỗi lần thay đổi code thì chúng ta lại đổi tên file đi để trình duyệt tự tải lại.\nThay vì làm thủ công thì chúng ta sẽ cấu hình trong webpack để webpack tự động build file với tên mới và tự động nhúng vào file html. Kết hợp option entry + module và plugin HtmlWebpackPlugin + MiniCssExtractPlugin, chúng ta có thể bundle ra các file JS, CSS với tên theo dạng hash hoặc contenthash rồi tự động nhúng vào file html.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32  const HtmlWebpackPlugin = require(\u0026#39;html-webpack-plugin\u0026#39;); const MiniCssExtractPlugin = require(\u0026#39;mini-css-extract-plugin\u0026#39;);  module.exports = {  entry: {  index: {  import: \u0026#39;./src/index.js\u0026#39;,  filename: \u0026#39;index.[contenthash].js\u0026#39;,  },  },   output: {  path: `${__dirname}/dist`,  clean: true,  },   plugins: [  new HtmlWebpackPlugin(),  new MiniCssExtractPlugin({  filename: \u0026#39;[name].[contenthash].css\u0026#39;,  }),  ],   module: {  rules: [  {  test: /.s?css$/,  use: [MiniCssExtractPlugin.loader, \u0026#39;css-loader\u0026#39;],  },  ],  }, };   Chú ý ở trên mình có sử dụng thêm thư viện css-loader để cho phép import file CSS.\nTest thử bằng cách tạo thêm 1 file css trong thư mục src, ví dụ style.css:\n1 2 3  h1 {  color: red; }   Tiếp đó import vào file index.js:\n1 2 3 4 5  import \u0026#39;./style.css\u0026#39;;  import $ from \u0026#39;jquery\u0026#39;;  $(\u0026#39;body\u0026#39;).prepend(\u0026#39;\u0026lt;h1\u0026gt;Hello Webpack\u0026lt;/h2\u0026gt;\u0026#39;);   Chạy lại lệnh build và kết quả sẽ như này:\n6. Multiple pages Tương tự, thêm 1 chút cấu hình ở entry và HtmlWebpackPlugin, chúng ta sẽ có thể code nhiều file HTML (multiple pages), và cho phép inject các script khác nhau vào từng file. Ví dụ:\nCấu hình multiple entry cho 2 trang là index và about:\n1 2 3 4 5 6 7 8 9 10  entry: {  index: {  import: \u0026#34;./src/js/index.js\u0026#34;,  filename: \u0026#34;js/index.[contenthash].js\u0026#34;,  },  about: {  import: \u0026#34;./src/js/about.js\u0026#34;,  filename: \u0026#34;js/about.[contenthash].js\u0026#34;,  }, },   Cấu hình plugin HtmlWebpackPlugin để cho phép sử dụng file html sẵn làm template thay vì tự sinh ra file html:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  plugins: [  new HtmlWebpackPlugin({  template: \u0026#34;./src/index.html\u0026#34;,  inject: \u0026#34;body\u0026#34;,  filename: \u0026#34;index.html\u0026#34;,  }),  new HtmlWebpackPlugin({  template: \u0026#34;./src/about.html\u0026#34;,  inject: \u0026#34;body\u0026#34;,  filename: \u0026#34;about.html\u0026#34;,  }),   new MiniCssExtractPlugin({  filename: \u0026#34;css/[name].[contenthash].css\u0026#34;,  }), ],   Tự build lại để xem kết quả!\n7. Cấu hình theo môi trường Ở trên chúng ta đã cấu hình được tương đối đầy đủ các chức năng. Nhưng nếu mỗi lần code lại phải build lại code để xem kết quả thì rất mất công, và khi debug cũng khó vì code đã được minify + obfuscate. Vì vậy chúng ta sẽ bổ sung thêm cấu hình cho môi trường dev, và bổ sung lệnh chạy cho 2 môi trường riêng biệt.\nThay vì dùng chung 1 file config thì bây giờ mình sẽ tạo ra 3 file:\n webpack.common.js: Chứa cấu hình chung cho cả 2 môi trường. webpack.dev.js: Chứa cấu hình cho môi trường development. webpack.prod.js: Chứa cấu hình cho môi trường production.  File webpack.common.js giữ nguyên, file webpack.dev.js và webpack.prod.js thì sử dụng thư viện webpack-merge để copy cấu hình từ file common sang và bổ sung thêm 1 số cấu hình riêng.\nVí dụ file webpack.prod.js:\n1 2 3 4 5 6  const { merge } = require(\u0026#39;webpack-merge\u0026#39;); const common = require(\u0026#39;./webpack.common.js\u0026#39;);  module.exports = merge(common, {  mode: \u0026#39;production\u0026#39;, });   File webpack.dev.js:\n1 2 3 4 5 6 7 8 9 10 11 12 13  const { merge } = require(\u0026#39;webpack-merge\u0026#39;); const common = require(\u0026#39;./webpack.common.js\u0026#39;);  module.exports = merge(common, {  mode: \u0026#39;development\u0026#39;,   devtool: \u0026#39;inline-source-map\u0026#39;,   devServer: {  contentBase: \u0026#39;./dist\u0026#39;,  hot: true,  }, });   Khi chạy ở môi trường dev thì dùng lệnh:\n1  npx webpack serve --open --config webpack.dev.js   Khi chạy ở môi trường production thì dùng lệnh:\n1  npx webpack --config webpack.prod.js   Để cho nhanh thì chúng ta có thể dùng npm scripts bằng cách sửa file package.json mục scripts:\n1 2 3 4  \u0026#34;scripts\u0026#34;: {  \u0026#34;start\u0026#34;: \u0026#34;webpack serve --open --config webpack.dev.js\u0026#34;,  \u0026#34;build\u0026#34;: \u0026#34;webpack --config webpack.prod.js\u0026#34; },   Sau đó khi cần dev thì chạy lệnh: npm start hoặc yarn start, còn khi build production thì chạy lệnh: npm run build hoặc yarn build.\nVậy là xong, chúng ta đã setup xong một project static web sử dụng Webpack để bundle và tối ưu cho production. Các bạn có thể tự tìm hiểu thêm trên trang chủ của webpack để cấu hình chi tiết hơn cho từng project.\nTham khảo cấu hình và cấu trúc thư mục demo đầy đủ hơn tại đây: https://github.com/robinhuy/webpack-static-pages-template.\n","permalink":"https://robinhuy.github.io/blog/toi-uu-static-page-voi-webpack/","summary":"Nếu bạn là một Web Frontend Developer thì chắc sẽ không xa lạ gì với Webpack, một công cụ bundle code mạnh mẽ. Webpack thường được tích hợp sẵn trong các thư viện như React, Angular, Vue, … và có nhiều người thậm chí còn không biết đến sự tồn tại cũng như tác dụng của nó 😅\nBài viết này mình sẽ hướng dẫn các bạn cách tối ưu một static website (web tĩnh chỉ gồm HTML CSS JS) bằng Webpack.","title":"Tối ưu static website với Webpack"},{"content":"Bạn có 1 trang web tĩnh (static web chỉ gồm HTML CSS JS) và muốn đẩy lên mạng để chia sẻ cho người khác mà không mất phí? Hãy tận dụng Github - nền tảng lưu trữ, quản lý và chia sẻ mã nguồn mở hàng đầu hiện nay.\nNgoài việc lưu trữ và quản lý mã nguồn, Github còn cung cấp một dịch vụ cho phép hosting static web là Github Pages. Source code lưu trữ trực tiếp trên Github và truy cập thông qua tên miền miễn phí là [username].github.io. Ví dụ username trên github của bạn là robinhuy thì bạn sẽ có 1 tên miền miễn phí là https://robinhuy.github.io.\n Nếu bạn chưa biết sử dụng Github hoặc chưa biết cách đẩy code lên Github thì tham khảo bài viết này Các cách đẩy code lên Github.\n Cách 1: Tạo repository với tên trùng với tên miền Github Pages Ví dụ bạn có username là sophshep, vậy chỉ cần tạo 1 public repository với tên là sophshep.github.io và đẩy code web tĩnh lên đó là xong. Chúng ta sẽ có ngay 1 website và truy cập theo đường dẫn giống với vị trí của file HTML trong repository. Chú ý không viết sai chính tả.\nVới tên file là index.html thì khi gõ lên trình duyệt có thể bỏ qua. Ví dụ gõ https://sophshep.github.io/ và https://sophshep.github.io/index.html thì kết quả là như nhau.\nCách 2: Tạo một repository với tên bất kỳ khác tên miền Github Pages Tạo 1 public repository và đẩy code web tĩnh lên. Sau đó vào mục Settings của repository đó để bật cấu hình Github Pages.\nKéo xuống phần Github Pages, chọn chuyển qua trang cấu hình cho Github Pages.\nChọn branch mà bạn muốn đẩy code lên (thường là main hoặc master), sau đó bấm Save.\nSau khi cấu hình xong, chúng ta coi như repository hiện tại là một thư mục con của repository https://[username].github.io và truy cập tương tự.\nVí dụ tên repository là my-website thì sẽ truy cập địa chỉ là https://[username].github.io/my-website/ (trong repository có file index.html).\nCách 3: Sử dụng website raw.githack.com Sử dụng website https://raw.githack.com, chúng ta có thể xem kết quả hiển thị của bất kỳ file HTML trong bất kỳ repository nào.\nTuy nhiên trang web có thể sẽ bị lỗi hiển thị nếu như bị sai đường dẫn (do đường dẫn bị thay đổi khi xem qua raw.githack.com).\nNgoài 3 cách trên, nếu bạn còn biết cách nào nữa thì hãy comment chia sẻ xuống dưới nhé.\n","permalink":"https://robinhuy.github.io/blog/cac-cach-tao-web-tinh-voi-github/","summary":"Bạn có 1 trang web tĩnh (static web chỉ gồm HTML CSS JS) và muốn đẩy lên mạng để chia sẻ cho người khác mà không mất phí? Hãy tận dụng Github - nền tảng lưu trữ, quản lý và chia sẻ mã nguồn mở hàng đầu hiện nay.\nNgoài việc lưu trữ và quản lý mã nguồn, Github còn cung cấp một dịch vụ cho phép hosting static web là Github Pages.","title":"Các cách tạo web tĩnh với Github"},{"content":"Github là một dịch vụ cung cấp kho lưu trữ (repository) mã nguồn Git trên nền tảng Web.\nChúng ta đẩy code lên trên Github để quản lý và chia sẻ code dễ dàng hơn, giúp làm việc nhóm một cách hiệu quả.\n Ngoài ra đẩy code lên Github còn giúp chúng ta tạo ra các static page một cách nhanh chóng và miễn phí, tham khảo bài viết này Các cách tạo web tĩnh với Github.\n Đầu tiên các bạn cần đăng ký tài khoản trên Github trước, sau đó tạo một repository mới (là nơi để chứa source code của project). Chú ý đặt username hay một chút vì Github sẽ tặng mỗi tài khoản một tên miền miễn phí dạng username.github.io.\nKhi tạo repository các bạn có thể để chế độ public hoặc private, và có tạo sẵn một số file hay có như README và .gitignore hay không.\nNếu như tạo một repository rỗng (không chọn thêm sẵn file) thì chúng ta có thể đẩy một Git project có sẵn từ trên máy lên. Còn nếu tạo sẵn file thì chúng ta cần clone project đó về máy rồi sau đó mới chỉnh sửa source code và đẩy lên.\nCác bước đẩy code lên Git repository bao gồm:  Add files: Lên danh sách các file chỉnh sửa trong project để chuẩn bị commit. Commit: Xác nhận thay đổi và lưu lại trong lịch sử chỉnh sửa của project. Push: Đẩy code từ trên máy (local repository) lên kho lưu trữ online (remote).  Dưới đây là 1 số cách đẩy code lên Github dành cho người mới, chưa biết sử dụng Git.\nCách 1: Upload trực tiếp Cách này dễ nhất, nhưng nhược điểm là chỉ có thể upload từng file một, và không cho phép upload thư mục.\nNếu muốn upload thư mục thì cần tạo ra thư mục trước bằng cách chọn Create new file để tạo file mới, và tạo file đó nằm trong thư mục luôn. Ví dụ gõ tên file là css/style.css thì sẽ tạo ra thư mục css và file style.css nằm trong thư mục css.\nCách 2: Sử dụng phần mềm có giao diện Có nhiều phần mềm hỗ trợ việc sử dụng Git dễ dàng, ví dụ Github Desktop. Tải phần mềm về và đăng nhập vào tài khoản Github để sử dụng.\nBạn có thể tạo repository mới, hoặc clone 1 repository có sẵn về máy. Chú ý khi clone repository về máy thì cần chọn đường dẫn lưu code trên máy và nhớ vị trí để cho dễ truy cập và quản lý.\nSau khi đã clone project về máy thì mở thư mục source code vừa clone về và chỉnh sửa tùy ý. Sau khi code xong thì lại bật Github Desktop lên để đẩy code mới chỉnh sửa lên Github.\nNgoài ra nếu bạn lập trình bằng Visual Studio Code thì có thể dùng luôn trình quản lý Git có sẵn.\nCách 3: Sử dụng Terminal Terminal là phần mềm thao tác với máy tính qua các mã lệnh. Trên các hệ điều hành Mac, Linux thì đều có sẵn. Còn trên Windows thì các bạn phải cài thêm Git.\nSau khi cài xong thì chúng ta có thể thao tác với Git qua câu lệnh. Và đi kèm với Git sẽ có 1 phần mềm là Git Bash tương tự như Terminal trên Linux. Các bạn có thể bấm chuột phải vào màn hình và chọn Git Bash Here để bật Git Bash lên ở ngay tại thư mục hiện hành.\nKhi sử dụng Terminal thì chúng ta sẽ thao tác với Git qua các câu lệnh, mới đầu sẽ chưa quen nhưng dùng nhiều thì sẽ nhớ. Tra cứu các câu lệnh của Git ở đây.\nMột số lệnh hay dùng: Clone 1 repository về máy:\ngit clone [địa chỉ repository]\nCác lệnh sau cần vào trong thư mục chứa source code (local git repository) thì mới gõ được:\ncd [thư mục chứa source code]\n  Add files để chuẩn bị Commit:\ngit add --all (add toàn bộ các file trong project)\nhoặc\ngit add . (add các file ở thư mục hiện tại)\n  Commit:\ngit commit -m \u0026quot;Chú thích cho lần commit này\u0026quot;\n  Push code:\ngit push origin main\nChú ý main là tên branch (nhánh) mặc định khi tạo repository, với các repository cũ thì tên nhánh mặc định là master.\n  ","permalink":"https://robinhuy.github.io/blog/cac-cach-day-code-len-github/","summary":"Github là một dịch vụ cung cấp kho lưu trữ (repository) mã nguồn Git trên nền tảng Web.\nChúng ta đẩy code lên trên Github để quản lý và chia sẻ code dễ dàng hơn, giúp làm việc nhóm một cách hiệu quả.\n Ngoài ra đẩy code lên Github còn giúp chúng ta tạo ra các static page một cách nhanh chóng và miễn phí, tham khảo bài viết này Các cách tạo web tĩnh với Github.","title":"Các cách đẩy code lên Github"},{"content":"Với những người mới học React thì Redux thực sự là một cơn ác mộng. Cũng giống như bạn đang code jQuery quen và chuyển sang React, đang chỉnh sửa DOM trực tiếp lại phải chuyển qua dùng state, …\nVậy để làm quen với Redux một cách nhanh nhất, chúng ta sẽ thử làm 1 ứng dụng đơn giản như sau:\n Một website có 2 trang: Dashboard và Login. Ở trang login cho phép nhập username, và khi chuyển qua trang Dashboard thì hiển thị username đó, nếu không nhập gì thì hiển thị là “Guest”. Demo: https://react-router-redux-toolkit.stackblitz.io.  Yêu cầu cần biết kiến thức cơ bản về React như state, props, function component, …\nBước 1: Tạo project Các bạn có thể tạo project local bằng create-react-app hoặc code online trên stackblitz.com.\nSau khi tạo project thì cài các thư viện cần thiết: react-router-dom, @reduxjs/toolkit và react-redux\n1  yarn add react-router-dom @reduxjs/toolkit react-redux     Trên stackblitz thì thêm thư viện ở mục Dependencies   Bước 2: Cấu hình router Để tạo một website có nhiều page, mình sử dụng thêm thư viện React Router.\nTrong ví dụ có 2 trang là Dashboard và Login, thì chúng ta tạo 2 function component tương ứng cho 2 trang, và cấu hình router (đường dẫn cho các trang) ở root component là App như sau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28  import React from \u0026#34;react\u0026#34;; import {  BrowserRouter as Router,  Switch,  Route,  Redirect, } from \u0026#34;react-router-dom\u0026#34;;  import Dashboard from \u0026#34;./pages/Dashboard.js\u0026#34;; import Login from \u0026#34;./pages/Login.js\u0026#34;;  export default function App() {  return (  \u0026lt;Router\u0026gt;  \u0026lt;Switch\u0026gt;  \u0026lt;Route path=\u0026#34;/login\u0026#34;\u0026gt;  \u0026lt;Login /\u0026gt;  \u0026lt;/Route\u0026gt;   \u0026lt;Route path=\u0026#34;/dashboard\u0026#34;\u0026gt;  \u0026lt;Dashboard /\u0026gt;  \u0026lt;/Route\u0026gt;   \u0026lt;Redirect to=\u0026#34;/login\u0026#34; /\u0026gt;  \u0026lt;/Switch\u0026gt;  \u0026lt;/Router\u0026gt;  ); }   Đoạn code trên có ý nghĩa: Nếu người dùng truy cập website theo đường dẫn “/login” thì sẽ hiển thị nội dung của Login component, “/dashboard” thì hiển thị nội dung của Dashboard component, còn các trường hợp khác thì sẽ chuyển về đường dẫn “/login” (redirect).\nComponent Login và Dashboard đặt trong thư mục pages và có nội dung đơn giản như sau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31  // pages/Login.js  import React, { useState } from \u0026#34;react\u0026#34;; import { useHistory } from \u0026#34;react-router-dom\u0026#34;;  export default function Login() {  // Khởi tạo state lưu giá trị người dùng nhập vào ô input username  const [username, setUsername] = useState(\u0026#34;\u0026#34;);   // Sử dụng hook useHistory() của react-router-dom để chuyển hướng người dùng sang trang khác  const history = useHistory();   // Hàm xử lý khi người dùng bấm nút login  function handleLogin() {  // Chuyển hướng họ sang trang dashboard  history.push(\u0026#34;/dashboard\u0026#34;);  }   return (  \u0026lt;\u0026gt;  \u0026lt;h1\u0026gt;Login\u0026lt;/h2\u0026gt;  \u0026lt;input  type=\u0026#34;text\u0026#34;  placeholder=\u0026#34;Username\u0026#34;  value={username}  onChange={event =\u0026gt; setUsername(event.target.value)}  /\u0026gt;{\u0026#34; \u0026#34;}  \u0026lt;button onClick={handleLogin}\u0026gt;Login\u0026lt;/button\u0026gt;  \u0026lt;/\u0026gt;  ); }   1 2 3 4 5 6 7 8 9 10 11 12 13 14  // pages/Dashboard.js  import React from \u0026#34;react\u0026#34;; import { Link } from \u0026#34;react-router-dom\u0026#34;;  export default function Dashboard() {  return (  \u0026lt;\u0026gt;  \u0026lt;h1\u0026gt;Dashboard\u0026lt;/h2\u0026gt;  \u0026lt;h2\u0026gt;Welcome Guest\u0026lt;/h2\u0026gt;  \u0026lt;Link to=\u0026#34;/login\u0026#34;\u0026gt;Log out\u0026lt;/Link\u0026gt;  \u0026lt;/\u0026gt;  ); }   Mục tiêu cần làm tiếp theo là khi người dùng nhập dữ liệu vào ô input ở trang Login sau đó bấm nút login thì chuyển qua trang Dashboard, và ở trang này hiển thị được giá trị vừa nhập chỗ “Welcome Guest”. Để làm được chức năng này thì có nhiều cách, và trong bài này thì mình sẽ hướng dẫn cách sử dụng Redux. Tham khảo thêm bài viết Truyền dữ liệu giữa các Component trong React.\nBước 3: Cấu hình Redux Store Để truyền dữ liệu giữa các Component dễ hơn thì chúng ta dùng Redux, và cụ thể hơn trong ví dụ này chúng ta dùng Redux Toolkit để cấu hình nhanh hơn và dễ hơn dùng Redux Core.\nCác bạn hình dung Redux Store như 1 nơi lưu state global mà tất cả các Component trong phạm vi của Store đều có thể truy xuất để lấy dữ liệu hoặc cập nhật dữ liệu. Giao diện của các Component đó cũng sẽ được tự động cập nhật khi state thay đổi.\nVới các dự án lớn thì trong Store có thể chia ra nhiều Slice (nhóm các state theo chức năng). Với ví dụ đơn giản này thì chúng ta chỉ cần 1 Slice, tạo Slice là userSlice tương tự như sau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31  // store/userSlice.js  import { createSlice } from \u0026#34;@reduxjs/toolkit\u0026#34;;  // Khởi tạo state cho slice, có thể kèm giá trị mặc định ban đầu const initialState = {  username: \u0026#34;Guest\u0026#34;, // State username với giá trị mặc định là \u0026#34;Guest\u0026#34;  // Có thể khai báo nhiều state khác nữa };  // Cấu hình slice export const userSlice = createSlice({  name: \u0026#34;user\u0026#34;, // Tên của slice, mỗi slice đặt 1 tên khác nhau để phân biệt  initialState,  // Reducers chứa các hàm xử lý cập nhật state  reducers: {  updateUsername: () =\u0026gt; {},  }, });  // Export action ra để sử dụng cho tiện. export const { updateUsername } = userSlice.actions;  // Action là 1 hàm trả về object dạng {type, payload}, chạy thử console.log(updateUsername()) để xem chi tiết  // Hàm giúp lấy ra state mong muốn. // Hàm này có 1 tham số là root state là toàn bộ state trong store, chạy thử console.log(state) trong nội dung hàm để xem chi tiết export const selectUsername = (state) =\u0026gt; state.user.username;  // Export reducer để nhúng vào Store export default userSlice.reducer;   Tạo 1 file để cấu hình Store:\n1 2 3 4 5 6 7 8 9 10 11  // store/index.js  import { configureStore } from \u0026#34;@reduxjs/toolkit\u0026#34;; import userReducer from \u0026#34;./userSlice\u0026#34;;  export const store = configureStore({  reducer: {  user: userReducer, // Khai báo 1 slide tên là user với giá trị là userReducer được export ở file userSlice  // Có thể khai báo nhiều slide khác tương tự  }, });   Khai báo phạm vi sử dụng Store, ở đây mình dùng cho toàn bộ website nên sẽ khai báo ở file index.js:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14  import React from \u0026#34;react\u0026#34;; import ReactDOM from \u0026#34;react-dom\u0026#34;; import { Provider } from \u0026#34;react-redux\u0026#34;; import { store } from \u0026#34;./store/index\u0026#34;;  import App from \u0026#34;./App\u0026#34;;  // Bọc App component vào trong Store Provider để App và toàn bộ Component con đều có thể truy xuất đến Store ReactDOM.render(  \u0026lt;Provider store={store}\u0026gt;  \u0026lt;App /\u0026gt;  \u0026lt;/Provider\u0026gt;,  document.getElementById(\u0026#34;root\u0026#34;) );   Bước 4: Tương tác với Redux Store Để cập nhật state trong Store thì chúng ta cần dispatch action tương ứng được khai báo trong phần reducers của slice.\nSửa lại Login Component như sau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  import React, { useState } from \u0026#34;react\u0026#34;; import { useHistory } from \u0026#34;react-router-dom\u0026#34;;  // Import hook useDispatch từ react-redux và action updateUsername từ userSlice import { useDispatch } from \u0026#34;react-redux\u0026#34;; import { updateUsername } from \u0026#34;../store/userSlice\u0026#34;;  export default function Login() { const [username, setUsername] = useState(\u0026#34;\u0026#34;); const history = useHistory();  const dispatch = useDispatch();  function handleLogin() {  // Dispatch action updateUsername vào store, action này có payload (dữ liệu đi kèm) là username  dispatch(updateUsername(username));   history.push(\u0026#34;/dashboard\u0026#34;); }  ...  }   Sửa lại nội dung hàm updateUsername trong reducers của userSlice:\n1 2 3 4 5 6 7 8  reducers: {  // Hàm có 2 tham số là state hiện tại và action truyền vào  updateUsername: (state, action) =\u0026gt; {  // Cập nhật state username với giá trị truyền vào qua action (action.payload)  // Chạy thử console.log(action) để xem chi tiết giá trị action truyền vào  state.username = action.payload;  }; }   Chú ý là khi cập nhật state chúng ta có thể thay đổi trực tiếp state chứ không như khi dùng local state trong Component, đó là do Redux Toolkit sử dụng thêm thư viện immer. Và ở trong các reducer này chúng ta chỉ viết code sync chứ không viết code async. Để viết code async các bạn tham khảo bài viết sau Hướng dẫn sử dụng createAsyncThunk trong Redux Toolkit hoặc Hướng dẫn sử dụng RTK Query trong Redux Toolkit.\nTiếp theo sửa lại Dashboard component để hiển thị giá trị state username ra ngoài màn hình:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  import React from \u0026#34;react\u0026#34;; import { Link } from \u0026#34;react-router-dom\u0026#34;;  // Import hook useSelector từ react-redux và hàm selectUsername từ userSlice import { useSelector } from \u0026#34;react-redux\u0026#34;; import { selectUsername } from \u0026#34;../store/userSlice\u0026#34;;  export default function Dashboard() { // Lấy ra state username từ store // Hàm useSelector cần truyền vào 1 hàm callback có tham số là root state và trả về state cần lấy const username = useSelector(selectUsername);  return (  \u0026lt;\u0026gt;  \u0026lt;h1\u0026gt;Dashboard\u0026lt;/h2\u0026gt;   {/* In biến username ra màn hình */}  \u0026lt;h2\u0026gt;Welcome {username}\u0026lt;/h2\u0026gt;   \u0026lt;Link to=\u0026#34;/login\u0026#34;\u0026gt;Log out\u0026lt;/Link\u0026gt;  \u0026lt;/\u0026gt;  ); }   Vậy là chúng ta đã hoàn thành ứng dụng demo sử dụng React Router và Redux Toolkit. Các bạn có thể xem toàn bộ code mẫu tại đây: https://stackblitz.com/edit/react-router-redux-toolkit?file=src/index.js.\nHappy coding 😎\n","permalink":"https://robinhuy.github.io/blog/huong-dan-su-dung-react-router-va-redux-toolkit/","summary":"Với những người mới học React thì Redux thực sự là một cơn ác mộng. Cũng giống như bạn đang code jQuery quen và chuyển sang React, đang chỉnh sửa DOM trực tiếp lại phải chuyển qua dùng state, …\nVậy để làm quen với Redux một cách nhanh nhất, chúng ta sẽ thử làm 1 ứng dụng đơn giản như sau:\n Một website có 2 trang: Dashboard và Login.","title":"Hướng dẫn sử dụng React Router và Redux Toolkit"},{"content":"Để quản lý State trong React Function Component, chúng ta dùng Hook useState().\nHàm useState trả về 1 mảng 2 phần tử, phần tử đầu tiên là để khởi tạo state, phần tử thứ 2 là hàm để cập nhật state. Tham số truyền vào hàm useState là giá trị khởi tạo của state. Ví dụ:\nKhai báo State Phải khai báo useState ở top level của một Function Component (hoặc một custom Hook), không khai báo ở trong một scope nào khác như vòng lặp, điều kiện, hay function con. Ví dụ khai báo state như sau là sai:\n1 2 3 4 5 6 7  export default function App() {  function handleClick() {  const [count, setCount] = useState(0); // Phải khai báo state ở top-level của Function component  }   ... }   Có thể khai báo nhiều biến state khác nhau. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11  export default function App() {  const [count1, setCount1] = useState(0);  const [count2, setCount2] = useState(0);   return (  \u0026lt;div\u0026gt;  \u0026lt;p\u0026gt;{count1}\u0026lt;/p\u0026gt;  \u0026lt;p\u0026gt;{count2}\u0026lt;/p\u0026gt;  \u0026lt;/div\u0026gt;  ); }   Cập nhật State Mỗi khi state được cập nhật thì Component sẽ re-render (function được chạy lại và giao diện được cập nhật lại theo state). Cần chú ý là không được thay đổi trực tiếp biến state (immutable) mà phải cập nhật thông qua hàm cập nhật state.\nVí dụ như sau là sai:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  export default function App() {  const [count, setCount] = useState(0);   function handleClick() {  count++; // Không được thay đổi trực tiếp state như này  }   return (  \u0026lt;div\u0026gt;  \u0026lt;p\u0026gt;{count}\u0026lt;/p\u0026gt;  \u0026lt;p\u0026gt;  \u0026lt;button onClick={handleClick}\u0026gt;Increase count\u0026lt;/button\u0026gt;  \u0026lt;/p\u0026gt;  \u0026lt;/div\u0026gt;  ); }   Ví dụ như này là đúng:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  export default function App() {  const [count, setCount] = useState(0);   function handleClick() {  setCount(count + 1); // Thay đổi state bằng cách gọi hàm setCount  }   return (  \u0026lt;div\u0026gt;  \u0026lt;p\u0026gt;{count}\u0026lt;/p\u0026gt;  \u0026lt;p\u0026gt;  \u0026lt;button onClick={handleClick}\u0026gt;Increase count\u0026lt;/button\u0026gt;  \u0026lt;/p\u0026gt;  \u0026lt;/div\u0026gt;  ); }   Chú ý với các state là array hoặc object thì nên copy giá trị ra 1 biến mới để không làm thay đổi giá trị cũ (pass by reference). Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14  export default function App() {  const [arr, setArr] = useState([1, 2, 3]);   // Cách sai  function addItemToArray1(item) {  arr.push(item); // Hàm push làm thay đổi giá trị của state arr  setArr(arr); // React không phát hiện có sự thay đổi state nào nên không cập nhật lại giao diện  }   // Cách đúng  function addItemToArray2(item) {  setArr([...arr, item]); // Không thay đổi trực tiếp state arr mà tạo ra 1 mảng mới bằng spread syntax  } }   Cập nhật state sử dụng callback function Thay vì cập nhật state bằng cách truyền vào giá trị mới, thì chúng ta có thể cập nhật state bằng cách truyền vào một hàm callback (có tham số là giá trị cũ) và trả về kết quả là giá trị mới. Ví dụ:\n1  setCount((prevCount) =\u0026gt; prevCount + 1);   Dùng cách này thì khi cập nhật state sẽ đảm bảo giá trị mới phụ thuộc vào giá trị cũ chứ không phụ thuộc vào giá trị của state ở thời điểm hiện tại. Tham khảo thêm https://reactjs.org/docs/hooks-reference.html#usestate.\nVí dụ với ứng dụng đếm số lượt bấm nút, nếu sửa lại hàm tăng số lượt bấm thành tăng số lượt bấm sau 1 khoảng thời gian (ví dụ 3 giây).\nNếu cập nhật state bằng cách sau thì khi người dùng bấm nút nhiều lần trong khoảng thời gian 3 giây, thì sau 3 giây giá trị của state cũng chỉ tăng lên 1 (lấy giá trị của state ở thời điểm hiện tại cộng thêm 1).\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  export default function App() {  const [count, setCount] = useState(0);   function handleClick() {  setTimeout(() =\u0026gt; {  setCount(count + 1); // Thay đổi state dựa theo giá trị của state hiện tại  }, 3000);  }   return (  \u0026lt;div\u0026gt;  \u0026lt;p\u0026gt;{count}\u0026lt;/p\u0026gt;  \u0026lt;p\u0026gt;  \u0026lt;button onClick={handleClick}\u0026gt;Increase count after 3 seconds\u0026lt;/button\u0026gt;  \u0026lt;/p\u0026gt;  \u0026lt;/div\u0026gt;  ); }   Nếu cập nhật state bằng cách truyền vào một hàm thì trong 3 giây delay, người dùng bấm nút bao nhiêu lần thì giá trị của state sẽ tăng lên bấy nhiêu.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18  export default function App() {  const [count, setCount] = useState(0);   function handleClick() {  setTimeout(() =\u0026gt; {  setCount((prevCount) =\u0026gt; prevCount + 1); // Thay đổi state dựa theo giá trị của state trước đó  }, 3000);  }   return (  \u0026lt;div\u0026gt;  \u0026lt;p\u0026gt;{count}\u0026lt;/p\u0026gt;  \u0026lt;p\u0026gt;  \u0026lt;button onClick={handleClick}\u0026gt;Increase count after 3 seconds\u0026lt;/button\u0026gt;  \u0026lt;/p\u0026gt;  \u0026lt;/div\u0026gt;  ); }   Như vậy tùy từng trường hợp mà chúng ta sẽ lựa chọn cách sử dụng sao cho hợp lý.\nCode demo: https://codepen.io/robinhuy/pen/MWjEaYx?editors=0010.\n","permalink":"https://robinhuy.github.io/blog/su-dung-usestate-hook-trong-react-nhu-nao-cho-dung/","summary":"Để quản lý State trong React Function Component, chúng ta dùng Hook useState().\nHàm useState trả về 1 mảng 2 phần tử, phần tử đầu tiên là để khởi tạo state, phần tử thứ 2 là hàm để cập nhật state. Tham số truyền vào hàm useState là giá trị khởi tạo của state. Ví dụ:\nKhai báo State Phải khai báo useState ở top level của một Function Component (hoặc một custom Hook), không khai báo ở trong một scope nào khác như vòng lặp, điều kiện, hay function con.","title":"Sử dụng useState() Hook trong React như nào cho đúng?"},{"content":" ReactJS là một Javascript framework rất phổ biến với giới lập trình Web Frontend hiện nay, số lượng tuyển dụng lập trình viên ReactJS cũng rất lớn. Do đó nhiều người mới học Web Frontend sau khi học xong một chút HTML CSS JS là muốn nhảy vào lập trình ReactJS ngay. Điều này dẫn đến hệ lụy là các bạn sẽ bị hổng kiến thức cơ bản, hoặc khi học ReactJS sẽ rất chật vật vì không hiểu cú pháp, không hiểu bản chất, \u0026hellip;\n Vậy trước khi học ReactJS hoặc React Native, các bạn nên nẵm vững HTML CSS và những kiến thức sau trong JavaScript (ngoài các kiến thức ban đầu như biến, vòng lặp, điều kiện, \u0026hellip;):\n  Variable scope và closure.\n  Import và Exports.\n  Arrow function.\n  - Destructuring assignment.\n Rest parameters and spread syntax.  - Các hàm xử lý mảng như map, filter, reduce, push, splice, \u0026hellip;\n ES6/ES7 Class (mặc dù hiện tại code ReactJS đang dần chuyển sang hướng function nhưng vẫn nên biết).  Variable scope và closure Cần nắm vững khái niệm Scope (code block, nested function, \u0026hellip;) trong Javascript. Khai báo biến thì sử dụng let thay cho var (kiểu cũ), với hằng số hoặc magic number thì dùng const.\nKhái niệm closure thì hơi khó hiểu nhưng cũng nên tìm hiểu trước để khi gặp không bị bỡ ngỡ 😅.\nTham khảo Variable scope, closure.\nImport và Exports Trong các project chúng ta sẽ có nhiều file để đảm bảo code ngắn gọn và dễ bảo trì. Cần chú ý giữa named export và default export.\nVí dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26  /* * File util.js */  // export một mảng export let month = [  \u0026#34;Jan\u0026#34;,  \u0026#34;Feb\u0026#34;,  \u0026#34;Mar\u0026#34;,  \u0026#34;Apr\u0026#34;,  \u0026#34;Aug\u0026#34;,  \u0026#34;Sep\u0026#34;,  \u0026#34;Oct\u0026#34;,  \u0026#34;Nov\u0026#34;,  \u0026#34;Dec\u0026#34;, ];  // export một hằng số export const YEAR = 2020;  // export một class export class User {  constructor(name) {  this.name = name;  } }   1 2 3  // Import ở 1 file khác để sử dụng  import { month, YEAR, User } from \u0026#34;./util.js\u0026#34;;   Tham khảo Import - Export.\nArrow function Arrow function được sử dụng khá nhiều bởi cách viết ngắn gọn và không có \u0026ldquo;this\u0026rdquo;.\nVí dụ với function thông thường:\n1 2 3  function sum(a, b) {  return a + b; }   Viết lại bằng arrow function:\n1  const sum = (a, b) =\u0026gt; a + b;   Tham khảo Arrow function basics và Arrow function revisited.\nDestructuring assignment Là cú pháp cho phép tách các object, array ra thành các biến, giúp cho code ngắn gọn hơn thay vì khai báo biến nhiều lần. Ví dụ hay sử dụng trong ReactJS:\n1 2 3 4 5 6 7 8 9 10 11 12  // Không sử dụng destructuring assignment function MyComponent(props) {  let navigation = props.navigation;  let route = props.route;   ... }  // Sử dụng destructuring assignment function MyComponent({navigation, route}) {  ... }   Hoặc khi sử dụng useState hook:\n1 2 3  function MyComponent() {  const [count, setCount] = React.useState(0); }   Tham khảo Destructuring assignment.\nRest parameters and spread syntax Rest parameters cho phép chúng ta viết 1 hàm với số lượng tham số là linh động (không biết trước). Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12  function sumAll(...args) {  // args là tên biến đại diện cho mảng các tham số  let sum = 0;   for (let arg of args) sum += arg;   return sum; }  alert(sumAll(1)); // 1 alert(sumAll(1, 2)); // 3 alert(sumAll(1, 2, 3)); // 6   hoặc:\n1 2 3 4 5 6 7 8 9 10 11  function showName(firstName, lastName, ...otherNames) {  alert(firstName + \u0026#34; \u0026#34; + lastName); // Robin Huy   // Rest parameters ...otherNames đại diện cho các tham số còn lại ngoài 2 tham số đầu tiên  // ví dụ ở đây otherNames sẽ là [\u0026#34;Robin\u0026#34;, \u0026#34;Huy\u0026#34;]  alert(otherNames[0]); // Chris  alert(otherNames[1]); // Robert  alert(otherNames.length); // 2 }  showName(\u0026#34;Robin\u0026#34;, \u0026#34;Huy\u0026#34;, \u0026#34;Chris\u0026#34;, \u0026#34;Robert\u0026#34;);   Spread syntax có cú pháp và cách hoạt động gần giống như rest parameters, nó cho phép \u0026ldquo;duỗi\u0026rdquo; một object hoặc array ra thành nhiều biến. Ví dụ:\n1 2 3 4 5 6 7  // Hàm Math.max cần truyền vào các số để tính max alert(Math.max(1, 3, 5, 2)); // Trả về 5  // Tuy nhiên nếu có 1 mảng các số thì chúng ta có thể spread mảng đó ra để sử dụng hàm Math.max const numbers = [1, 3, 5, 2]; alert(Math.max(numbers)); // Trả về NaN alert(Math.max(...numbers)); // Trả về 5   Tham khảo Rest parameters and spread syntax.\nCác hàm xử lý mảng Code ReactJS sẽ phải làm việc với mảng rất nhiều nên phải sử dụng thành thạo các phương thức xử lý mảng như: map, filter, reduce, push, splice, \u0026hellip;\nVí dụ sử dụng phương thức map kết hợp arrow function:\n1 2  const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map((number) =\u0026gt; number * 2);   Hoặc sử dụng filter để lọc phần tử ra khỏi mảng:\n1 2 3 4 5 6  // Xóa 1 sản phẩm ra khỏi mảng các sản phẩm dựa theo ID truyền vào function removeProduct(productId) {  const newProducts = products.filter((product) =\u0026gt; product.id !== productId);   setProducts(newProducts); }   Tham khảo Array methods.\n Trên đây là một vài kiến thức cơ bản mình nghĩ newbie cần phải biết trước khi học ReactJS. Nếu các bạn thấy còn thiếu thì góp ý bổ sung giúp mình ở phần bình luận bên dưới nhé 😘.\n","permalink":"https://robinhuy.github.io/blog/kien-thuc-javascript-co-ban-can-phai-biet-truoc-khi-hoc-react-native-reactjs/","summary":"ReactJS là một Javascript framework rất phổ biến với giới lập trình Web Frontend hiện nay, số lượng tuyển dụng lập trình viên ReactJS cũng rất lớn. Do đó nhiều người mới học Web Frontend sau khi học xong một chút HTML CSS JS là muốn nhảy vào lập trình ReactJS ngay. Điều này dẫn đến hệ lụy là các bạn sẽ bị hổng kiến thức cơ bản, hoặc khi học ReactJS sẽ rất chật vật vì không hiểu cú pháp, không hiểu bản chất, \u0026hellip;","title":"Kiến thức Javascript cơ bản cần phải biết trước khi học React Native / ReactJS"},{"content":"Nếu bạn dùng Mac Bước 1: Cài đặt chung Cài Node và Watchman dùng Homebrew\n1 2  brew install node brew install watchman   Riêng với Node có thể cài trực tiếp bằng cách tải bộ cài tại đây https://nodejs.org/en (cài bản LTS) hoặc sử dụng nvm nếu bạn muốn sử dụng node với các phiên bản khác nhau.\nCài Java Development Kit dùng Homebrew\n1  brew install --cask adoptopenjdk/openjdk/adoptopenjdk8   Hoặc có thể tải bộ cài tại đây https://www.oracle.com/ae/java/technologies/javase/javase-jdk8-downloads.html.\nBước 2: Cài môi trường phát triển cho iOS app Đảm bảo máy đã cài Xcode (bản 9.4 hoặc mới hơn).\nCài Xcode Command Line Tools: Vào Preferences\u0026hellip; → Locations\nCài Simulator:\nCài CocoaPods dùng Ruby (đã có sẵn trên Mac):\n1  sudo gem install cocoapods   Bước 3: Cài môi trường phát triển cho Android app Download và cài đặt Android Studio: https://developer.android.com/studio/index.html. Bảo đảm rằng cài đặt đầy đủ các gói sau:\n Android SDK Android SDK Platform Android Virtual Device  Sau khi cài xong, bật phần quản lý SDK bằng cách mở Android Studio, chọn Configure → SDK Manager(hoặc chọn Preferences\u0026hellip; → Appearance \u0026amp; Behavior → System Settings → Android SDK)\nChọn tab SDK Platforms, tích chọn Show Package Details, mở phần Android 10.0 (Q) và đảm bảo chọn những option sau:\n Android SDK Platform 29. Intel x86 Atom_64 System Image hoặc Google APIs Intel x86 Atom System Image.  Bấm Apply để bắt đầu download và cài đặt các tool đã chọn.\nCấu hình biến môi trường: Sửa file $HOME/.bash_profile hoặc $HOME/.bashrc(nếu bạn dùng trình shell khác không phải bash thì sửa file config tương ứng, ví dụ với zsh thì là ~/.zprofile hoặc ~/.zshrc)\n1 2 3 4 5  export ANDROID_HOME=$HOME/Library/Android/sdk export PATH=$PATH:$ANDROID_HOME/emulator export PATH=$PATH:$ANDROID_HOME/tools export PATH=$PATH:$ANDROID_HOME/tools/bin export PATH=$PATH:$ANDROID_HOME/platform-tools   Chạy lệnh source với file config vừa sửa để load vào shell đang chạy, ví dụ:\n1  source $HOME/.bash_profile   Nếu bạn dùng Windows Bước 1: Cài đặt chung Cài đặt Node bằng cách tải bộ cài tại đây https://nodejs.org/en (cài bản LTS) và Java Development Kit bằng cách tải bộ cài tại đây https://www.oracle.com/ae/java/technologies/javase/javase-jdk8-downloads.html.\nHoặc có thể dùng chocolatey để cài:\n1  choco install -y nodejs.install openjdk8   Bước 2: Cài môi trường phát triển cho Android app (trên Windows không chạy được iOS app) Cài đặt Android Studio và Android SDK tương tự trên Mac.\nCấu hình biến môi trường:\n Mở Windows Control Panel. Chọn User Accounts, sau đó tiếp tục chọn User Acounts. Chọn Change my environment variables. Chọn New\u0026hellip; để tạo biến môi trường ANDROID_HOME trỏ vào thư mục cài Android SDK:   Chú ý trên hình có \\Users\\hramos là đường dẫn mặc định của user hramos, sửa lại cho phù hợp     Thêm platform-tools vào Path:\n Mở Windows Control Panel. Chọn User Accounts, sau đó tiếp tục chọn User Acounts. Chọn Change my environment variables. Chọn Path variable. Chọn Edit. Chọn New và thêm platform-tools vào danh sách.  Tham khảo: https://reactnative.dev/docs/environment-setup.\n","permalink":"https://robinhuy.github.io/blog/thiet-lap-moi-truong-lap-trinh-react-native/","summary":"Nếu bạn dùng Mac Bước 1: Cài đặt chung Cài Node và Watchman dùng Homebrew\n1 2  brew install node brew install watchman   Riêng với Node có thể cài trực tiếp bằng cách tải bộ cài tại đây https://nodejs.org/en (cài bản LTS) hoặc sử dụng nvm nếu bạn muốn sử dụng node với các phiên bản khác nhau.\nCài Java Development Kit dùng Homebrew\n1  brew install --cask adoptopenjdk/openjdk/adoptopenjdk8   Hoặc có thể tải bộ cài tại đây https://www.","title":"Thiết lập môi trường lập trình React Native"},{"content":" Một mã nguồn tốt là mã nguồn có tài liệu hướng dẫn đầy đủ, dễ hiểu, giúp cho người đọc có thể dễ dàng theo dõi và sử dụng.\n Với các mã nguồn trên Github (hoặc các nền tảng tương tự) thì tài liệu hướng dẫn được viết luôn trong file README.md nằm ở ngay thư mục gốc của project. File này được viết bằng cú pháp markdown, chứ không dùng Rich text editor nên cũng sẽ có 1 số hạn chế nhất định. Trong bài viết này mình sẽ hướng dẫn các bạn cách chèn ảnh (cả ảnh tĩnh lẫn ảnh động) vào trong file README.md trên Github để giúp tài liệu hướng dẫn mã nguồn trở nên sinh động và thu hút hơn.\nChèn ảnh bằng cú pháp markdown Cú pháp chèn ảnh trong markdown như sau:\n1  ![alt](http://~)    Phần trong dấu ngoặc vuông là thuộc tính alt của ảnh (mô tả ảnh, hiển thị khi ảnh bị lỗi). Phần trong dấu ngoặc tròn là đường dẫn ảnh.  Như vậy để chèn ảnh vào file README.md, chúng ta cần có đường dẫn của ảnh. Các bạn sẽ cần phải upload ảnh lên 1 hosting nào đó rồi lấy link về để chèn. Hoặc các bạn có thể upload luôn ảnh vào trong project, tuy nhiên cách này sẽ làm tăng dung lượng project nếu có nhiều ảnh, và cũng làm mã nguồn chứa thêm những file không cần thiết.\nCòn 1 cách khác mà ít người biết, đó là upload ảnh lên Github CDN Vào phần tạo Issue của một project bất kỳ (có thể vào luôn project của mình).\nKéo thả ảnh vào trong phần mô tả issue hoặc một comment bất kỳ, ảnh này sẽ được upload lên github và chờ vài giây chúng ta sẽ có link ảnh (kèm luôn cú pháp markdown để hiển thị ảnh).\nSau đó bạn chỉ cần copy luôn vào trong file README.md là xong.\nTùy chỉnh kích thước ảnh Mặc định ảnh khi nhúng vào file README theo cú pháp markdown thì chúng ta không style được, mà nó sẽ có style mặc định là max-width: 100% (để ảnh có thể thu nhỏ lại ở màn hình mobile).\nTuy nhiên trong 1 số trường hợp khi dùng ảnh to quá, chúng ta có thể muốn ảnh hiển thị lên nhỏ hơn so với kích thước gốc, hoặc là khi dùng nhiều ảnh mà muốn các ảnh có kích thước đều nhau, thì có thể viết code HTML như sau:\n1  \u0026lt;img src=\u0026#34;https://...\u0026#34; alt=\u0026#34;...\u0026#34; width=\u0026#34;250\u0026#34; /\u0026gt;   Dùng HTML attribute để giới hạn kích thước ảnh (ví dụ ở đây là 250px) và chỉ nên giới hạn kích thước theo chiều rộng, còn chiều cao thì để ảnh tự co giãn cho đung tỉ lệ.\nCác bạn có thể tham khảo file README trong repository React Native Expo Example của mình tại đây: https://github.com/robinhuy/react-native-expo-example (bấm vào file, chọn Raw để xem code).\n","permalink":"https://robinhuy.github.io/blog/thu-thuat-chen-anh-tren-github/","summary":"Một mã nguồn tốt là mã nguồn có tài liệu hướng dẫn đầy đủ, dễ hiểu, giúp cho người đọc có thể dễ dàng theo dõi và sử dụng.\n Với các mã nguồn trên Github (hoặc các nền tảng tương tự) thì tài liệu hướng dẫn được viết luôn trong file README.md nằm ở ngay thư mục gốc của project. File này được viết bằng cú pháp markdown, chứ không dùng Rich text editor nên cũng sẽ có 1 số hạn chế nhất định.","title":"Thủ thuật chèn ảnh trên Github"},{"content":"Không lập trình viên nào code mà không có bug. Tuy nhiên có rất nhiều lỗi cơ bản chúng ta nên tránh để tạo ra ít bug hơn, code sạch và trong sáng hơn, dễ bảo trì hơn, đỡ bị ăn chửi hơn, …\n1. Xử lý quá nhiều thứ trong một function Theo nguyên tắc Single Responsibility, một function chỉ nên thực hiện một và chỉ một nhiệm vụ duy nhất. Nhiều lập trình viên hay viết một function vừa lấy dữ liệu, xử lý dữ liệu và hiển thị dữ liệu. Thay vì như vậy, hãy chia nhỏ function này ra làm 3 function: Function lấy dữ liệu, function xử lý dữ liệu và function hiển thị dữ liệu.\nViệc giữ một function chỉ tập trung thực hiện một nhiệm vụ sẽ giúp code dễ đọc và dễ bảo trì hơn. Như ví dụ trên, giả sử API để lấy dữ liệu bị thay đổi thì ta chỉ cần cập nhật lại function lấy dữ liệu, không bị ảnh hưởng đến các thao tác ở sau.\n2. Code bị comment Trong một ứng dụng lớn có nhiều lập trình viên tham gia, nhiều khi bạn sẽ thấy có các hàm, các đoạn code lớn bị comment. Bạn sẽ không hiểu đoạn code bị comment này để làm gì, ý đồ của tác giả là gì. Các lập trình viên khác có thể sẽ không dám xoá đoạn code này vì có thể tác giả của đoạn comment còn cần đến nó.\nNếu gặp trường hợp như vậy và project có sử dụng hệ thống quản lý code như git, svn, … thì hãy mạnh dạn xoá đoạn code này đi, code sẽ trở nên sạch đẹp hơn. Còn sau này nếu tác giả của đoạn code đó muốn tìm lại thì họ sẽ phải tự tìm trong các commit cũ.\n3. Đặt tên biến, tên hàm không rõ ràng Đặt tên biến là một công việc khó nhưng cũng rất quan trọng. Một tên biến rõ ràng sẽ giúp việc đọc code trở nên dễ dàng, dễ hiểu.\nHãy đặt tên biến mô tả đúng chức năng, ý nghĩa của nó, dài một chút cũng được. Tránh đặt tên biến kiểu viết tắt (trừ trường hợp phổ biến hoặc đã thống nhất từ trước) hoặc tên biến không có ý nghĩa như a, b, c, …\n4. Magic number và string Magic number và string là các giá trị duy nhất được sử dụng nhiều lần trong ứng dụng mà không có giải thích ý nghĩa rõ ràng. Những giá trị này hoàn toàn có thể thay thế bằng các biến (với điều kiện biến phải được đặt tên một cách rõ ràng).\nVí dụ với đoạn code sau:\n1 2 3  for ($i = 1; $i \u0026lt;= 52; $i++) {  ... }   Trong ví dụ trên thì 52 là một magic number, và người đọc code sẽ không hiểu được 52 có ý nghĩa là gì. Thậm chí kể cả tác giả đoạn code, sau một thời gian quay lại đọc code của mình cũng không hiểu, phải dò lại toàn bộ chương trình.\nĐoạn code trên có thể viết lại như sau:\n1 2 3 4  $cardDeckSize = 52; for ($i = 1; $i \u0026lt;= $cardDeckSize; $i++) {  ... }   Như vậy đọc đoạn code này sẽ hiểu ngay là đang thực hiện một vòng lặp qua từng quân bài trong bộ bài và 52 có nghĩa là số lá bài trong bộ bài. Ở các phần bên dưới cũng có thể dùng lại biến $cardDeckSize và khi cần thay đổi giá trị số lượng lá bài trong bộ bài ta cũng chỉ cần thay đổi giá trị của biến này một lần duy nhất thay vì phải sửa nhiều chỗ.\nTương tự với number, chúng ta cũng có magic string:\n1 2 3  if (userPasswordIsValid($user, \u0026#34;6yP4cZ\u0026#34;.$password)) {  ... }   Thay vì viết như trên ta viết lại:\n1 2 3 4  $salt = \u0026#34;6yP4cZ\u0026#34;; if (userPasswordIsValid($user, $salt.$password)) {  ... }   và code sẽ trở nên dễ hiểu hơn.\n5. Code format lộn xộn Với những lập trình viên không có kinh nghiệm và cẩu thả thì họ sẽ viết code lộn xộn, không có format. Code không format sẽ rất khó đọc và dễ dẫn đến code sai cú pháp và rất khó debug. Ví dụ như code HTML có thẻ mở mà không có thẻ đóng dẫn đến sai cấu trúc code làm hỏng cả CSS, lỗi này cũng khó debug vì dù code sai thì cũng sẽ không báo lỗi lên trình duyệt.\nĐa số các IDE hoặc code editor hiện đại đều có hỗ trợ chức năng format code theo từng ngôn ngữ, hoặc là người dùng chủ động cài thêm các plugin, extension hỗ trợ cho việc format code. Trong một project nếu các lập trình viên dùng chung một chuẩn format code cũng sẽ giúp code đồng bộ và ít bị xung đột.\n6. Hard code Hard code là nhập dữ liệu trực tiếp vào trong source code, dữ liệu này bị fix cứng và không thay đổi, cấu hình được.\nTrong một số trường hợp chúng ta vẫn dùng hard code, tuy nhiên nếu code của bạn bị hard code quá nhiều tức là đang có vấn đề. Thay vì hard code dữ liệu trong source code, hãy tách chúng ra bằng cách lấy dữ liệu qua file cấu hình, lấy từ cơ sở dữ liệu hoặc API, hay qua biến môi trường, …\nBài viết được biên dịch lại từ medium.com.\n","permalink":"https://robinhuy.github.io/blog/nhung-loi-co-ban-trong-lap-trinh-ma-developer-nen-tranh/","summary":"Không lập trình viên nào code mà không có bug. Tuy nhiên có rất nhiều lỗi cơ bản chúng ta nên tránh để tạo ra ít bug hơn, code sạch và trong sáng hơn, dễ bảo trì hơn, đỡ bị ăn chửi hơn, …\n1. Xử lý quá nhiều thứ trong một function Theo nguyên tắc Single Responsibility, một function chỉ nên thực hiện một và chỉ một nhiệm vụ duy nhất.","title":"Những lỗi cơ bản trong lập trình mà developer nên tránh"},{"content":"Sau khi code xong 1 ứng dụng bằng Create React App, chúng ta có thể đẩy sản phẩm lên Internet theo 1 trong 3 cách miễn phí sau chỉ với 3 bước (còn nhiều cách khác nhưng tác giả lười viết 😅):\n1. Github Pages Giả sử bạn đã có tài khoản trên github là robinhuy, và có 1 repository chứa source code ứng dụng tạo bởi Create React App là react-app.\nBước 1: Cài thêm thư viện gh-pages (devDependencies)\n1  npm i gh-pages --save-dev   Bước 2: Sửa lại file package.json, bổ sung thêm thuộc tính homepage, và scripts\nBước 3: Deploy lên Github Pages bằng lệnh\n1  npm run deploy   Sau đó truy cập ứng dụng tại địa chỉ: http://robinhuy.github.io/react-app\n2. ZEIT Now Bước 1: Cài đặt Now CLI\n1  npm i -g now   Bước 2: Tạo tài khoản trên https://zeit.co và đăng nhập bằng Now CLI (gõ email rồi truy cập email để xác thực)\n1  now login   Bước 3: Đẩy code lên bằng lệnh\n1  now   Chú ý nếu đẩy code lên ZEIT Now thì không cấu hình homepage như Github Pages vì mỗi project sẽ có subdomain riêng. Có thể kết nối với Github để mỗi lần push code lên Github sẽ tự động deploy lên Now.\n3. Heroku Bước 1: Tạo tài khoản trên https://heroku.com, sau đó tạo 1 App (tương tự tạo repository trên Github). Truy cập mục Settings của App vừa tạo để add thêm buildpack với địa chỉ https://github.com/mars/create-react-app-buildpack\nBước 2: Cài Heroku CLI, sau đó đăng nhập tương tự ZEIT Now\n1  heroku login   Bước 3: Đẩy code lên tương tự như đẩy code lên Github\n1 2 3  heroku git:remote -a react-app git push heroku master heroku open   Chú ý nếu đẩy code lên Heroku thì không cấu hình homepage như Github Pages vì mỗi project sẽ có subdomain riêng. Có thể kết nối với Github để mỗi lần push code lên Github sẽ tự động deploy lên Heroku.\n","permalink":"https://robinhuy.github.io/blog/day-code-create-react-app-len-internet-mien-phi/","summary":"Sau khi code xong 1 ứng dụng bằng Create React App, chúng ta có thể đẩy sản phẩm lên Internet theo 1 trong 3 cách miễn phí sau chỉ với 3 bước (còn nhiều cách khác nhưng tác giả lười viết 😅):\n1. Github Pages Giả sử bạn đã có tài khoản trên github là robinhuy, và có 1 repository chứa source code ứng dụng tạo bởi Create React App là react-app.","title":"Đẩy code Create React App lên Internet miễn phí"},{"content":"Bài viết được dịch từ uxdesign.cc, có lược bớt và chỉnh sửa theo sở thích của người dịch 😜\nNhững lỗi thiết kế form và cách tối ưu Form là một thành phần rất quan trọng trên website. Bài viết này sẽ chỉ ra những điều nên và không nên khi thiết kế Form. Chú ý đây chỉ là đề xuất chứ không phải nguyên lý nên sẽ có ngoại lệ.\nNhóm label với input của nó Trình bày Label và Input của nó gần nhau hơn, khoảng cách giữa các trường với nhau phải đủ lớn để User không bị nhầm lẫn giữa các trường.\nTránh viết hoa toàn bộ Label viết hoa toàn bộ sẽ khiến User khó đọc hơn.\nNếu có 5 lựa chọn trở xuống, hãy hiển thị toàn bộ ra Nếu đặt các lựa chọn trong Dropdown thì các lựa chọn này sẽ bị ẩn và User cũng phải bấm 2 lần để chọn. Chỉ nên dùng Dropdown khi có nhiều lựa chọn và khi có quá nhiều lựa chọn thì nên dùng Dropdown kết hợp Search trong Dropdown.\nĐặt các lựa chọn Checkbox (hoặc Radio) theo chiều dọc để dễ theo dõi Xếp theo chiều dọc thì đọc lướt sẽ dễ hơn.\nMô tả rõ Call To Action (CTA) Nút kêu gọi hành động phải có nội dung rõ ràng, không khiến User phân vân.\nChỉ rõ lỗi ngay trên dòng Chỉ rõ lỗi ở dòng nào và là lỗi gì.\nĐừng ẩn các chỉ dẫn Luôn hiển thị các chỉ dẫn khi có thể và đặt nó ở gần input.\nPhân biệt Action chính và phụ Action chính cần nổi bật hơn action phụ.\nĐể độ dài ô input phù hợp Độ dài của ô input nên tương đương với dữ liệu User sẽ nhập cho input đó. Ví dụ những trường như Zip code, số điện thoại, … không nên để quá dài.\nBỏ dấu sao (*, mang ý nghĩa bắt buộc) và thay bằng ghi chú Optional (tùy chọn, không bắt buộc) Nhìn dấu sao nhiều khó chịu và không phải ai cũng biết dấu sao nghĩa là bắt buộc. Ghi chú rõ ràng những trường không bắt buộc sẽ tốt hơn.\nGom nhóm các thông tin liên quan Với những Form dài thì việc gom nhóm các thông tin liên quan lại với nhau sẽ giúp User dễ theo dõi hơn.\nHãy luôn tự hỏi Bỏ bớt những trường tùy chọn đi và nghĩ ra những cách khác để thu thập dữ liệu từ User. Thời gian là vàng bạc và User sẽ không muốn mất nhiều thời gian để nhập dữ liệu vào Form.\nHãy luôn tự hỏi xem những thông tin nào thật sự cần thiết cho vào Form, những dữ liệu nào có thể bỏ đi hoặc để thu thập sau.\nViệc thu thập dữ liệu đang theo hướng tự động hóa. Ví dụ như điện thoại di động hoặc đồng hồ thông minh có thể thu thập rất nhiều dữ liệu từ User trong khi họ không hề hay biết. Hãy nghĩ theo cách tận dụng mạng xã hội, tin nhắn, email, địa điểm, … để lấy thông tin từ User.\n","permalink":"https://robinhuy.github.io/blog/lam-sao-de-thiet-ke-form-tot-hon/","summary":"Bài viết được dịch từ uxdesign.cc, có lược bớt và chỉnh sửa theo sở thích của người dịch 😜\nNhững lỗi thiết kế form và cách tối ưu Form là một thành phần rất quan trọng trên website. Bài viết này sẽ chỉ ra những điều nên và không nên khi thiết kế Form. Chú ý đây chỉ là đề xuất chứ không phải nguyên lý nên sẽ có ngoại lệ.","title":"Làm sao để thiết kế Form tốt hơn?"},{"content":"Bài viết được biên dịch và tóm tắt lại từ https://towardsdatascience.com, code demo được chuyển sang dùng Hooks.\nXử lý dữ liệu trong React có thể hơi khó khăn một chút, nhưng cũng không quá phức tạp. Tôi đã tổng kết lại 3 cách để truyền dữ liệu giữa các Component trong React:\n Từ Parent (Component cha) đến Child (Component con) sử dụng Props. Từ Child đến Parent sử dụng Callbacks. Giữa các Siblings (anh em, họ hàng, hàng xóm, \u0026hellip;)  Kết hợp cách 1 và 2. Sử dụng Redux (hoặc các thư viện có chức năng tương tự). Sử dụng Context API của React.    1. Từ Parent đến Child sử dụng Props Giả sử ứng dụng có cấu trúc Component như sau:\nĐây là trường hợp phổ biến và dễ nhất khi truyền dữ liệu trong React.\n1 2 3 4 5 6 7 8 9 10 11 12 13  import React, {useState} from \u0026#39;react\u0026#39;;  function Parent() {  const [data, setData] = useState(\u0026#39;Hello World\u0026#39;)   return (  \u0026lt;div\u0026gt;  \u0026lt;Child1/\u0026gt; // Không truyền dữ liệu  \u0026lt;Child2 dataFromParent=\u0026#34;Hello\u0026#34; /\u0026gt; // Truyền dữ liệu qua Props  \u0026lt;Child2 dataFromParent={data} /\u0026gt; // Truyền dữ liệu qua Props  \u0026lt;/div\u0026gt;  ); }   Ở Child component chỉ cần dùng props.dataFromParent để lấy dữ liệu đã được truyền từ Parent ( dataFromParent chỉ như một biến, một thuộc tính tự mình đặt ra để truyền dữ liệu qua props):\n1 2 3 4 5 6 7  function Child2(props) {  return (  \u0026lt;div\u0026gt;  Dữ liệu nhận được từ Parent: {props.dataFromParent}  \u0026lt;/div\u0026gt;  ); }   2. Từ Child đến Parent sử dụng Callbacks Để truyền dữ liệu từ Child lên Parent chúng ta thực hiện theo các bước sau:\nBước 1: Định nghĩa 1 callback function ở Parent component, function này sẽ có tham số để chứa dữ liệu được truyền đi từ Child component.\nBước 2: Truyền callback function đã được định nghĩa ở trên vào Child component qua props (tương tự truyền dữ liệu từ Parent đến Child).\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  import React, {useState} from \u0026#39;react\u0026#39;;  function Parent() {  const [message, setMessage] = useState(\u0026#39;\u0026#39;)   callbackFunction = (childData) =\u0026gt; {  setMessage(childData)  },   return (  \u0026lt;div\u0026gt;  \u0026lt;Child parentCallback={callbackFunction}/\u0026gt;  \u0026lt;p\u0026gt; {message} \u0026lt;/p\u0026gt;  \u0026lt;/div\u0026gt;  ); }   Bước 3: Ở Child component truyền dữ liệu ngược lại Parent bằng cách gọi props.callback(dataToParent)\n1 2 3 4 5 6 7 8 9  function Child(props) {  sendData = () =\u0026gt; {  props.parentCallback(\u0026#34;Message from Child\u0026#34;);  },   return (  // Gọi function sendData bất cứ khi nào bạn muốn truyền dữ liệu lên Parent component (khi có sự kiện xảy ra như onClick, onChange, ...)  ) };   Truyền dữ liệu giữa các Siblings Cách 1: Kết hợp 2 cách truyền dữ liệu ở trên Cách này chỉ dùng trong trường hợp đơn giản, không nên sử dụng trong trường hợp các Component lồng nhiều cấp (cây gia phả quá lớn, họ hàng bắn đại bác không tới), vì code sẽ trùng lặp nhiều và khó theo dõi luồng dữ liệu.\nCách 2: Sử dụng một Global store (Redux) để quản lý State cho tất cả các Component cần truyền dữ liệu và tương tác với nhau Cách 3: Sử dụng Context API của React Tham khảo thêm 1 số bài viết giới thiệu về React Context API:\n Using Context in React React Context API — A Replacement for Redux? You Might Not Need Redux  ","permalink":"https://robinhuy.github.io/blog/truyen-du-lieu-giua-react-components/","summary":"Bài viết được biên dịch và tóm tắt lại từ https://towardsdatascience.com, code demo được chuyển sang dùng Hooks.\nXử lý dữ liệu trong React có thể hơi khó khăn một chút, nhưng cũng không quá phức tạp. Tôi đã tổng kết lại 3 cách để truyền dữ liệu giữa các Component trong React:\n Từ Parent (Component cha) đến Child (Component con) sử dụng Props. Từ Child đến Parent sử dụng Callbacks.","title":"Truyền dữ liệu giữa React Components"},{"content":"Video demo: https://youtu.be/O6Agt4cLbfo.\nDựng Server local Yêu cầu máy tính đã cài và chạy được Git + NodeJS.\nCác bước thực hiện:\n  Clone repository sau (hoặc fork về nếu muốn quản lý source code, nhớ star để ủng hộ tác giả): https://github.com/robinhuy/fake-rest-api-nodejs.git\n1   git clone https://github.com/robinhuy/fake-rest-api-nodejs.git     Cài đặt dependencies\n1 2   cd fake-rest-api-nodejs  npm install     Chạy server\n1   npm start     Vậy là chúng ta đã có 1 Server API chạy trên http://localhost:3000 với 1 resource có sẵn là /users với các API theo chuẩn REST:\nGET /users GET /users/1 POST /users PUT /users/1 PATCH /users/1 DELETE /users/1 Ngoài ra Server còn cung cấp thêm 1 phương thức Authentication bằng JWT (Bearer token), xác thực user bằng email và password qua API:\nPOST /login (dữ liệu truyền lên dạng { email: \u0026ldquo;example@gmail.com\u0026rdquo;, password: \u0026ldquo;secret\u0026rdquo; }, thông tin lấy trong resource users).\nNhững resource và method được khai báo trong protected_resources thì cần đăng nhập để thực hiện, ví dụ:\n1 2 3 4  \u0026#34;protected_resources\u0026#34;: {  \u0026#34;users\u0026#34;: [\u0026#34;GET\u0026#34;, \u0026#34;POST\u0026#34;, \u0026#34;PUT\u0026#34;, \u0026#34;PATCH\u0026#34;, \u0026#34;DELETE\u0026#34;],  \u0026#34;products\u0026#34;: [\u0026#34;POST\u0026#34;, \u0026#34;PUT\u0026#34;, \u0026#34;PATCH\u0026#34;, \u0026#34;DELETE\u0026#34;] }   Nếu bạn muốn tuỳ chỉnh lại hoặc bổ sung cơ chế Authentication thì có thể chỉnh sửa lại ở file server.js phần Access control\nChú ý: Sau khi chỉnh sửa code cần khởi động lại server!\nChỉnh sửa API API mặc định là thông tin về User, toàn bộ được lưu vào trong file database.json. Có thể chỉnh sửa hoặc thêm dữ liệu vào file đó miễn khai báo đúng cấu trúc (chú ý users hiện đang được dùng luôn cho cả authentication). Ví dụ thêm 1 resource về products:\nServer sử dụng thư viện JSON Server, để xem đầy đủ tài liệu hướng dẫn về API hãy xem tại đây: https://github.com/typicode/json-server.\nNếu cần mockup dữ liệu lớn và ngẫu nhiên thì có thể dùng thêm dịch vụ sau https://mockaroo.com. Mockaroo cho phép mockup dữ liệu rất đa dạng, cấu hình được tỉ lệ, viết thêm các function điều kiện để tạo dữ liệu và cho phép xuất mockup ra dưới nhiều định dạng trong đó có JSON.\nDựng server online Nếu là đại gia thì có thể sử dụng VPS hoặc nếu không thì có thể sử dụng dịch vụ của Heroku (hoặc các dịch vụ tương tự): Đăng ký tài khoản, tạo App và đẩy source code kèm file database.json lên. Khi deploy code có thể chọn Heroku Git và gõ lệnh theo hướng dẫn bên dưới hoặc chọn kết nối với Github repo để deploy code qua 1 nút bấm (hoặc auto deploy khi có commit lên github).\nCần chú ý là nếu dùng gói Free thì khi Server không hoạt động (không có truy cập) sau 30 phút thì Server sẽ rơi vào trạng thái Sleep, lần truy cập kế tiếp sẽ hơi chậm 1 chút và mọi dữ liệu sẽ reset về ban đầu (như trong file database.json).\n=\u0026gt; Nếu dùng Heroku để host thì có thể chọc phá API thoải mái 😄\n","permalink":"https://robinhuy.github.io/blog/tao-1-rest-api-phuc-vu-cho-muc-dich-hoc-tap-trong-30-giay/","summary":"Video demo: https://youtu.be/O6Agt4cLbfo.\nDựng Server local Yêu cầu máy tính đã cài và chạy được Git + NodeJS.\nCác bước thực hiện:\n  Clone repository sau (hoặc fork về nếu muốn quản lý source code, nhớ star để ủng hộ tác giả): https://github.com/robinhuy/fake-rest-api-nodejs.git\n1   git clone https://github.com/robinhuy/fake-rest-api-nodejs.git     Cài đặt dependencies\n1 2   cd fake-rest-api-nodejs  npm install     Chạy server","title":"Tạo 1 REST API phục vụ cho mục đích học tập trong 30 giây"},{"content":"Note: Pug ở đây là 1 View Template Engine (chứ không phải tên 1 loại chó), có thể dùng ở cả phía Server lẫn Client. Trong bài viết này sẽ hướng dẫn sử dụng Pug phía client cho Frontend Dev.\nTạo khung project Sau khi đã thiết kế xong giao diện website, chúng ta sẽ bắt đầu xây dựng khung project. Ví dụ cấu trúc thông thường của 1 project web tĩnh:\nVới cấu trúc trên chúng ta có mỗi file HTML tương ứng với 1 trang tĩnh. Trong cùng 1 website thì các trang tĩnh có thể dùng chung các thành phần giống nhau (header, footer, sidebar, \u0026hellip;), dẫn đến việc code bị trùng lặp khó bảo trì. Trong 1 trang tĩnh cũng có thể có số lượng code rất lớn (ví dụ trang chủ, landing page) thì cũng sẽ khó đọc và bảo trì. Do đó chúng ta nên chia nhỏ các file html ra tương tự như khi chia nhỏ các file CSS và Javascript. Ví dụ:\nMặc định trong HTML không cho phép nhúng 1 file HTML khác, trừ khi dùng Javascript thao tác với DOM. Và Pug sẽ giúp chúng ta làm việc này bằng cách code HTML theo cú pháp của Pug (ngắn gọn hơn HTML thuần và cho phép include file) sau đó dùng pug-cli để chuyển từ Pug template sang code HTML. Lúc này cấu trúc project sẽ có dạng như sau:\nCài đặt Để sử dụng Pug chúng ta sẽ cần cài đặt lên máy qua 2 bước sau:\n  Bước 1: Cài đặt NodeJS trên trang https://nodejs.org, nên chọn bản LTS (Long Term Support - Recommended for most users). Sau khi cài xong chúng ta sẽ có luôn npm dùng để cài các thư viện rất hữu ích trong việc phát triển web, trong đó có Pug.\n  Bước 2: Cài đặt Pug qua NPM (chú ý ở đây mình cài pug-cli để chạy các lệnh của pug qua command line):\n  1  npm install -g pug-cli   Sử dụng pug-cli Tạo khung project như bình thường, nhưng không có file HTML, chúng ta sẽ code bằng template engine Pug rồi dùng pug-cli để tạo ra các file HTML.\nTạo thêm 1 thư mục là template (hoặc đặt tên khác cũng được, không bắt buộc). Thư mục này sẽ chứa các file template Pug, còn file HTML được tạo ra thì sẽ nằm ở bên ngoài như bình thường. Trong thư mục template cũng có thể chia nhỏ ra thành nhiều thư mục con để gom nhóm các file nếu như số lượng file lớn (ví dụ ở đây mình tạo thêm thư mục template-part để chứa các template nhỏ là các thành phần nhỏ trong 1 trang).\nSau khi tạo xong các file .pug thì chúng ta bật terminal tại thư mục gốc của project và chạy lệnh sau để bật pug-cli:\n1  pug -w ./template -o ./ -P   Khi chạy lệnh thành công thì chương trình sẽ theo dõi thay đổi ở file .pug và tự động render ra file .html ở bên ngoài. Chú ý các options trong lệnh trên:\n -w (watch - những file cần theo dõi thay đổi để render ra HTML), trong trường hợp này là tất cả các file trong thư mục template. -o (output - nơi xuất ra những file HTML được render từ file pug), trong trường hợp này là thư mục hiện tại (thư mục gốc của project, nơi chạy lệnh trên). -P (pretty - xuất ra HTML có format code). Option này có thể bỏ đi nếu không cần format đẹp, code xuất ra sẽ dồn hết lại thành 1 dòng.  Ví dụ code Tạo 1 file là layout.pug đặt trong thư mục template/template-part, file này sẽ là khung giao diện chung cho toàn bộ website (nếu website có nhiều kiểu layout thì tạo nhiều file layout):\nCú pháp của template tương tự code HTML nhưng tối giản đi, chỉ cần tên thẻ, không cần thẻ đóng, nhưng cần chú ý thụt dòng đúng. Ngoài ra có thể dùng include để nhúng nội dung 1 file này vào file khác (ví dụ nhúng nội dung menu.pug và footer.pug vào layout.pug).\nTạo 1 file là index.pug nằm trong thư mục template (trang chủ, sẽ render ra trang index.html). Trang này \u0026ldquo;kế thừa\u0026rdquo; (extends) layout trên và chỉ thay đổi nội dung các block:\nCác trang khác thì làm tương tự trang chủ.\nSau khi tạo xong các trang thì chạy lại lệnh pug như hướng dẫn phần cài đặt. Nếu đã chạy rồi thì phải tắt đi chạy lại khi có thêm file template mới bằng cách bấm Ctrl + C\nXong phần cài đặt và thiết lập project, phần tiếp theo là học qua 1 số cú pháp cơ bản của Pug (không cần phải biết hết) và \u0026ldquo;cắt HTML CSS từ giao diện có sẵn\u0026rdquo; các bạn hãy tự làm nốt nhé, bài viết đến đây là quá dài rồi.\nHappy coding!!!\n","permalink":"https://robinhuy.github.io/blog/code-web-tinh-de-hon-voi-pug/","summary":"Note: Pug ở đây là 1 View Template Engine (chứ không phải tên 1 loại chó), có thể dùng ở cả phía Server lẫn Client. Trong bài viết này sẽ hướng dẫn sử dụng Pug phía client cho Frontend Dev.\nTạo khung project Sau khi đã thiết kế xong giao diện website, chúng ta sẽ bắt đầu xây dựng khung project. Ví dụ cấu trúc thông thường của 1 project web tĩnh:","title":"Code web tĩnh dễ hơn với Pug"},{"content":"Thông thường 1 trang web viết bằng WordPress có thể đẩy lên Internet qua Free Hosting, Share Hosting, VPS, \u0026hellip; Với những ai mới học mà muốn tiết kiệm chi phí thì thường dùng Free Hosting, nhưng Free Hosting thường là host nước ngoài, có rất nhiều hạn chế và hay bị lỗi. Trong bài viết này mình sẽ hướng dẫn các bạn tạo một website WordPress miễn phí trên Heroku và chức năng có thể sử dụng gần như thuê 1 con VPS vậy 😎.\nBước 1 Tạo tài khoản trên https://heroku.com.\nXác nhận tài khoản bằng cách thêm hình thức thanh toán (credit card). Chỉ là thêm hình thức thanh toán chứ không mất phí. Sau khi thêm xong thì sẽ được hưởng thêm rất nhiều quyền lợi và được sử dụng thêm các add-on như database (cần khi cài WordPress). Nếu ai không có Credit Card thì ra ngân hàng đăng ký rất nhanh, nhiều ngân hàng cho phép lấy luôn ngay sau khi đăng ký.\nBước 2 Tạo 1 app mới trong Heroku:\nTải source code WordPress về và giải nén, ta được thư mục wordpress.\nĐẩy source code WordPress lên app vừa tạo bằng Heroku CLI:\nBước 3 Tạo CSDL cho website bằng cách vào tab Resource, phần add-ons và thêm add-on JawsDB Maria (có thể dùng CSDL khác như JawsDB MySQL, \u0026hellip;). Chú ý chỉ có tài khoản đã xác thực (đã thêm Credit Card) thì mới thêm add-ons được:\nSau khi thêm add-on JawsDB Maria thì bấm vào biểu tượng của add-on để chuyển qua trang cấu hình của add-on. Tại đây sẽ có hiển thị thông số để kết nối với CSDL:\nBật website WordPress đã deploy từ trước lên và cài đặt với các thông số kết nối CSDL của add-on JawsDB Maria. Sau đó chúng ta đã có thể tận hưởng thành quả của mình.\nChú ý: Trên Heroku không cho lưu static files nên các themes, plugins hoặc file upload muốn được lưu trữ lại phải nằm trong source code 😅\n","permalink":"https://robinhuy.github.io/blog/tao-website-wordpress-mien-phi-tren-heroku/","summary":"Thông thường 1 trang web viết bằng WordPress có thể đẩy lên Internet qua Free Hosting, Share Hosting, VPS, \u0026hellip; Với những ai mới học mà muốn tiết kiệm chi phí thì thường dùng Free Hosting, nhưng Free Hosting thường là host nước ngoài, có rất nhiều hạn chế và hay bị lỗi. Trong bài viết này mình sẽ hướng dẫn các bạn tạo một website WordPress miễn phí trên Heroku và chức năng có thể sử dụng gần như thuê 1 con VPS vậy 😎.","title":"Tạo website WordPress miễn phí trên Heroku"},{"content":" Có rất nhiều lý do để học về bảo mật web như:\n Bạn lo lắng về việc để lộ thông tin cá nhân trên mạng. Bạn quan tâm đến tính bảo mật cho website hoặc ứng dụng của mình. Bạn là lập trình viên và đang đi xin việc, bạn muốn chuẩn bị sẵn cho trường hợp nhà tuyển dụng hỏi về các vấn đề bảo mật web.  \u0026hellip; và nhiều lý do khác nữa.\nBài viết này sẽ giải thích một vài vấn đề bảo mật web thông dụng kèm theo thuật ngữ chuyên ngành của nó.\n Hai khái niệm cốt lõi trong bảo mật   Không một ai có thể an toàn 100%.\n  Một lớp bảo vệ là không đủ.\n  Cross-Origin Resource Sharing (CORS) Bạn đã bao giờ gặp một thông báo lỗi dạng như này chưa?\n1 2  No \u0026#39;Access-Control-Allow-Origin\u0026#39; header is present on the requested resource. Origin \u0026#39;null\u0026#39; is therefore not allowed access.   Nếu đã gặp phải lỗi này, bạn sẽ thử tìm giải pháp trên Google. Và bạn sẽ thấy ai đó hướng dẫn cài 1 extension giúp cho lỗi này biến mất và trang web của bạn lại hoạt động bình thường.\nNhưng liệu đây có phải cách làm tốt?\nCORS được sinh ra là để bảo vệ bạn chứ không phải để gây khó khăn cho bạn Trước khi giải thích về CORS, chúng ta hãy cùng tìm hiểu lại về Cookies, đặc biệt là Authentication Cookies. Authentication Cookies được sử dụng để thông báo cho server biết rằng bạn đã đăng nhập vào hệ thống, và chúng được tự động gửi kèm mỗi request lên server.\nGiả sử bạn đã đăng nhập vào Facebook, và họ sử dụng Authentication Cookies.\nSau đó bạn click vào 1 link bất kỳ trên mạng, ví dụ link video full 9 phút và nó sẽ redirect bạn về 1 website nào đó của hacker. Website này sẽ tự động chạy 1 đoạn code Javascript để thực hiện request lên facebook.com có kèm theo authentication cookie của bạn!\nTrong một thế giới không có CORS, hacker có thể thực hiện các thao tác trên Facebook với tài khoản của bạn mà bạn không hề hay biết. Ví dụ như đăng tin lên trên dòng thời gian của bạn kèm theo link video full 9 phút, sau đó bạn bè của bạn click vào link trên và cũng thực hiện hành vi tương tự, \u0026hellip; Vòng lặp này cứ tiếp diễn cho đến khi toàn bộ mạng xã hội facebook đều thấy xuất hiện link video full 9 phút 😆\nThực tế, với sự bảo vệ của CORS, Facebook sẽ chỉ cho phép những request với Origin (đính kèm trong request header) là facebook.com lên server của họ. Tức là chỉ có request thực hiện từ website facebook.com mới được chấp nhận. Hay nói cách khác, họ đã giới hạn việc chia sẻ tài nguyên giữa các tên miền khác nhau (cross-origin resource sharing).\nBạn cũng có thể tự hỏi:\n- \u0026ldquo;Vậy nếu website của hacker cố tình thay đổi header origin khi gửi request thì sao?\u0026rdquo;. Đúng, họ có thể làm như vậy. Nhưng trình duyệt sẽ tự động bỏ qua và chỉ gửi lên origin thực sự (là tên miền của website thực hiện request).\n- \u0026ldquo;Vậy nếu request được thực hiện từ phía server chứ không phải client?\u0026rdquo;. Trong trường hợp này hacker có thể vượt qua được CORS nhưng họ lại không thể gửi kèm được authentication cookie bởi vì nó nằm ở phía client.\nContent Security Policy (CSP) Để hiểu về CSP (chính sách bảo mật nội dung), trước tiên chúng ta cần tìm hiểu về một lỗ hổng rất thông dụng trên web, đó là XSS (cross-sitescripting, ký hiệu X thay cho C để tránh nhầm lẫn với CSS). XSS là khi kẻ xấu nhúng code Javascript vào trong code phía client của bạn.\nBạn có thể nghĩ rằng: \u0026ldquo;Nhúng code Javascript vào thì làm được gì? Thay đổi màu chữ từ đỏ sang xanh? \u0026hellip;\u0026rdquo;\nGiả sử một ai đó nhúng được code Javascript vào website mà bạn đang truy cập. Khi đó họ có thể:\n Giả dạng bạn để thực hiện một HTTP request. Nhúng 1 iframe trông như 1 phần website và yêu cầu bạn nhập mật khẩu rồi gửi request đến server của hacker. Chèn hoặc sửa một đường dẫn trên website gốc, dẫn đến một website giả mạo với giao diện giống hệt website gốc để thực hiện hành vi lừa đảo (ví dụ yêu cầu đăng nhập, yêu cầu nhập thông tin tài khoản, \u0026hellip;)  \u0026hellip; và muôn vàn khả năng khác.\nCSP sẽ cố gắng ngăn chặn điều này ngay từ đầu bằng cách giới hạn:\n Cái gì có thể được phép mở trong một iframe. Style nào có thể được tải. Request có thể được thực hiện ở đâu. \u0026hellip;  Vậy nó hoạt động như nào? Khi bạn bấm vào một đường link hoặc gõ địa chỉ website trên trình duyệt thì trình duyệt sẽ thực hiện một GET request. Và server sẽ trả về HTML kèm theo một vài HTTP headers. Nếu bạn muốn biết mình nhận được header như nào thì hãy bật tab Network trong DevTools và truy cập thử một website. Bạn có thể sẽ thấy một response header như sau:\n1 2 3 4 5 6 7 8 9  content-security-policy: default-src * data: blob:; script-src *.facebook.com *.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* \u0026#39;unsafe-inline\u0026#39; \u0026#39;unsafe-eval\u0026#39; *.atlassolutions.com blob: data: \u0026#39;self\u0026#39;; style-src data: blob: \u0026#39;unsafe-inline\u0026#39; *; connect-src *.facebook.com facebook.com *.fbcdn.net *.facebook.net *.spotilocal.com:* wss://*.facebook.com:* https://fb.scanandcleanlocal.com:* *.atlassolutions.com attachment.fbsbx.com ws://localhost:* blob: *.cdninstagram.com \u0026#39;self\u0026#39; chrome-extension://boadgeojelhgndaghljhdicfkmllpafd chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm;   Đó là chính sách bảo mật nội dung của Facebook. Tìm hiểu chi tiết hơn các directives (chỉ thị):\n  default-src: Hạn chế tất cả các CSP directive mà không được liệt kê rõ ràng.\n  script-src: Giới hạn những script có thể được load.\n  style-src: Giới hạn những style có thể được load.\n  connect-src: Giới hạn những URL nào có thể được load sử dụng script như fetch, XHR, ajax, \u0026hellip;\n  Có nhiều CSP directive khác nữa ngoài 4 cái ở trên. Trình duyệt sẽ đọc CSP header và áp dụng toàn bộ những directive đó cho mọi thứ trên trang HTML.\nHTTPS hay HTTP Secure Chắc chắn bạn đã từng nghe nói đến HTTPS. Có thể bạn nghe nói rằng Chrome sẽ đánh dấu trang web của bạn là không an toàn (insecure) nếu nó không có HTTPS.\nVề bản chất thì HTTPS khá đơn giản. HTTPS thì được mã hóa còn HTTP thì không.\nVậy cái này thì có liên quan gì nếu như bạn không gửi những dữ liệu nhạy cảm? Hãy cùng tìm hiểu thêm về một thuật ngữ khác: MITM ( M an i n t he M iddle).\nNếu bạn đang sử dụng Wi-Fi công cộng (không đặt mật khẩu) ở một quán cà phê, một ai đó có thể dễ dàng bắt được request của bạn. Nếu dữ liệu của bạn không được mã hóa, họ có thể đọc được làm bất cứ thứ gì họ muốn. Họ có thể chỉnh sửa HTML, CSS hoặc Javascript trước khi trình duyệt của bạn nhận được dữ liệu. Tương tự như XSS ở trên, bạn có thể hình dung được hacker có thể làm được những gì.\nSử dụng HTTPS thì mọi dữ liệu truyền và nhận giữa máy tính của bạn và server đều được mã hóa khiến cho hacker không thể đọc hay chỉnh sửa tùy ý được.\nHTTP Strict-Transport-Security (HSTS) Tiếp tục sử dụng Facebook header làm ví dụ:\n1  strict-transport-security: max-age=15552000; preload   Header trên chỉ áp dụng nếu bạn truy cập trang sử dụng HTTPS:\n max-age: Chỉ định thời gian trình duyệt ghi nhớ để bắt buộc người dùng truy cập website bằng HTTPS. preload: Không quan trọng lắm với mục đích của chúng ta, nó là một dịch vụ được host bởi Google.  Giả sử bạn truy cập trang Facebook lần đầu tiên và bạn biết HTTPS an toàn hơn HTTP nên bạn truy cập qua HTTPS. Khi trình duyệt nhận được header trên nó sẽ ghi nhớ chuyển hướng những request sau này của bạn về HTTPS. Một tháng sau có ai đó gửi cho bạn một link đến Facebook sử dụng HTTP và bạn bấm vào nó. Do 1 tháng thì nhỏ hơn 15552000 giây (giá trị của max-age) nên trình duyệt sẽ gửi request dưới dạng HTTPS để tránh tấn công MITM.\nTổng kết Bảo mật web rất quan trọng bất kể bạn làm ở vị trí nào trong mảng phát triển web. Bạn càng tiếp xúc, càng tìm hiểu nó kỹ hơn thì bạn càng có lợi.\nBảo mật nên là thứ quan trọng với tất cả mọi người chứ không chỉ riêng những chuyên gia bảo mật 👮.\nNguồn: https://medium.freecodecamp.org/a-quick-introduction-to-web-security-f90beaf4dd41\n","permalink":"https://robinhuy.github.io/blog/co-ban-ve-bao-mat-web/","summary":"Có rất nhiều lý do để học về bảo mật web như:\n Bạn lo lắng về việc để lộ thông tin cá nhân trên mạng. Bạn quan tâm đến tính bảo mật cho website hoặc ứng dụng của mình. Bạn là lập trình viên và đang đi xin việc, bạn muốn chuẩn bị sẵn cho trường hợp nhà tuyển dụng hỏi về các vấn đề bảo mật web.  \u0026hellip; và nhiều lý do khác nữa.","title":"Cơ bản về bảo mật Web"},{"content":"Để quản lý Dependencies (packages) trong Go chúng ta có thể dùng nhiều tool hỗ trợ. Về bản chất thì chúng tương tự nhau nhưng cũng có nhưng ưu nhược điểm riêng, ví dụ một số tool mình đã từng sử dụng như Dep hoặc Glide (tham khảo bài viết Quản lý package trong Go).\nHiện mình đang sử dụng một tool khác là Govendor, và theo ý kiến cá nhân thì mình thấy tool này dễ sử dụng hơn, và cách cấu hình cũng như hoạt động của nó khá giống với npm trên NodeJS. Do đó sẽ dễ tiếp cận hơn đối với các lập trình viên NodeJS hoặc Javascript nói chung.\nDưới đây là một số hướng dẫn cơ bản để sử dụng Govendor.\nCài đặt Dùng Go get để cài như các package khác:\n1  go get -u github.com/kardianos/govendor   Chú ý project vẫn phải nằm trong $GOPATH/src.\nSử dụng Khởi tạo Govendor (trong thư mục chứa source code của project):\ngovendor init Lệnh này sẽ tạo thêm thư mục vendor trong project, và trong thư mục này có 1 file để cấu hình dependencies là vendor.json.\nCài đặt dependencies (ví dụ github.com/jinzhu/gorm):\n1  govendor fetch github.com/jinzhu/gorm   _Lệnh trên sẽ tạo mới (hoặc update) gói github.com/jinzhu/gorm (lưu vào trong thư mục vendor)._\nTrong trường hợp cần chỉ định rõ version của dependency thì ta dùng lệnh sau:\n1 2 3 4 5  // Lấy version mới nhất bắt đầu với tên v1 govendor fetch github.com/jinzhu/gorm@v1  // Lấy chính xác version v1.9 govendor fetch github.com/jinzhu/gorm@=v1.9   Hoặc chúng ta có thể sửa file vendor.json, bổ sung thêm trường \u0026ldquo;version\u0026rdquo; và \u0026ldquo;versionExact\u0026rdquo;:\n{ \u0026#34;checksumSHA1\u0026#34;: \u0026#34;qUfWkFlJqc24xFWAsWxKkyyO+zw=\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;github.com/jinzhu/gorm\u0026#34;, \u0026#34;revision\u0026#34;: \u0026#34;742154be9a26e849f02d296073c077e0a7c23828\u0026#34;, \u0026#34;revisionTime\u0026#34;: \u0026#34;2018-10-07T00:49:37Z\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;versionExact\u0026#34;: \u0026#34;v1.9.1\u0026#34; }, Để xóa dependency ta dùng lệnh remove:\n1  govendor remove github.com/jinzhu/gorm   Kiểm tra lại các dependencies trong project:\n1  govendor list     Ví dụ hiển thị trên terminal   Chú ý ký tự v ở trước là trạng thái của package. Trạng thái của package có thể là vendor (v), external (e), \u0026hellip; tham khảo chi tiết trên github.\nTip Khi quản lý source code (ví dụ dùng Git), chúng ta sẽ commit cả thư mục vendor lên. Như vậy khi người khác pull source code về sẽ không cần cài lại dependencies nữa. Đặc biệt khi deploy code dùng Docker thì ở bước build chúng ta cũng không cần lấy dependencies qua mạng.\nMột cách khác đó là không lưu thư mục vendor vào trong source code (để giảm bớt dung lượng), thì chúng ta chỉ cần lưu file vendor.json(tương tự package.json khi sử dụng npm). Sau đó khi pull code về thì cài lại dependencies bằng lệnh govendor sync.\n","permalink":"https://robinhuy.github.io/blog/quan-ly-dependencies-trong-go-bang-govendor/","summary":"Để quản lý Dependencies (packages) trong Go chúng ta có thể dùng nhiều tool hỗ trợ. Về bản chất thì chúng tương tự nhau nhưng cũng có nhưng ưu nhược điểm riêng, ví dụ một số tool mình đã từng sử dụng như Dep hoặc Glide (tham khảo bài viết Quản lý package trong Go).\nHiện mình đang sử dụng một tool khác là Govendor, và theo ý kiến cá nhân thì mình thấy tool này dễ sử dụng hơn, và cách cấu hình cũng như hoạt động của nó khá giống với npm trên NodeJS.","title":"Quản lý Dependencies trong Go bằng Govendor"},{"content":"Tôi vừa trở về sau một chuyến du lịch 2 tuần đến Nhật Bản.\nMột trong những hình ảnh quen thuộc ở đây đó là số lượng lớn các cửa hàng cà phê Starbucks, đặc biệt là quanh khu vực Shinjuku và Roppongi. Trong khi chờ đợi một tách ca cao nóng, tôi đã nghĩ về cách thức mà Starbucks phục vụ khách hàng.\nStarbucks cũng như hầu hết các công ty khác thì đều muốn tối đa số lượng khách hàng có thể phục vụ. Càng đông khách thì lợi nhuận càng cao. Do đó họ đã sử dụng quy trình bất đồng bộ (asynchronous processing).\nKhi bạn yêu cầu đồ uống, nhân viên thu ngân sẽ đánh dấu một chiếc cốc với đơn hàng của bạn và đưa nó vào một hàng đợi (queue). Hàng đợi chỉ đơn giản là một dãy những chiếc cốc xếp hàng và chuyển sang cho thợ pha cà phê. Bằng cách này, nhân viên thu ngân sẽ có thể tiếp tục nhận thêm đơn hàng kể cả khi đơn hàng cũ chưa xử lý xong. Trong trường hợp cửa hàng quá đông khách, họ có thể áp dụng kịch bản Competing Consumer để phục vụ khách tốt hơn: Vẫn chỉ một nhân viên thu ngân nhưng thuê nhiều nhân viên pha cà phê.\nCorrelation problem Giải quyết vấn đề theo cách bất đồng bộ mang lại cho Starbucks nhiều lợi ích, tuy nhiên đi kèm với nó cũng có khá nhiều vấn đề cần giải quyết.\nVí dụ về vấn đề liên hệ (correlation problem) như đồ uống không hoàn thành theo đúng thứ tự như khi khách hàng yêu cầu. Điều này có thể xảy ra khi:\n Các nhân viên pha cà phê khi pha chế sử dụng các công cụ khác nhau. Pha chế đồ uống hỗn hợp có thể lâu hơn là cà phê. Nhân viên pha cà phê cũng có thể thực hiện cùng một lúc các công việc giống nhau trước để tối ưu thời gian pha chế.  Kết quả là đồ uống khi pha chế xong sẽ được mang ra cho khách không theo thứ tự và cần phải mang đến đúng vị khách đã yêu cầu đồ uống.\nStarbucks giải quyết vấn đề này theo một pattern được sử dụng trong kiến trúc messaging: Correlation Identifier. Ở Mỹ, hầu hết các cửa hàng Starbucks sẽ xử lý vấn đề này bằng cách viết tên bạn lên cốc và gọi lên khi đồ uống hoàn thành. Ở những quốc gia khác, bạn sẽ phải liên hệ bằng loại đồ uống (ví dụ như nâu đá, americano, \u0026hellip;).\nException Handling Xử lý ngoại lệ (exception handling) trong môi trường asynchronous messaging có thể khá là khó. Hãy cùng xem Starbucks giải quyết các trường hợp ngoại lệ như nào.\n Khi khách hàng không thể trả tiền, họ sẽ vứt bỏ đồ uống nếu nó đã được pha chế hoặc nếu không thì họ sẽ loại bỏ nó ra khỏi hàng đợi. Nếu họ mang cho khách hàng một đồ uống không phù hợp hay có vấn đề thì họ sẽ làm lại nó. Nếu như máy pha cà phê bị hỏng và họ không thể pha chế được đồ uống thì họ sẽ trả lại tiền cho khách hàng..  Mỗi một ví dụ trên miêu tả cho một cách xử lý ngoại lệ phổ biến:\n Write-off: Cách đơn giản nhất đó là không làm gì hoặc loại bỏ hết những cái vừa làm. Nghe có vẻ không phải là một cách hay, tuy nhiên trong thực tế thì lựa chọn này có thể chấp nhận được. Nếu như mất mát là nhỏ thì việc xây dựng một giải pháp xử lý ngoại lệ còn tốn kém hơn là bỏ qua nó. Ví dụ như tôi đã từng làm việc cho một số nhà cung cấp dịch vụ Internet mà áp dụng cách giải quyết này cho các lỗi xảy ra ở chu trình thanh toán và cung cấp dịch vụ. Một số khách hàng có thể sử dụng dịch vụ khi chưa thực hiện xong thanh toán. Doanh thu mất đi đủ nhỏ để cho phép nghiệp vụ vận hành bình thường và cứ sau một khoảng thời gian định kỳ họ sẽ thống kê lại những tài khoản chưa thực hiện thanh toán để ngắt dịch vụ. Retry: Khi có một số hành động trong một nhóm các hành động (ví dụ transaction) bị thất bại, chúng ta sẽ có 2 lựa chọn là undo những cái thành công hoặc retry những cái thất bại. Retry (thực hiện lại hành động) là một lựa chọn tốt nếu như có khả năng retry thành công. Ví dụ như xung đột nghiệp vụ thì retry có thể sẽ không thành công nhưng nếu là do một hệ thống bên ngoài tạm thời không đáp ứng thì retry có thể thành công. Một ví dụ điển hình là Idempotent Receiver, trong trường hợp này chúng ta có thể đơn giản retry lại tất cả hành động vì receivers thành công sẽ bỏ qua các message trùng lặp. Compensating Action: Lựa chọn cuối cùng đó là undo lại những hành động đã hoàn thành để đưa hệ thống trở lại trạng thái trước đó. Cách này sẽ hoạt động tốt trong các hệ thống tài chính, chúng ta có thể cộng bù lại những khoản tiền đã bị trừ đi trước đó.  Tất cả các cách giải quyết trên đều khác với phương pháp Two-phase commit mà dựa trên các bước riêng biệt là prepare và execute. Trong ví dụ của Starbucks, Two-phase commit tương đương với việc khách hàng chờ ở quầy thanh toán cho đến khi đồ uống được pha chế xong, sau đó khách hàng trả tiền và nhận đồ uống + biên lai thanh toán. Cả nhân viên thu ngân lẫn khách hàng đều không thể rời đi cho đến khi giao dịch hoàn tất. Sử dụng Two-phase commit có thể khiến Starbucks phá sản bởi vì số lượng khách hàng mà họ có thể phục vụ trong một khoảng thời gian là quá thấp.\nCần chú ý rằng Two-phase commit có thể làm ảnh hưởng đến cách hoạt động tự do của message (và cả khả năng mở rộng) bởi vì nó phải duy trì trạng thái của các tài nguyên trong giao dịch qua các hành động bất đồng bộ.\nConversations Sự tương tác trong quán cà phê là một ví dụ tốt của một pattern đơn giản nhưng phổ biến: Conversation pattern.\nSự tương tác giữa hai bên (quán cà phê và khách hàng) có chứa một tương tác ngắn đồng bộ (gọi nước và trả tiền) và một tương tác dài bất đồng bộ (pha chế và nhận đồ uống). Loại tương tác này khá bổ biến trong các kịch bản mua bán.\nVí dụ khi đặt một đơn hàng trên Amazon, tương tác ngắn đồng bộ gán một mã đơn hàng và tất cả các bước tiếp theo (trừ tiền trong thẻ, đóng gói hàng, vận chuyển, \u0026hellip;) thì được thực hiện bất đồng bộ. Bạn được thông báo qua email khi các bước bổ sung hoàn thành. Nếu có bất kỳ vấn đề sai nào xảy ra, Amazon thường sẽ bồi thường (qua credit card) hoặc thử lại (gửi lại các sản phẩm bị mất).\nTóm lại chúng ta có thể thấy rằng thế giới thực thường là bất đồng bộ. Cuộc sống hàng ngày của chúng ta có nhiều tương tác bất đồng bộ. Nó có nghĩa là kiến trúc truyền tin bất đồng bộ có thể là một cách tự nhiên để thực thể hóa những tương tác này. Nó cũng có nghĩa là chúng ta nhìn vào các ví dụ trong cuộc sống hàng ngày để thiết kế ra những giải pháp hợp lý.\nDomo arigato gozaimasu! (xin cảm ơn rất nhiều)\nBài viết được dịch từ một chương trong sách The best software writting I, tác giả Gregor\u0026nbsp;Hohpe.\n","permalink":"https://robinhuy.github.io/blog/starbucks-khong-su-dung-two-phase-commit/","summary":"Tôi vừa trở về sau một chuyến du lịch 2 tuần đến Nhật Bản.\nMột trong những hình ảnh quen thuộc ở đây đó là số lượng lớn các cửa hàng cà phê Starbucks, đặc biệt là quanh khu vực Shinjuku và Roppongi. Trong khi chờ đợi một tách ca cao nóng, tôi đã nghĩ về cách thức mà Starbucks phục vụ khách hàng.\nStarbucks cũng như hầu hết các công ty khác thì đều muốn tối đa số lượng khách hàng có thể phục vụ.","title":"Starbucks không sử dụng Two-Phase Commit"},{"content":"Một Database Transaction, theo như định nghĩa sẽ phải thỏa mãn các tính chất sau: Atomic, Consistency, Isolation và Durable (hay thường được gọi là ACID). Bài viết này sẽ tập trung vào tính chất Consistency (tính nhất quán), và so sánh giữa Eventual Consistency với Strong Consistency trong hệ thống Distributed Databases qua các ví dụ đời thường.\nLý thuyết Distributed Database (hệ thống cơ sở dữ liệu phân tán): Là hệ thống Cơ sở dữ liệu (CSDL) mà có thể được phân tải, lưu trữ ở nhiều nơi. Ví dụ như ứng dụng sử dụng nhiều CSDL và các CSDL có thể nằm ở các máy chủ vật lý khác nhau.\nStrong Consistency (tính nhất quán mạnh): Sau khi một cập nhật được diễn ra thì tất cả các lần đọc dữ liệu sau đó đều trả về giá trị mới được cập nhật.\nEventual Consistency (tính nhất quán cuối cùng, là một dạng của tính nhất quán yếu - Weak Consistency): Sau khi một cập nhật được diễn ra, các lần đọc sau đó không đảm bảo sẽ luôn trả về giá trị mới được cập nhật (có thể có lần đọc vẫn trả về dữ liệu cũ). Tuy nhiên sau một khoảng thời gian (đồng bộ giữa các CSDL) thì cuối cùng các lần đọc đều trả về giá trị mới nhất.\nVí dụ đời thường Một anh chàng tên Duy, có sở thích sưu tập phim và toàn bộ những bộ phim anh ta tải được trên mạng đều được lưu vào laptop. Laptop ở đây đóng vai trò như một CSDL.\nDo sợ vào một ngày đẹp trời laptop bị ăn trộm hoặc bị hỏng sẽ làm mất hết toàn bộ phim trong máy nên Duy mua thêm một ổ cứng di động 8TB và một tài khoản Dropbox để sao lưu lại toàn bộ dữ liệu cho an toàn. Lúc này Laptop, ổ cứng di động và tài khoản Dropbox được coi như một hệ CSDL phân tán (theo mô hình Master - Slave).\n1. Eventual Consistency Khi sử dụng nhiều replica (bản sao) cho một CSDL, giả sử có một write request đến một replica (insert, update, delete dữ liệu) thì chúng ta phải làm sao cho các replica còn lại cũng nhận được request tương ứng để đồng bộ dữ liệu.\nViệc đồng bộ dữ liệu này sẽ tốn thời gian (có thể rất nhỏ), nhưng trong khoảng thời gian đó, nếu có một read request đến một replica chưa đồng bộ xong thì request đó sẽ nhận được kết quả cũ hơn (stale data).\nQuay lại ví dụ của Duy:\n  Cứ mỗi tuần vào tối Thứ Sáu, Duy sẽ đồng bộ dữ liệu trong laptop với ổ cứng di động.\n  Tối Chủ Nhật, Long là bạn của Duy mượn ổ cứng di động của Duy để xem phim. Lúc này Long có thể lấy luôn ổ cứng mang về và có toàn bộ dữ liệu cho đến lần sao lưu gần nhất (tức là nếu Thứ Bảy hoặc sáng Chủ Nhật Duy download thêm phim thì trong ổ cứng sẽ không có).\n  Sáng Thứ Hai tuần sau, Long trả lại ổ cứng cho Duy và đến tối Chủ Nhật lại mượn tiếp. Thời điểm đó Long sẽ lại có được toàn bộ phim (bao gồm cả những phim chưa kịp đồng bộ trong tuần này).\n  2. Strong Consistency Tương tự như Eventual Consistency, tuy nhiên để đảm bảo tính nhất quán thì trong trường hợp Strong Consistency toàn bộ các replica sẽ bị delay (trạng thái bận) cho đến khi quá trình đồng bộ hoàn tất.\nChỉ đến khi dữ liệu giữa các replica đã nhất quán thì chúng ta mới trả về kết quả cho client, do đó nó sẽ đảm bảo mọi read request đến sau luôn lấy được dữ liệu mới nhất (nhưng thời gian trả về kết quả lâu hơn).\nQuay lại ví dụ của Duy:\n  Cứ mỗi tuần vào tối Thứ Bảy, Duy sẽ đồng bộ dữ liệu trong laptop Dropbox.\n  Tối Chủ Nhật, Dương cũng là bạn của Duy xin link Dropbox để tải phim. Lúc này Duy bảo: \u0026ldquo;Tôi đã share cho ông link qua Slack rồi nhé, nhưng để mai hãy down thì sẽ có phim mới nhất tôi vừa down sáng nay\u0026rdquo;.\n  Như vậy để có phim mới nhất xem thì Dương sẽ phải chờ một thời gian khá dài để Duy đồng bộ phim lên Dropbox. Thời gian nhanh hay chậm còn tùy thuộc vào dung lượng phim, tốc độ internet, \u0026hellip;\n  3. Kết luận Strong Consistency cho phép dữ liệu luôn nhất quán và được cập nhật mới nhất, nhưng nó có độ trễ cao.\nEventual Consistency thì có độ trễ thấp, kết quả trả về nhanh nhưng dữ liệu nhận được có thể không phải mới nhất.\n=\u0026gt; Do đó việc thiết kế CSDL hay sử dụng thuộc tính nhất quán nào sẽ tùy vào yêu cầu của từng project cụ thể.\nTham khảo bài viết gốc Eventual vs Strong Consistency in Distributed Databases.\n","permalink":"https://robinhuy.github.io/blog/eventual-consistency-va-strong-consistency-trong-he-thong-co-so-du-lieu-phan-tan/","summary":"Một Database Transaction, theo như định nghĩa sẽ phải thỏa mãn các tính chất sau: Atomic, Consistency, Isolation và Durable (hay thường được gọi là ACID). Bài viết này sẽ tập trung vào tính chất Consistency (tính nhất quán), và so sánh giữa Eventual Consistency với Strong Consistency trong hệ thống Distributed Databases qua các ví dụ đời thường.\nLý thuyết Distributed Database (hệ thống cơ sở dữ liệu phân tán): Là hệ thống Cơ sở dữ liệu (CSDL) mà có thể được phân tải, lưu trữ ở nhiều nơi.","title":"Eventual Consistency và Strong Consistency trong hệ thống Cơ sở dữ liệu phân tán"},{"content":"Bài viết được dịch (có chỉnh sửa) từ cuốn Patterns Principles and Practices of Domain Driven Design của Scott Millett và Nick Tune, dành cho những ai đã và đang cần tìm hiểu về CQRS (Command Query Responsibility Segregation). Trong bài viết có sử dụng nhiều thuật ngữ chuyên ngành, mình sẽ dẫn link đến các tài liệu Tiếng Anh tương ứng chứ không dịch (không biết dịch như nào hoặc dịch ra nghe rất củ chuối 😬).\n1. CQRS rất khó Xét về mức cơ bản thì CQRS chỉ là một pattern đơn giản triển khai nguyên tắc Single Responsibility Principle (SRP) ở lớp Domain Model.\nCQRS không phải là một framework hay là một hệ thống multiple database, nó là pattern được áp dụng vào bounded context, dùng để chia Domain Model thành 2 model: Write Model (Command side) và Read Model (Query side), chúng được xử lý riêng rẽ để đạt hiệu quả tốt hơn.\nCQRS thiên về khái niệm, tư tưởng hơn là một tập hợp các nguyên tắc và pattern phức tạp mà bạn phải tuân theo.\n2. CQRS là Eventually Consistent Eventually Consistency được áp dụng để Read Model được cập nhật bất đồng bộ (asynchronous) với Write Model (sẽ có một bài viết khác giải thích chi tiết hơn về Eventually Consistency).\nĐây không phải là điều kiện tiên quyết của CQRS, nhưng nó thường được sử dụng để cho phép bên Read side có thể scale (mở rộng) dễ dàng.\nCQRS không yêu cầu bạn phải thực hiện Eventually Consistent. Bạn có thể sử dụng cùng một database và transaction để cập nhật Read Model hoặc sử dụng caching để có Strong Consistency.\n3. Model phải dùng Event Sourcing Event Sourcing là một cách rất hiệu quả để xây dựng cả Read Model và Write Model nhưng nó không bắt buộc phải có khi sử dụng CQRS.\nEvent Sourcing là một giải pháp lưu trữ dữ liệu theo lịch sử một cách chính xác, nhưng nó cũng giúp xây dựng Read Model dễ dàng hơn bởi vì bạn có thể tạo ra bất kỳ projection mong muốn từ những dữ liệu sự kiện theo lịch sử.\n4. Command nên là Asynchronous CQRS không bắt buộc Command phải được gửi theo kiểu fire-and-forget (không quan tâm đến response).\nVới các trường hợp tương tác cao, nhiều user thực hiện thay đổi vào cùng dữ liệu thì Asynchronous Command sẽ hiệu quả. Nó sẽ giúp ứng dụng dễ scale và không bị quá tải.\nTuy nhiên các Command mà không phản hồi lại thành công hay thất bại sẽ cần phải có những cách khác để cập nhật cho user kết quả của hành động. Nó có thể là qua email hoặc qua các thao tác phụ xử lý message lỗi.\n5. CQRS chỉ hoạt động được với hệ thống Messaging Nếu bạn đang tìm cách áp dụng một Read store theo cách eventually consistent hoặc xử lý Command theo kiểu bất đồng bộ thì dùng một messaging framework có thể là một ý tưởng tốt.\nNgược lại, sử dụng một hệ thống messaging khi không cần thiết sẽ làm cho ứng dụng của bạn trở nên phức tạp và khó bảo trì hơn.\n6. Cần phải sử dụng Domain Event với CQRS Sử dụng Event để dựng Read Model là một phương pháp hiệu quả để giữ Read Model và Write Model được tách biệt.\nTuy nhiên nó không phải là cách duy nhất, và bạn có thể sử dụng nhiều method khác để tạo Read store, ví dụ như dựng trực tiếp từ dữ liệu quan hệ của Write Model.\nHy vọng những chú ý trên đây sẽ giúp các bạn xây dựng hệ thống sử dụng CQRS một cách chính xác hơn, phù hợp với yêu cầu của ứng dụng.\n","permalink":"https://robinhuy.github.io/blog/nhung-quan-niem-sai-lam-ve-cqrs/","summary":"Bài viết được dịch (có chỉnh sửa) từ cuốn Patterns Principles and Practices of Domain Driven Design của Scott Millett và Nick Tune, dành cho những ai đã và đang cần tìm hiểu về CQRS (Command Query Responsibility Segregation). Trong bài viết có sử dụng nhiều thuật ngữ chuyên ngành, mình sẽ dẫn link đến các tài liệu Tiếng Anh tương ứng chứ không dịch (không biết dịch như nào hoặc dịch ra nghe rất củ chuối 😬).","title":"Những quan niệm sai lầm về CQRS"},{"content":"Chắc hẳn các lập trình viên, đặc biệt là các Backend developer, sẽ không còn xa lạ với Cookie (thường đi kèm với Session). Tuy nhiên nhiều người có thể chưa thực sự hiểu rõ vì nó thường hay được giới thiệu kèm với một ngôn ngữ lập trình phía Backend (ví dụ như PHP, NodeJS, \u0026hellip;) và sử dụng các các thư viện để thao tác. Bài viết này sẽ giới thiệu rõ ràng hơn về Cookie cả phía Backend lẫn Frontend và một số vấn đề bảo mật liên quan đến Cookie để các web developer thận trọng hơn khi sử dụng.\nCookie là gì? Cookie (tên đầy đủ HTTP Cookies) là một file text nhỏ được lưu trữ bởi trình duyệt (browser) ở trên máy người dùng (client). Nó thường được sử dụng với 3 mục đích chính:\n Quản lý phiên làm việc (Session management). Cá nhân hóa thông tin người dùng (Personalization). Theo dõi, phân tích hành vi người dùng (Tracking).  Ví dụ với trường hợp Quản lý phiên làm việc:\n  User truy cập một trang trong website (thực hiện 1 request lên server), web server yêu cầu trình duyệt lưu thông tin truy cập của user vào Cookie. Sau đó, mỗi khi user thực hiện một request khác vẫn website đó thì trình duyệt lại gửi thông tin đã lưu trong Cookie lên. Nhờ đó web server có thể biết được các request là đến từ cùng 1 User. Nếu bạn sử dụng 2 trình duyệt khác nhau trên cùng một máy thì server sẽ hiểu các request là đến từ 2 người vì các trình duyệt khác nhau lưu Cookies khác nhau.   Thông tin lưu trữ trong cookie có thể trong một thời gian dài hoặc chỉ trong một phiên làm việc tùy vào cách cấu hình Cookies. Thông thường ta hay sử dụng Session Cookies để lưu trạng thái của phiên làm việc (SessionID), thông tin sẽ bị xóa khi hết phiên làm việc (khi người dùng tắt trình duyệt).\n  Cách tạo ra Cookie Cookie có thể được tạo ra từ phía Server hoặc Client. Khi nhận được một HTTP request, nếu muốn tạo Cookie từ server, ta sẽ trả responve về cho trình duyệt một HTTP Header với tên là Set-Cookie, giá trị là các dữ liệu cần lưu vào Cookie (theo dạng key=value) và các tùy chọn như: Loại Cookies, thời gian sống, \u0026hellip; Ví dụ tạo một Cookies đơn giản:\nSet-Cookie: sid=huydq Khi trình duyệt thực hiện các request khác, nó sẽ gửi kèm dữ liệu trong Cookies trở lại server cũng qua HTTP Header với tên là Cookie:\nCookie: sid=huydq; Cookies cũng có thể được tạo, chỉnh sửa từ phía Client thông qua Javascript bằng cách sử dụng document.cookie. Cú pháp tương tự như tạo Cookies từ phía server:\n1  document.cookie = \u0026#34;sid=huydq;\u0026#34;;   Để lấy ra giá trị của Cookies ta cũng chỉ cần gọi document.cookie.\nBảo mật Cookies Do Cookies thường được sử dụng để quản lý trạng thái của người dùng (ví dụ trạng thái đăng nhập), do đó có thể có một số vấn đề liên quan đến bảo mật khi sử dụng Cookies. Ví dụ như Cookies lưu trạng thái đăng nhập của người dùng bị đánh cắp, hacker có thể dùng Cookies này để giả mạo làm người dùng và thực hiện tương tác với trang web mà không cần đăng nhập. Một số extension của trình duyệt cho phép share Cookies để những người dùng khác nhau cùng truy cập vào 1 tài khoản mà không cần biết thông tin truy cập như username và password.\nĐể bảo mật Cookies hơn thì khi tạo Cookies ta sẽ cần sử dụng thêm một số options như domain, path, \u0026hellip; dùng để giới hạn việc gửi Cookies chỉ cho phép theo domain và đường dẫn thiết lập trước. Ví dụ:\nSet-Cookie: sid=huydq; domain=huydq.dev; path=/auth Với các website hỗ trợ https thì ta thêm thuộc tính secure để đảm bảo việc gửi Cookies sẽ chỉ qua SSL, giao thức https:\nSet-Cookie: sid=huydq; domain=huydq.dev; path=/auth; secure Với các dữ liệu cần bảo mật hơn (ví dụ như SessionID) thì ta sẽ chỉ thiết lập từ phía server và bổ sung thêm options HttpOnly để không cho phép truy cập dữ liệu này thông qua Javascript (document.cookie). Ví dụ:\nSet-Cookie: sid=huydq; domain=huydq.dev; path=/auth; secure; HttpOnly ","permalink":"https://robinhuy.github.io/blog/cookies-va-van-de-bao-mat/","summary":"Chắc hẳn các lập trình viên, đặc biệt là các Backend developer, sẽ không còn xa lạ với Cookie (thường đi kèm với Session). Tuy nhiên nhiều người có thể chưa thực sự hiểu rõ vì nó thường hay được giới thiệu kèm với một ngôn ngữ lập trình phía Backend (ví dụ như PHP, NodeJS, \u0026hellip;) và sử dụng các các thư viện để thao tác. Bài viết này sẽ giới thiệu rõ ràng hơn về Cookie cả phía Backend lẫn Frontend và một số vấn đề bảo mật liên quan đến Cookie để các web developer thận trọng hơn khi sử dụng.","title":"Cookies và vấn đề bảo mật"},{"content":"Trong các ứng dụng cho phép người dùng nhập dữ liệu thì đều cần phải có validate để bảo mật và đảm bảo ứng dụng chạy đúng.\nGolang có một số thư viện open source hỗ trợ chúng ta làm việc này một cách nhanh chóng, ví dụ như govalidator.\nCài đặt Cài govalidator qua package manager hoặc đơn giản là dùng go get:\n1  go get github.com/asaskevich/govalidator   Import vào trong project:\n1  import validator \u0026#34;github.com/asaskevich/govalidator\u0026#34;   Sử dụng Govalidator cung cấp rất nhiều function hỗ trợ chúng ta validate dữ liệu theo các dạng thông dụng như: URL, Email, Alpha, Numeric Alphanumeric, Regex, \u0026hellip; Danh sách các hàm hỗ trợ: https://github.com/asaskevich/govalidator#list-of-functions.\nNgoài ra Govalidator còn hỗ trợ chúng ta validate struct (kiểm tra tính hợp lệ của các field trong struct) bằng cách sử dụng tag valid. Ví dụ:\n1 2 3 4 5 6  type User struct { \tId int `valid:\u0026#34;required\u0026#34;` \tName string `valid:\u0026#34;required\u0026#34;` \tEmail string `valid:\u0026#34;email\u0026#34;` \tPassword string `valid:\u0026#34;required\u0026#34;` }   Sau khi khai báo Struct xong, để kiểm tra toàn bộ struct ta gọi hàm ValidateStruct():\n1 2 3 4 5 6 7 8 9 10 11 12 13  user := User{  Id: 1, \tName: \u0026#34;\u0026#34;, \tPhone: \u0026#34;1080\u0026#34;, \tEmail: \u0026#34;fake_email\u0026#34;, \tPassword: \u0026#34;secret\u0026#34;, }  if ok, err := govalidator.ValidateStruct(user); err != nil { \tfmt.Print(err) // In ra các thông báo lỗi } else { \tfmt.Print(\u0026#34;Struct hợp lệ\u0026#34;) }   Tiptrick Nếu trong Struct có nhiều trường cần require thì ta có thể cấu hình mặc định các field là require:\n1 2 3  func init() {  govalidator.SetFieldsRequiredByDefault(true) }   Sau đó trường nào không cần require thì thêm tag valid:\u0026quot;-\u0026quot;.\nĐể thay đổi message báo lỗi mặc định ta dùng ký tự ~ vào sau kiểu validate (chú ý không có khoảng trắng):\n1  Email string `valid:\u0026#34;email~Email không hợp lệ\u0026#34;   Nếu một field validate nhiều kiểu dữ liệu thì ngăn cách bằng dấu phẩy, và nếu có custom message thì cần thiết lập riêng message cho từng kiểu dữ liệu:\n1  Name string `valid:\u0026#34;required~Tên không được để trống,runelength(1|50)~Tên không hợp lệ (từ 1 - 50 ký tự)\u0026#34;   ","permalink":"https://robinhuy.github.io/blog/validate-du-lieu-trong-go-su-dung-govalidator/","summary":"Trong các ứng dụng cho phép người dùng nhập dữ liệu thì đều cần phải có validate để bảo mật và đảm bảo ứng dụng chạy đúng.\nGolang có một số thư viện open source hỗ trợ chúng ta làm việc này một cách nhanh chóng, ví dụ như govalidator.\nCài đặt Cài govalidator qua package manager hoặc đơn giản là dùng go get:\n1  go get github.com/asaskevich/govalidator   Import vào trong project:","title":"Validate dữ liệu trong Go sử dụng Govalidator"},{"content":"Go không phải một ngôn ngữ hướng đối tượng (OOP). Tuy nhiên chúng ta có thể áp dụng một số ưu điểm của hướng đối tượng vào trong Go.\nChúng ta có Struct (tương tự Class), Interface và Method. Để sử dụng thuộc tính \u0026ldquo;kế thừa\u0026rdquo; trong Go ta sẽ dùng embedded type.\nEmbedded Type là khai báo một type nằm trong một type khác nhưng không khai báo tên, trường mà không khai báo tên còn được gọi là embedded field. Ví dụ như sau 1 2 3 4 5 6 7 8 9 10  type Author struct {  AuthorName string  AuthorAge int }  type Post struct {  Title string  Content string  Author // Embedded field }   Trong ví dụ trên nếu chúng ta đặt tên cho trường Author như bình thường thì chúng ta sẽ có Struct lồng nhau, còn nếu dùng Embedded field thì chúng ta có thể coi như Struct Post có đầy đủ các trường của cả 2 Struct (tên trường không được phép trùng nhau)\n1 2 3 4 5 6 7  // Tương tự ví dụ trên type Post struct {  Title string  Content string  AuthorName string  AuthorAge int }   Bằng cách này chúng ta sẽ có thể sử dụng cả 2 Struct Post và Author mà không cần khai báo lại các trường trùng lặp.\nKhi lấy dữ liệu cũng có thể lấy trực tiếp mà không cần qua Struct trung gian, ví dụ lấy tên tác giả thay vì post.Author.AuthorName thì ta chỉ cần post.AuthorName. Xem ví dụ đầy đủ tại đây.\nVí dụ khác Với một project có sử dụng database.\n  Khi tương tác với database ta sẽ dùng Struct User:\n1 2 3 4 5 6 7  type User struct {  Id int  Role int  Name string  Email string  Password string }     Dữ liệu gửi lên có thể là một Struct khác ít thông tin hơn, ví dụ như chức năng Login chỉ cần Emailvà Password. Lúc này ta có thêm Struct Login chẳng hạn:\n1 2 3 4  type Login struct {  Email string  Password string }     Như vậy 2 trường Email và Password bị trùng lặp và khi cần lấy dữ liệu ta có 2 Struct khác nhau, cần phải gán dữ liệu từng trường. Trong trường hợp này ta có thể dùng embedded type để giảm bớt thao tác khai báo và gán dữ liệu.\n","permalink":"https://robinhuy.github.io/blog/golang-embedded-type-ke-thua-trong-go/","summary":"Go không phải một ngôn ngữ hướng đối tượng (OOP). Tuy nhiên chúng ta có thể áp dụng một số ưu điểm của hướng đối tượng vào trong Go.\nChúng ta có Struct (tương tự Class), Interface và Method. Để sử dụng thuộc tính \u0026ldquo;kế thừa\u0026rdquo; trong Go ta sẽ dùng embedded type.\nEmbedded Type là khai báo một type nằm trong một type khác nhưng không khai báo tên, trường mà không khai báo tên còn được gọi là embedded field.","title":"Golang embedded type - Kế thừa trong Go"},{"content":"Để sử dụng 1 package trong Go ta dùng lệnh import:\n  Với local package (các package nằm trong project) thì ta có thể sử dụng đường dẫn tương đối.\n  Với external package (các package bên ngoài project) thì ta sẽ phải cài đặt vào trong $GOPATH (đây là một biến môi trường để thiết lập nơi cài package).\n  Để cài đặt package thì chúng ta có thể dùng các cách sau:\nGo Get Đây là package manager mặc định của Go, nó giúp chúng ta cài external package vào $GOPATH, ví dụ cài package go-pg(PostgreSQL ORM cho Go):\n1  go get github.com/go-pg/pg   Lệnh trên sẽ tải thư viện go-pg/pg và lưu vào trong đường dẫn $GOPATH/src. Ví dụ $GOPATH=\u0026quot;/home/robin\u0026quot; thì package sẽ được tải về và lưu vào trong đường dẫn /home/robin/src với cấu trúc thư mục là [domain]/[organization]/[repository](để tránh trùng lặp vì các package có thể trùng tên)\nƯu điểm khi sử dụng go get:\n Quản lý package theo kiểu phân tán (decentralized), các package không lưu tập trung trong project mà lưu trong $GOPATH. Package chỉ cần cài một lần, sử dụng được cho nhiều project (chỉ cần import không cần cài lại). Các package có thể gọi lẫn nhau mà không bị chồng chéo (package lồng vào nhau nhiều cấp). Một số package cung cấp file binary đặt trong $GOPATH/bin, cho phép chúng ta sử dụng ở mọi chỗ (ví dụ protoc-gen-go cho phép tạo ra code Go từ file Proto).  Nhược điểm:\n Khi cài package phải cài từng gói và trong một project lớn sẽ không rõ có những package nào đang được sử dụng. Không quản lý được version của package. Go get luôn lấy package từ HEAD của default branch (code mới nhất), do đó package trong Go phải luôn đảm bảo stable. Thực ra đây cũng không hẳn là nhược điểm bởi các package của Go sẽ luôn publish theo cách ổn định nhất, nếu package có version mới không tương thích ngược toàn bộ với version cũ thì sẽ đặt ở một repository khác.  Dep Dep cũng là một package manager cho Go. Nó cung cấp một số chức năng mà go get không có:\n Quản lý được các package sử dụng trong project. Cài nhiều package một lúc dựa vào phân tích code và thiết lập được version của các package.  Dep cũng có nhược điểm như: Khó sử dụng hơn go get, khó import local package, \u0026hellip; do đó cần cân nhắc trước khi sử dụng.\nChúng ta có thể cài Dep qua go get, trên MacOS thì có thể dùng Homebrew, còn với các hệ điều hành khác ta chạy lệnh sau:\n1  curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh   Sau khi cài xong, chúng ta có thể dùng lệnh dep trên terminal để cài các package.\n1 2  dep init dep ensure   Lệnh dep init dùng để khởi tạo ra môi trường làm việc với Dep, nó sẽ sinh ra thư mục vendor ngay trong project để chứa các package đã cài thay cho $GOPATH và 2 files:\n Gopkg.toml: Định nghĩa một số quy định quản lý của Dep như phiên bản nào của package được sử dụng, source của package, \u0026hellip; (tương tự package.json của npm hay gemfile của Ruby, \u0026hellip;). Gopkg.lock: Được tự động sinh ra mỗi khi thực hiện lệnh dep init hoặc dep ensure, nó như một bản snapshot, ghi lại các trạng thái và thao tác khi update hay cài đặt package.  Lệnh dep ensure dùng để cài đặt các package dựa vào file Gopkg.toml và các file .go trong project (Dep sẽ đọc các lệnh import trong các file .go để tìm ra các package đang sử dụng). Các package được cài vào trong thư mục vendor của project với cấu trúc giống như trong $GOPATH.\nMột số chú ý khi sử dụng Dep:\n Project phải được đặt trong $GOPATH như các package khác. Khi lần đầu chạy dep init nếu là một project có sẵn chưa sử dụng Dep thì thời gian chạy lệnh có thể lâu do Dep phải đọc toàn bộ source có sẵn để tìm ra các package đang sử dụng. Nếu import local package thì không sử dụng đường dẫn tương đối như go get mà sử dụng đường dẫn như với các external package.  Tham khảo thêm về Dep: https://golang.github.io/dep/docs/introduction.html\nGlide Tương tự như Deb, tuy nhiên theo quan điểm cá nhân thì Glide dễ dùng hơn Deb.\nCài đặt: xem hướng dẫn tại đây: https://github.com/Masterminds/glide#install.\nCách dùng cũng tương tự Deb, có 2 file là:\n glide.lock: lưu thông tin version của các package đã cài đặt glide.yaml: dùng để cấu hình các package dùng cho dự án.  Một số lệnh dùng trong Glide:\n$ glide create # Khởi tạo project $ glide get github.com/Masterminds/cookoo # Lấy package và thêm vào glide.yaml $ glide install # Cài đặt package $ glide up # Update version mới của package (theo glide.yaml) ","permalink":"https://robinhuy.github.io/blog/quan-ly-package-trong-go/","summary":"Để sử dụng 1 package trong Go ta dùng lệnh import:\n  Với local package (các package nằm trong project) thì ta có thể sử dụng đường dẫn tương đối.\n  Với external package (các package bên ngoài project) thì ta sẽ phải cài đặt vào trong $GOPATH (đây là một biến môi trường để thiết lập nơi cài package).\n  Để cài đặt package thì chúng ta có thể dùng các cách sau:","title":"Quản lý package trong Go"},{"content":"Các phần trước:\nPhần 1: Packages, variables và functions.\nPhần 2: Điều khiển luồng với if, else, switch và defer.\nPhần 3: Arrays và Slices.\nPhần 4: Structs và Maps.\nMethods Trong Go không có class, chúng ta có thể dùng struct thay cho class như ở phần trước. Tuy nhiên trong struct mới chỉ có thuộc tính chứ chưa có phương thức. Để ứng dụng được phương thức (method) như các ngôn ngữ hướng đối tượng khác ta sẽ cần khai báo function kèm theo một tham số đặc biệt gọi là receiver argument. Receiver argument nằm ở giữa từ khóa func và tên của function, nó sẽ chỉ ra một type(thường là một struct) để áp dụng hàm này làm phương thức. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  package main  import (  \u0026#34;fmt\u0026#34;  \u0026#34;math\u0026#34; )  // Định nghĩa struct Vertex với 2 thuộc tính X và Y type Vertex struct {  X, Y float64 }  // Tạo phương thức Abs() cho struct Vertex (receiver argument) func (v Vertex) Abs() float64 {  return math.Sqrt(v.X*v.X + v.Y*v.Y) }  func main() {  // Khởi tạo struct  v := Vertex{3, 4}   // Gọi phương thức Abs() của struct  fmt.Println(v.Abs()) }   Interfaces Interface là một định nghĩa các tập hợp phương thức mà một đối tượng cần tuân thủ (tương tự như ở trong các ngôn ngữ hướng đối tượng khác). Khi một type có chứa các phương thức như đã khai báo trong interface thì nó đang triển khai (implement) interface đó. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27  package main  import \u0026#34;fmt\u0026#34;  // Định nghĩa interface I với 1 method là M() type I interface {  M() }  // Định nghĩa struct T với 1 field là S kiểu string type T struct {  S string }  // Định nghĩa phương thức M() cho struct T // Struct T sẽ tự động implement interface I func (t T) M() {  fmt.Println(t.S) }  func main() {  // Khởi tạo biến i có kiểu là interface I  var i I = T{\u0026#34;hello\u0026#34;}   // Gọi phương thức M()  i.M() }   Type đã triển khai interface buộc phải có đầy đủ các method được định nghĩa trong interface. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31  package main  import \u0026#34;fmt\u0026#34;  // Định nghĩa interface I với 1 method là M() và N() type I interface {  M(),  N() }  // Định nghĩa struct T type T struct {  S string }  // Định nghĩa phương thức M() cho struct T // Struct T sẽ tự động implement interface I func (t T) M() {  fmt.Println(t.S) }  func main() {  // Khởi tạo biến i có kiểu là interface I  var i I = T{\u0026#34;hello\u0026#34;}   // Gọi phương thức M()  i.M()   // Kết quả sẽ báo lỗi vì struct T implement interface I,  // nhưng không có đủ các method đã khai báo (thiếu method N()) }   Một interface mà không có chứa method nào thì gọi là interface rỗng (Emtpy Interface). Interface rỗng có thể lưu mọi loại dữ liệu nên thường được dùng trong trường hợp các hàm xử lý mà cần tham số động (không biết trước kiểu dữ liệu). Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21  package main  import \u0026#34;fmt\u0026#34;  func main() { \tvar i interface{} // i là một empty interface   // Họi hàm describe với tham số truyền vào là một số \ti = 42 \tdescribe(i)   // Họi hàm describe với tham số truyền vào là một chuỗi \ti = \u0026#34;hello\u0026#34; \tdescribe(i) }  // Hàm describe có tham số truyền vào là một empty interface // do đó khi thực thi ta có thể truyền vào kiểu dữ liệu nào cũng được func describe(i interface{}) { \tfmt.Printf(\u0026#34;(%v, %T)\\n\u0026#34;, i, i) }   Series Học Go cấp tốc sẽ tạm dừng ở đây. Mục đích là để các bạn làm quen và nắm được tổng quan của ngôn ngữ Go. Sẽ có một Series khác hướng dẫn chi tiết hơn về lập trình Golang dành cho những ai muốn tìm hiểu sâu hơn để áp dụng vào công việc thực tế.\nHappy coding 😎\n","permalink":"https://robinhuy.github.io/blog/hoc-go-cap-toc-phan-5-methods-va-interfaces/","summary":"Các phần trước:\nPhần 1: Packages, variables và functions.\nPhần 2: Điều khiển luồng với if, else, switch và defer.\nPhần 3: Arrays và Slices.\nPhần 4: Structs và Maps.\nMethods Trong Go không có class, chúng ta có thể dùng struct thay cho class như ở phần trước. Tuy nhiên trong struct mới chỉ có thuộc tính chứ chưa có phương thức. Để ứng dụng được phương thức (method) như các ngôn ngữ hướng đối tượng khác ta sẽ cần khai báo function kèm theo một tham số đặc biệt gọi là receiver argument.","title":"Học Go cấp tốc Phần 5:  Methods và Interfaces"},{"content":"Các phần trước:\nPhần 1: Packages, variables và functions.\nPhần 2: Điều khiển luồng với if, else, switch và defer.\nPhần 3: Arrays và Slices.\nStructs Tương tự C, một struct trong Go là tập hợp các trường (field) do người dùng tự định nghĩa. Mỗi trường có thể có kiểu dữ liệu khác nhau, thậm chí có thể là một struct. Trong Go không có class như các ngôn ngữ hướng đối tượng, do đó chúng ta có thể dùng struct thay cho class. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27  package main  import \u0026#34;fmt\u0026#34;  // Định nghĩa một struct với từ khóa type type Student struct { \tname string \tage int }  func main() {  // Khởi tạo biến s1 có giá trị là struct Student  s1 := Student{\u0026#34;Robin\u0026#34;, 30} // {\u0026#34;Robin\u0026#34;, 30}   // Khởi tạo biến s2 có giá trị là struct Student với 1 field là name  // Field còn lại sẽ có giá trị mặc định (zero value)  s2 := Student{name: \u0026#34;Robin\u0026#34;} // {\u0026#34;Robin\u0026#34;, 0}   // Khởi tạo biến s3 có giá trị là struct Student và không khai báo giá trị cho trường nào  s3 := Student{} // {\u0026#34;\u0026#34;, 0}   // Thay đổi giá trị field trong struct  s3.name = \u0026#34;Robert\u0026#34;  s3.age = 25   fmt.Println(s3) // s3 = {\u0026#34;Robert\u0026#34;, 25} }   Struct có thể so sánh được nếu các field của nó có thể so sánh được, và 2 biến kiểu struct có giá trị giống nhau nếu toàn bộ các field có giá trị giống nhau:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28  package main  import ( \t\u0026#34;fmt\u0026#34; )  type Student struct { \tname string \tage int }  func main() { \ts1 := Student{\u0026#34;Steve\u0026#34;, 30} \ts2 := Student{\u0026#34;Steve\u0026#34;, 30}  s3 := Student{\u0026#34;Job\u0026#34;, 30}  \tif s1 == s2 { \tfmt.Println(\u0026#34;s1 = s2\u0026#34;) // s1 bằng s2 \t} else { \tfmt.Println(\u0026#34;s1 != s2\u0026#34;) \t}  \tif s2 == s3 { \tfmt.Println(\u0026#34;s2 = s3\u0026#34;) \t} else { \tfmt.Println(\u0026#34;s2 != s3\u0026#34;) // s2 khác s3 \t} }   Maps Map là một tập hợp các phần tử được lưu trữ dưới dạng key - value. Key trong map có kiểu dữ liệu so sánh được và không bị trùng lặp . Để tạo map ta dùng hàm make() với công thức như sau:\nmake(map[type of key]type of value 1 2 3 4 5 6 7 8 9 10  // Định nghĩa biến demoMap có kiểu dữ liệu map với key kiểu string và value kiểu int var demoMap map[string]int  // Map không so sánh được như struct, nhưng có thể dùng toán tử == để kiểm tra nil if demoMap == nil {  fmt.Println(\u0026#34;Map có giá trị nil.\u0026#34;)   // Tạo Map bằng hàm make  demoMap = make(map[string]int) }   Thêm phần tử hoặc thay đổi giá trị của một phần tử trong map m ta dùng công thức:\nm[key] = value 1 2 3 4 5 6 7 8 9 10 11  // Khởi tạo map languages := make(map[string]float32)  // Thêm phần tử vào map languages[\u0026#34;go\u0026#34;] = 0.63 languages[\u0026#34;java\u0026#34;] = 1.03  // Cập nhật lại giá trị của phần tử \u0026#34;go\u0026#34; languages[\u0026#34;go\u0026#34;] = 0.73  fmt.Println(languages) // map[go:0.73 java:1.03]   Để xóa phần tử trong map thì ta dùng hàm delete() và cung cấp key của phần tử cần xóa. Ví dụ xóa phần tử có key = \u0026ldquo;go\u0026rdquo; trong map languages:\n1  delete(languages, \u0026#34;go\u0026#34;)   Để truy xuất đến phần tử trong map, ta gọi map kèm theo key của phần tử. Nếu key đó không tồn tại thì ta sẽ thu được giá trị là zero value (tùy theo kiểu dữ liệu). Ví dụ:\n1 2 3 4 5 6 7  m := make(map[string]int)  m[\u0026#34;Answer\u0026#34;] = 42 fmt.Println(m[\u0026#34;Answer\u0026#34;]) // Lấy giá trị của phần tử có key = \u0026#34;Answer\u0026#34;, kết quả là 42  delete(m, \u0026#34;Answer\u0026#34;) fmt.Println(m[\u0026#34;Answer\u0026#34;]) // Phần tử có key = \u0026#34;Answer\u0026#34; đã bị xóa, kết quả là 0 (zero value của int)   Để kiểm tra xem một phần tử có tồn tại trong map hay không, ta sẽ lấy cùng lúc 2 kết quả khi truy xuất đến phần tử trong map. Giá trị đầu tiên giống ví dụ trên, giá trị thứ 2 sẽ là true nếu phần tử có trong map và false nếu phần tử không tồn tại (gần giống callback error trong javascript).\n1 2 3 4  // Tiếp theo ví dụ trên v, ok := m[\u0026#34;Answer\u0026#34;] fmt.Println(\u0026#34;Giá trị của phần tử là: \u0026#34;, v) // v = 0 fmt.Println(\u0026#34;Kiểm tra phần tử có tồn tại hay không: \u0026#34;, ok) // ok = false   Tiếp theo: Phần 5: Methods và Interfaces.\n","permalink":"https://robinhuy.github.io/blog/hoc-go-cap-toc-phan-4-structs-va-maps/","summary":"Các phần trước:\nPhần 1: Packages, variables và functions.\nPhần 2: Điều khiển luồng với if, else, switch và defer.\nPhần 3: Arrays và Slices.\nStructs Tương tự C, một struct trong Go là tập hợp các trường (field) do người dùng tự định nghĩa. Mỗi trường có thể có kiểu dữ liệu khác nhau, thậm chí có thể là một struct. Trong Go không có class như các ngôn ngữ hướng đối tượng, do đó chúng ta có thể dùng struct thay cho class.","title":"Học Go cấp tốc Phần 4:  Structs và Maps"},{"content":"Các phần trước:\nPhần 1: Packages, variables và functions.\nPhần 2: Điều khiển luồng với if, else, switch và defer.\nArrays Array (mảng) trong Go tương tự các ngôn ngữ khác, tuy nhiên nó có kích thước cố định (fixed size) và các phần tử bên trong phải cùng loại dữ liệu. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  // Khởi tạo một mảng gồm 2 string var a [2]string  // Gán giá trị cho các phần tử trong mảng a[0] = \u0026#34;Hello\u0026#34; a[1] = \u0026#34;World\u0026#34;  // In kết quả ra console fmt.Println(a[0], a[1]) fmt.Println(a)  // Khởi tạo một mảng gồm 6 số int và gán luôn giá trị cho nó primes := [6]int{2, 3, 5, 7, 11, 13} fmt.Println(primes)  // Khởi tạo mảng nhưng không ghi rõ kích thước (thay bằng dấu ba chấm), // trình biên dịch sẽ tự hiểu dựa vào số phần tử đã khai báo numbers := [...]int{12, 78, 50} fmt.Println(numbers)   Không giống đa số các ngôn ngữ khác, Array trong Go không phải là dạng tham chiếu (reference types) mà là dạng tham trị (value types). Khi gán giá trị nó cho một biến mới thì nó sẽ tạo ra một bản copy của Array cũ, và mọi thay đổi ở Array mới không ảnh hưởng gì đến Array cũ:\n1 2 3 4 5 6  a := [...]int{1, 2, 3, 4, 5} b := a // b là một array mới có giá trị giống a b[0] = 9 // Thay đổi giá trị một phần tử của b  fmt.Println(\u0026#34;a is \u0026#34;, a) // In ra 1 2 3 4 5 fmt.Println(\u0026#34;b is \u0026#34;, b) // In ra 9 2 3 4 5   Slices Slice là một tham chiếu đến Array, nó mô tả một phần (hoặc toàn bộ) Array. Nó có kích thước động nên thường được sử dụng nhiều hơn Array.\nSlice có thể tạo ra từ một Array bằng cách cung cấp 2 chỉ số (low và high) xác định vị trí phần tử trong Array. Ví dụ:\n1 2 3 4 5 6 7 8  // Khởi tạo Array primes primes := [6]int{2, 3, 5, 7, 11, 13}  // Khởi tạo Slice s bằng cách cắt từ phần tử ở vị trí 1 (low) đến phần tử ở vị trí 3 (high - 1) của Array primes var s []int = primes[1:4]  // In ra giá trị của Slice s fmt.Println(s) // Giá trị của s là [3, 5, 7]   Một Slice sẽ có 2 thuộc tính là length(len)và capacity(cap). Length là số phần tử chứa trong Slice, còn capacity là số phần tử chứa trong Array mà Slice tham chiếu đến (bắt đầu tính từ phần tử đầu tiên của Slice). Để lấy ra length của Slice ta dùng hàm len(), còn để lấy ra capacity thì ta dùng hàm cap(). Ví dụ:\n1 2 3 4 5 6  s := []int{2, 3, 5, 7, 11, 13}  s = s[0:0] // s = [], len(s) = 0, cap(s) = 6 s = s[0:4] // s = [2, 3, 5, 7], len(s) = 4, cap(s) = 6 s = s[2:4] // s = [5, 7], len(s) = 2, cap(s) = 4, cap được tính từ vị trí số 2 trở đi s = s[0:4] // s = [5, 7, 11, 13], len(s) = 4, cap(s) = 4   Khi tạo Slice ta có thể bỏ qua các chỉ số low và high, khi đó Go sẽ tự sử dụng giá trị mặc định: 0 cho low và length của Slice cho high. Ví dụ:\n1 2 3 4 5 6  s := []int{2, 3, 5, 7, 11, 13}  s = s[:0] // s = [0:0] s = s[:4] // s = [0:4] s = s[2:] // s = [2:len(s)] =\u0026gt; s = [2:4] s = s[:4] // s = [0:4]   Ngoài việc tạo Slice như trên, chúng ta có thể tạo theo các cách sau:\n  Khai báo như một mảng nhưng không chỉ ra kích thước mảng:\n1  q := []int{2, 3, 5, 7, 11, 13}     Sử dụng hàm make() với công thức sau:\n1  func make([]T, len, cap) []T   1 2  a := make([]int, 5) // len(a)=5 b := make([]int, 0, 5) // len(b)=0, cap(b)=5     Slice có zero value là nil (length = 0 và capacity = 0), nil tương đương với giá trị null trong các ngôn ngữ lập trình khác.\nDo Slice chỉ là tham chiếu đến Array, do đó thay đổi giá trị của Slice sẽ làm thay đổi giá trị của Array mà nó tham chiếu đến. Nếu có nhiều Slice cùng tham chiếu đến một Array thì khi thay đổi giá trị một Slice có thể làm thay đổi giá trị các Slice khác. Ví dụ:\n1 2 3 4 5 6 7 8 9  numbers := [4]int{1, 2, 3, 4}  a := numbers[0:2] // a = [1, 2] b := numbers[1:3] // b = [2, 3]  b[0] = 5 // Thay đổi giá trị phần tử đầu tiên của Slice b  fmt.Println(a, b) // a = [1, 5], b = [5, 3] fmt.Println(numbers) // numbers = [1, 5, 3, 4]   Append Để bổ sung thêm phần tử cho slice, ta dùng hàm append() với công thức sau:\n1  func append(s []T, vs ...T) []T   Hàm này sẽ trả về một slice có chứa toàn bộ các phần tử của slice ban đầu và các phần tử mới thêm vào. Trong trường hợp slice ban đầu có sức chứa nhỏ (Array mà nó tham chiếu đến có size nhỏ), một Array mới có kích thước lớn hơn sẽ được tạo ra và slice mới sẽ tham chiếu đến Array đó.\n1 2 3 4 5 6 7 8 9 10  var s []int  // Append có thể hoạt động với nil slice. s = append(s, 0) // s = [0]  // Append thêm một phần tử vào slice. s = append(s, 1) // s = [0, 1]  // Append thêm nhiều phần tử vào slice. s = append(s, 2, 3, 4) // s = [0, 1, 2, 3, 4]   Range Range là một hình thức của vòng lặp for dùng để duyệt qua một slice hoặc map(sẽ nhắc đến ở phần sau). Mỗi một vòng lặp sẽ trả về 2 giá trị: Giá trị đầu tiên là chỉ số (vị trí) của phần tử, và giá trị thứ hai là bản sao của phần tử đó (cùng giá trị). Ví dụ:\n1 2 3 4  var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} for i, v := range pow {  fmt.Printf(\u0026#34;i = %d, v = %d \\n\u0026#34;, i, v) }   Trong trường hợp khi lặp chỉ sử dụng 1 trong 2 giá trị trả về thì ta sẽ bỏ qua giá trị còn lại bằng cách thay tên biến bằng ký tự gạch dưới (vì nếu không thì khi biên dịch sẽ báo lỗi biến được định nghĩa mà không sử dụng). Ví dụ:\n1 2 3 4  var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} for _, v := range pow {  fmt.Printf(\u0026#34;v = %d \\n\u0026#34;, v) }   Tiếp theo: Phần 4: Structs và Maps.\n","permalink":"https://robinhuy.github.io/blog/hoc-go-cap-toc-phan-3-arrays-va-slices/","summary":"Các phần trước:\nPhần 1: Packages, variables và functions.\nPhần 2: Điều khiển luồng với if, else, switch và defer.\nArrays Array (mảng) trong Go tương tự các ngôn ngữ khác, tuy nhiên nó có kích thước cố định (fixed size) và các phần tử bên trong phải cùng loại dữ liệu. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  // Khởi tạo một mảng gồm 2 string var a [2]string  // Gán giá trị cho các phần tử trong mảng a[0] = \u0026#34;Hello\u0026#34; a[1] = \u0026#34;World\u0026#34;  // In kết quả ra console fmt.","title":"Học Go cấp tốc Phần 3:  Arrays và Slices"},{"content":"Phần trước: Học Go cấp tốc Phần 1: Packages, variables và functions.\nPhần tiếp theo chúng ta sẽ cùng tìm hiểu cách điều khiển luồng (flow control) trong Go.\nVòng lặp Trong Go chỉ có 1 kiểu vòng lặp là sử dụng for. Cách dùng tương tự các ngôn ngữ khác nhưng phần khai báo biến, điều kiện lặp, \u0026hellip; không cần đặt trong cặp ngoặc tròn:\n1 2 3 4 5 6  // Tính tổng các số từ 0 - 9 sum := 0 for i := 0; i \u0026lt; 10; i++ {  sum += i } fmt.Println(sum)   Vòng lặp for khi chỉ có điều kiện lặp thì hoạt động giống while trong các ngôn ngữ khác:\n1 2 3 4 5  sum := 0 for sum \u0026lt; 10 {  sum += 1 } fmt.Println(sum)   Điều kiện Lệnh điều kiện trong Go sử dụng if, else, switch, và cũng như vòng lặp for chúng ta không cần cặp ngoặc tròn.\nVới câu lệnh if chúng ta có thể khai báo biến ngay trong câu lệnh điều kiện, và biến này sẽ chỉ hoạt động ở trong block của lệnh if hoặc else:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  import (  \u0026#34;fmt\u0026#34;  \u0026#34;math\u0026#34; )  func pow(x, n, limit float64) float64 {  // Khai báo biến v trong lệnh điều kiện của if sẽ chỉ sử dụng được trong block if hoặc else  if v := math.Pow(x, n); v \u0026lt; limit {  return v  } else {  fmt.Printf(\u0026#34;%g \u0026gt;= %g\\n\u0026#34;, v, limit) \t}  \t// Không sử dụng được biến v ở bên ngoài, ví dụ return v sẽ báo lỗi \treturn lim }   Lệnh switch tương tự các ngôn ngữ khác, tuy nhiên có một số điểm khác biệt:\n  Biểu thức trong switch không được sử dụng hằng số (constant). Không cần lệnh break trong mỗi case(mặc định các case tự break). Do đó chỉ có trường hợp thỏa mãn đầu tiên được chạy (tính từ trên xuống dưới). Ví dụ:\n1 2 3 4 5 6 7 8  switch num := 10; {  case num \u0026lt; 50:  fmt.Printf(\u0026#34;%d \u0026lt; 50\\n\u0026#34;, num) // In ra 10 \u0026lt; 50  case num \u0026lt; 100:  fmt.Printf(\u0026#34;%d \u0026lt; 100\\n\u0026#34;, num) // Lệnh này không chạy mặc dù cũng thỏa mãn điều kiện  default:  fmt.Printf(\u0026#34;I don\u0026#39;t know\u0026#34;, num) }     Có thể sử dụng nhiều điều kiện trong một case,hoặc sử dụng từ khóa fallthrough để cho phép chạy tiếp xuống câu lệnh tiếp theo:\n1 2 3 4 5 6 7 8 9 10 11  num := 10;  switch { // Tương đương với switch true  case num \u0026gt;= 0 \u0026amp;\u0026amp; num \u0026lt;= 50:  fmt.Printf(\u0026#34;%d \u0026lt; 50 \\n\u0026#34;, num) // In ra 10 \u0026lt; 50  fallthrough  case num \u0026lt; 100:  fmt.Printf(\u0026#34;%d \u0026lt; 100 \\n\u0026#34;, num) // In ra 10 \u0026lt; 100  default:  fmt.Printf(\u0026#34;I don\u0026#39;t know\u0026#34;, num) }     Trì hoãn Trì hoãn (defer) là một khái niệm khá mới trong điều khiển luồng. Nó cho phép một câu lệnh được gọi ra nhưng không thực thi ngay mà hoãn lại đến khi các lệnh xung quanh trả về kết quả. Ví dụ:\n1 2 3 4 5 6 7 8 9  package main  import \u0026#34;fmt\u0026#34;  func main() {  defer fmt.Println(\u0026#34;World\u0026#34;) // Hoãn lệnh in ra chữ \u0026#34;World\u0026#34;  fmt.Println(\u0026#34;Hello\u0026#34;) // In ra chữ \u0026#34;Hello\u0026#34;  // Kết quả cuối cùng là \u0026#34;Hello World\u0026#34; }   Các lệnh được gọi qua từ khóa defer sẽ được đưa vào một stack, tức là hoạt động theo cơ chế vào sau ra trước (last-in-first-out). Lệnh nào defer sau sẽ được thực thi trước, giống như xếp 1 chồng đĩa thì chiếc đĩa sau cùng (ở trên cùng) sẽ được lấy ra trước. Ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12  package main  import \u0026#34;fmt\u0026#34;  func main() {  for i := 0; i \u0026lt; 10; i++ {  defer fmt.Println(i) // In ra giá trị của biến i  }   // Kết quả trả về ngược so với vòng lặp:  // 9 8 7 6 5 4 3 2 1 0 }   Chú ý là khi gọi lệnh defer thì giá trị của biến trong câu lệnh sẽ là giá trị tại thời điểm gọi chứ không phải giá trị tại thời điểm thực thi.\nTiếp theo: Phần 3: Arrays và Slices.\n","permalink":"https://robinhuy.github.io/blog/hoc-go-cap-toc-phan-2-dieu-khien-luong-voi-if-else-switch-va-defer/","summary":"Phần trước: Học Go cấp tốc Phần 1: Packages, variables và functions.\nPhần tiếp theo chúng ta sẽ cùng tìm hiểu cách điều khiển luồng (flow control) trong Go.\nVòng lặp Trong Go chỉ có 1 kiểu vòng lặp là sử dụng for. Cách dùng tương tự các ngôn ngữ khác nhưng phần khai báo biến, điều kiện lặp, \u0026hellip; không cần đặt trong cặp ngoặc tròn:\n1 2 3 4 5 6  // Tính tổng các số từ 0 - 9 sum := 0 for i := 0; i \u0026lt; 10; i++ {  sum += i } fmt.","title":"Học Go cấp tốc Phần 2:  Điều khiển luồng với if, else, switch và defer"},{"content":" Go(hay thường được gọi là Golang) là một ngôn ngữ lập trình mã nguồn mở được tạo ở Google vào năm 2009 bởi Robert Griesemer, Rob Pike, và Ken Thompson. Nó được ra đời nhằm mục đích phát triển các trang web nhanh hơn, dễ dàng hơn và đáp ứng được yêu cầu truy cập lớn. Về lịch sử ra đời cũng như giới thiệu chi tiết hơn các bạn có thể xem ở wikipedia 😅\n Để học Go thì chúng ta có nhiều nguồn và tài liệu khác nhau. Với những người đã biết ít nhất một ngôn ngữ lập trình thì có thể thực hành luôn theo theo Tour Golang, nó sẽ gồm các ví dụ và bài tập mẫu mô tả các chức năng cơ bản trong Go và đi kèm 1 editor online để chúng ta có thể thực hành luôn mà không cần cài đặt.\nTrong quá trình học theo Tour Golang, mình sẽ viết bài tổng kết lại một số kiến thức cơ bản, điểm khác biệt của Go so với các ngôn ngữ khác (có thể sẽ sử dụng ví dụ khác với nguyên mẫu). Nếu bạn muốn học Go và thực hành luôn trên Tour Golang thì có thể tham khảo series này (hoặc có thể đọc lướt qua để có một cái nhìn tổng quát về Go).\nPackages Mọi chương trình viết từ Go đều được tạo bởi các package và package chính dùng để chạy là main.\nĐể sử dụng các package khác thì chúng ta phải import, ví dụ muốn in 1 đoạn text ra console thì ta phải dùng package fmt:\n1 2 3 4 5 6 7  package main  import \u0026#34;fmt\u0026#34;  func main() {  fmt.Println(\u0026#34;Lại là Hello World\u0026#34;) }   Variables Cú pháp của Go tương tự C nhưng cũng có nhiều điểm khác, ví dụ không có dấu chấm phẩy ở cuối các câu lệnh hay kiểu dữ liệu được khai báo ở sau tên biến. Về việc tại sao Go lại khai báo kiểu dữ liệu ngược so với hầu hết các ngôn ngữ khác các bạn tự tìm hiểu tại đây Go's Declaration Syntax.\nKhai báo biến trong Go ngoài việc cú pháp hơi dị một chút, còn đâu thì vẫn tương tự như các ngôn ngữ khác:\n1 2 3 4 5 6 7 8 9 10 11 12  // Khai báo biến message có kiểu dữ liệu string var message string  // Khai báo 3 biến c, python, java đều có kiểu dữ liệu là bool var c, python, java bool  // Khai báo 2 biến i, j có kiểu dữ liệu là int và khởi tạo luôn giá trị cho chúng var i, j int = 1, 2  // Khai báo ngắn gọn biến k và khởi tạo giá trị luôn cho nó. // Không dùng từ khóa var mà dùng dấu hai chấm, lúc này kiểu dữ liệu sẽ được ngầm định tùy theo giá trị của biến. k := 3   Các kiểu dữ liệu trong Go, ở phần mô tả của Tour of Go có liệt kê đầy đủ: Go basic types\nKhi khai báo biến mà không khởi tạo giá trị ban đầu cho nó thì biến đó sẽ có giá trị zero tùy thuộc vào kiểu dữ liệu:\n 0 cho kiểu số. false cho kiểu boolean. \u0026quot;\u0026quot; cho kiểu chuỗi.  Khi thực hiện tính toán giữa các biến với kiểu dữ liệu khác nhau sẽ cần ép kiểu (type conversions) theo công thức T(v) với T là kiểu dữ liệu (type) còn v là giá trị (value):\n1 2 3 4  i := 55 // kiểu int j := 67.8 // kiểu float64 sum := i + int(j) // Để tính tổng cần phải ép kiểu j về int (sum = 55 + 67) fmt.Println(sum) // Kết quả trả về là 122   Khai báo hằng số thì tương tự khai báo biến nhưng dùng từ khóa const và không dùng được cú pháp viết tắt :=\n1  const Pi = 3.14   Functions Khai báo hàm sử dụng từ khóa func, và chú ý tham số truyền vào cũng khai báo kiểu dữ liệu sau tên tham số:\n1 2 3 4  // Hàm tính tổng, có 2 tham số truyền vào với kiểu dữ liệu int và kết quả trả về cũng là int func add(x int, y int) int {  return x + y }   Khi các tham số truyền vào cùng kiểu dữ liệu thì có thể viết tắt như sau:\n1 2 3 4  // Hàm tính tổng, có 2 tham số truyền vào với kiểu dữ liệu int và kết quả trả về cũng là int func add(x, y int) int {  return x + y }   Điểm đặc biệt trong Go đó là function có thể trả về nhiều kết quả, ví dụ:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16  package main  import \u0026#34;fmt\u0026#34;  // Hàm swap trả về kết quả là 2 chuỗi func swap(x, y string) (string, string) { \treturn y, x }  func main() {  // Gán kết quả của hàm swap vào 2 biến a và b  a, b := swap(\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;)   // In ra giá trị của a và b  fmt.Println(a, b) }   Kết quả trả về có thể đặt tên để sử dụng luôn trong hàm, ví dụ:\n1 2 3 4 5 6 7 8  // Hàm split khai báo 2 kết quả trả về là x và y có kiểu dữ liệu là int func split(sum int) (x, y int) {  x = sum * 4 / 9  y = sum - x   // return có thể để trống, function sẽ tự động trả về x và y (không khuyến khích)  return }   Tiếp theo: Phần 2: Điều khiển luồng với if, else, switch và defer.\n","permalink":"https://robinhuy.github.io/blog/hoc-go-cap-toc-phan-1-packages-variables-va-functions/","summary":"Go(hay thường được gọi là Golang) là một ngôn ngữ lập trình mã nguồn mở được tạo ở Google vào năm 2009 bởi Robert Griesemer, Rob Pike, và Ken Thompson. Nó được ra đời nhằm mục đích phát triển các trang web nhanh hơn, dễ dàng hơn và đáp ứng được yêu cầu truy cập lớn. Về lịch sử ra đời cũng như giới thiệu chi tiết hơn các bạn có thể xem ở wikipedia 😅","title":"Học Go cấp tốc Phần 1: Packages, variables và functions"},{"content":"Kong cung cấp một RESTful Admin API cho phép chúng ta thực hiện việc cấu hình và quản lý các API. Mặc định Admin API sẽ lắng nghe ở cổng 8001 và cổng 8444 cho giao thức https. Chú ý API này chỉ dùng trong nội bộ, không public ra ngoài, vì nó cho phép quản lý toàn bộ hệ thống API gateway của Kong.\nĐể dễ hình dung, bài viết này sẽ hướng dẫn tạo mới các API và bảo mật cơ bản bằng plugins sử dụng Konga (GUI cho Admin API). Tuy nhiên các bạn có thể dùng curl hay Postman để test. Tài liệu tham khảo: Danh sách các endpoint và tham số của Kong\u0026nbsp;Admin\u0026nbsp;API.\nSau khi cài đặt và đăng nhập vào Konga, chúng ta sẽ kết nối Konga với Kong Admin API (chú ý Active connection):\nThêm mới API Endpoint: Add API\nCác tham số cần thiết:\n name: Tên của API (sử dụng để quản lý API). Ngoài name ra cũng thể dùng id để quản lý API (do hệ thống tự tạo). hosts, uris, methods: Dùng để phân biệt giữa các API, luôn phải có ít nhất 1 trong 3 thuộc tính này. Hosts là phân biệt qua Headers của request, uris là phân biệt qua đường dẫn và methods là phân biệt qua phương thức của request. upstream_url: Đường dẫn gốc của API.    Ví dụ thêm API trỏ đến techmaster.vn qua đường dẫn /techmaster   Các API sẽ chạy qua cổng 8000, trong ví dụ này chúng ta chạy trên localhost nên sẽ truy cập vào localhost:8000. Với cấu hình như ví dụ trên thì khi chúng ta truy cập vào localhost:8000/techmaster, Kong sẽ forward sang trang techmaster.vn. Tuy nhiên đường dẫn một số file như css sẽ bị sai do đường dẫn không bắt đầu từ tên domain (đường dẫn gốc). Do đó nếu sử dụng đường dẫn để phân biệt API thì với ứng dụng web frontend nên dùng cho đường dẫn gốc là /.\nCấu hình plugins Để bảo mật API, chúng ta sẽ cấu hình thêm một số plugins của Kong. Ví dụ để Authentication chúng ta có thể dùng một số plugins như: Basic Authentication, Key Authentication, JWT, \u0026hellip; (một số plugins có nhãn Enterprise sẽ phải trả phí để sử dụng).\nPlugins có thể áp dụng cho toàn bộ API hoặc cho cụ thể từng API qua name hoặc id của chúng. Endpoint Add Plugin.\nVí dụ cấu hình plugin Basic Authentication:\nSau khi kích hoạt Plugin, để truy cập được vào API chúng ta sẽ phải qua một bước Authentication của browser: Nếu user nhập đúng Username và Password thì mới truy cập được vào API. Nếu là gửi request thì trong Headers của request phải có thuộc tính Proxy-Authorization hoặc Authorization có chứa Username và Password được mã hóa base64 theo cú pháp Username:Password.\nTạo Consumer Bước thiếp theo chúng ta phải tạo Consumer để truy cập API. Endpoint Create Consumer.\nTrong Consumer, chúng ta tạo thêm các Credentials tương ứng với các tài khoản truy cập cho Authentication:\nVậy là chúng ta đã cấu hình xong Basic Authentication cho API. Với các Plugins khác thì cũng làm tương tự.\n","permalink":"https://robinhuy.github.io/blog/them-moi-va-bao-mat-api-trong-kong-api-gateway/","summary":"Kong cung cấp một RESTful Admin API cho phép chúng ta thực hiện việc cấu hình và quản lý các API. Mặc định Admin API sẽ lắng nghe ở cổng 8001 và cổng 8444 cho giao thức https. Chú ý API này chỉ dùng trong nội bộ, không public ra ngoài, vì nó cho phép quản lý toàn bộ hệ thống API gateway của Kong.\nĐể dễ hình dung, bài viết này sẽ hướng dẫn tạo mới các API và bảo mật cơ bản bằng plugins sử dụng Konga (GUI cho Admin API).","title":"Thêm mới và bảo mật API trong Kong API Gateway"},{"content":" ASP.NET Core là một open-source web framework mới của Microsoft. Nó cho phép phát triển và chạy ứng dụng web đa nền tảng. Giờ đây bạn có thể lập trình .NET trên cả Linux và MacOS.\n Để lập trình chúng ta có thể sử dụng Visual Studio (trên Windows) hoặc Visual Studio for Mac (trên Mac). Ngoài ra chúng ta cũng có thể dùng Visual Studio Code (VS Code) để lập trình trên cả Windows, MacOS và Linux. VS Code là một Editor hoàn toàn miễn phí và chạy rất nhẹ. Các chức năng của nó cũng khá đầy đủ và có thể cài thêm các Extension để hỗ trợ, bổ sung thêm tính năng tùy vào từng dự án.\nDưới đây là một số cấu hình, tùy chỉnh của mình cho VS Code khi lập trình ASP.NET Core.\n1. Cài đặt Extension Có nhiều Extension hỗ trợ cho việc lập trình ASP.NET Core, tuy nhiên đến thời điểm hiện tại thì có Extension C# (ms-vscode.csharp) là được dùng nhiều nhất và được khuyến cáo sử dụng.\n2. Tùy chỉnh phím tắt Việc thiết lập phím tắt sẽ giúp bạn code nhanh hơn. Chúng ta có thể sửa lại các phím tắt mặc định theo ý mình bằng cách chọn File / Preferences / Keyboard Shortcuts. Tìm đến chức năng muốn thiết lập, chọn biểu tượng Edit hoặc bấm Enter để gán phím tắt cho chức năng đó.\nNếu thiết lập sai (ví dụ bị trùng phím tắt), ta có thể khôi phục lại như cũ bằng cách bấm chuột phải vào chức năng và chọn Reset Keybinding.\n3. Cấu hình Editor VS Code cho phép chúng ta tùy chỉnh việc hiển thị, cách thức làm việc, \u0026hellip; trong phần File / Preferences / Settings. Để cấu hình chúng ta sẽ khai báo các thuộc tính và giá trị trong một file JSON. Chúng ta sẽ copy cấu hình mặc định (Default Setings) sang bên User Settings (hoặc Workspace Settings) với các giá trị mới. Với các cấu hình giống nhau thì VS Code sẽ nhận các giá trị theo mức độ ưu tiên Workspace Settings \u0026gt; User Settings \u0026gt; Default Setings.\n4. Cấu hình launch.json Đây là file cấu hình chạy Debug ứng dụng trên VS Code (nằm trong thư mục .vscode của project). Các bạn có thể tham khảo tài liệu chi tiết ở đây. Với ứng dụng ASP.NET Core thì khi bật Debug, mặc định nó sẽ mở một tab mới trên trình duyệt. Để bỏ chức năng này thì chúng ta sửa lại cấu hình launchBrowser enabled thành false:\n1 2 3 4 5 6  ...  \u0026#34;launchBrowser\u0026#34;: {  \u0026#34;enabled\u0026#34;: false,  ...  } ...   5. Cấu hình task.json Task là một chức năng của VS Code cho phép chúng ta tích hợp thêm các External Tools vào trong project như MSbuild, Grunt, Gulp, \u0026hellip; và chúng ta sẽ cấu hình trong file .vscode/task.json. Tham khảo tài liệu chi tiết ở đây.\nTrong project ASP.NET Core thì cần sửa lại cấu hình của msCompile để khi compile lỗi chúng ta có thể chuyển nhanh sang đoạn code gây lỗi.\n  Khi gặp lỗi có thể bấm vào các dòng thông báo để bật nhanh đoạn code bị lỗi   Mặc định msCompile dùng kiểu đường dẫn đầy đủ, nên có thể sẽ gặp lỗi không mở được file:\n  Không mở được file do hiểu nhầm đường dẫn   Sửa lại cấu hình trong file task.json sang kiểu đường dẫn tương đối (relative):\n1 2 3 4 5 6  ...  \u0026#34;problemMatcher\u0026#34;: {  \u0026#34;base\u0026#34;: \u0026#34;$msCompile\u0026#34;,  \u0026#34;fileLocation\u0026#34;: [\u0026#34;relative\u0026#34;, \u0026#34;${workspaceRoot}\u0026#34;]  } ...   Trên đây là một số kinh nghiệm của mình khi thiết lập môi trường làm việc ASP.NET Core với Visual Studio Code. Các bạn có thể tham khảo để setup cho mình một môi trường phát triển tiện lợi và phù hợp nhất.\nHappy Coding 😁 .\n","permalink":"https://robinhuy.github.io/blog/tuy-chinh-visual-studio-code-khi-lap-trinh-aspnet-core/","summary":"ASP.NET Core là một open-source web framework mới của Microsoft. Nó cho phép phát triển và chạy ứng dụng web đa nền tảng. Giờ đây bạn có thể lập trình .NET trên cả Linux và MacOS.\n Để lập trình chúng ta có thể sử dụng Visual Studio (trên Windows) hoặc Visual Studio for Mac (trên Mac). Ngoài ra chúng ta cũng có thể dùng Visual Studio Code (VS Code) để lập trình trên cả Windows, MacOS và Linux.","title":"Tùy chỉnh Visual Studio Code khi lập trình ASP.NET Core"},{"content":"Thông thường khi làm dự án với Entity Framework, chúng ta hay dùng cơ sở dữ liệu (CSDL) MS SQL Server. Tuy nhiên tùy theo yêu cầu công việc, bạn có thể sẽ phải làm việc với các cơ sở dữ liệu khác. Bài viết này mình sẽ chia sẻ 1 số kinh nghiệm của bản thân khi phải làm việc với CSDL Oracle (cụ thể là Oracle phiên bản 11g).\nCài đặt provider Để cài đặt provider thì chúng ta có thể cài đặt qua NuGet hoặc download trên trang chủ của Oracle. Nhưng do tính chất công việc cần bảo mật, mình không được sử dụng internet nên chọn cách sử dụng bộ cài ODAC trên Oracle: http://www.oracle.com/technetwork/topics/dotnet/utilsoft-086879.html. Các bạn có thể sử dụng phiên bản ODAC 11.2 trở lên, ở đây mình dùng hẳn luôn bản mới ODAC 12c Release 4 vì nó có tính tương thích ngược.\nĐể cài ODAC thì các bạn chỉ cần giải nén file tải về và chạy tệp setup.exe (Oracle Universal Installer), nhưng sẽ có 1 số lưu ý sau:\n  Nếu trên máy đã có ODAC bản cũ thì cần gỡ ra trước khi cài mới bằng cách sử dụng Universal Installer (Windows Start Menu \u0026ndash;\u0026gt; All Programs \u0026ndash;\u0026gt; Oracle - \u0026ndash;\u0026gt; Oracle Installation Products \u0026ndash;\u0026gt; Universal Installer).\n  Khi cài đến phần chọn thư mục cài đặt (Specify Installation Location) thì cần chú ý trong đường dẫn không được phép chứa các ký tự đặc biệt (ví dụ như dấu cách). Có thể lúc cài thì vẫn cài được nhưng sau đó sẽ bị lỗi (mình đã bị dính lỗi này khi sử dụng Windows Built-in Account có username chứa dấu cách).\n  Một số khác biệt khi sử dụng CSDL Oracle   Trong Oracle không dùng khái niệm database như các CSDL khác mà sử dụng khái niệm Schemas, và Schemas tương ứng với Users. Tức là ở trong MS SQL Server chúng ta tạo mới một database thì ở đây chúng ta tạo mới một user (schema). Ví dụ chúng ta cần tạo một ứng dụng quản lý sinh viên thì thay vì tạo database student, chúng ta tạo mới một user tên là student.\n  Tất cả tên bảng trong schema không được vượt quá 30 ký tự.\n  Lỗi ORA-01918: user \u0026ldquo;dbo\u0026rdquo; does not exist khi sử dụng EF Migration, đó là do Oracle hiểu nhầm dbo.table_name là bảng table_name trong schema dbo. Sửa bằng cách đổi lại schema trong class DbContext bằng cách ghi đè phương thức OnModelCreating, ví dụ schema của mình là ROBIN (chú ý viết hoa):\n  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  public class ApplicationDbContext : IdentityDbContext {  public ApplicationDbContext() : base(\u0026#34;ROBIN\u0026#34;, throwIfV1Schema: false)  {  }   public static ApplicationDbContext Create()  {  return new ApplicationDbContext();  }   protected override void OnModelCreating(DbModelBuilder modelBuilder)  {  base.OnModelCreating(modelBuilder);  modelBuilder.HasDefaultSchema(\u0026#34;ROBIN\u0026#34;);  } }   Hy vọng một số kinh nghiệm trên đây sẽ giúp những ai mới làm việc với Oracle giảm bớt thời gian tìm hiểu cài đặt hay fix những bug kể trên.\n","permalink":"https://robinhuy.github.io/blog/su-dung-co-so-du-lieu-oracle-voi-entity-framework/","summary":"Thông thường khi làm dự án với Entity Framework, chúng ta hay dùng cơ sở dữ liệu (CSDL) MS SQL Server. Tuy nhiên tùy theo yêu cầu công việc, bạn có thể sẽ phải làm việc với các cơ sở dữ liệu khác. Bài viết này mình sẽ chia sẻ 1 số kinh nghiệm của bản thân khi phải làm việc với CSDL Oracle (cụ thể là Oracle phiên bản 11g).","title":"Sử dụng cơ sở dữ liệu Oracle với Entity Framework"},{"content":"Bài trước mình đã hướng dẫn cách tạo chứng chỉ SSL miễn phí với Let\u0026rsquo;s Encrypt. Tiếp theo mình sẽ hướng dẫn các bạn cách tạo chứng chỉ SSL qua dịch vụ của Namecheap (mất phí nhé 😂)\nĐăng ký dịch vụ của Namecheap\nĐầu tiên chúng ta sẽ cần đăng ký tài khoản tại Namecheap và chọn 1 loại chứng chỉ phù hợp https://www.namecheap.com/security/ssl-certificates.aspx.\nSau khi đã thanh toán, truy cập vào Dashboard để active dịch vụ\n  Di chuột đến phần tên user góc trên bên phải để truy cập Dashboard     Trên menu chọn phần Product List / SSL Certificates     Chọn ACTIVATE để kích hoạt dịch vụ   Tiếp theo để kích hoạt dịch vụ chúng ta sẽ cần điền một CSR (Certificate Signing Request), đó là 1 đoạn code mã hóa thông tin về công ty và tên miền. Để tạo mã CSR, chúng ta cần truy cập vào server và gõ lệnh sau:\n$ openssl req -new -newkey rsa:2048 -nodes -keyout server.key -out server.csr Khi trên server có nhiều website thì chúng ta nên thay server.key và server.csr bằng tên miền để tránh nhầm lẫn, ví dụ nctest.info.key và nctest.info.csr hoặc lưu vào 1 thư mục riêng ví dụ /etc/ssl/nctest.info\nKhi thực hiện lệnh, chúng ta sẽ phải điền 1 số thông tin của công ty và domain ví dụ như sau:\nCountry Name (2 letter code) [AU]: VN State or Province Name (full name) [Some-State]: Hanoi Locality Name (eg, city) []: Hanoi Organization Name (eg, company) [Internet Widgits Pty Ltd]: NCTEST Ltd Organizational Unit Name (eg, section) []: Training Common Name (e.g. server FQDN or YOUR name) []: nctest.info Email Address []: administrator@nctest.info Kết quả chúng ta sẽ có 2 file là server.key (Private Key) và server.csr (mã CSR). File server.key sẽ dùng để chứng thực nên cần lưu lại cẩn thận để dùng đến sau này. File server.csr sẽ dùng để gửi dữ liệu lên namecheap, chúng ta dùng lệnh sau để đọc nội dung file và copy để điền vào form đăng ký cat server.csr\n  Copy mã CSR và điền vào form, chọn Web-Server Nginx sau đó Submit   Phần nào không điền được thì có thể để \u0026lsquo;NA\u0026rsquo; và chú ý chỉ sử dụng ký tự alphabet, tiếng Anh không dấu.\nChọn phương thức xác thực\nCó 3 loại phương thức xác thực là Email, HTTP-based và DNS-based. Ở đây mình sẽ hướng dẫn theo cách đầu tiên là xác thực qua Email, 2 cách còn lại các bạn xem hướng dẫn tiếng Anh tại website.\n  Ví dụ xác thực qua Email       Xác nhận gửi email   Xong, tiếp theo chúng ta chờ Namecheap gửi email xác thực và chứng chỉ SSL qua email đăng ký (ví dụ admin@nctest.info).\nChứng chỉ SSL sẽ được đính kèm theo email, tải về và upload lên server và giải nén chúng ta sẽ được 2 file dạng như sau nctest.info.crt và nctest.info.ca-bundle, nối 2 file lại bằng lệnh cat (nếu có nhiều file hơn thì cũng nối lại thành 1 file tương tự):\n$ cat nctest.info.crt nctest.info.ca-bundle \u0026gt;\u0026gt; cert_chain.crt Chúng ta sẽ sử dụng file đã nối là cert_chain.crt và file Private key tạo lúc ban đầu là server.key để cài đặt SSL.\nCấu hình Nginx\nSửa file cấu hình cho domain, nếu chưa lắng nghe ở cổng 443 thì bổ sung thêm, và trỏ đường dẫn vào 2 file cert_chain.crt và server.key:\nserver { listen 443; ssl on; ssl_certificate /etc/ssl/nctest.info/cert_chain.crt; ssl_certificate_key /etc/ssl/nctest.info/server.key; server_name nctest.info; access_log /var/log/nginx/nginx.vhost.access.log; error_log /var/log/nginx/nginx.vhost.error.log; location / { root /var/www/; index index.html; } } Hoặc tham khảo file cấu hình ở bài hướng dẫn trước. Sau khi sửa xong nhớ khởi động lại Nginx và hưởng thụ thành quả 😊\n","permalink":"https://robinhuy.github.io/blog/cai-dat-chung-chi-ssl-cua-namecheap-voi-nginx/","summary":"Bài trước mình đã hướng dẫn cách tạo chứng chỉ SSL miễn phí với Let\u0026rsquo;s Encrypt. Tiếp theo mình sẽ hướng dẫn các bạn cách tạo chứng chỉ SSL qua dịch vụ của Namecheap (mất phí nhé 😂)\nĐăng ký dịch vụ của Namecheap\nĐầu tiên chúng ta sẽ cần đăng ký tài khoản tại Namecheap và chọn 1 loại chứng chỉ phù hợp https://www.namecheap.com/security/ssl-certificates.aspx.\nSau khi đã thanh toán, truy cập vào Dashboard để active dịch vụ","title":"Cài đặt chứng chỉ SSL của Namecheap với Nginx"},{"content":"Hiện nay hầu hết các trang web đều đã hỗ trợ SSL (Secure Socket Layer). Nó mã hóa dữ liệu truyền đi giữa máy chủ web và trình duyệt và làm tăng tính bảo mật cho website. Ngoài ra, việc sử dụng SSL certificate (chứng chỉ SSL) là cần thiết bởi hiện tại Google đã ưu tiên xếp hạng website dựa theo giao thức https (HTTP + SSL), những website mà chỉ sử dụng giao thức http sẽ bị coi là \u0026ldquo;unsafe\u0026rdquo; (không an toàn).\nCó nhiều loại chứng chỉ SSL cung cấp các mức độ bảo mật khác nhau. Ví dụ chúng ta có thể mua một Chứng chỉ SSL tại Namecheap với các mức giá khác nhau tùy từng loại. Tuy nhiên trong bài viết này chúng ta sẽ chỉ nói đến loại cơ bản nhất và làm thế nào để có được nó một cách miễn phí 😁\nMột số cách để có chứng chỉ SSL miễn phí\n  Sử dụng Cloudflare: Đây là một website cung cấp dịch vụ tăng tốc và bảo mật website, họ có cung cấp chứng chỉ SSL ở gói Free. Việc đăng ký rất dễ dàng nên mình sẽ không hướng dẫn ở đây. Chú ý là với website chỉ phục vụ người dùng tại Việt Nam thì chạy qua Cloudflare có thể sẽ chậm hơn 1 chút do sử dụng CDN server ngoài Việt Nam.\n  Sử dụng Let's Encrypt: Sử dụng dịch vụ này chúng ta sẽ tự tạo SSL certificate cho riêng mình và hoàn toàn miễn phí.\n  Cách tạo SSL certificate với Let\u0026rsquo;s Encrypt\nGiả sử chúng ta đang sử dụng 1 server Ubuntu với tài khoản truy cập có quyền sudo và sử dụng web server là Nginx.\nBước 1: Cài đặt gói letsencrypt (với bản mới sẽ đổi tên là certbot và dùng lệnh certbot thay cho letsencrypt)\n$ sudo apt-get update $ sudo apt-get install letsencrypt hoặc làm theo hướng dẫn tại trang chủ https://certbot.eff.org\nBước 2: Tạo SSL certificate\n Thêm đoạn cấu hình sau vào block server của file cấu hình cho website (thường nằm trong /etc/nginx/sites-enabled hoặc /etc/nginx/conf.d) để cho phép truy cập vào thư mục ẩn (.well-known) phục vụ cho việc xác thực:  ... location ~ /.well-known { allow all; } ...  Kiểm tra lại cấu hình xem có sai cú pháp chỗ nào không bằng lệnh:  1  $ nginx -t    Nếu có báo lỗi thì sửa theo hướng dẫn, sau đó khởi động lại Nginx:  $ sudo systemctl restart nginx  Tạo SSL certificate (thay example.com bằng tên miền của bạn và /var/www/example.com là đường dẫn đến thư mục gốc của website):  $ sudo letsencrypt certonly -a webroot --webroot-path=/var/www/example.com -d example.com -d www.example.com  Nếu thành công output sẽ trông như sau:  IMPORTANT NOTES: - If you lose your account credentials, you can recover through e-mails sent to sammy@digitalocean.com - Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/example.com/fullchain.pem. Your cert will expire on 2016-03-15. To obtain a new version of the certificate in the future, simply run Let\u0026#39;s Encrypt again. ... Bước 3: Cấu hình SSL cho website\n Để tăng tính bảo mật, tạo Strong Diffie-Hellman Group:  $ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048  Tạo 1 snippet cho Nginx để có thể tái sử dụng được khi muốn cấu hình cho nhiều website:  $ sudo nano /etc/nginx/snippets/ssl-params.conf Nội dung file như sau:\nssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers \u0026#34;EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH\u0026#34;; ssl_ecdh_curve secp384r1; ssl_session_cache shared:SSL:10m; ssl_stapling on; ssl_stapling_verify on; ssl_dhparam /etc/ssl/certs/dhparam.pem; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; add_header Strict-Transport-Security \u0026#34;max-age=63072000; includeSubdomains\u0026#34;; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; Tham khảo cấu hình SSL bảo mật tại https://cipherli.st và https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html\n Sửa file cấu hình cho website:  Tạo redirect 301 cho block server listen 80 (http) nếu bạn chỉ muốn support https (khi người dùng truy cập với giao thức http sẽ tự động chuyển thành https)\nserver { listen 80; server_name example.com www.example.com; return 301 https://$server_name$request_uri; } Tạo thêm 1 block server listen 443 (https)\nserver { listen 443 ssl http2; server_name example.com www.example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; include snippets/ssl-params.conf; root /var/www/example.com; location ~ /.well-known { allow all; } } Chú ý thay toàn bộ example.com bằng domain của mình và đường dẫn root /var/www/example.com giống bước tạo SSL certificate.\n Sau khi cấu hình xong thì khởi động lại Nginx:  $ sudo systemctl restart nginx  Truy cập thử website để xem thành quả hoặc kiểm tra bằng trang sau https://www.ssllabs.com/ssltest/analyze.html.  Gia hạn SSL certificate\n SSL tạo theo cách này sẽ hết hạn sau 90 ngày và chúng ta sẽ phải gia hạn bằng lệnh sau:  $ sudo letsencrypt renew Để tự động hóa việc này chúng ta có thể cấu hình cronjob để tự động gia hạn chứng chỉ.\n Ví dụ cấu hình cronjob để tự động gia hạn mỗi 60 ngày:  $ sudo crontab -e Thêm vào dòng sau (đặt lịch cứ mỗi 2 tháng tự động chạy lệnh renew vào lúc 0h30):\n30 0 1 */2 * /usr/bin/letsencrypt renew \u0026amp;\u0026amp; /bin/systemctl reload nginx Xong, vậy là website của chúng ta đã có thể truy cập qua giao thức https và không lo bị hết hạn 👍.\n","permalink":"https://robinhuy.github.io/blog/chung-chi-ssl-mien-phi-voi-lets-encrypt/","summary":"Hiện nay hầu hết các trang web đều đã hỗ trợ SSL (Secure Socket Layer). Nó mã hóa dữ liệu truyền đi giữa máy chủ web và trình duyệt và làm tăng tính bảo mật cho website. Ngoài ra, việc sử dụng SSL certificate (chứng chỉ SSL) là cần thiết bởi hiện tại Google đã ưu tiên xếp hạng website dựa theo giao thức https (HTTP + SSL), những website mà chỉ sử dụng giao thức http sẽ bị coi là \u0026ldquo;unsafe\u0026rdquo; (không an toàn).","title":"Chứng chỉ SSL miễn phí với Let's Encrypt"},{"content":"Ở bài trước mình có hướng dẫn cài đặt Gitlab trên Private Server, tuy nhiên cách cài này là cài trực tiếp lên server, có thể sẽ xung đột với các gói phần mềm có sẵn như: Redis, Nginx, \u0026hellip; Do đó bài này chúng ta sẽ thử cài Gitlab qua Docker - một công nghệ đang rất hot trong thời điểm hiện tại.\nViệc đầu tiên chúng ta phải làm đó là cài đặt Docker, các bạn tham khảo tại đây, hướng dẫn này rất chi tiết rồi mình sẽ không nhắc lại nữa.\nSau khi cài xong Docker, chúng ta chỉ việc bật terminal lên và gõ\n$ docker run -d --name local-gitlab --restart always -p 80:80 gitlab/gitlab-ce Giải thích 1 chút về câu lệnh trên:\n docker run -d: Khởi tạo và chạy 1 Container cho Gitlab dưới dạng Detached mode (có thể hình dung như 1 máy ảo, trong máy ảo đó có Gitlab) \u0026ndash;name local-gitlab: Đặt tên cho Container là local-gitlab, sau này chúng ta sẽ tương tác với Container qua tên này. \u0026ndash;restart always: Luôn luôn khởi động lại Container khi bị thoát (khi server reboot hay restart docker service) -p 80:80: Publish port 80 từ trong Container ra ngoài host để chúng ta có thể truy cập vào gitlab qua host, chúng ta cũng có thể publish ra 1 cổng khác ví dụ -p 8080:80, nếu là trên localhost thì truy cập vào gitlab như sau http://localhost:8080 gitlab/gitlab-ce: Tên của một Image trên https://hub.docker.com (cái này tương tự như Github nhưng thay vì chứa source code thì nó chứa các Image - các bản đóng gói do người khác upload lên). Chúng ta cũng có thể dùng một Image khác bằng cách search ở trên Docker hub (nên chọn Image có STARS cao và nhiều lượt PULL)  Sau khi câu lệnh chạy xong là việc cài Gitlab cũng đã hoàn tất, tuy nhiên chúng ta sẽ phải chờ thêm một vài phút để Gitlab hoàn thiện việc cấu hình. Nếu nóng lòng truy cập ngay có thể gặp lỗi như sau:\n  Nếu gặp lỗi này chỉ cần chờ 1 lúc rồi Refresh trang là được 😬   Vậy là với Docker, chúng ta có thể cài Gitlab chỉ trong 1 nốt nhạc. Còn nếu bạn muốn cấu hình cho Gitlab thì có thể dùng lệnh docker exec, ví dụ muốn sửa file cấu hình của gitlab thì chúng ta gõ lệnh sau:\n$ docker exec -it local-gitlab vim /etc/gitlab/gitlab.rb lệnh trên sẽ bật file /etc/gitlab/gitlab.rb​ trong Container gitlab bằng vim (nếu không thích dùng vim thì thay bằng nano cũng được)\nSau khi cấu hình xong thì phải restart lại Container bằng lệnh:\n$ docker restart local-gitlab Tham khảo thêm về các câu lệnh của Docker tại đây\n","permalink":"https://robinhuy.github.io/blog/cai-dat-gitlab-bang-docker/","summary":"Ở bài trước mình có hướng dẫn cài đặt Gitlab trên Private Server, tuy nhiên cách cài này là cài trực tiếp lên server, có thể sẽ xung đột với các gói phần mềm có sẵn như: Redis, Nginx, \u0026hellip; Do đó bài này chúng ta sẽ thử cài Gitlab qua Docker - một công nghệ đang rất hot trong thời điểm hiện tại.\nViệc đầu tiên chúng ta phải làm đó là cài đặt Docker, các bạn tham khảo tại đây, hướng dẫn này rất chi tiết rồi mình sẽ không nhắc lại nữa.","title":"Cài đặt Gitlab bằng Docker"},{"content":"Gitlab là một công cụ để quản lý source code rất nổi tiếng hiện nay. Nó cho phép chúng ta tạo và quản lý các Git source repository tương tự nhu trên Github, tuy nhiên nó cho phép chúng ta tạo không giới hạn các private repository và nhiều chức năng thú vị khác như: code reviews, issue tracking, activity feeds, wikis, \u0026hellip;\nChúng ta có thể sử dụng gitlab bằng cách truy cập trang https://gitlab.com hoặc cài gitlab lên 1 server riêng. Có 2 cách để cài gitlab lên private server đó là: Cài từ source git và cài theo Omnibus package. Cài theo cách thứ 2 thì sẽ đơn giản hơn rất nhiều, chúng ta chỉ cần vào mục download, sau đó chọn server cần cài đặt và làm theo các bước hướng dẫn bên dưới.\n  Ví dụ cài Gitlab lên server Debian 8   Tóm tắt các lệnh cài đặt trên server Debian 8\n$ sudo apt-get install curl openssh-server ca-certificates postfix $ curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash $ sudo apt-get install gitlab-ce $ sudo gitlab-ctl reconfigure Sau khi cài đặt xong chúng ta có thể dùng gitlab-ctl để quản lý service:\n# Kiểm tra trạng thái $ sudo gitlab-ctl status # Bật gitlab $ sudo gitlab-ctl start # Tắt gitlab $ sudo gitlab-ctl stop # Khởi động lại gitlab $ sudo gitlab-ctl restart Chú ý\n  Gitlab Omnibus bao gồm nhiều gói package bên trong như: Nginx, Postgresql, Redis, Sidekiq, Unicorn, .. nên dung lượng khá nặng\n  Nếu trên server đang sử dụng Nginx làm web server thì có thể bị trùng cổng. Chúng ta có thể disable gói Nginx trong Gitlab Omnibus đi để dùng Nginx có sẵn:\n  Bật file /etc/gitlab/gitlab.rb và sửa các cấu hình sau:\nnginx[\u0026#39;enable\u0026#39;] = false web_server[\u0026#39;external_users\u0026#39;] = [\u0026#39;www-data\u0026#39;]```   Cập nhật lại cấu hình của Gitlab\n$ gitlab-ctl reconfigure   Cấu hình lại Nginx cho Gitlab, có thể tham khảo file cấu hình mẫu tại đây.\n  Cấu hình gửi email SMTP, có thể tham khảo tại đây.\n  Tham khảo document của Gitlab Omnibus: https://docs.gitlab.com/omnibus\n","permalink":"https://robinhuy.github.io/blog/huong-dan-cai-dat-gitlab-tren-private-server/","summary":"Gitlab là một công cụ để quản lý source code rất nổi tiếng hiện nay. Nó cho phép chúng ta tạo và quản lý các Git source repository tương tự nhu trên Github, tuy nhiên nó cho phép chúng ta tạo không giới hạn các private repository và nhiều chức năng thú vị khác như: code reviews, issue tracking, activity feeds, wikis, \u0026hellip;\nChúng ta có thể sử dụng gitlab bằng cách truy cập trang https://gitlab.","title":"Hướng dẫn cài đặt Gitlab trên Private Server"},{"content":"Khi sử dụng thẻ img để chèn ảnh vào trang web, có 1 hiện tượng khá thú vị mà không phải ai cũng biết hoặc để ý đến. Đó là nếu bọc thẻ img bởi 1 thẻ div (hoặc bất kỳ 1 thẻ nào khác) thì sẽ xuất hiện 1 khoảng trắng nhỏ ở bên dưới ảnh.\nNếu chúng ta thiết lập padding cho thẻ div thì do khoảng trắng này mà khoảng cách phần bên dưới vẫn lớn hơn phần bên trên. Vậy khoảng trắng này từ đâu mà có?\nĐể hiểu rõ về vấn đề này, chúng ta phải nắm rõ về HTML CSS, đặc biệt là khái niệm về Inline element và Block element.\nMặc định thẻ img trong HTML là một Inline element, tức là nó được coi như là text. Do đó nó sẽ được browser xử lý như với text thông thường. Đến đây ta lại phải biết một chút về text, một số khái niệm về text trong kỹ thuật in ấn:\n Trên cùng 1 dòng, các chữ cái sẽ nằm trên một đường thẳng gọi là baseline(đó cũng là giá trị mặc định của thuộc tính vertical-align trong CSS) Một số chữ cái, ký tự đặc biệt sẽ có 1 phần nằm dưới baseline ví dụ như y, j, p, g, \u0026hellip; và phần nằm dưới đó gọi là descenders(tương tự, có 1 phần nằm trên text gọi là ascenders)   Do đó trình duyệt sẽ tạo ra 1 khoảng trắng dành cho phần descenders của text, nếu chúng ta viết thêm 1 vài ký tự có descenders vào bên cạnh ảnh và tăng font-size của chúng lên, ta sẽ thấy rõ tại sao lại có khoảng trắng nhỏ bên dưới ảnh.\nVậy là chúng ta đã khám phá được khoảng trắng bí ẩn bên dưới thẻ img. Và để xử lý khoảng trắng đó ta có thể dùng các cách sau:\n Dùng thuộc tính vertical-align cho thẻ img với giá trị là middle. Dùng thuộc tính line-height cho thẻ div với giá trị là 0. Chuyển img thành Block element với thuộc tính display.  ","permalink":"https://robinhuy.github.io/blog/khoang-trang-bi-an-ben-duoi-the-img/","summary":"Khi sử dụng thẻ img để chèn ảnh vào trang web, có 1 hiện tượng khá thú vị mà không phải ai cũng biết hoặc để ý đến. Đó là nếu bọc thẻ img bởi 1 thẻ div (hoặc bất kỳ 1 thẻ nào khác) thì sẽ xuất hiện 1 khoảng trắng nhỏ ở bên dưới ảnh.\nNếu chúng ta thiết lập padding cho thẻ div thì do khoảng trắng này mà khoảng cách phần bên dưới vẫn lớn hơn phần bên trên.","title":"Khoảng trắng bí ẩn bên dưới thẻ Img"},{"content":"\u0026hellip; tiếp theo 29 câu lệnh Linux bạn nên biết phần 1\n17. help \u0026ndash;help xem thông tin trợ giúp và các tùy chỉnh của câu lệnh.\nCó thể viết tắt là -h\n18. whatis – What is this command whatis hiển thị mô tả về câu lệnh.\n19. man – Manual man ​ hiển thị trang hướng dẫn cho câu lệnh.\n20. exit exit ​ thoát khỏi phiên làm việc. Tương tự như việc thoát khỏi một ứng dụng trên giao diện người dùng.\n21. ping ping \u0026lt;địa chỉ host\u0026gt; ​ ping một host từ xa (server) bằng cách gửi các gói tin đến host đó. Nó thường dùng để kiểm tra kết nối mạng đến server.\n22. who – Who Is logged in who ​ hiển thị danh sách các tài khoản đang đăng nhập vào hệ thống.\n23. su – Switch User su ​ chuyển sang đăng nhập bằng một tài khoản khác. Tài khoản root có thể chuyển sang đăng nhập bằng các tài khoản khác mà không cần nhập mật khẩu.\n24. uname uname ​ hiển thị ra một số thông tin hệ thống như tên kernel, tên host, bộ xử lý, \u0026hellip;\nBạn có thể dùng lệnh uname -a ​ để hiển thị tất cả thông tin.\n25. free – Free memory free ​ xem thông tin về bộ nhớ: bộ nhớ đã sử dụng, bộ nhớ còn trống trên hệ thống\nBạn có thể dùng lệnh free -m ​ để xem bộ nhớ với đơn vị KBs hoặc free -g ​ để xem với đơn vị GBs\n26. df – Disk space Free df ​ xem thông tin về dung lượng đĩa cứng (đã sử dụng, còn trống, \u0026hellip;) và các thiết bị lưu trữ khác.\nBạn có thể dùng lệnh df -h ​ để xem thông tin dưới dạng human readable (hiển thị với đơn vị KBs, GBs cho dễ đọc).\n27. ps – Processes ps ​ hiển thị thông tin về các tiến trình đang chạy.\n28. top – Top processes top ​ hiển thị thông tin về các tiến trình đang chạy, sắp xếp theo hiệu suất CPU.\nBạn cũng có thể dùng lệnh top -u ​ để xem thông tin các tiến trình đang chạy của tài khoản đó.\n29. shutdown shutdown ​ lệnh tắt máy tính. Có thể dùng shutdown -r để khởi động lại máy tính.\n Nguồn: http://www.hongkiat.com/blog/basic-linux-commands\n","permalink":"https://robinhuy.github.io/blog/29-cau-lenh-linux-ban-nen-biet-phan-2/","summary":"\u0026hellip; tiếp theo 29 câu lệnh Linux bạn nên biết phần 1\n17. help \u0026ndash;help xem thông tin trợ giúp và các tùy chỉnh của câu lệnh.\nCó thể viết tắt là -h\n18. whatis – What is this command whatis hiển thị mô tả về câu lệnh.\n19. man – Manual man ​ hiển thị trang hướng dẫn cho câu lệnh.\n20. exit exit ​ thoát khỏi phiên làm việc.","title":"29 câu lệnh Linux bạn nên biết - Phần 2"},{"content":"Các bản phân phối Linux đều có hỗ trợ giao diện người dùng để tương tác với máy tính. Tuy nhiên trong một số trường hợp sử dụng giao diện command line để điều khiển máy tính sẽ nhanh hơn và đơn giản hơn.\nTrong giao diện command line, các câu lệnh (command) dùng để ra chỉ thị cho máy tính thực hiện một tác vụ nào đó. Bạn có thể sử dụng câu lệnh để tắt máy tính, xem danh sách các file trong thư mục, sao chép file, di chuyển và xóa file, \u0026hellip;\nDưới đây tôi sẽ liệt kê các câu lệnh Linux cơ bản thường gặp để các bạn mới làm quen với Linux hoặc các quản trị viên Linux có thể dễ dàng học tập, tra cứu.\n1. ls - List ls liệt kê nội dung (file và thư mục) trong thư mục hiện hành. Nó cũng tương tự với việc bạn mở một thư mục và xem nội dung trong đó trên giao diện người dùng.\n2. mkdir - Make Directory mkdir tạo một thư mục mới. Nó cũng tương tự với việc bạn chọn new/create directory để tạo một thư mục mới trên giao diện người dùng.\n3. pwd - Print Working Directory pwd in ra đường dẫn đầy đủ đến thư mục hiện hành.\n4. cd - Change Directory cd chuyển một thư mục thành thư mục hiện hành cho phiên làm việc hiện tại. Nó cũng tương tự với việc bạn mở một thư mục và thao tác với các file và thư mục bên trong đó trên giao diện người dùng.\n5. rmdir - Remove Directory rmdir xóa một thư mục.\n6. rm - Remove rm xóa file. Bạn cũng có thể sử dụng rm -r để xóa thư mục và toàn bộ dữ liệu trong thư mục đó.\n7. cp - Copy cp sao chép file từ vị trí nguồn đến vị trí đích.\nBạn cũng có thể sử dụng cp -r để sao chép thư mục và toàn bộ dữ liệu bên trong.\n8. mv - Move mv \u0026lt;đích\u0026gt; di chuyển một file hoặc thư mục từ vị trí này sang vị trí khác. Lệnh này cũng dùng để đổi tên file hoặc thư mục nếu như và \u0026lt;đích\u0026gt; là cùng một thư mục.\n9. cat – concatenate and print files cat đọc và in ra nội dung của file ra màn hình.\n10. tail – print TAIL tail đọc và in ra nội dung 10 dòng cuối cùng của file (mặc định).\nBạn có thể sử dụng tail -n N để chỉ định in N dòng ra màn hình.\n11. less – print LESS less in ra nội dung của một file theo từng trang trong trường hợp nội dung của file quá lớn và phải đọc theo trang. Bạn có thể dùng Ctrl+F để chuyển trang tiếp theo và Ctrl+B để chuyển về trang trước.\n12. grep grep tìm kiếm nội dung của file theo chuỗi cung cấp.\nBạn có thể dùng grep -i để tìm kiếm không phân biệt hoa thường hoặc grep -r để tìm kiếm trong toàn thư mục\n13. find find -name tìm kiếm file trong theo .\nBạn cũng có thể dùng find -iname để tìm kiếm không phân biệt hoa thường.\n14. tar tar -cvf tạo file nén (.tar) từ các file có sẵn.\ntar -tvf xem nội dung file nén (.tar).\ntar -xvf giải nén (file .tar).\n15. gzip gzip tạo file nén (.gz). Sử dụng gzip -d để giải nén (file .gz).\n16. unzip unzip giải nén một file nén (.zip). Sử dụng unzip -l để xem nội dung file zip mà không cần giải nén.\nPhần tiếp theo: 29 câu lệnh Linux bạn nên biết - Phần 2\n","permalink":"https://robinhuy.github.io/blog/29-cau-lenh-linux-ban-nen-biet-phan-1/","summary":"Các bản phân phối Linux đều có hỗ trợ giao diện người dùng để tương tác với máy tính. Tuy nhiên trong một số trường hợp sử dụng giao diện command line để điều khiển máy tính sẽ nhanh hơn và đơn giản hơn.\nTrong giao diện command line, các câu lệnh (command) dùng để ra chỉ thị cho máy tính thực hiện một tác vụ nào đó. Bạn có thể sử dụng câu lệnh để tắt máy tính, xem danh sách các file trong thư mục, sao chép file, di chuyển và xóa file, \u0026hellip;","title":"29 câu lệnh Linux bạn nên biết - Phần 1"},{"content":"Hi there 👋\nCảm ơn bạn đã ghé thăm blog của mình, một blog về công nghệ (chủ yếu là lập trình), nhưng cũng có thể có nhiều bài viết linh tinh nhảm nhí tùy tâm trạng.\nHy vọng các bài viết ở đây sẽ giúp phần nào cho công việc của các bạn (nếu bạn là lập trình viên), hoặc ít nhất nó cũng giúp bạn giải trí sau những giờ làm việc căng thẳng 😎.\n Blog được tạo từ 2022, sử dụng tool GoHugo và theme PaperMod. Tuy nhiên các bạn sẽ thấy có những bài được publish từ \u0026hellip; 2015 😅. Đó là thời điểm mình bắt đầu học viết blog (thực ra là dịch các bài blog Tiếng Anh là chủ yếu). Hồi đó không có blog cá nhân, mà mình viết bài trên trang techmaster.vn, và bây giờ copy lại, để thấy hồi xưa mình viết ngáo như nào (dù bây giờ vẫn thế). Nếu bạn muốn tự xây dựng một website (blog) cá nhân tương tự như website này (và hoàn toàn miễn phí) thì có thể tham khảo bài viết Cách tạo một trang blog cá nhân miễn phí dành cho dev.\nHave fun!\n","permalink":"https://robinhuy.github.io/about/","summary":"Hi there 👋\nCảm ơn bạn đã ghé thăm blog của mình, một blog về công nghệ (chủ yếu là lập trình), nhưng cũng có thể có nhiều bài viết linh tinh nhảm nhí tùy tâm trạng.\nHy vọng các bài viết ở đây sẽ giúp phần nào cho công việc của các bạn (nếu bạn là lập trình viên), hoặc ít nhất nó cũng giúp bạn giải trí sau những giờ làm việc căng thẳng 😎.","title":"About"},{"content":"This privacy statement (“Privacy Statement”) applies to the treatment of personally identifiable information submitted by, or otherwise obtained from, you in connection with the associated application (“Application”). The Application is provided by Master Mind X (and may be provided by Master Mind X on behalf of a huydq.dev licensor or partner (“Application Partner”). By using or otherwise accessing the Application, you acknowledge that you accept the practices and policies outlined in this Privacy Statement.\nWHAT PERSONAL INFORMATION DOES MASTER MIND X COLLECT? We don\u0026rsquo;t collect any types of information from our users, because it is not neccessary.\nCONDITIONS OF USE If you decide to use or otherwise access the Application, your use/access and any possible dispute over privacy is subject to this Privacy Statement and our Terms of Use, including limitations on damages, arbitration of disputes, and application of California state law.\nCAN CHILDREN USE THE APPLICATION? Yes. Because it\u0026rsquo;s a simple game to training IQ and it does not use any image or illegal content.\nCHANGES TO THIS PRIVACY STATEMENT Master Mind X may amend this Privacy Statement from time to time. Use of information we collect now is subject to the Privacy Statement in effect at the time such information is used. If we make changes in the way we use personal information, we will notify you by posting an announcement on our Site or sending you an email. Users are bound by any changes to the Privacy Statement when he or she uses or otherwise accesses the Application after such changes have been first posted.\nQUESTIONS OR CONCERNS If you have any questions or concerns regarding privacy on our Website, please send us a detailed message at creative.dev.vn@gmail.com. We will make every effort to resolve your concerns.\nEffective Date: January 1, 2021\n","permalink":"https://robinhuy.github.io/master-mind-x-application-privacy-statement/","summary":"This privacy statement (“Privacy Statement”) applies to the treatment of personally identifiable information submitted by, or otherwise obtained from, you in connection with the associated application (“Application”). The Application is provided by Master Mind X (and may be provided by Master Mind X on behalf of a huydq.dev licensor or partner (“Application Partner”). By using or otherwise accessing the Application, you acknowledge that you accept the practices and policies outlined in this Privacy Statement.","title":"Application Privacy Statement"},{"content":"This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.\nWe use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy.\nInterpretation and Definitions Interpretation The words of which the initial letter is capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.\nDefinitions For the purposes of this Privacy Policy:\n  Account means a unique account created for You to access our Service or parts of our Service.\n  Company (referred to as either \u0026ldquo;the Company\u0026rdquo;, \u0026ldquo;We\u0026rdquo;, \u0026ldquo;Us\u0026rdquo; or \u0026ldquo;Our\u0026rdquo; in this Agreement) refers to huydq.dev.\n  Cookies are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses.\n  Country refers to: Vietnam\n  Device means any device that can access the Service such as a computer, a cellphone or a digital tablet.\n  Personal Data is any information that relates to an identified or identifiable individual.\n  Service refers to the Website.\n  Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.\n  Usage Data refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).\n  Website refers to huydq.dev, accessible from https://huydq.dev\n  You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.\n  Collecting and Using Your Personal Data Types of Data Collected Personal Data While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:\n Usage Data  Usage Data Usage Data is collected automatically when using the Service.\nUsage Data may include information such as Your Device\u0026rsquo;s Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.\nWhen You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.\nWe may also collect information that Your browser sends whenever You visit our Service or when You access the Service by or through a mobile device.\nTracking Technologies and Cookies We use Cookies and similar tracking technologies to track the activity on Our Service and store certain information. Tracking technologies used are beacons, tags, and scripts to collect and track information and to improve and analyze Our Service. The technologies We use may include:\n Cookies or Browser Cookies. A cookie is a small file placed on Your Device. You can instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not accept Cookies, You may not be able to use some parts of our Service. Unless you have adjusted Your browser setting so that it will refuse Cookies, our Service may use Cookies. Web Beacons. Certain sections of our Service and our emails may contain small electronic files known as web beacons (also referred to as clear gifs, pixel tags, and single-pixel gifs) that permit the Company, for example, to count users who have visited those pages or opened an email and for other related website statistics (for example, recording the popularity of a certain section and verifying system and server integrity).  Cookies can be \u0026ldquo;Persistent\u0026rdquo; or \u0026ldquo;Session\u0026rdquo; Cookies. Persistent Cookies remain on Your personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close Your web browser. Learn more about cookies on the Free Privacy Policy website article.\nWe use both Session and Persistent Cookies for the purposes set out below:\n  Necessary / Essential Cookies\nType: Session Cookies\nAdministered by: Us\nPurpose: These Cookies are essential to provide You with services available through the Website and to enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use these Cookies to provide You with those services.\n  Cookies Policy / Notice Acceptance Cookies\nType: Persistent Cookies\nAdministered by: Us\nPurpose: These Cookies identify if users have accepted the use of cookies on the Website.\n  Functionality Cookies\nType: Persistent Cookies\nAdministered by: Us\nPurpose: These Cookies allow us to remember choices You make when You use the Website, such as remembering your login details or language preference. The purpose of these Cookies is to provide You with a more personal experience and to avoid You having to re-enter your preferences every time You use the Website.\n  For more information about the cookies we use and your choices regarding cookies, please visit our Cookies Policy or the Cookies section of our Privacy Policy.\nUse of Your Personal Data The Company may use Personal Data for the following purposes:\n  To provide and maintain our Service, including to monitor the usage of our Service.\n  To manage Your Account: to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.\n  For the performance of a contract: the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.\n  To contact You: To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application\u0026rsquo;s push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.\n  To provide You with news, special offers and general information about other goods, services and events which we offer that are similar to those that you have already purchased or enquired about unless You have opted not to receive such information.\n  To manage Your requests: To attend and manage Your requests to Us.\n  For business transfers: We may use Your information to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.\n  For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.\n  We may share Your personal information in the following situations:\n With Service Providers: We may share Your personal information with Service Providers to monitor and analyze the use of our Service, to contact You. For business transfers: We may share or transfer Your personal information in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company. With Affiliates: We may share Your information with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us. With business partners: We may share Your information with Our business partners to offer You certain products, services or promotions. With other users: when You share personal information or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside. With Your consent: We may disclose Your personal information for any other purpose with Your consent.  Retention of Your Personal Data The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.\nThe Company will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period of time, except when this data is used to strengthen the security or to improve the functionality of Our Service, or We are legally obligated to retain this data for longer time periods.\nTransfer of Your Personal Data Your information, including Personal Data, is processed at the Company\u0026rsquo;s operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction.\nYour consent to this Privacy Policy followed by Your submission of such information represents Your agreement to that transfer.\nThe Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.\nDelete Your Personal Data You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.\nOur Service may give You the ability to delete certain information about You from within the Service.\nYou may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any personal information that You have provided to Us.\nPlease note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.\nDisclosure of Your Personal Data Business Transactions If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.\nLaw enforcement Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).\nOther legal requirements The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:\n Comply with a legal obligation Protect and defend the rights or property of the Company Prevent or investigate possible wrongdoing in connection with the Service Protect the personal safety of Users of the Service or the public Protect against legal liability  Security of Your Personal Data The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially acceptable means to protect Your Personal Data, We cannot guarantee its absolute security.\nChildren\u0026rsquo;s Privacy Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 13 without verification of parental consent, We take steps to remove that information from Our servers.\nIf We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent\u0026rsquo;s consent before We collect and use that information.\nLinks to Other Websites Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party\u0026rsquo;s site. We strongly advise You to review the Privacy Policy of every site You visit.\nWe have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.\nChanges to this Privacy Policy We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.\nWe will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the \u0026ldquo;Last updated\u0026rdquo; date at the top of this Privacy Policy.\nYou are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.\n","permalink":"https://robinhuy.github.io/privacy-policy/","summary":"This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.\nWe use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy.\nInterpretation and Definitions Interpretation The words of which the initial letter is capitalized have meanings defined under the following conditions.","title":"Privacy Policy"},{"content":"Apple Calculator     Kanban Board     Master Mind     Memory Card     Shopping Cart     Online Shop     Fake API NodeJS     React Native Expo Examples     React Native TypeScript Examples     Master Mind X (Android App)     Touchpad Scroll Carousel      ","permalink":"https://robinhuy.github.io/projects/","summary":"Apple Calculator     Kanban Board     Master Mind     Memory Card     Shopping Cart     Online Shop     Fake API NodeJS     React Native Expo Examples     React Native TypeScript Examples     Master Mind X (Android App)     Touchpad Scroll Carousel      ","title":"Projects"},{"content":"Google SEO phần 1     Google SEO phần 2     Google SEO phần 3      Tổng quan về phần cứng máy tính phần 1     Tổng quan về phần cứng máy tính phần 2     Tổng quan về mạng Internet     Cách bấm dây mạng      Tạo website bằng WordPress phần 1     Tạo website bằng WordPress phần 2     Tạo website bằng WordPress phần 3     Tạo website bằng WordPress phần 4     Tạo website bằng WordPress phần 5      Introduce Docker     About Bitcoin      ","permalink":"https://robinhuy.github.io/slides/","summary":"Google SEO phần 1     Google SEO phần 2     Google SEO phần 3      Tổng quan về phần cứng máy tính phần 1     Tổng quan về phần cứng máy tính phần 2     Tổng quan về mạng Internet     Cách bấm dây mạng      Tạo website bằng WordPress phần 1     Tạo website bằng WordPress phần 2     Tạo website bằng WordPress phần 3     Tạo website bằng WordPress phần 4     Tạo website bằng WordPress phần 5      Introduce Docker     About Bitcoin      ","title":"Slides"},{"content":"Nếu bạn cảm thấy blog có ích, có thể ủng hộ mình phí duy trì website bằng cách mua hàng qua link ủng hộ (không mất thêm bất kỳ chi phí gì và vẫn áp dụng được toàn bộ chương trình giảm giá). Sản phẩm đã được dùng thử và ưu tiên shop ở Hà Nội.\nSách  Sách - Đọc hiểu kết quả xét nghiệm máu (MedInsights) (Shopee)  Đồ gia dụng  Bấm móng tay có móc khóa (Shopee) Chiếu bạc cách nhiệt ngủ văn phòng loại dày 3,5mm (Shopee) Dây nhảy thể thao, lõi thép cao cấp tự động đếm số, tích hợp đo calo (Shopee) Khăn Mặt Khô Dùng Một Lần Animerry (Shopee) Ly cốc giữ nhiệt coffee S6 lõi Inox, có khắc tên theo yêu cầu (Shopee) Lọ nước vệ sinh kính mắt chuyên dụng (Shopee) Xịt Thơm Quần Áo Dky - Lưu Hương Lâu, Chống Muỗi Đốt (Shopee) Xịt vệ sinh khử trùng mũ/nón bảo hiểm DERPU (Shopee) Set 36 miếng dán hỗ trợ giảm mụn vô hình Sevich (Shopee)  Thời trang - Thể thao  Áo khoác dù nam có nón hàn quốc AK23 (xanh đen, Đen) (Shopee) Áo len nữ giữ nhiệt dài tay cổ cao (Lazada) Mũ tai bèo chống nắng dành cho cả nam và nữ (Lazada) Găng tay cảm ứng ngón trỏ điện thoại Naturehike dành cho cả nam và nữ (Shopee) Quần bơi nam lửng cao cấp, 2 lớp chính hãng HAIZID kiểu dáng Sport co giãn 4 chiều chống thấm tốt, nhanh khô (Shopee) Kính bơi cận thị chống sương mù, độ nét cao chính hãng Lining (Shopee) Đồng hồ Xiaomi Mi Band 5, Miband 4C - Nguyên seal BH 1 năm (Shopee) Kìm bóp tập cơ tay 5-60KG, găng tay tập thể thao (Shopee)  Du lịch  Ô Dù Mini để túi siêu gọn, chống tia UV (Shopee) Gậy tự sướng chụp ảnh Bluetooth Xiaomi Tripod 3 chân XMZPG01YM (Lazada) Ổ cắm điện đa năng du lịch Universal Travel Adapter nhiều đầu (Shopee) Khăn lau kính Nano, chống bám hơi nước, chống mờ sương, chống nhờn (Shopee)  - - - - -\nThank you for your support 😘\n","permalink":"https://robinhuy.github.io/support/","summary":"Nếu bạn cảm thấy blog có ích, có thể ủng hộ mình phí duy trì website bằng cách mua hàng qua link ủng hộ (không mất thêm bất kỳ chi phí gì và vẫn áp dụng được toàn bộ chương trình giảm giá). Sản phẩm đã được dùng thử và ưu tiên shop ở Hà Nội.\nSách  Sách - Đọc hiểu kết quả xét nghiệm máu (MedInsights) (Shopee)  Đồ gia dụng  Bấm móng tay có móc khóa (Shopee) Chiếu bạc cách nhiệt ngủ văn phòng loại dày 3,5mm (Shopee) Dây nhảy thể thao, lõi thép cao cấp tự động đếm số, tích hợp đo calo (Shopee) Khăn Mặt Khô Dùng Một Lần Animerry (Shopee) Ly cốc giữ nhiệt coffee S6 lõi Inox, có khắc tên theo yêu cầu (Shopee) Lọ nước vệ sinh kính mắt chuyên dụng (Shopee) Xịt Thơm Quần Áo Dky - Lưu Hương Lâu, Chống Muỗi Đốt (Shopee) Xịt vệ sinh khử trùng mũ/nón bảo hiểm DERPU (Shopee) Set 36 miếng dán hỗ trợ giảm mụn vô hình Sevich (Shopee)  Thời trang - Thể thao  Áo khoác dù nam có nón hàn quốc AK23 (xanh đen, Đen) (Shopee) Áo len nữ giữ nhiệt dài tay cổ cao (Lazada) Mũ tai bèo chống nắng dành cho cả nam và nữ (Lazada) Găng tay cảm ứng ngón trỏ điện thoại Naturehike dành cho cả nam và nữ (Shopee) Quần bơi nam lửng cao cấp, 2 lớp chính hãng HAIZID kiểu dáng Sport co giãn 4 chiều chống thấm tốt, nhanh khô (Shopee) Kính bơi cận thị chống sương mù, độ nét cao chính hãng Lining (Shopee) Đồng hồ Xiaomi Mi Band 5, Miband 4C - Nguyên seal BH 1 năm (Shopee) Kìm bóp tập cơ tay 5-60KG, găng tay tập thể thao (Shopee)  Du lịch  Ô Dù Mini để túi siêu gọn, chống tia UV (Shopee) Gậy tự sướng chụp ảnh Bluetooth Xiaomi Tripod 3 chân XMZPG01YM (Lazada) Ổ cắm điện đa năng du lịch Universal Travel Adapter nhiều đầu (Shopee) Khăn lau kính Nano, chống bám hơi nước, chống mờ sương, chống nhờn (Shopee)  - - - - -","title":"Support"}]