Hỏi nhanh đáp gọn- ReactJS27 min read

bởi Ng. Minh Trí
0 bình luận
React

Chào mừng bạn đến với series “Hỏi Nhanh Đáp Gọn” – nơi bạn sẽ tìm thấy những câu hỏi và câu trả lời ngắn gọn, súc tích nhưng không kém phần sâu sắc về các chủ đề trong ngành tech. Với mục tiêu giúp bạn (thật sự là mình) nắm bắt kiến thức một cách nhanh chóng, series này sẽ tập trung vào những khái niệm quan trọng, các câu hỏi phỏng vấn thường gặp, và cả những tình huống thực tế mà bạn có thể đối mặt.

Bài viết đầu tiên: ReactJS

Trong bài viết đầu tiên này, chúng ta sẽ khám phá ReactJS – một thư viện JavaScript mạnh mẽ, phổ biến để xây dựng giao diện người dùng (UI). React không chỉ là công cụ quen thuộc của front-end developer, mà còn là chủ đề thường xuyên xuất hiện trong các buổi phỏng vấn.

 

Câu hỏi lý thuyết

  • ReactJS là gì? Tại sao nên sử dụng ReactJS thay vì các framework khác như Angular hoặc Vue?

Answer: ReactJS là một thư viện JavaScript mã nguồn mở được phát triển bởi Facebook (2013), dùng để xây dựng giao diện người dùng (UI) dựa trên các components. React cho phép phát triển các ứng dụng web động, hiệu năng cao và dễ quản lý thông qua cách tiếp cận dựa trên trạng thái (state).

ReactJS là một thư viện chứ không phải framework toàn diện như Angular. Do đó, nó linh hoạt hơn, cho phép tự do tích hợp các công cụ hoặc thư viện khác như Redux, React Router, NextJS,…

Bonus, Follow-up câu trước: À anh có nghe em nói về web động, vậy web động là gì? Nó khác gì web tĩnh? Khi nào sử dụng web động, khi nào sử dụng web tĩnh?

Web động là các trang web có nội dung được tạo ra hoặc cập nhật một cách tự động dựa trên tương tác của người dùng hoặc dữ liệu từ cơ sở dữ liệu. Nội dung của web động thay đổi theo thời gian thực mà không cần phải chỉnh sửa thủ công.

Ví dụ: Một trang thương mại điện tử hiển thị sản phẩm khác nhau tùy thuộc vào người dùng đang tìm kiếm gì.

Web tĩnh là các trang web có nội dung cố định, được viết sẵn bằng HTML, CSS và JavaScript. Khi người dùng truy cập, nội dung của trang web không thay đổi trừ khi chỉnh sửa mã nguồn và cập nhật lại.

  • Ví dụ: Trang giới thiệu doanh nghiệp (About Us), Trang 1 bài viết trong mục blog, Trang FAQs,…

Nói chung thì web động là HTML, CSS, JavaScript + Backend (PHP, Node.js, Python Django/ Flask, v.v.)

  • Component trong React là gì? Phân biệt giữa Class Component và Functional Component.

Component là một thành phần nhỏ độc lập và có thể tái sử dụng trong React. Nó đại diện cho một phần của giao diện người dùng (UI) và quản lý logic, trạng thái, và hiển thị riêng của nó. Một ứng dụng React thường được xây dựng bằng cách kết hợp nhiều component lại với nhau.

Ví dụ, trong một trang web, có thể tạo các component riêng biệt cho header, footer, hoặc form.

Class Component: Sử dụng trong các dự án cũ hoặc khi cần tận dụng các lifecycle methods truyền thống. Thường phù hợp nếu đang làm việc với một codebase đã viết trước khi React Hooks được giới thiệu.

Functional Component: Là tiêu chuẩn hiện nay, nhờ sự hỗ trợ của React Hooks. Nên sử dụng trong các dự án mới vì cú pháp đơn giản, nhẹ, và hiệu năng tốt hơn. React khuyến khích chuyển đổi từ Class Component sang Functional Component trong các phiên bản mới, vì React Hooks đã làm giảm sự cần thiết của Class Component.

import React, { useState } from 'react';

