Bí kíp toàn thư về React mà bạn cần phải biết (phần 1)

Năm ngoái tôi có viết một cuốn sách ngắn vọn vẻn 100 trang về React.js. Còn năm nay, tôi sẽ tự thử thách mình bằng cách tóm gọn lại vọn vẻn trong bài viết này mọi thứ bạn cần biết về React.js.

Bài viết sẽ không giải thích React.js là gì hay vì sao bạn phải học nó. Thay vào đó, tôi sẽ tập trung viết về những thông tin thực tiễn về phần nền tảng của React.js dành cho những bạn đã biết về JavaScript cũng như hiểu tương đối với DOM API.

Tất cả những ví dụ dưới đây đều mang tính tham khảo. Mục đích là giúp bạn hiểu rõ hơn về những khái niệm trong bài viết.

Nền tảng #1: Mọi thứ của React đều là về components

React được tạo ra xoay quanh khái niệm components tái sử dụng được. Bạn define các component nhỏ và gộp chúng lại để tạo ra những component to hơn.

Tất cả các component đều có thể tái sử dụng, thậm chí là qua nhiều project khác nhau.

Một React component – ở dạng đơn giản nhất – chính là JavaScript function:

// Example 1
// https://jscomplete.com/repl?j=Sy3QAdKHW
function Button (props) {
  // Returns a DOM element here. For example:
  return <button type="submit">{props.label}</button>;
}
// To render the Button component to the browser
ReactDOM.render(<Button label="Save" />, mountNode)

Các bạn đừng lo nếu chưa hiểu rõ những code trên bởi nó sẽ được giải thích trong những phần tiếp theo trong bài viết, kể cả ReactDOM. Bạn chỉ cần hiểu đơn giản nó là một  render function.

Ở đoạn cuối, ReactDOM.render ám chỉ địa điểm của các yếu tố của DOM mà React sẽ lấy và điều khiển. Trong jsComplete REPL, thì bạn có thể làm mọi thứ trên với một variable đặc biệt là mountNode.

Những điều tiếp theo sẽ dựa trên ví dụ #1:

  • Component name bắt đầu với một kí tự in hoa. Đây là yêu cầu bắt buộc bởi chúng ta phải làm việc với cả HTML và React. Nếu bạn đặt tên thường như “button” cho React component thì ReactDOM sẽ bỏ qua function và render một HTML button trống.
  • Mọi component đều nhận một list của các attributes, giống như HTML. Trong React, list này được gọi là props. Với một function component, bạn có thể đặt tên nó tùy ý thích.
  • Trong ví dụ trên, cách viết có vẻ giống HTML trong returned output của Button function component. Tuy vậy, nó không phải là JavaScript hay HTML, cũng chả phải là React.js. Thế nhưng lại trở nên khá nổi tiếng tới mức được xem là chuẩn trong các React applications. Nó có tên gọi là JSX và là một JavaScript extension. Hơn nữa, JSX là một compromise! Dù bạn có thử call và return bất cứ HTML element trong function trên thì chúng đề support cho nhau cả.

Nền tảng #2: JSX là cái quái gì vậy?

Ví dụ 1 ở trên có thể được viết hoàn toàn bằng React.js mà không cần tới JSX như sau:

// Example 2 -  React component without JSX
// https://jscomplete.com/repl?j=HyiEwoYB-
function Button (props) {
  return React.createElement(
    "button",
    { type: "submit" },
    props.label
  );
}
// To use Button, you would do something like
ReactDOM.render(
  React.createElement(Button, { label: "Save" }),
  mountNode
);

createElementfunction là function chính trong React top-level API. Nó vốn chỉ là 1 trong 7 phần của level đó mà bạn cần phải học. React API nhỏ tới mức như vậy đấy.

Như DOM, bản thân nó cũng có document.createElement function để tạo ra một yếu tố được xác định bởi một tag name, React’s createElement là một function cao cấp với khả năng làm được mọi thứ mà document.createElement có thể làm được. Đồng thời, nó còn có thể tạo ra một yếu tố đại diện cho React component. Chúng ta thực hiện điều đó trong ví dụ trên với  Button component.

