+8

[Javascript] Concept cần nắm vững trước khi học React

Có thể bạn đã, React là một thư viện dùng để tạo ra các UI components có thể được sử dụng đề làm base của ứng dụng web và mobile. Điểm khác biệt của React với một số đối thủ cạnh tranh là đoạn mã của nó được viết hoàn toàn bằng Javascript. Ngay cả các đoạn HTML cũng có thể viết bằng JS bằng cách sử dụng JSX, là một phần mở rộng của JS để có thể cấu trúc các UI components.
Bài viết này nhằm mục đích giúp cho những ai đang có ý định làm quen với React thì trước hết phải thực sự thành thạo những concept hơn là việc đi luôn vào React mà bỏ qua chúng. Trước hết hay nhớ một điều rằng, React được xậy dựng dựa trên việc sử dụng các tính năng Javascript hiện đại, về cơ bản chủ yếu vẫn là cú pháp ES2015.

Logic có điều kiện với câu lệnh if, toán tử ba ngôi và toán tử logic

Những toán tử trên đã thực sự là một phần rất đỗi quen thuộc của Javascript. Trong React, chúng đặc biệt hữu dụng cho việc rendering các components.
Toán tử ba ngôi sẽ trông giống như:

const buttonLabel = playback === "stop" ? "play ▶️" : "stop ⏹️";

Không có gì nhiều để nói ở đây, về cơ bản, có thể coi đây là cách viết tắt của cú pháp dưới đây:

let buttonLabel;
if (playback === "stop") {
  buttonLabel = "play ▶️";
}
else {
  buttonLabel = "stop ⏹️"
}

Tất nhiên chúng ta có thể sử dụng câu lệnh if...else như trên, nhưng toán tử ba ngôi thường được lựa chọn cho việc bạn cần sử dụng biểu thức đơn dòng cho việc rendering các phần tử có điều kiện
Nếu không, bạn phải gọi một hàm nơi bạn đặt đoạn mả của mình để hỗ trợ cho việc rendering có điều kiện. Trong React, bạn cũng có thể sử dụng logic điều kiện phức tạp hơn(ví dụ:if...else) và lưu trữ các giá trị trên các biến có thể được sử dụng để rendering có điều kiện trong đoạn mã JSX.
Toán tử logic && hoặc || rất hữu dụng cho việc xây dựng các React components.

const isLoggedIn = true;
const userComponent = isLoggedIn && getUserComponent();

Trong ví dụ trên, toán hạng bên trái(isLoggedIn) so với &&true. Do đó, kết quả của toạn hạng bên phải(getUserComponent()) sẽ được gán luôn cho userComponent
Khái niệm này cũng rất hữu ích cho việc rendering có điều kiện trong Reacttrue && jsxComponent return jsxComponentfalse && jsxComponent return false. Nếu bạn trả về false, React sẽ bỏ qua nó và chỉ hiển thị không có gì.
Cũng có thể kết hợp nhiều điều kiện. Trong ví dụ tiếp theo, kết quả của getOtherUsersComponent() được trả về khi cả hai điều kiện được đáp ứng.

const otherUsers = isLoggedIn && users?.length > 0 && getOtherUsersComponent();

Chú ý, toán tử ? ở chỗ users?.length > 0. Đây là optional chaining, hạn chế sử dụng nó trong các dự án React nhé.
Nếu bạn trả về null, React sẽ không render bất cứ thứ gì, ngược lại với undefined, null đại diện cho việc giá trị đó có, nhưng tạm thời đang vắng mặt.

if (shouldRenderComponent()) {
  return getComponent();
}
else {
  return null;
}

Điều này rất hữu ích để ngăn các components được render ra.

Object literals và inline functions

Có tương đối nhiều cách để tạo ra objects. Khởi tạo các object với literal sẽ trông như sau:

const foo = { bar: 3, hello: "world" };

Ký hiệu này thường được sử dụng trong các dự án React để tạo các object mà không cần gán chúng cho một biến. Ví dụ cho việc khợi tạo trạng thái ban đàu của useReducer.

// 2nd function argument uses inline object literal
foo("bar", { hello: "world" })

Với cú pháp ES2015, bạn cũng có thể sử dụng shorthand propertiesmethod names.

function foo(id) {
  return {
    name: "dummy",
    id: id,
    bar: function() {
      console.log("bar");
    }
  }
}

// thay vào đó bạn có thể làm như sau
function foo(id) {
  return {
    name: "dummy",
    id, // shorthand property name
    bar() { // shorthand method name
      console.log("bar");
    }
  }
}

