Tag: react

  • [React] Kết hợp các mẫu lập trình để sử dụng hiệu quả Runtime Type Checking

    [React] Kết hợp các mẫu lập trình để sử dụng hiệu quả Runtime Type Checking

    Như các bạn đã biết, Static Type Checking giúp chúng ta kiểm soát kiểu dữ liệu tốt hơn khi viết mã nguồn. Các bạn có thể sử dụng Flow hoặc TypeScript đều được. Tuy nhiên khi dữ liệu được liên kết với API chẳng hạn, khi đó bạn không thể kiểm soát được giá trị được truyền vào cho biến nào đó. Do đó chúng ta vẫn cần thêm một bước nữa để hạn chế các lỗi tiềm ẩn bằng Runtime Type Checking. Trong bài viết này tôi sẽ hướng dẫn các bạn sử dụng Runtime Type Checking như thế nào cho hiệu quả.

    Runtime Type Checking là gì?

    Runtime Type Checking là quá trình xác minh lại kiểu dữ liệu của giá trị truyền vào cho biến có đúng với kiểu dữ liệu mong muốn hay không. Trong ứng dụng React khi bạn khai báo propTypes cho component nào đó chính là lúc bạn đang định nghĩa kiểu dữ liệu mong muốn. Tại thời điểm thực thi các định nghĩa này sẽ được kiểm tra giúp bạn kiểm soát việc binding dữ liệu từ API vào component có tương thích với nhau hay không.

    Định nghĩa propTypes

    React cung cấp gói prop-types giúp bạn định nghĩa các thuộc tính cần cho Runtime Type Checking. Trước đây thì gói này nằm trong React, hiện tại thì nó đã tách ra thành một gói riêng rồi. Các bạn khai báo propTypes như sau:

    import React from 'react';
    import PropTypes from 'prop-types';
    
    export function Octocat(props) {
      const { login, avatar_url, name } = props;
      return (
        <ul>
          <li>{login}</li>
          <li>{name}</li>
          <li>
            <img src={avatar_url} alt={login} />
          </li>
        </ul>
      );
    }
    
    Octocat.propTypes = {
      login: PropTypes.string,
      avatar_url: PropTypes.string,
      name: PropTypes.string
    };
    

    Trong quá trình thực thi nếu dữ liệu được truyền vào component không đúng với định nghĩa propTypes bạn sẽ nhận được một thông báo tương tự như sau:

    prop-types

    Kết hợp với Spread Attributes

    Bạn có để ý rằng khi chúng ta khai báo giá trị truyền vào trong props thì khi sử dụng component này bạn phải khai báo như sau:

    <Octocat login="abc" avatar_url="def" name="xyz" />
    

    Nếu số lượng thuộc tính trong component này tăng lên thì việc viết mã nguồn sẽ khá là vất vả. Rất may là trong JSX cung cấp cho bạn tính năng Spead Attributes và bạn có thể viết code như sau:

    const data = {login: "abc" avatar_url: "def" name: "xyz"};
    <Octocat {...data} />
    

    Toán tử ... được gọi là toán tử spead.

    Kết hợp với Destructuring Props

    Các bạn có thấy rằng khi khai báo Octocat component tôi đã truyền props như làm tham số của một hàm và sau đó mới gán lại vào các biến. Với Destructuring Props bạn có thể viết mã nguồn đơn giản hơn như sau:

    import React from 'react';
    import PropTypes from 'prop-types';
    
    export function Octocat({ login, avatar_url, name }) {
      return (
        <ul>
          <li>{login}</li>
          <li>{name}</li>
          <li>
            <img src={avatar_url} alt={login} />
          </li>
        </ul>
      );
    }
    
    Octocat.propTypes = {
      login: PropTypes.string,
      avatar_url: PropTypes.string,
      name: PropTypes.string
    };
    

    Tài liệu tham khảo:

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết sẽ giúp các bạn viết mã nguồn tốt hơn.

  • [React] Static Type Checking là gì? Flow và TypeScript, bạn chọn cách nào?

    [React] Static Type Checking là gì? Flow và TypeScript, bạn chọn cách nào?

    Static Type Checking và Runtime Type Checking là hai thuật ngữ ban đầu cảm giác có vẻ không quen thuộc cho nhưng nó lại lai thứ có lẽ các bạn đang dùng hàng ngày. Như các bạn biết thì JavaScript là ngôn ngữ định kiểu động, nghĩa là kiểu dữ liệu chỉ được xác định tại thời điểm thực thi. Do đó khi lập trình chúng ta không cần xác định kiểu dữ liệu cho biến. Điều này vô hình chung làm cho mã nguồn của chúng ta trở nên khó đọc hơn và khó debug hơn nữa. Chính vì vậy việc xác định kiểu dữ liệu giúp cho chúng ta kiểu soát và debug dễ dàng hơn. Trong bài viết này tôi sẽ chia sẻ với các bạn cách công đồng đang làm để hỗ trợ hai kiểu kiểm tra kiểu dữ liệu này.

    Static Type Checking là gì?

    Hiện nay có hai xu hướng để kiểm tra kiểu dữ liệu tĩnh là FlowTypeScript. Static Type Checking sẽ phân tích mã nguồn tĩnh trước khi biên dịch mã nguồn sang mã JavsScript(ES5) là mã nguồn mà trình duyệt có thể đọc được. Vậy thì chúng khác nhau ở điểm nào?

    Flow

    Flow là một phần mở rộng của Babel cung cấp cho bạn việc kiểm tra kiểu dữ liệu tĩnh bằng cách sử dụng các chỉ thị bằng comment mà bạn thêm vào mã nguồn.

    Ví dụ cách sử dụng flow như bên dưới:

    // @flow
    function square(n: number): number {
      return n * n;
    }
    
    square('2'); // Error!
    

    TypeScript

    TypeScript là ngôn ngữ dựa trên JavaScript được Microsoft phát triển. TypeScript khác với Flow là nó không phân tích mã nguồn dự trên các chỉ thị bằng comment. Kiểu dữ liệu được hỗ trợ từ trong ngôn ngữ luôn. Dữ liệu được kiểm tra tại thời điểm phân tích cú pháp của chương trình dịch. Ngoài ra thì TypeScript hỗ trợ rất mạnh OOP nên nó rất phù hợp với các bạn quen thuộc với các ngôn ngữ định kiểu như Java và C#.

    const add = (x: number, y: number) => {
      return x + y;
    };
    

    Sử dụng Static Type Checking có lợi ích gì?

    • Khi sử dụng Static Type Checking bạn được hỗ trợ việc kiểm soát dữ liệu tại thời điểm viết mã nguồn, tất cả việc gán hoặc gọi hàm với kiểu dữ liệu không phù hợp sẽ được phát hiện tại thời điểm này.

    • IDE hỗ trợ việc kiểm soát kiểu dữ liệu giúp bạn viết code nhanh hơn và sai sốt ít hơn.

    • Mã nguồn dễ đọc hơn cho người mới.

    Bạn mất gì khi sử dụng Static Type Checking?

    Static Type Checking mang những lợi ích nhất định trong việc kiểm soát kiểu dữ liệu, tuy nhiên nó cũng có những nhiểm điểm:

    • Bạn phải viết code nhiều hơn thì mới kiểm soát được kiểu dữ liệu, việc này đương nhiên sẽ tốn công sức. Tuy nhiên so với việc mất thời gian vào debug thì có lẽ nó vẫn tốt hơn.

    • Bạn vẫn không kiểm soát được kiểu dữ liệu tại thời điểm chạy chương trình, lý do là nếu chương trình của bạn có liên kết với API thì không có gì đảm bảo kiểu dữ liệu của bạn chạy đúng với backend cả. Đương nhiên cái này là vấn đề không thể giải quyết được rồi. Tôi sẽ hướng dẫn các bạn giải quyết vấn đề này trong bài viết Runtime Type Checking sau nhé.

    Kết luận

    Đây chỉ là kết luận mang tính cá nhân của mình :). Các bạn tự suy ngẫm để đưa ra lựa chọn phù hợp nhé.

    • Việc viết theo Flow có cảm giác không tự nhiên lắm, thường xuyên phải thêm comment khiến bạn khá mất thời gian. Thêm một vấn đề nữa là lúc viết code sử dụng Flow khá tốn tài nguyên của máy. Về vấn đề này thì mình thấy TypeScript có vẻ ổn hơn.

    • TypeScript không đơn giản là một sự mở rộng của JavaScript, Microsoft đã thêm vào nhiều thứ hơn thế và nó cũng không dựa trên các tiêu chuẩn của ES6, ES7, … (có support ES6, ES7, … nhưng có những thêm vào nhiều thứ khác nữa). Về điểm này thì Flow có vẻ tốt hơn.

    • Sau sự ra đời của React Hooks thì mọi thế mạnh của TypeScript gần như không còn nữa so với Flow, bản thân mình cũng không dùng TypeScript nữa.

    • Một điểm quan trong nữa là cả TypeScript và Flow đều không thể xác mình kiểu dữ liệu lúc thực thi nên bạn vẫn cần Runtime Type Checking.

    Các bạn có dùng Static Type Checking không? Mình thì đã không dùng nữa và chuyển sang xác mình toàn bộ bằng Runtime Type Checking rồi. Thời gian tốn cho debug cũng không phát sinh nhiều so với việc phải viết mã nguồn xác định kiểu dữ liệu.

    Cảm ơn các bạn đã theo dõi bài viết. Các bạn cùng chờ bài viết mình hướng dẫn về Runtime Type Checking nhé.

  • [React] Sử dụng mẫu container trong React như thế nào?

    [React] Sử dụng mẫu container trong React như thế nào?

    Chắc hẳn ai cũng biết tới nguyên lý đầu tiên của SOLID đó là trách nhiệm đơn nhất. Nghĩa là mỗi một đối tượng chỉ làm một nhiệm vụ duy nhất. Đơn giản vì với tính đơn nhất chúng ta sẽ dễ dàng kiểm soát mã nguồn hơn (cho cả người code lần người review). Chưa kể tới việc với tính đơn nhất thì mã nguồn của bạn cũng trở nên trong sáng hơn. Với anh em lập trình React cũng vậy, bài toán khá đau đầu là làm sao tách logic ra khỏi phần mã nguồn kết xuất HTML. Cách làm như vậy giúp chúng ta thay vì viết một React Component để là cả hai việc thì bây giờ chúng ta sẽ viết hai React Component để làm hai nhiệm vụ khác nhau. Trong bài viết này tôi sẽ hướng dẫn các bạn sử dụng mấu container để làm việc đó.

    Container component là gì?

    Chúng ta không có một định nghĩa chính xác nào về container component cả, container component đơn giản là một React Component làm nhiệm vụ goi API để lấy dữ liệu và sau đó truyền nó xuống một React Component để kết xuất mã HTML.

    Định nghĩa container

    Để các bạn dễ hình dung tôi sẽ viết thử một container, container này sẽ thực hiện lấy thông tin của octocat

    src/containers/OctocatContainer.jsx

    import React, { useState, useEffect } from 'react';
    import { Octocat } from '../components/Octocat';
    export function OctocatContainer() {
      const [octocat, setOctocat] = useState(null);
    
      useEffect(() => {
        async function getOctocat() {
          // gọi hàm fetch với phướng thực mặc định là GET
          const response = await fetch('https://api.github.com/users/octocat');
          const body = await response.json();
          // sử dụng React Hook để thông tin octocat vào state
          setOctocat(body);
        }
    
        getOctocat();
      }, []); // tham số mảng rỗng thể hiện hàm effect không phụ thuộc vào đối tượng nào, nó tương đương với hàm componentDidMount
    
      return <Octocat {...octocat} />;
    }
    

    Trong ví dụ trên tôi có sử dụng thêm JSX spread attributes để tránh việc phải khai báo props cho Octocat component. Mẫu này đặc biệt có ý nghĩa đối với các component mà bạn cần khai báo nhiều props.

    Định nghĩa component để kết xuất mã HTML

    Sau khi có dữ liệu rồi thì chúng ta sẽ kết xuất nó ra HTML như sau:

    src/components/Octocat/Octocat.jsx

    import React from 'react';
    
    export function Octocat(props) {
      const { login, avatar_url, name } = props;
      return (
        <ul>
          <li>{login}</li>
          <li>{name}</li>
          <li>
            <img src={avatar_url} alt={login} />
          </li>
        </ul>
      );
    }
    

    Như các bạn thấy, component trên chỉ làm nhiệm vụ bind dữ liệu lên màn hình mà thôi.

    Cùng xem kết quả nhé 🙂

    octocat

  • [React] Tôi đã làm toast component như thế nào?

    [React] Tôi đã làm toast component như thế nào?

    Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.

    Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.

    Mục đích của tôi khi viết component này phải đảm bảo nó dễ dùng như hàm window.alert() vậy. Trong bài viết này tôi sẽ sử dụng Material UI để hiển thị thông báo lỗi.

    Toast Context

    Vì chúng ta cần hàm hiển thị thông báo lỗi có thể sử dụng như window.alert() nên chúng ta cần khai báo một nơi lưu trữ để từ đó chúng ta có thể gọi được ở bất kỳ đâu trong ứng dụng. Có nhiều bạn sẽ nghĩ tới Redux nhưng trong bài viết này tôi sẽ sử dụng React Context API để làm việc đó. Chúng ta bắt đầu bằng việc khai báo ToastContext nào.

    src/contexts/toast.js

    import React, { useContext } from 'react';
    
    export const ToastContext = React.createContext();
    
    export const useToast = () => useContext(ToastContext);
    

    Toast Provider

    Trong React Context API có định nghĩa Provider để khi có sử thay đổi trong component thì các component bên trong có thể nhận biết sử thay đổi đó và phản anh kết quả thay đổi lên màn hình. Ở đây chúng ta cần hiển thị message lỗi mỗi khi có component nào đó thông báo về việc hiển thị message lỗi.

    import React, { useState } from 'react';
    import { Snackbar } from '@material-ui/core';
    import { Alert } from '@material-ui/lab';
    import { ToastContext } from '../contexts/toast';
    
    export const ToastProvider = (props) => {
      const { children } = props;
      const [state, setState] = useState({ isOpen: false });
    
      const show = (message) => {
        setState({ isOpen: true, message });
      };
    
      const hide = () => setState({ isOpen: false });
    
      const error = (message) => {
        show({ type: 'error', text: message });
      };
    
      const warn = (message) => {
        show({ type: 'warning', text: message });
      };
    
      const info = (message) => {
        show({ type: 'info', text: message });
      };
    
      const success = (message) => {
        show({ type: 'success', text: message });
      };
      const { isOpen, message } = state;
      return (
        <ToastContext.Provider
          value={{
            error: error,
            warn: warn,
            info: info,
            success: success,
            hide: hide
          }}>
          {children}
          {message && (
            <Snackbar open={isOpen} autoHideDuration={6000} onClose={hide}>
              <Alert elevation={6} variant="filled" onClose={hide} severity={message.type}>
                {message.text}
              </Alert>
            </Snackbar>
          )}
        </ToastContext.Provider>
      );
    };
    

    Sử dụng các hàm error, warn, info, success để hiển thị thông báo

    Để sử dụng được các hàm này trong ứng dụng bạn cần khai báo nó trong component chính của ứng dụng

    import React from 'react';
    import { ToastProvider } from './providers/ToastProvider';
    import { useToast } from './contexts/toast';
    import './App.css';
    
    function ButtonList() {
      const { error, warn, info, success } = useToast();
      return (
        <>
          <button onClick={() => error('error message!')}>error</button>
          <button onClick={() => warn('warn message!')}>warn</button>
          <button onClick={() => info('info message!')}>info</button>
          <button onClick={() => success('success message!')}>success</button>
        </>
      );
    }
    
    function App() {
      return (
        <ToastProvider>
          <ButtonList />
        </ToastProvider>
      );
    }
    export default App;
    

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết đã cung cấp cho các bạn thêm một các để hiển thị thông báo tốt hơn cho ứng dụng của bạn.

  • [React] Class Component và Function Component, bạn chọn viết theo cách nào?

    [React] Class Component và Function Component, bạn chọn viết theo cách nào?

    Trong thế giới React thì chắc hẳn ai cũng biết đến Class Component và Function Component. Tuy nhiên có thể có những hiểu nhầm về hai loại component này. Trong bài viết này tôi sẽ thử so sánh hai cách viết này để giúp bạn có thể lựa chọn viết theo cách nào. Chúng ta cùng bắt đầu nhé.

    Cú pháp

    Khác nhau đầu tiên giữa Class ComponentFunction Component thể hiện ngay ở cách khai báo.

    Class Component

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      // phương pháp này bắt buộc phải khai báo hàm để kết xuất mã HTML
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Cách khai báo này khá quen thuộc với các bạn có nền tảng lập trình hướng đối tượng (OOP). Với những bạn mới học React hoặc chuyển sang học React thì phương pháp tiếp cận này có vẻ phù hợp và dễ hiểu.

    Function Component

    import React from 'react';
    
    export function TestComponent() {
      // phương pháp xem kết xuất mã HTML như là giá trị trả về của hàm
      return <div>TestComponent</div>;
    }
    

    Function component sử dụng cách tiếp cận khác đó là sử dụng pure function để khai báo component. Ban đầu function component được sử dụng để viết các component chỉ với mục đích kết xuất HTML mà thôi. Với các component với theo hướng tiếp cận này thì bạn sẽ không can thiệp được vào lifecycle của component. Do đó nó thướng được biết đến với tên gọi Stateless Component.

    Props

    Class Component

    props trong Class Component được xem như giá trị truyển vào cho hàm khởi tạo class.

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      constructor(props) {
        super(props); // bắt buộc phải có dòng này để gọi hàm khởi tạo của class cha nhé
      }
    
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Function Component

    props trong Function Component thì được xem như là giá trị truyền vào hàm pure function khi định nghĩa component.

    import React from 'react';
    
    export function TestComponent(props) {
      return <div>TestComponent</div>;
    }
    

    Định nghĩa defaultPropspropTypes thì không có sự khác biệt giữa Class Component và Function Component.

    TestComponent.defaultProps = {};
    
    TestComponent.propTypes = {};
    

    State

    Trước khi React Hooks ra đời thì như đã nói ở trên Function Component con được biết đến với tên gọi Stateless Component. Nghĩa là nó không có state. Khi React Hooks ra đời thì Function Component cũng có state của riêng nó.

    Class Component

    state trong Class Component dược định nghĩa như sau:

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      constructor(props) {
        super(props);
        // khởi tạo giá trị state
        this.state = { isLoading: false };
      }
    
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Khi muốn thay đổi giá trị state bạn gọi phương thức setState của component:

    this.setState((state) => ({ isLoading: true }));
    

    Function Component

    state trong Function Component được định nghĩa như sau:

    import React, { useState } from 'react';
    
    export function TestComponent(props) {
      // giá trị khởi tạo state được truyền vào trong useState hook
      const [state, setState] = useState({ isLoading: false });
    
      return <div>TestComponent</div>;
    }
    

    Các bạn để ý hàm useState trả về giá trị của component state trong biến state và hàm setState. Khi muốn thay đổi giá trị của state thì bạn có thể gọi hàm setState.

    setState({ isLoading: true });
    

    Component Lifecycle

    Với Class component các bạn sẽ thấy component lifecycle khá rõ ràng với các hàm như componentDidMount, componentDidUpdate. Function Component thì không như vậy, toàn bộ việc sử lý lifecycle đều thông qua useEffect hook.

    // componentDidMount
    useEffect(() => {
      return () => {}; // componentWillUnmount
    }, []);
    
    // componentDidUpdate
    useEffect(() => {
      return () => {}; // componentWillUnmount
    }, [state]);
    

    Như các bạn thấy thì componentDidMountcomponentDidUpdate không chỉ định rõ khi nào thì hàm được gọi. Việc gọi hàm thì tự chúng ta hiểu dựa theo lifecycle của React component thôi. Với Function Component và useEffect thì khác, bạn có thể thấy [][state] chị định rõ ràng đối tượng phụ thuộc mà khi chúng thay đổi thì hàm truyển vào useEffect sẽ được gọi. Có vẻ như nó lại tường mình hơn là các hàm lifecycle trong class Component.

    Sau sự có mặt của TypeScript thì có lẽ Class Component và React Hooks thì có lẽ bạn Class Component chiếm ưu thế tuyệt đối. Thời điểm đó anh em thi nhau viết Class Component với TypeScript và tôi cũng thế :). Ngay đến cả facebook cũng support TypeScript với create-react-app nữa cơ mà. Thế nhưng khi React Hooks xuất hiện thì có vẻ gió đã đổi chiều, việc xử lý state và lifecycle với hook có vẻ đơn giản hơn rất nhiều. Các bạn thì thấy thế nào. Bạn sẽ chọn cách nào với dự án của mình?

  • [React] Những điều có thể bạn chưa biết về Function Component

    [React] Những điều có thể bạn chưa biết về Function Component

    Chắc hẳn khi đọc bài này bạn cũng đã từng nghe nói đến các khái niệm Function Component và Class Component hay Stateless Component và Stateful Component. Cũng có thể bạn cũng từng nghe Function Component là Stateless Component. Vậy nó có thực sự là như thế. Trong bài viết này chúng ta cùng nhau tìm hiểu nhé.

    Nội dung bài viết gồm các phần sau:

    • Function Component là gì?
    • Function Component có props không?
    • Function component và sức mạnh đến từ React Hook

    Function component là gi?

    Function component là React Component được viết bằng Pure Function. Dễ hiểu hơn thì nó được viết theo dạng sau:

    export function Counter() {
      return <div>count</div>;
    }
    

    Function Component có props không?

    Có bạn sẽ đặt câu hỏi sau khi nhìn thấy cách viết trên, Function Component có props không? Câu trả lời là có. Bạn có thể truyền props cho component như sau:

    import React from 'react';
    
    export function Counter(props) {
      return <div>{props.count}</div>;
    }
    

    Và bạn vẫn có thể khai báo propTypes cho component như bình thường

    Counter.propTypes = {
      count: PropTypes.number
    };
    

    Mọi thứ đều ổn đến lúc này và có lẽ bạn cũng có nghe ai đó nhắc đến Stateless component. Có thể bạn cũng nghe ai đó nói Function Component là Stateless component. Vậy thì Function component có thực sự là Stateless không?

    Function component và sức mạnh đến từ React Hook

    Trước khi React Hooks ra đời, Function component thực sự là Stateless component. Đơn giản bởi bạn chẳng có cách nào thêm state cho nó cả. Function component khi đó chỉ có thể được dùng để render HTML với props truyền vào mà thôi.

    Khi React Hooks ra đời thì lại là câu chuyện khác. Function component có thể có state của riêng nó. Vậy làm sao để thêm state cho Function component? Với useState bạn có thể thêm state cho nó.

    Thay đổi một chút đoạn source code bên trên như sau là Functiona component của bạn đã support state rồi đấy.

    import React, { useState } from 'react';
    import PropTypes from 'prop-types';
    
    export function Counter() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <button onClick={() => setCount(count - 1)}>-</button>
          {count}
          <button onClick={() => setCount(count + 1)}>+</button>
        </div>
      );
    }
    
    Counter.propTypes = {
      count: PropTypes.number
    };
    

    Kết luận lại là Function Component sẽ là Stateless Component nếu không có sự trợ giúp của React Hooks. Với React Hooks thì Function Component đã có toàn bộ sức mạnh như Class Component rồi.

    Cám ơn các bạn đã dành thời gian theo dõi bài viết. Hy vọng bày viết sẽ giúp ích cho các bạn.

  • [React] Tôi đã tạo thư viện React Component như thế nào

    [React] Tôi đã tạo thư viện React Component như thế nào

    Trong bài viết này tôi sẽ hướng dẫn các bạn tạo ra một thư viện component của riêng mình. Bạn cũng có thể public thư viện nếu thích. Nhiều bạn sẽ tự hỏi rằng có nhiều thư viện lắm rồi thì viết thêm làm chi. Mục đích mình viết bài này để có thể tự tạo ra một package gồm những common component có thể sử dụng lại được. Kết hợp với việc dùng mono repository gồm nhiều packages trong yarn workspaces, các bạn có thể tách code của mình thành các package cho dễ quản lý. Chúng ta cùng bắt đầu nhé.

    Thêm các dependencies cần thiết

    • react & react-dom React component mà nên cần 2 thư viện này là đương nhiên rồi.
    hieunv@HieuNV uikit % yarn add react react-dom
    yarn add v1.22.0
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    success Saved lockfile.
    success Saved 3 new dependencies.
    info Direct dependencies
    ├─ [email protected]
    └─ [email protected]
    info All dependencies
    ├─ [email protected]
    ├─ [email protected]
    └─ [email protected]
    ✨  Done in 7.91s.
    
    yarn add v1.22.0
    info No lockfile found.
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    ✨  Done in 15.05s.
    
    hieunv@HieuNV uikit % yarn add react-scripts -D
    yarn add v1.22.0
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    ✨  Done in 41.42s.
    

    Chúng ta sẽ sử dụng webpack config đã được config trong react-scripts

    Cấu hình styleguidist

    styleguidist.config.js

    module.exports = {
      propsParser: require('react-docgen').parse,
      webpackConfig: require('react-scripts/config/webpack.config')
    };
    

    Thêm script run styleguidist

    package.json

    {
      "name": "uikit",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT",
      "scripts": {
        "dev": "npx styleguidist server"
      },
      "devDependencies": {
        "react-scripts": "^3.4.0",
        "react-styleguidist": "^10.6.2"
      },
      "dependencies": {
        "react": "^16.13.0",
        "react-dom": "^16.13.0"
      }
    }
    

    Khởi động dev server thôi nào

    hieunv@HieuNV uikit % yarn dev
    yarn run v1.22.0
    $ npx styleguidist server
    Loading webpack config from:
    /Users/hieunv/Projects/hieunv/uikit/node_modules/react-scripts/config/webpack.config.js
    
    ℹ 「wds」: Project is running at http://localhost:6060/
    ℹ 「wds」: webpack output is served from undefined
    ℹ 「wds」: Content not from webpack is served from /Users/hieunv/Projects/hieunv/uikit
    You can now view your style guide in the browser:
    
      Local:            http://localhost:6060/
      On your network:  http://192.168.1.11:6060/
    

    Sau khi khởi động server xong các bạn truy cập và link http://192.168.1.11:6060/

    Do chưa có component nào được định nghĩa nên các bạn sẽ nhận được thông báo như trên.

    Bắt đầu viết component đầu tiên thôi nào

    styleguidist sẽ tự động đọc các components trong thư mục src/components/*README.md trong thư mục component tương ứng để generate ra document sử dụng react-docgen trên styleguidist server

    src/component/Button/Button.jsx

    module.exports = {
      propsParser: require('react-docgen').parse,
      webpackConfig: require('react-scripts/config/webpack.config')
    };
    

    src/component/Button/README.md

    <Button>Test Button</Button>
    

    Chúng ta truy cập lại vào http://192.168.1.11:6060/ sẽ thấy component được sử dụng để test và có cả document nữa luôn.

    Styleguidist server với react-docgen

    Export component ra bên ngoài để có thể sử dụng ở package khác

    src/index.js

    export * from './components/Button/Button';
    
    

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết sẽ giúp ích cho các dự án của bạn.

  • Hướng dẫn sử dung mono repository với yarn workspaces

    Hướng dẫn sử dung mono repository với yarn workspaces

    Thông thường khi viết ứng dụng node, chúng ta thương sử dụng các thử viện có sẵn trên npmjs. Trong bài viết này tôi sẽ hướng dẫn các bạn tạo một dự án node có thể chia nhỏ thành các packages nhưng vẫn có thể viết trên cùng một repository. Yarn cung cấp cho bạn chức năng để tạo và link các packages với nhau bằng yarn workspaces. Chúng ta tìm hiểu yarn workspaces hoạt động ra sao nhé

    Các bạn có thể tham khảo hướng dẫn cài đặt yarn ở đây

    Tạo mới project

    hieunv@HieuNV hieunv % mkdir mono
    hieunv@HieuNV hieunv % cd mono
    hieunv@HieuNV mono % yarn init
    yarn init v1.22.0
    question name (mono):
    question version (1.0.0):
    question description:
    question entry point (index.js):
    question repository url:
    question author:
    question license (MIT):
    question private:
    success Saved package.json
    ✨  Done in 5.46s.
    hieunv@HieuNV mono %
    

    Enable yarn workspaces

    • Thêm đoạn "workspaces": ["packages/*"] vào package.json

    package.json

    {
      "name": "mono",
      "workspaces": ["packages/*"],
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT"
    }
    

    Tiến hành tạo mono repository theo các bước sau:

    • Tạo 3 package trong thư mục packages như sau:
    hieunv@HieuNV mono % find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'
    .
    |____package.json
    |____packages
    | |____package-c
    | | |____index.js
    | | |____package.json
    | |____package-b
    | | |____index.js
    | | |____package.json
    | |____package-a
    | | |____index.js
    | | |____package.json
    
    • Tại thư mục package-apackage-b, thực hiện đăng ký package:

    package-a

    hieunv@HieuNV package-a % yarn link
    yarn link v1.22.0
    success Registered "package-a".
    info You can now run `yarn link "package-a"` in the projects where you want to use this package and it will be used instead.
    ✨  Done in 0.04s.
    

    package-b

    hieunv@HieuNV package-b % yarn link
    yarn link v1.22.0
    success Registered "package-b".
    info You can now run `yarn link "package-b"` in the projects where you want to use this package and it will be used instead.
    ✨  Done in 0.04s.
    
    • Thêm package-apackage-b như là dependency của package-c:
    hieunv@HieuNV package-c % yarn link "package-a"
    yarn link v1.22.0
    success Using linked package for "package-a".
    ✨  Done in 0.04s.
    hieunv@HieuNV package-c % yarn link "package-b"
    yarn link v1.22.0
    success Using linked package for "package-b".
    ✨  Done in 0.04s.
    
    • Trong package-a tạo các file sau:

    package-a/package.json

    {
      "type": "module",
      "name": "package-a",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT"
    }
    

    package-a/index.js

    export function packageA() {
      console.log('Package A');
    }
    
    • Trong của package-b tạo các file sau:

    package-b/package.json

    {
      "type": "module",
      "name": "package-b",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT"
    }
    

    package-b/index.js

    export function packageB() {
      console.log('Package B');
    }
    
    • Sử dụng các hàm được khai báo package-apackage-b

    package-c/package.json

    {
      "type": "module",
      "name": "package-c",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT"
    }
    

    package-c/index.js

    import { packageA } from 'package-a';
    import { packageB } from 'package-b';
    
    packageA();
    packageB();
    
    • Run package-c/index.js
    hieunv@HieuNV package-c % node index.js
    (node:10165) ExperimentalWarning: The ESM module loader is experimental.
    Package A
    Package B
    

    Tài liệu tham khảo:

    • https://classic.yarnpkg.com/en/docs/workspaces/