Không như  document.createElement, React’s  createElement chấp nhận cả những yếu tố đại diện như là con của yếu tố khác. Nói cách khác  createElement thật sự tạo ra cả một cây dòng dõi các yếu tố.

Sau đây là một ví điển hình:

// Example 3 -  React’s createElement API
// https://jscomplete.com/repl?j=r1GNoiFBb
const InputForm = React.createElement(
  "form",
  { target: "_blank", action: "https://google.com/search" },
  React.createElement("div", null, "Enter input and click Search"),
  React.createElement("input", { name: "q", className: "input" }),
  React.createElement(Button, { label: "Search" })
);
// InputForm uses the Button component, so we need that too:
function Button (props) {
  return React.createElement(
    "button",
    { type: "submit" },
    props.label
  );
}
// Then we can use InputForm directly with .render
ReactDOM.render(InputForm, mountNode);

Hẳn bạn cũng có thể thấy rằng:

  • InputFormkhông phải là một React component; mà chỉ là một React element. Đó là nguyên nhân ta dùng nó trực tiếp trong  ReactDOM.render call chứ không phải với  <InputForm />.
  • React.createElement  function chấp nhận nhiều arguments.
  • Chúng ta có thể nest React.createElement call là bởi vì nó toàn là JavaScript.
  • Điểm đáng tranh cãi thứ 2 trong  React.createElement  có thể là null hoặc một object trống khi không cần có attributes hay props cho element.
  • Chúng ta có thể trộn HTML element với React components. Cứ nghĩ đơn giản là HTML elements như built-in React components.
  • React’s API cố gắng trở nên giống DOM API nhất có thể, đó là nguyên nhân vì sao chúng ta dùng  className thay vì class cho input element. Và mọi người cũng đều mong React’s API sẽ thành một phần của DOM API bởi nó sẽ cực kì tiện lợi.

Những đoạn code trong ví dụ trên là điều mà browser sẽ hiểu khi bạn thêm React library. Browser không hề đụng tới bất cứ JSX nào. Tuy vậy, chúng ta thường thích làm việc với HTML thay vì các  createElement calls này. Do đó mà mới có những JSX compromise. Chúng ta có thể viết lại những code trên với một cú pháp tương tự như HTML:

// Example 4 - JSX (compare with Example 3)
// https://jscomplete.com/repl?j=SJWy3otHW
const InputForm =
  <form target="_blank" action="https://google.com/search">
    <div>Enter input and click Search</div>
    <input name="q" className="input" />
    <Button label="Search" />
  </form>;
// InputForm "still" uses the Button component, so we need that too.
// Either JSX or normal form would do
function Button (props) {
  // Returns a DOM element here. For example:
  return <button type="submit">{props.label}</button>;
}
// Then we can use InputForm directly with .render
ReactDOM.render(InputForm, mountNode);

Một số điều nổi bật trong ví dụ trên:

  • Nó không phải là HTML. Chúng ta vẫn dùng className  thay vì  class.
  • Chúng ta vẫn xem những thứ trông giống HTML ở trên như JavaScript.

Ví dụ 4 này chính là JSX. Và ví dụ 3 chính là phiên bản được compiled của nó dành cho browser. Để thực hiện được điều đó ta cần dùng tới một quá trình tiền xử lý để convert JSX version thành  React.createElement version.

Và đó chính là JSX. nó là một compromise cho phép chúng ta viết các React components trong một cú pháp tương tự như HTML.

Mặt khác, JSX cũng có thể được dùng lên chính nó.

Nền tảng #3: Bạn có thể dùng JavaScript expressions bất cứ nơi đâu trong JSX.

Trong một JSX section, bạn có thể dùng bất cứ JavaScript expression nào miễn nằm trong dấu ngoặc nhọn:

// Example 5 -  Using JavaScript expressions in JSX
// https://jscomplete.com/repl?j=SkNN3oYSW
const RandomValue = () => 
  <div>
    { Math.floor(Math.random() * 100) }
  </div>;
// To use it:
ReactDOM.render(<RandomValue />, mountNode);

