Dưới đây là các hướng tiếp cận chưa hợp lý, thường thấy nhất trong các ứng dụng React và cách khắc phục chúng. Những điều này sẽ khiến code của bạn trở thành cơn ác mộng nếu bạn không nhận ra và ngăn chặn chúng ngay từ đầu.

Tống mọi thứ vào Redux

Redux rất đỉnh. Nó cho phép chúng ta dễ dàng sử dụng các global state của ứng dụng.

Vấn đề là một khi một người mới học Redux, họ bắt đầu sử dụng nó như một phép màu để giải quyết tất cả các vấn đề của họ.

Sẽ có những hạn chế khi bạn đưa tất cả mọi thứ vào Redux như:

  • Nếu mọi thứ đều có trong Redux, thì không rõ các state trong code của bạn là có phạm vi cục bộ hay toàn cầu. Việc thực hiện thay đổi sẽ phức tạp hơn vì bạn không tự tin về các phần khác của ứng dụng sẽ bị ảnh hưởng hay không.
  • Hiệu suất sẽ giảm khi bạn sử dụng Redux cho các sự kiện thường xuyên, như là theo dõi các form input. Vì Redux ảnh hưởng đến trạng thái toàn cục của ứng dụng nên nó sẽ gây ra nhiều re-renders.

Quy tắc chung: Chỉ sử dụng Redux cho dữ liệu toàn cầu thực sự, như với một phiên làm việc của user hoặc với các chủ đề chính của app. Đối với bất kỳ điều gì khác, thay vào đó, nên tạo context cho các phần cụ thể của app.

Lưu trữ mọi thứ như một state

Rất nhiều trường hợp chúng ta có thể tính toán được các biến số mà không cần phải lưu vào state. Ví dụ bạn có list các mục check box. Bạn không cần phải lưu một biến checkedCount vào state. Bạn có thể lấy checkedCount bằng cách lặp qua mảng các item đó và lọc ra các các item đã checked.

Nguyên tắc chung: trước khi lưu một biến vào state, tự hỏi lại xem: "Bằng cách nào đó, có thể lấy biến này dựa trên dữ liệu khác mà đã được lưu trữ không?"

Truyền props xuống bằng cách sử dụng spread operator ở mọi nơi

Mẹo này đã được sử dụng rất nhiều trong các app React.

Bạn có thể truyền props xuống component con bằng cách sử dụng {...props}. Nhìn rất gọn gàng và nó làm code của chúng ta ngắn hơn hơn nhiều đúng không? Nhưng sự thật là chỉ một thời gian sau thì code của bạn sẽ rất khó để có thể đoán và hiểu được.

Bạn không thể nhìn ra được component con thực sự cần prop nào. Việc refactor trở lên khó khăn hơn rất nhiều. Thậm chí sau khi refactor không may nó còn gây cho bạn thêm một đống lỗi nữa.

Nguyên tắc: Tránh truyền props xuống bằng cách sử dụng spread operator

Khai báo component bên trong một component khác

Khai báo component bên trong một component khác trông như thế này:

import { useState } from "react";

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

    const InnerComponent = () => {
        return <p>Hello world {count}</p>;
    };

    return (
        <div>
            <InnerComponent />
        </div>
    );
    }

Đây là một ý tưởng tồi vì hai lý do sau:

  • Code của bạn sẽ không linh động. Component con sẽ bị phụ thuộc vào scope của component cha.
  • Hiệu năng bị giảm. Component cha sẽ phải khởi tạo lại các function của component con mỗi khi render.

Nguyên tắc:
Không khai báo các component bên trong cha mẹ của chúng.

Truyền quá nhiều thông tin cho các components

Không cần phải quá hà tiện trong việc viết component. Hãy cố gắng tách biệt các state full và state less component.

State less là những component chỉ có output duy nhất là HTML. Chúng không chứa các state và cũng không xử lý một logic nào cả.

State full là những component có quản lý các state và cung cấp dữ liệu, hành vi cho các state less component bằng cách thực hiện các API requests, redux, v.v.

Với các state less component, bạn chỉ nên truyền những data cần thiết cho việc render. Không nên giao những logic như có hiển thị hay là không cho nó. Thay vào đó hãy giao cho các state full component.

Ví dụ đoạn code sau:

import { useSelector } from "react-redux";
import { getUsers, getCurrentUser } from ".selector/users";

function StateLessComponent({ user, currentUser }) {
    const userFound = users.find((item) => item.id === currentUser.id);

    if (!userFound) return null;
    return (
        <div>
            <p>Welcome!</p>
        </div>
        );
}

function StateFullComponent() {
    const user = userSelector(getUsers);
    const currentUser = userSelector(getCurrentUser);

    return (
        <div>
            <StateLessComponent user={users} currentUser={currentUser} />
        </div>
);
}

Khi bạn nhìn vào component cha, Không thể biết được component con của nó có chứa logic điều kiện render. Chúng ta có thể làm rõ đoạn này bằng cách đặt lại logic có điều kiện render cho các component cha quyết định xem có hiển thị con của nó hay không.

import { useSelector } from "react-redux";
import { getUsers, getCurrentUser } from ".selector/users";

function StateLessComponent({ user, currentUser }) {
    return (
        <div>
            <p>Welcome!</p>
        </div>
        );
}

function StateFullComponent() {
    const user = userSelector(getUsers);
    const currentUser = userSelector(getCurrentUser);
        const userFound = users.find((item) => item.id === currentUser.id);
    return <div>{userFound && <StateLessComponent />}</div>
}