function Greeting() {
  const [name, setName] = useState('mintrishere.com');

  return <h1>Xin chào, {name}!</h1>;
}

export default Greeting;

//Ket qua: >Xin chào, mintrishere.com!
  • Props và State khác nhau như thế nào trong React? Khi nào nên sử dụng chúng?

Props là viết tắt của “Properties”. Đây là một cơ chế để truyền dữ liệu từ component cha xuống component con. Props là immutable, tức là không thể thay đổi được trong component con. Component con chỉ có thể đọc và hiển thị dữ liệu từ props, chứ không thể sửa đổi nó. Props thường được sử dụng khi chúng ta cần truyền dữ liệu để cấu hình hoặc hiển thị thông tin trong component con.

Ví dụ: Người dùng có một danh sách sản phẩm và muốn hiển thị mỗi sản phẩm dưới dạng một card. Dữ liệu của sản phẩm (như tên, giá, hình ảnh, mô tả) sẽ được truyền từ component cha (ProductList) xuống các component con (ProductCard) qua Props.

//Product Card
export default function ProductCard({ product }) {
  return ( 
  <img
        src={product.image}
        alt={product.name}
        style={{ width: "100%", height: "150px", objectFit: "cover" }}
      />
      <h3>{product.name}</h3>
      <p>{product.description}</p>
      <h4>${product.price}</h4>
      <button style={{ padding: "5px 10px", background: "#007bff", color: "#fff", border: "none", borderRadius: "3px", cursor: "pointer" }}>
        Thêm vào giỏ hàng
      </button>
    </div>
  );
}
import ProductCard from "./ProductCard"; // Component con