Shorthand properties đặc biệt được sử dụng khắp mọ nơi trong quá trình phán triển React vì đơn giản chúng giúp chúng ta loại bỏ những đoạn mã thừa.
Do đó bạn phải nắm được sự khác biệt giữa object literal và một biến của object
Trong một số trường hợp, vì mục đích tối ưu hóa hiệu suất React, bạn nên tránh chuyển các object literal cho các React components bởi bì một object mới sẽ được tạo mỗi lần, gây ra re-render không cần thiết.
Tương tự cũng áp dụng cho cách anonymous functions (ví dụ như inline functions), điều này nên tránh trong một số trường hợp tăng hiệu suất React app

// inline function
foo(() => {console.log("bar")});

// hãy truyền biến trỏ tới hàm
const barFunc = () => console.log("bar");
foo(barFunc);

Template literals

Template literals, hay templates strings, được giới thiệu trong ES2015 cho phép tạo mội chuỗi với các biểu thức Javascriptđược nhúng bên trong. Trong dấu gạch ngược, bạn chó thể kết hợp các chuỗi mã hardcoded với các biểu thức Javascript bên trong ${}.

const name = "pa st";
console.log(`Hello, my name is ${name}`); // Hello, my name is pa st

Cũng có thể viết cách biểu thức phức tạp hơn, kiểu inline calculations,hay gọi một functions

const name = "pa st";
const getRandomIndex = max =>  Math.floor(Math.random() * Math.floor(max))
const food = ["fish", "sandwich", "hamburger", "pizza", "cakes"];
const getFood = index => food[index]
console.log(`Hello, my name is ${name} 
and I'm hungry for ${getFood(getRandomIndex(food.length))}`);

Hello, my name is pa st 
and I'm hungry for pizza

Switch statement

Trong các ứng dụng React vừa và lớn, rất có thể chúng ta sẽ gặp phải câu lệnh switch để quản lý state giữa các components. Cũng tương tự như useReducer Hook hoặc Redux thường được sử dụng cho các tác vụ như vậy.
Ví dụ sau đây cho thấy một cái gọi là reducer function sử dụng câu lệnh switch để quản lý state. Bạn không nhất thiết phải sử dụng câu lệnh switch với một reducer, nhưng đó là thực sự là một "pattern tốt".

export default (state, action) => {
  switch (action.type) {
    case "TOGGLE_DARK_MODE":
      return {
        ...state,
        darkMode: action.darkMode,
      };
     case "UPDATE_PLAYBACK": {
      return {
        ...state,
        currentSound: action.currentSound,
      };
    }  
    default:
      return state;
  }
};

Ví dụ trên kiểm tra giá trị của action.type và thực thị mã của từng trường hợp.
Trong ví dụ trên, mọi mệnh đề case (và default) đều trả về một object mới, đại diện cho state React mới. Điều này đưa chúng ta đến một chủ đề quan trọng về phát triển React.

Object destructuring

Nguyên tác của object destructuring khá đơn giản. Với cú pháp đơn giản dưới đây, chugns ta có thể trích xuất các thuộc tính thành các biến.

const creatures = {
  human: ["men", "women", "children", "man", "woman"],
  supernatural: ["robot", "ai", "unknow", "demon", "ghost"]
};
const { human, supernatural } = creatures;

console.log(human); // ["men", "women", "children", "man", "woman"]
console.log(supernatural); // ["robot", "ai", "unknow", "demon", "ghost"]

Nếu bạn sử dụng phép gán mà không khai báo biến, lúc đó hãy sử dụng dấu ngoặc đơn.

const creatures = {
  human: ["men", "women", "children", "man", "woman"],
  supernatural: ["robot", "ai", "unknow", "demon", "ghost"]
};
let human, supernatural;
({human, supernatural} = creatures);

console.log(human); // ["men", "women", "children", "man", "woman"]
console.log(supernatural); // ["robot", "ai", "unknow", "demon", "ghost"]

Object destructuring cung cấp cho bạn cú pháp để tiết kiệm thêm các dòng code.

// you can do this
const { human, supernatural } = creatures;

// instead of
const human = creatures.human;
const supernatural = creatures.supernatural;

Trong bối cảnh của React, cấu trúc đối tượng thường được sử dụng với function parameters.

const infos = {name: "pa st", hobby: "React" };
function printInfos({name, hobby}) {
  console.log(name, hobby);
}
printInfos(infos);