Khi có thể, chỉ truyền các biến nguyên thủy cho các state less component. Làm như vậy sẽ đơn giản hóa việc tối ưu hiệu suất của chúng sau này. Giả sử bạn đang chuyển toàn bộ object user như vậy:

function StateLessComponent({ user }) {
    return (
        <div>
            <p>
                Welcome {user.firstName} {user.LastName}
            </p>
            <p>Your last login was on {user.date}</p>
        </div>
    );
}

Thay vào đó, bạn có thể chuyển tên, họ và ngày của user:

function StateLessComponent({ first, last, date }) {
    return (
        <div>
            <p>
                Welcome! {`${first} ${last}`}
            </p>
            <p>Your last login was on {date}</p>
        </div>
    );
}

Điều này giúp giảm số lần hiển thị lại khi dùng React.memo. Lý do là React so sánh các prop dựa trên tham chiếu, trong khi các biến nguyên thủy được so sánh dựa trên tham trị.

Tóm lại, đây là các vấn đề khi chuyển quá nhiều thông tin đến các component:

  • Khó phân biệt giữa các component có xử lý logic và các component chỉ phụ trách hiển thị.
  • Hiệu suất kém đi. Khi bạn chuyển quá nhiều prop cho các component, nó sẽ bị hiển thị lại mỗi khi các prop đó thay đổi, dẫn đến render thừa.

Tối ưu hiệu suất quá mức

Đôi khi người ta bắt đầu tối ưu hóa code của họ trước khi có bất kỳ vấn đề thực sự nào. Điều đó thực sự không tốt vì hai lý do đơn giản:

  • Cố gắng giải quyết vấn đề trước khi có một vấn đề thì chắc chắn sẽ làm code của bạn phức tạp thêm.
  • Lãng phí thời gian. Thay vào đó, bạn có thể xây dựng các tính năng mới và giải quyết các vấn đề quan trọng khác.

Theo kinh nghiệm cá nhân, việc tách biệt, phân chia các component một cách hợp lý sẽ giải quyết được ~ 90% các vấn đề về hiệu suất trong các ứng dụng React.

Cây Component quá dài, phức tạp

Thông thường, vấn đề này phát sinh khi bạn chưa phân tách các component trong ứng dụng của mình triệt để.

Ví dụ hãy xem component này:

import { useSelector } from "react-redux";
import { userHistory } from "react-router-dom";
import { getUsers, getCurrentUser } from ".selector/users";
import { trackAcction } from "./tracker";
import { Avatar } from "./UserAvatar";

function LargeComponentTree({ user, isAuthenticated, avatar }) {
    const history = useHistory();

    return (
        <div>
            {isAuthenticated && avatar && !avatar.isExpired && <Avatar />}
            {isAuthenticated ? (
                <>
                    <p>Welcome! {`${user.first} ${user.last}`} </p>
                    <p>Your last login was on {user.date}</p>
                </>
            ) : null}
            <div>
                <button
                    onClick={() => {
                        history.push("/settings");
                        trackAction("click", "Settings button");
                    }}
                >
                Settings
                </button>
                <button
                    onClick={() => {
                        history.push("/my_page);
                        trackAction("click", "My page button");
                    }}
                >
                    My Page
                </button>
                <button
                    onClick={() => {
                        history.push("/logout");
                        trackAction("click", "Logout button");
                    }}
                >
                Logout
                </button>
            </div>
        </div>
    );

Nhìn hơi choáng đúng không? Quá khó để đọc xem đoạn code trên viết cái gì. Ở đoạn code trên chúng ta cần cải thiện những vấn đề sau:

  • Cấu trúc lại các câu lệnh điều kiện dài thành các biến riêng biệt.
  • Chia nhỏ component ra.
  • Đưa các arrow function inline ra khỏi component

Hãy áp dụng những điều này và xem component trông như thế nào:

import { userHistory } from "react-router-dom";
import { trackAcction } from "./tracker";
import { Avatar } from "./UserAvatar";

const  Greeting = ({ first, last, date }) => {
    return (
        <div>
            <p>Welcome! {`${first} ${last}`}</p>
            <p>Your last login was on {date}</p>
        </div>
    );
};

function LargeComponentTree({ user, isAuthenticated, avatar }) {
    const history = useHistory();

    const showAvartar = isAuthenticated && avatar && !avatar.isExpired;

    const handleClick = (route, acctionName) => {
        return () => {
            hisory.push(route);
            trackAction("click", actionName);
        };
    };

    return (
        <div>
            {showAvatar && <Avartar />}
            {isAuthenticated ? (
                <Greeting first={user.first} last={user.last} date={user.date} />
            ) : null}
            <div>
                <button onClick={handleClick("/settings", "Setting button")}>Settings</button>
                <button onClick={handleClick("/my_page", "My page button")}>My Page</button>
                <button onClick={handleClick("/logout", "Logout button")}>Logout</button>
            </div>
        </div>
    );

Cây component này trông đã đẹp và dễ hiểu hơn nhiều rồi phải không?

Kết Luận

Trong bài viết này chúng ta đã đề cập đến các cách thiết kế chưa hợp lý (anti-partterns) thường thấy trong các ứng dụng react và các cách để tránh xa chúng. Tránh được những vấn đề này ngay từ đầu, bạn sẽ không cần mất nhiều thời gian để refactor lại code của mình trong tương lai.

Bài viết được dịch lại từ link.