function ProductList() {
  // Dữ liệu danh sách sản phẩm
  const products = [
    {
      id: 1,
      name: "Laptop Dell XPS 13",
      price: 2400,
      image: "https://via.placeholder.com/150",
      description: "Mỏng nhẹ, hiệu năng cao.",
    },
    {
      id: 2,
      name: "Tai nghe Sony WH-1000XM5",
      price: 350,
      image: "https://via.placeholder.com/150",
    }]
  return (
    <div>
      <h1>Danh sách sản phẩm</h1>
      <div style={{ display: "flex", gap: "20px" }}>
        {products.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}

export default ProductList;

State: State là nơi lưu trữ dữ liệu nội tại của component. Đây là dữ liệu có thể thay đổi trong vòng đời của component. State là mutable, tức là có thể thay đổi bằng cách sử dụng hàm setState (class component) hoặc hook useState (functional component). State được sử dụng khi chúng ta cần quản lý dữ liệu động, như trạng thái người dùng nhập liệu, dữ liệu thay đổi từ API, hoặc trạng thái tương tác (như bật/tắt, click).

Khi nào sử dụng: Sử dụng Props khi dữ liệu cần được truyền từ component cha xuống và không cần thay đổi trong component con. Sử dụng State khi dữ liệu cần được quản lý và thay đổi trong chính component

  • Hãy giải thích cách sử dụng useStateuseEffect. Cho ví dụ thực tế trong dự án đã thực hiện.

useState là một hook giúp ta quản lý trạng thái trong functional components. Ta có thể khai báo một state trong component mà không cần phải sử dụng class. Ví dụ, ở nút Back to Home, useState giúp lưu trữ và cập nhật trạng thái của việc hiển thị nút và vị trí cuộn trước đó. Cụ thể:

  const [visible, setVisible] = useState(false);
  const [prevScrollPos, setPrevScrollPos] = useState(0);

Nút sẽ không hiển thị khi mới bắt đầu. Khi người dùng cuộn lên, setVisible(true) được gọi để hiển thị nút, và khi cuộn xuống, setVisible(false) ẩn nút đi. prevScrollPos giúp lưu trữ vị trí cuộn của trang trước đó, để so sánh với vị trí cuộn hiện tại và quyết định có hiển thị nút “scroll to top”.

Về useEffect, hook này dùng để thực hiện các tác vụ có side effects như gọi API, lắng nghe sự kiện hoặc cập nhật DOM. Trong dự án, ta có thể sử dụng useEffect để gọi API, lấy danh sách sản phẩm, nút back to home,… Ta thiết lập hook này để chạy khi component render lần đầu hoặc khi bộ lọc thay đổi.

useEffect(() => {
  const handleScroll = () => {
    const currentScrollPos = window.pageYOffset;

    if (currentScrollPos < prevScrollPos && currentScrollPos > 100) {
      setVisible(true);
    } else {
      setVisible(false);
    }

    setPrevScrollPos(currentScrollPos);
  };

  window.addEventListener('scroll', handleScroll);

  return () => window.removeEventListener('scroll', handleScroll);
}, [prevScrollPos]);

Mỗi khi người dùng cuộn lên (tức là currentScrollPos < prevScrollPos), và vị trí cuộn vượt quá 100px, setVisible(true) được gọi để hiển thị nút “scroll to top”. Sau mỗi lần xử lý sự kiện cuộn, ta cập nhật prevScrollPos với giá trị cuộn hiện tại để dùng cho lần cuộn tiếp theo. Ta sử dụng hàm return trong useEffect để dọn dẹp sự kiện khi component bị unmount hoặc khi prevScrollPos thay đổi. Điều này giúp tránh việc sự kiện cuộn tiếp tục chạy sau khi component không còn tồn tại trong DOM.

  • Lifting State Up là gì? Tại sao chúng ta cần thực hiện điều này trong React? Cho ví dụ thực tế.

Lifting State Up trong React là quá trình di chuyển state từ một hoặc nhiều component con lên component cha của chúng. Điều này giúp chia sẻ dữ liệu hoặc trạng thái giữa các component con thông qua component cha, tạo ra một nguồn sự thật duy nhất (single source of truth).

Sử dụng Lifting State Up khi:

  1. Đồng bộ dữ liệu: Khi nhiều component cần dùng chung một trạng thái hoặc dữ liệu, ta cần lưu trữ state ở component cha để đảm bảo mọi component con đều truy cập cùng một giá trị.
  2. Giảm phức tạp: Việc chia sẻ state trực tiếp giữa các component con thường phức tạp, trong khi dùng component cha làm trung gian sẽ giúp quản lý dễ dàng hơn.
  3. Tính tái sử dụng: Component con chỉ nhận dữ liệu từ props mà không cần tự quản lý state, giúp chúng dễ tái sử dụng.

Có hai component: Component cha quản lý state của giỏ hàng và chia sẻ nó với các component con.

  1. ProductList: Hiển thị danh sách sản phẩm và nút “Add to Cart”.
  2. Cart: Hiển thị danh sách sản phẩm đã thêm vào giỏ hàng.

Trong ví dụ này, component cha EcommerceApp chịu trách nhiệm quản lý state cart để lưu danh sách sản phẩm được thêm vào giỏ hàng, đồng thời cung cấp hàm addToCart để cập nhật trạng thái giỏ hàng. Component con ProductList nhận hàm addToCart qua props và gọi hàm này khi người dùng nhấn nút “Add to Cart”. Component con Cart nhận danh sách giỏ hàng (cart) qua props và hiển thị nội dung tương ứng. Đây là một trường hợp của Lifting State Up vì trạng thái cart được lưu trữ tại component cha (EcommerceApp), từ đó chia sẻ xuống cả hai component con (ProductListCart) thông qua props. Điều này đảm bảo dữ liệu luôn đồng bộ giữa các thành phần trong ứng dụng.

import React, { useState } from "react";

function EcommerceApp() {
  const [cart, setCart] = useState([]);

  const addToCart = (product) => {
    setCart((prevCart) => [...prevCart, product]);
  };

  return (
    <div>
      <h1>E-Commerce App</h1>
      <ProductList addToCart={addToCart} />
      <Cart cart={cart} />
    </div>
  );
}

const ProductList = ({ addToCart }) => {
  const products = [
    { id: 1, name: "Laptop", price: 1000 },
    { id: 2, name: "Phone", price: 500 },
  ];

  return (
    <div>
      <h2>Products</h2>
      {products.map((product) => (
        <div key={product.id}>
          {product.name} - ${product.price}
          <button onClick={() => addToCart(product)}>Add to Cart</button>
        </div>
      ))}
    </div>
  );
};

const Cart = ({ cart }) => (
  <div>
    <h2>Cart</h2>
    {cart.length === 0 ? (
      <p>No items in cart</p>
    ) : (
      <ul>
        {cart.map((item, index) => (
          <li key={index}>
            {item.name} - ${item.price}
          </li>
        ))}
      </ul>
    )}
  </div>
);

export default EcommerceApp;
  • Hãy giải thích khái niệm “one-way data binding” trong React. Tại sao React chọn phương pháp này? One-way data binding khác gì lifting state up?

One-way data binding trong React là khái niệm dữ liệu chỉ di chuyển theo một hướng: từ component cha xuống component con thông qua props. Điều này có nghĩa là trạng thái hoặc dữ liệu được quản lý trong component cha và được truyền xuống các component con để hiển thị hoặc xử lý.

Với one-way data binding, trạng thái chỉ được quản lý tập trung tại một nơi (thường là component cha hoặc qua state management như Redux). Điều này giảm nguy cơ xảy ra lỗi khi nhiều component cố gắng cập nhật cùng một trạng thái. React ưu tiên one-way data binding vì sự đơn giản và dễ bảo trì, đồng thời vẫn cho phép thực hiện two-way data binding khi cần thông qua các hàm callback.

One-way Data Bindingmột nguyên tắc tổng quát mà React luôn áp dụng. Dữ liệu chỉ đi từ cha xuống con qua props. Component con không có quyền thay đổi trực tiếp trạng thái của component cha mà chỉ sử dụng dữ liệu để hiển thị hoặc xử lý.

Lifting State Upmột kỹ thuật cụ thể để giải quyết vấn đề chia sẻ dữ liệu giữa các component. Dữ liệu được di chuyển từ con lên cha, sau đó component cha chia sẻ lại dữ liệu với các component con khác. Điều này xảy ra khi các component con cần sử dụng chung một trạng thái và không thể tự quản lý độc lập.

One-way Data Binding đảm bảo rằng luồng dữ liệu trong ứng dụng e-commerce luôn từ cha xuống con, ví dụ: danh sách sản phẩm từ component cha ProductList xuống từng ProductCard. Trong khi đó, Lifting State Up sẽ được sử dụng nếu cần các component như ProductCardCart chia sẻ cùng trạng thái giỏ hàng thông qua component cha EcommerceApp.

  • JSX là gì? Nó khác gì với JavaScript thông thường?

JSX (JavaScript XML) là một cú pháp mở rộng của JavaScript, được sử dụng trong React để mô tả giao diện người dùng một cách trực quan và dễ đọc. JSX cho phép viết HTML trực tiếp trong JavaScript, nhưng thực chất, nó được biên dịch thành các lệnh JavaScript thông qua Babel (hoặc các công cụ tương tự) để tạo nên giao diện.

// JSX: Có thể nhúng các biểu thức JavaScript bằng cách sử dụng {}.
const name = 'John';
const element = <h1>Hello, {name}!</h1>;
// JavaScript thông thường: Biểu thức phải được truyền thủ công vào hàm React.createElement.
const name = 'John';
const element = React.createElement('h1', null, `Hello, ${name}!`);
// JSX: Sử dụng cú pháp giống HTML, nhưng có một số điểm khác biệt như className thay cho class, htmlFor thay cho for.
<div className="container" htmlFor="input-id"></div>
  • DOM là gì? Virtual DOM là gì? Tại sao nó lại quan trọng trong React?

DOM (Document Object Model) là một cấu trúc dữ liệu dạng cây, đại diện cho toàn bộ nội dung và cấu trúc của một trang web. Nó cho phép JavaScript tương tác, truy cập, và thay đổi nội dung hoặc giao diện của trang web.

<div id="app">
  <h1>Hello, world!</h1>
</div>
/*
- HTML
  - body
    - div (id="app")
      - h1
        - "Hello, world!"

Mỗi lần DOM được thay đổi, trình duyệt phải cập nhật, tính toán lại bố cục (reflow) và vẽ lại giao diện (repaint), điều này gây tốn tài nguyên và thời gian.

Virtual DOM là một khái niệm được React sử dụng để cải thiện hiệu suất khi thao tác với giao diện. Nó là một bản sao ảo của DOM thực, được lưu trong bộ nhớ và chỉ tồn tại trong JavaScript.

  1. Tạo bản sao ảo: Khi giao diện thay đổi, React sẽ cập nhật Virtual DOM thay vì thao tác trực tiếp với DOM thật.
  2. So sánh (Diffing): React so sánh Virtual DOM mới với phiên bản trước đó để tìm ra các thay đổi (diff).
  3. Cập nhật tối ưu (Reconciliation): Chỉ những phần thực sự thay đổi được áp dụng lên DOM thật, giúp giảm thiểu số lần thao tác DOM.

Thay vì cập nhật toàn bộ DOM, React chỉ cập nhật những phần thực sự thay đổi. Điều này giảm thiểu số lần reflow và repaint, giúp ứng dụng nhanh hơn, đặc biệt khi giao diện phức tạp. React cho phép tập trung vào việc mô tả giao diện dựa trên trạng thái (state) mà không cần lo lắng về cách DOM được cập nhật. Với Virtual DOM, React tự động xử lý logic cập nhật giao diện, giảm thiểu lỗi liên quan đến việc thao tác DOM thủ công.

  • React Router là gì? Làm thế nào để tạo một ứng dụng đa trang trong React?
  • Controlled Component và Uncontrolled Component khác nhau như thế nào? Cho ví dụ.

Controlled Component: Là các component mà dữ liệu của chúng được kiểm soát bởi state của React. Input trong Controlled Component không lưu giá trị của nó bên trong DOM, mà giá trị được quản lý thông qua state và cập nhật thông qua các sự kiện. React chịu trách nhiệm lưu trữ và cập nhật giá trị của input.

Uncontrolled Component: Là các component mà dữ liệu của chúng được kiểm soát trực tiếp bởi DOM. React không kiểm soát giá trị của input; thay vào đó, sử dụng một ref để truy xuất giá trị từ DOM khi cần thiết. Giá trị input được lưu trực tiếp trong DOM thay vì state của React.

Tiêu chíControlled ComponentUncontrolled Component
Quản lý dữ liệuDữ liệu được quản lý thông qua state trong React.Dữ liệu được lưu trữ trực tiếp trong DOM.
Truy cập giá trịSử dụng state và các hàm cập nhật state (setState).Sử dụng ref để truy cập giá trị DOM.
Độ phức tạpPhức tạp hơn vì cần quản lý state.Đơn giản hơn, không cần quản lý state.
Trường hợp sử dụngKhi cần kiểm soát chặt chẽ giá trị input, ví dụ: form validation, xử lý phức tạp.Khi chỉ cần lấy dữ liệu input một lần hoặc xử lý đơn giản.

Ví dụ thực tế sử dụng Uncontrolled Component: Giả sử đang xây dựng một form đăng ký nhận bản tin (newsletter subscription form). Trong trường hợp này, chỉ cần lấy dữ liệu từ người dùng khi họ nhấn nút “Submit”, mà không cần theo dõi giá trị input theo thời gian thực. Đây là một trường hợp phù hợp để sử dụng Uncontrolled Component.

import React, { useRef } from "react";

function NewsletterForm() {
  const emailRef = useRef(); // Tạo ref để truy cập giá trị input
  const nameRef = useRef(); // Ref để lấy giá trị tên

  const handleSubmit = (e) => {
    e.preventDefault();
    const email = emailRef.current.value; // Lấy giá trị email từ DOM
    const name = nameRef.current.value; // Lấy giá trị name từ DOM
    if (email && name) {
      console.log("Subscribed with:", { name, email });
      alert(`Thank you for subscribing, ${name}!`);
    } else {
      alert("Please fill in all fields.");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          Name:
          <input type="text" ref={nameRef} placeholder="Enter your name" />
        </label>
      </div>
      <div>
        <label>
          Email:
          <input type="email" ref={emailRef} placeholder="Enter your email" />
        </label>
      </div>
      <button type="submit">Subscribe</button>
    </form>
  );
}

export default NewsletterForm;
  • Key trong React là gì? Tại sao cần sử dụng key khi render danh sách?
  • Hàm render() trong React hoạt động như thế nào? Khi nào nó được gọi?
  • Lifecycle Methods trong Class Component là gì? Liệt kê các giai đoạn chính.
  • Context API là gì? Nó giải quyết vấn đề gì trong React?
  • Sự khác biệt giữa useEffect với các phương thức lifecycle trong Class Component là gì?
  • Fragments trong React là gì? Tại sao nên sử dụng React.Fragment thay vì một thẻ div?

React Fragments là một tính năng trong React cho phép nhóm nhiều phần tử con lại với nhau mà không thêm bất kỳ thẻ DOM nào dư thừa vào kết quả render. Fragment không xuất hiện trong DOM thực, giúp tạo ra một cây DOM gọn gàng hơn.

//Dạng đầy đủ
<React.Fragment>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</React.Fragment>
//Dạng rút gọn
<>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</>

Tại sao nên sử dụng React.Fragment thay vì thẻ <div>

  1. Tránh thẻ DOM dư thừa: Khi sử dụng <div> để bọc các phần tử con, thẻ <div> sẽ được thêm vào DOM thực, dẫn đến cây DOM phức tạp hơn, đặc biệt khi có nhiều tầng lồng nhau.
  2. Việc thêm các thẻ DOM không cần thiết có thể làm tăng chi phí render và làm ứng dụng chậm hơn, đặc biệt khi số lượng phần tử lớn.
  3. Hỗ trợ các thuộc tính (nếu dùng React.Fragment): Fragment có thể nhận các thuộc tính như key (thường dùng trong danh sách động):
function Items({ data }) {
  return data.map((item) => (
    <React.Fragment key={item.id}>
      <h3>{item.title}</h3>
      <p>{item.description}</p>
    </React.Fragment>
  ));
}
  • Làm thế nào để tối ưu hiệu năng của một ứng dụng React? Đề cập đến memoization hoặc React.memo.
  • Redux là gì? Khi nào nên sử dụng Redux trong một ứng dụng React?
  • Error Boundaries là gì? Làm thế nào để sử dụng chúng để quản lý lỗi trong React?
  • Sự khác nhau giữa useMemouseCallback? Khi nào nên dùng mỗi cái?
  • React Fiber là gì, và nó cải thiện hiệu suất React như thế nào?
  • Compound Components là gì, và khi nào nên sử dụng?
  • Làm thế nào để sử dụng dynamic import với React để tối ưu hóa tải trang?
  • Render Props khác gì với Higher Order Components (HOC)? Khi nào nên chọn cái nào?
  • Prop drilling là gì, và làm thế nào để tránh vấn đề này?

Câu hỏi tình huống

Bạn có một danh sách lớn (hàng nghìn mục) cần hiển thị trên giao diện người dùng. Khi người dùng cuộn, ứng dụng bị giật lag. Bạn sẽ xử lý vấn đề này như thế nào?

Ứng dụng của bạn có nhiều component lồng nhau, và một state cần được chia sẻ giữa các component sâu bên trong. Làm thế nào bạn quản lý state trong trường hợp này?

Một component cha đang truyền props xuống cho nhiều component con. Khi cha re-render, tất cả các component con cũng bị re-render dù không có thay đổi. Bạn sẽ làm gì để tối ưu hóa?

Người dùng báo cáo rằng khi một API gọi bị lỗi, giao diện hiển thị một màn hình trắng và không có thông báo lỗi. Làm thế nào để cải thiện trải nghiệm người dùng trong trường hợp này?

Bạn cần hiển thị dữ liệu từ một API RESTful và đảm bảo rằng giao diện người dùng không bị đứng khi chờ API trả về dữ liệu. Bạn sẽ xử lý điều này như thế nào?

Ứng dụng của bạn cần xử lý dữ liệu người dùng nhạy cảm và bảo vệ chống lại các lỗ hổng bảo mật phổ biến như XSS (Cross-Site Scripting). Bạn sẽ thực hiện các bước nào?

Bạn cần xây dựng một bảng dữ liệu có thể sắp xếp, tìm kiếm và phân trang. Bạn sẽ thiết kế và tối ưu hóa component này như thế nào?

Có thể bạn sẽ thích

Để lại Bình luận