12/08/2018, 17:12

Một số khái niệm cần biết trong React

Giới thiệu Trong bài viết nay mình sẽ giới thiệu đến các bạn một số khái niệm quan trọng trong react. Đây là những khái niệm cần lắm rõ để trong quá trình phát triển với react tránh mắc phải việc mất thời gian cho những lỗi cơ bản mà mình đã gặp phải do chưa lắm vững kiến thức. Mình cũng ko lan ...

Giới thiệu

Trong bài viết nay mình sẽ giới thiệu đến các bạn một số khái niệm quan trọng trong react. Đây là những khái niệm cần lắm rõ để trong quá trình phát triển với react tránh mắc phải việc mất thời gian cho những lỗi cơ bản mà mình đã gặp phải do chưa lắm vững kiến thức. Mình cũng ko lan man nữa. Phần đâu tiên là vòng đời của react component.

Vòng đời trong React Component

Khái niệm component lifecycle trong react là một trong những khái niệm quan trọng nhất, hiểu rõ lifecycle của component sẽ giúp bạn biết khi component được tạo, thay đổi, những method nào được gọi và làm những công việc gì. Để có thể hiểu rõ về lifecycle thì chúng ta cần nắm được sự khác nhau của lifecycle khi component được khởi tạo (mounting), khi state, props thay đổi(Updating) và khi unmount component như hình dưới đây:

  1. Mounting

Trong giai đoạn nay sẽ bao gồm các phương thức khởi tạo state, props như sau :

constructor(props) {
    super(props);
    this.state = {
         arrayIdea: [],
         textIdea: "",
         booleanIdea: false
    };
    // props sẽ được truyền từ class cha xuống
    console.log(props);
  }

Tiếp theo phương thức componentWillMount sẽ được gọi ở đây và lưu ý hàm này chỉ được gọi 1 lần duy nhất trước khi thực hiện phương thức render(). Phương thức render sẽ trả về nội dung mà bạn muốn hiển thị bao gồm code html, css. Dưới đây là ví dụ một hàm render

render() {
    return (
      <div className="tile">
           <h2>Hello world</h2>
      </div>
    );
  }

Ngay sau khi phương thức render được gọi thì phương thức componentDidMount sẽ được gọi. Tại đây khi mà mọi thứ đã được xây dựng hoàn chỉnh, ta có thể thực hiện các thao tác với component vừa mới tạo ví dụ: thực hiện các hàm bất đồng bộ để lấy dữ liệu, thao các với DOM.

  1. Updating

Khi state thay đổi, hoặc props sẽ trigger đến những phương thức như sau:

Trường hợp thay khi props thay đổi( từ lớp cha chuyền dữ liệu mới lớp con) để có thể so sánh giữa props mới với props cũ, hoặc đơn giản là cập nhật lại state từ props mới ta có thể xử lý ngay tại đây như sau:

componentWillReceiveProps(nextProps) {
    this.setState({
      idea: nextProps.newIdea
    });
    //or showNotification()
  }

Tiếp theo shouldComponentUpdate sẽ được thực hiện vả trả về kết quả true/false . Vậy ta có thể sử dụng để kiểm tra xem có thỏa mãn điều kiện ko nếu có thì trả về true để update còn không sẽ trả về false như sau:

shouldComponentUpdate(nextProps, nextState) {
        console.log(nextState)
        if (someCondition) {
            return true;
        }
        return false;
    }

Ngay sau khi shouldComponentUpdate return true thì componentWillUpdate sẽ được gọi:

componentWillUpdate(nextProps, nextState) {
// shouldComponentUpdate return true
}

Cuối cùng componentDidUpdate sẽ được gọi sau khi render xong.

componentDidUpdate(prevProps, prevState) {

}
  1. Unmounting

Unmouting là khi component được xóa khỏi DOM.Chỉ có một method duy nhất được gọi trước khi Component được xóa khỏi DOM. Được sử dụng trong trường hợp muốn cleanup event hay timer chẳng hạn.

Immutability Helpers

Trong javascript làm việc với dữ liệu có dạng immutable rất là khó so với các ngôn ngữ khác. React cũng không phải ngoại lệ và chịu ảnh hường đến hiệu năng nếu ko giải quyết được việc xử lý immutable data nguyên nhân là do cơ chế render() như sau

Mình cũng đã nói ở phần vòng đời của react component phương thức render() chỉ được gọi lại khi có sự thay đổi từ state hoặc props. Trong trường hợp nếu state trong class react có cấu trúc đơn giản thì việc so sánh sẽ không tốn kém nhưng tưởng tượng object của bạn có dạng như sau:

this.state.myData.x.y.z = 7;
// or...
this.state.myData.a.b.push(9);

Và bạn phải thực hiện lần lượt các thao tác sau để tạo new state

const newData = deepCopy(this.state.myData);
newData.x.y.z = 8;
newData.a.b.push(10);
...
function deepCopy(data) {}

Việc xử lý như trên gây ra tốn kém về hiệu năng quá lớn đồng thời sẽ có những trường hợp ko thể làm như trên được và sinh bug cho hệ thống. Do đó React cung cấp cho ta một thư viện để xử lý việc deepCopy và thay đổi dữ liệu vừa được copy: Immutability Helpers

Để sử dụng thư việc Immutability Helpers của react trong đường dẫn dự án ta sử dụng npm để cài đặt

npm install immutability-helper --save