const printName = ({name}) => console.log(name);
printName(infos);

Để rõ ràng hơn, React developers sử dụng pattern này với props là đầu vào cho các React components.

function MyReactComponent({name, age}) {
  // ...
}

Việc gán kết hợp với đổi tên các biến có thể hữu ích để tăng khả nặng đọc hiệu đoạn mã của bạn hơn hẳn.

const creatures = {
  human: ["men", "womem", "children"]
};
const { human: people  } = creatures;
console.log(people); // ["men", "women", "children"]

Bạn cũng có thể xác định các giá trị mặc định trong khi gói các fields vào một object. Dưới đây là một cách.

const { human: people = ["men"], supernatural = ["robot", "unknow"] } = {
  human: ["men", "women", "children"]
};
console.log(people); // ["men", "women", "children"]
console.log(supernatural); // ["robot", "unknow"]

Nesting cũng có thể được, nhưng điều này không khuyến khích, việc đọc code sẽ khó khăn hơn.

const creatures = {
  animals: {
    wildlife: ["lobster", "snake"],
    pet: ["dog", "cat"]
  },
  human: ["men", "women", "children"]
};
const { animals: { pet }} = creatures;
console.log(pet); //  ["dog", "cat"]

Array destructuring

Với sự trợ giúp của destructuring assignment, một mảng có thể được giải nén theo cách mà các giá trị của nó được trích xuất thành các biến riêng biệt, kiểu như thế này này:

const array = [1, 2];
const [varForVal1, varForVal2] = array;

console.log(varForVal1); // 1
console.log(varForVal2); // 2

Các biến sẽ được gán từ trái qua phải của một mảng, vì vậy thứ tự sẽ là:

const [fruit, veggie] = ["berry", "cauliflower", "pizza", "sandwich", "cake", "hamburger"];

console.log(fruit); // berry
console.log(veggie); // cauliflower

Cũng có thể bỏ qua các giá trị bằng cách sau:

const [fruit,,pizza,,,burger] = ["berry", "cauliflower", "pizza", "sandwich", "cake", "hamburger"];
console.log(fruit); // berry
console.log(pizza); // pizza
console.log(burger); // hamburger

Follow theo chatty code sau đây để có thể hiểu chính xác điều gì đã xảy ra:

const [
  fruit,
  /* skip entry 2 (cauliflower) */,
  pizza,
  /* skip entry 4 (sandwich) */,
  /* skip entry 5 (cake) */,
  burger] = ["berry", "cauliflower", "pizza", "sandwich", "cake", "hamburger"];

Bạn cũng có thể gán nhiều giá trị cùng một lúc với mẫu pattern còn lại sau:

const [fruit, veggie, ...junkfood] = ["berry", "cauliflower", "pizza", "sandwich", "cake", "humburger"];
console.log(fruit); // berry
console.log(veggie); // cauliflower
console.log(junkfood); // ["pizza", "sandwich", "cake", "hamburger"]

Array destructuring cũng cho phép gán các giá trị mặc địch, tất nhiên cũng có thể áp dụng mẫu pattern này với lệch gọi hàm.

const getFood = () => ["berry", "cauliflower"];
const [fruit, veggie, junkfood = "pizza"] = getFood();
console.log(fruit); // berry
console.log(veggie); // cauliflower
console.log(junkfood); // pizza

Array destructuring được sử dụng thường xuyên với React Hooks vì bạn có thể nghĩ ra một vài dòng semantic code. Để tạo một biến state cùng với một hàm cập nhật một React component, bạn cũng có thể sử dụng React useState Hook.

const initialValue = false;
// thay vì 
const stateWithUpdater = useState(initialValue);
const darkMode = stateWithUpdater[0];
const darkModeUpdater = stateWithUpdater[1];

// you can do
const [darkMode, setDarkMode] = useState(initialValue);

Ví dụ sau chứng minh rằng, bạn có thẻ triên khai các hàm chung cho các trường hợp cụ thể. Array destructuring cho phép function call sử dụng các tên biến ngữ nghĩa(semantic variable names) .

const getFood = type => {
  let food = [];
  let error = false;
  if (type === "fruits") {
    food = ["strawberry", "kiwi", "banana"];
  }
  else if (type === "junk") {
    food = ["pizza", "sandwich", "cake"];
  }
  else {
    error = true;
  }
  const addFood = newFood => food.push(newFood);
  return [food, error, addFood];
};
const [healthyFood, noFruitsAvailable, addFruitFunc] = getFood("fruits");