Có thể thấy rằng không hề có giới hạn cho bất kì JavaScript expression trong dấu ngoặc nhọn. Nó tương tự như interpolation syntax  ${}  trong JavaScript template literals.

Chỉ có một hạn chế duy nhất trong JSX: phải là expression. Thế nên, ví dụ như, bạn sẽ không thể dùng một if statement nhưng một expression thứ ba thì mọi thứ lại bình thường.

JavaScript variables cũng được xem là các expressions, thế nên khi component nhận một list của prop (RandomValue component thì không, props là optional), ban có thể dùng những props này trong dấu ngoặc nhọn. Chúng ta cũng đã thực hiện điều này với Button  component (trong ví dụ 1)

Các objects JavaScript cũng là những expressions. Đôi khi chúng ta dùng JavaScript object trong dấu ngoặc nhọn, nhìn thì giống như xài cả hai lần dấu ngoặc nhọn nhưng thực chất nó là object trong dấu ngoặc nhọn.

// Example 6 - An object passed to the special React style prop
// https://jscomplete.com/repl?j=S1Kw2sFHb
const ErrorDisplay = ({message}) =>
  <div style={ { color: 'red', backgroundColor: 'yellow' } }>
    {message}
  </div>;
// Use it:
ReactDOM.render(
  <ErrorDisplay 
    message="These aren't the droids you're looking for" 
  />,
  mountNode
);

Lưu ý rằng tôi chỉ destructure message ra khỏi props argument. Đó là Javascript. ngoài ra, style attribute ở trên cũng là 1 trường hợp đặc biệt (không phải HTML, mà gần giống như DOM API). Chúng ta sử dụng 1 object với vai trò như giá trị của style attribute. Object đó sẽ định vị styles tương tự những gì chúng ta làm với Javascript.

Bạn còn có thể sử dụng element React trong JSX, vì đó cũng là 1 expression. Nên nhớ là element React là 1 hàm call:

// Example 7 - Using a React element within {}
// https://jscomplete.com/repl?j=SkTLpjYr-
const MaybeError = ({errorMessage}) =>
  <div>
    {errorMessage && <ErrorDisplay message={errorMessage} />}
  </div>;
  
// The MaybeError component uses the ErrorDisplay component:
const ErrorDisplay = ({message}) =>
  <div style={ { color: 'red', backgroundColor: 'yellow' } }>
    {message}
  </div>;
// Now we can use the MaybeError component:
ReactDOM.render(
  <MaybeError
    errorMessage={Math.random() > 0.5 ? 'Not good' : ''}
  />,
  mountNode
);

 MaybeError component ở trên sẽ chỉ hiển thị  ErrorDisplay component nếu có một  errorMessage string pass qua nó và một  div trống. React xem xét  {true}{false}{undefined}, và {null}  để thành valid element children, vốn không render bất cứ thứ gì.

Bạn cũng có thể dùng tất cả các phương pháp JavaScript functional trên collections (mapreducefilterconcat …) bên trong JSX. Đó là vì chúng là return expressions:

// Example 8 - Using an array map inside {}
// https://jscomplete.com/repl?j=SJ29aiYH-
const Doubler = ({value=[1, 2, 3]}) =>
  <div>
    {value.map(e => e * 2)}
  </div>;
// Use it
ReactDOM.render(<Doubler />, mountNode);

Bạn có thể thấy cách Tôi đã cho value prop một default value như trên, bởi vì nó hoàn toàn là Javascript. Cũng như việc tôi output một array expression trong  div. React hoàn toàn ok với điều đó.

Nền tảng #4: bạn có thể viết React components với JavaScript classes

Các function components đơn giản vô cùng thích hợp cho những nhu cầu đơn giản, nhưng đôi khi chúng ta cần hơn cả thế. React hỗ trợ tạo components bằng JavaScript class syntax. Sau đây là  Button component (trong ví dụ 1) được viết với class syntax:

// Example 9 - Creating components using JavaScript classes
// https://jscomplete.com/repl?j=ryjk0iKHb
class Button extends React.Component {
  render() {
    return <button>{this.props.label}</button>;
  }
}
// Use it (same syntax)
ReactDOM.render(<Button label="Save" />, mountNode);