Tiếp theo là các câu lệnh trong immutability-helper:

  • {$push: array} thêm tất cả items vào cuối array được chỉ định
  • {$unshift: array} thêm tất cả items vào đầu array được chỉ định
  • {$splice: array of arrays} với một array được truyền vào để thửc hiện remove phần tử trong array được chỉ định vào thay thế phần tử đó bằng số mảng khác
  • {$set: any} thay thế đối tượng chỉ định bằng đối tượng mới
  • {$merge: object} dùng cho đối tượng dangh hash (key: value)
  • {$apply: function}: áp dụng function cho đối tượng được chỉ định

Ví dụ: mình có viết thử 1 đoạn để cập nhât state của component sử dụng immutability-helper

import update from 'react-addons-update';
import React from 'react';
import ReactDOM from 'react-dom';

class Hello extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      ideas: [{
        "id": 1,
        "title": "A new cake recipe",
        "body": "Made of chocolate"
      },
      {
        "id": 2,
        "title": "A twitter client idea",
        "body": "Only for replying to mentions and DMs"
      }]
    };
  }

  helperReact = () => {
    let index = 0;
    let newData = update(this.state.ideas, {
      [index]: {"title": {$set: "this is a new title"}}
    });
    this.setState({
      ideas: newData
    });
  }

  showState = () => {
    console.log(this.state.ideas);
  }

  componentDidMount() {
    this.helperReact();
  }

  render() {
    this.showState();
    return (
      <div>Hello {this.props.name}</div>
    );
  }
}

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);

Hoặc bạn có thể chạy thử trên các ứng dụng editor online: đây là ví dụ mình viết trên online editor. Ngoài ra bạn có thể tham khảo các ví dụ khác trên trang chủ của react

AUTOBIND không còn hoạt động với react + es6

Trước tiên mình muốn nói qua về con trỏ this trong javascript. Trong các ngôn ngữ hướng đối tượng thông thường thì con trỏ this luôn để chỉ tới class chứa nó tuy nhiên trong javascript trở nên phức tạp hơn rất nhiều khi mà có thêm các khái niệm scoped function, callbacks, closured và promises khiến cỏn trỏ ta không thể kiểm soát được this đang tham chiếu ở đâu.

Quay lại với chủ đề, React đã chứng tỏ rằng nó là một thư viện thân thiện và tuyệt vời khi làm việc và việc xử lý từ khóa this cũng không ngoại lệ. React mặc định this sẽ được tham chiếu tới bối cảnh(context) bên trong của mỗi component React, ví dụ:

this.setState({ someKey: someValue })

Tuy nhiên vấn đề sẽ xảy ra ki bạn viết code như sau:

this.setState({ dataFetched: false })

fetch('/data').then(function fetchComplete() {
  this.setState({ dataFetched: true })
})

Bạn đã thấy vấn đề xảy ra không? khi mà dòng đầu chạy sẽ không có lỗi gì xảy ra. Tuy nhiên khi chạy đến dòng setState thứ 2 kết quả lỗi sẽ bị raise. Nguyên nhân là dó con trỏ this không còn tham chiếu tới React component nữa mà tham chiếu vào hàm fetchComplete.

Trước khi giải quyết vấn đề trên mình xin quay lại một chút kiến thức về javascript. Giả sử ta định nghĩa một đối tượng như sau:

//Định nghĩa 1 đối tượng với phương thức hiển thị thuộc tính ra màn hình
var Student = {
    name: 'John',
    printName: function() {
       console.log(this.name);
    }
};

//Bắt sự kiện click chuột lên button, sẽ thực thi hàm hiển thị tên của Student lên màn hình
$('button').click(Student.printName);    //kq: undefined

Kết quả hiện thị sẽ là undefined nguyên nhân là do con trỏ this không còn trỏ vào đối tượng Student nữa mà nó trỏ vào đối tượng $$‘button’). Để đoạn code trên chạy đúng như mong muốn thì ta phải đảm bảo context của hàm callback Student.printName phải được tham chiếu tới đối tượng Student. Cụ thể ta sẽ sử dụng hàm bind() để gắn context vào hàm callback đó.

$('button').click(Student.printName.bind(Student));

Các bạn có thể thử tại codepen:

Đó cũng là lí do tại sao khi viết code trong es6 mặc dù cú pháp hoàn toàn đúng nhưng không thể chạy như mong muốn được

class ExampleComponent extends React.Component {
  onClickHander() {
    this.setState({ clicked: true });
  }

  render() {
    return <div onClick={this.onClickHander}>Click me</div>;
  }

Bạn có thể kiểm chứng tại đây. Cuối cùng là một số giải pháp cho vấn đề trên

  1. Sử dụng bind khi render
render() {
    return <div onClick={this.onClickHander}>AAAA</div>;
  }
  1. Sử dụng arrow function trong JSX
render() {
    return (
        <div onClick={() => this.onClickHandler()}>AAAA</div>;
    )
};
  1. Bind ngay khi khởi tạo
class ExampleComponent extends React.Component {
    constructor() {
        super();
        this.onClickHandler = this.onClickHandler.bind(this);
    }
    ...
}
  1. Sử dụng thư viện cho phép autobinding
import autobind from 'autobind-decorator';

class ExampleComponent extends React.Component {
    @autobind
    update() {
      ...
    }

    ...
}
  1. Sử dụng arrow function làm class method
import React from 'react';

class ExampleComponent extends React.Component {
    update = () => {
      ...
    }

    ...
}
0