React Context API

Nếu đã làm việc với ReactJS hoặc React Native một thời gian, chắc chắn bạn đã từng gặp phải một vấn đề có tên là "prop drilling". Vấn đề này nôm na là bạn cần dữ liệu ở một component cháu chắt từ component cha, việc bạn phải làm là phải truyền props thông qua những component trung gian để đi tới đích, mặc dù những component này có thể chẳng quan tâm đến dữ liệu. Điều này khá phiền toái, và còn mệt mỏi hơn khi bạn thay đổi vị trí các components trong component tree.

Vấn đề này có thể được giảm thiểu bởi một số thư viện quản lý state như Redux, nó sẽ giúp chúng ta lấy dữ liệu từ 1 store và dễ dàng truy xuất từ bất kỳ component nào trong component tree. Việc mà ta cần làm là sử dụng 1 thứ gọi là Provider và rồi, kho dữ liệu (Store) của bạn sẽ được truy xuất bởi bất kỳ component nào được connect đến store, sẽ không phải lo lắng đến "prop drilling" nhiều nữa.

Đó cũng chính là hướng tiếp cận Context API. Nếu bạn còn nhớ thì ở những phiên bản trước của React, chính chủ Facebook cũng đưa ra khuyến cáo rằng "Context API có khả năng sẽ thay đổi trong các bản phát hành trong tương lai của React". Chính vì khuyến cáo này mà nhiều lập trình viên vẫn còn e ngại và ít quan tâm tới Context API, cũng một phần vì ở thời điển đó nó ko dễ để tiếp cận.

Nhưng có một sự thật rằng

mà tôi nhắc đến ở trên cũng đã sử dụng tính năng thử nghiệm context API. Thật vậy, Provider component đặt dữ liệu bên trong context, và HOC connect sẽ kéo dữ liệu ra khỏi context. Vậy nên trong thực tế, redux không làm công việc cho phép dữ liệu được truy xuất ở khắp mọi nơi trong component tree mà chính context đã làm việc đó. Dù sao thì bạn cũng đang gián tiếp sử dụng context API mà thôi. Và may thay là từ đợt release React 16.3, context API đã trở thành "first-class feature" của React cho nên hãy bớt lo ngại, context giờ đã ngon hơn rất nhiều rồi. 😃)

Cấu trúc Context API

Context API giờ đây bao gồm 3 phần chính:

  • React.createContext: phần được truyền như là giá trị ban đầu. Hàm này trả về một object với một Provider và một Consumer
  • Provider component: được sử dụng tại tầng cao hơn trong DOM tree (Component tree) và nhận vào một giá trị qua prop gọi là value
  • Consumer component: được sử dụng ở bất cứ đâu phía dưới Provider trong Component tree và có thể lấy được giá trị của prop value của Provider thông qua prop children

Khi nào thì sử dụng Context ?

Context được thiết kế để chia sẻ dữ liệu có thể coi là "global" cho một Component tree, ví dụ như trạng thái đăng nhập của user, theme hay ngôn ngữ sử dụng. Ví dụ, trong đoạn code bên dưới, chúng ta muốn sử dụng một "theme" prop để style cho component Button

Không có context

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

function Toolbar(props) {
  // The Toolbar component must take an extra "theme" prop
  // and pass it to the ThemedButton. This can become painful
  // if every single button in the app needs to know the theme
  // because it would have to be passed through all components.
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class ThemedButton extends React.Component {
  render() {
    return <Button theme={this.props.theme} />;
  }
}

Thay đổi với context API

// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');

class App extends React.Component {
  render() {
    // Use a Provider to pass the current theme to the tree below.
    // Any component can read it, no matter how deep it is.
    // In this example, we're passing "dark" as the current value.
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // Assign a contextType to read the current theme context.
  // React will find the closest theme Provider above and use its value.
  // In this example, the current theme is "dark".
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

Trước khi bạn sử dụng Context

Context được sử dụng chính khi một số dữ liệu cần được truy xuất bởi nhiều components ở nhiều level khác nhau trong component tree. Sử dụng context có thể làm cho component khó tái sử dụng hơn

Nếu mục đích duy nhất của bạn là chỉ muốn giải quyết vấn đề "prop drilling" thì "component composition" có thể là giải pháp đơn giản hơn sử dụng Context

Bạn có thể tìm đọc nhiều bài viết về Component composition trên internet. Đơn giản nó chính là việc thay vì truyền prop dữ liệu xuyên qua các component thì ta sẽ truyền thẳng một component xử lý dữ liệu đó xuống các component con.

Ví dụ về cách sử dụng Context API

Một ví dụ với dữ liệu động cho theme cùng với cơ chế thay đổi dữ liệu từ dưới component con:

theme-context.js

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};

export const ThemeContext = React.createContext(
  theme: themes.dark, // default value
  toggleTheme: () => {}
);

themed-toggle-button.js

import {ThemeContext} from './theme-context';

function ThemeTogglerButton() {
  // The Theme Toggler Button receives not only the theme
  // but also a toggleTheme function from the context
  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (
        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

export default ThemeTogglerButton;

app.js

import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };

    // State also contains the updater function so it will
    // be passed down into the context provider
    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,
    };
  }

  render() {
    // The entire state is passed to the provider
    return (
      <ThemeContext.Provider value={this.state}>
        <Content />
      </ThemeContext.Provider>
    );
  }
}

function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}

ReactDOM.render(<App />, document.root);

Tham khảo: https://reactjs.org/docs/context.html


All Rights Reserved