Class syntax này rất đơn giản. Define một class với việc mở rộng React.Component. Việc class định dạng một instance function đơn  render(), và render function returns lại virtual DOM object. Mỗi lần chúng ta dùng Button class-based component ở trên, React sẽ lập tức dùng một object trong class-based component cho DOM tree.

Đó là nguyên nhân vì sao ta dùng this.props.label trong JSX trong rendered output ở trên. Bởi các component đều có một instance property đặc biệt gọi là  props , vốn giữ tất cả các value được pass tới component đó.

Bởi chúng ta có instance liên quan tới một component một lần dùng, chúng ta có thể tinh chỉnh tùy ý instance đó. Chúng tôi có thể tinh chỉnh nó sau khi nó được tạo ra bởi JavaScript  constructor function:

// Example 10 -  Customizing a component instance
// https://jscomplete.com/repl?j=rko7RsKS-
class Button extends React.Component {
  constructor(props) {
    super(props);
    this.id = Date.now();
  }
  render() {
    return <button id={this.id}>{this.props.label}</button>;
  }
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);

Chúng ta cũng có thể định dạng hàm class prototype  và dùng nó ở bất cứ nơi nào, bao gồm cả ở trong JSX output:

// Example 11 — Using class properties
// https://jscomplete.com/repl?j=H1YDCoFSb
class Button extends React.Component {
  clickCounter = 0;
handleClick = () => {
    console.log(`Clicked: ${++this.clickCounter}`);
  };
  
  render() {
    return (
      <button id={this.id} onClick={this.handleClick}>
        {this.props.label}
      </button>
    );
  }
}
// Use it
ReactDOM.render(<Button label="Save" />, mountNode);

Bạn có thể thấy trong ví dụ 11 này:

Hàm handleClick được viết nhờ vào class-field syntax mới trong JavaScript. Dù chỉ mới ở stage-2 nhưng nó lại được xem như là phương pháp tốt nhất để truy cập vào component mounted instance. Nhưng bạn sẽ phải dùng tới compiler như Babel configured để hiểu stage-2 để truy cập vào code ở trên. jsComplete REPL thì đã được pre-configured sẵn như vậy.

Chúng tôi cũng define clickCounter  instance variables sử dụng class-field syntax. Nhờ đó mà không cần phải dùng tới class constructor call.

Khi ta chỉ định handleClick  function là giá trị cho onClick  React attribute, ta đã không call nó. Thay vào đó, ta pass trong reference tới  handleClickfunction. Call function với level như vậy là một lỗi thường gặp khi dùng React.

// Wrong:
onClick={this.handleClick()}
// Right:
onClick={this.handleClick}

Nền tảng #5: Events trong React: 2 điểm khác biệt quan trọng.

Khi nói tới events trong các yếu tố của React, có 2 điểm khác biệt quan trọng so với cách chúng ta thường làm với DOM API:

  • Mọi React elements attributes đều được đặt tên sử dụng camelCase, thay vì lowercase. Nói cách khác  onClick mới đúng, chứ không phải là  onclick
  • Chúng ta pass một JavaScript function thật sự như là một event handler thay vì là một string. Nó là  onClick={handleClick}, chứ không phải là onClick="handleClick".

React bao DOM event object với một object của chính nó để tối ưu hóa hiệu năng của events handling. Nhưng trong một event handler, chúng ta vẫn có thể truy cập mọi phương pháp có trong DOM event object. React sẽ pass event object đó tới từng handle call.

// Example 12 - Working with wrapped events
// https://jscomplete.com/repl?j=HkIhRoKBb
class Form extends React.Component {
  handleSubmit = (event) => {
    event.preventDefault();
    console.log('Form submitted');
  };
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <button type="submit">Submit</button>
      </form>
    );
  }
}
// Use it
ReactDOM.render(<Form />, mountNode);

(Hết phần 1)

Bí kíp toàn thư về React mà bạn cần phải biết (phần 2)

Phỏng dịch theo bài viết tại freecodecamp.org