console.log(healthyFood); // ["strawberry", "kiwi", "banana"]
console.log(noFruitsAvailable); // false
console.log(addFruitFunc("cherry")); 
console.log(healthyFood); // ["strawberry", "kiwi", "banana", "cherry"]

Việc trả về một mảng với hàm getFood() dẫn đến ngắn ngọn đoạn mã hơn, Array destructuring cho phếp đặt tên biến tùy chỉnh. Ngược lại, với Object destructuring bạn cần đổi tên cách biến.

const getFood = type => {
  // same function body as above, only different return statement
  return {food, error, addFood};
};
const {food: healthyFood, error: noFruitsAvailable, addFood: addFruitFunc} = getFood("fruits");

console.log(noFruitsAvailable); // false
console.log(addFruitFunc("cherry")); 
console.log(healthyFood); // ["strawberry", "kiwi", "banana", "cherry"]

Spread operator

Spread operator (...) cho phép một mục có thể lặp lại(ví dụ một mảng) được extracted các phần của nó để đặt vào những function, parameter, array, object để xử lý. Với cú pháp này, chúng ta có thể tách cách thuộc tính đối tượng hoặc các phần tử của mảng.

const numbers = [11, 5, 3, 1, 26];
// Math.max expects to be called like Math.max(11,5,3,1,26)

console.log(Math.max(...numbers)); // 26

Một trường hợp được sử dụng khác là dùng để sao chép các thuộc tính của đố tượng, tạo mới một đối tượng, ..

const food = {
  breakfast: ["sandwich", "egg"],
  lunch: ["hamburger", "French fries", "pizza"]
};
const foodAndDrinks = {
  ...food,
  drinks: ["🍷", "🍹", "🍺", "🥃"],
};

console.log(foodAndDrinks); 
/* 
{
  breakfast: ["sandwich", "egg"],
  lunch: ["hamburger", "French fries", "pizza"],
  drinks: ["🍷", "🍹", "🍺", "🥃"],
} */

Với cú pháp ngắn gọn này, bạn có thể tạo một bản sao của một mảng một cách thuận tiện

  const food = ["sandwich", "egg", "hambuerger", "French fries", "pizza"];
  const copy = [...food];
  console.log(copy); // ["sandwich", "egg", "hambrger", "French fries", "pizza"]
  console.log(food === copy); // false

Với React bạn không nên thao tác trực tiếp với các đối tượng state, thay vào đó, bạn cần tạo ra một đối tượng state hoàn toàn mới bất cứ khi nào bạn muốn cập nhật state. Điều này cực kỳ quan quạng với React

const restaurantState = {
  drinks: ["🍷", "🍹", "🍺", "🥃"],
  food: ["sandwich", "egg", "hambuger", "French fries", "pizza"],
  lastOrder: null
}

// the customer ordered a hamburger
const stateAfterOrder = {
  drinks: [...restaurantState.drinks], // copy drinks
  food: [...restaurantState.food], // copy food
  lastOrder:  hamburger // override lastOrder
}

Rest operator

Với Rest operator, bạn có thể merge một list các đối số của một hàm thành một mảng. Mặc dù cú pháp của toán tử này tương đối giống với spread operator nhưng vị trí sử dụng của chúng lại tạo nên sự khác biệt.
Nếu bán ửu dụng taons tử bên trong {} hoặc [], bạn sử dụng object hoặc mảng spreading. Mặt khác, nếu bạn sử dụng operator với đối số cuối cùng trong hàm, đó lại là rest parameter.
Mục đích của nó là merge một list các giá trị thành một mảng.

const cleanFoodIncluded = (...food) => food.findIndex(f => f === "cauliflower") !== -1;
console.log(cleanFoodIncluded("pizza", "sandwich", "cake", "cauliflower", "hamburger")); // true

Với rest operator chúng ta đưa vào một mảng với tên gọi là food. Phương thức findIndex() đang hoạt động trên một mảng và kiểm tra xem có bao gồm cauliflower hay không.

Tổng kết

Về cơ bản, chúng ta mới đi được nửa chặng đường, nhưng do nhiều quá có thể gây loãng nên tạm thời dừng ở đây, tiếp theo sau này chúng ta sẽ tiếp cận với Function declarations, function expressions, và cả arrow functions cú pháp phổ biến nhất hiện nay. Bên cạnh đó cũng điểm qua về Classes hay Callback functionspart 2
Tạm thời, mình xin kết thúc ở đây, Cảm ơn các bạn đã đọc!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí