[Series] Cấu trúc dữ liệu và thuật toán trong Javascript – P3. Linked list data structure

Linked list là 1 dạng cấu trúc dữ liệu mà mỗi phần tử trong đó sẽ liên kết với phần tử đứng đằng sau nó. Do tính chất của cấu trúc nên việc truy xuất 1 phần tử thông qua index cụ thể sẽ chậm hơn so với mảng vì Linked list phải loop thông qua các phần tử bắt đầu từ begining (head) cho đến khi tìm được phần tử đó

Implement:

B1: Khởi tạo node với 2 thuộc tính là value và next:

B2: Viết hàm push

    push(value) {
      const node = createNode(value);

      if (this.length === 0) {
        this.head = node;
        this.tail = node;
        this.length++;
        return node;
      }

      this.tail.next = node;
      this.tail = node;
      this.length++;
    }

B3: Viết hàm pop

    pop() {
      if (this.isEmpty()) {
        return null;
      }
      if (this.head === this.tail) {
        this.head = null;
        this.tail = null;
        this.length--;
        return null;
      }

      let current = this.head;
      let penultimate;

      while (current) {
        if (current.next === this.tail) {
          penultimate = current;
          break;
        }
        current = current.next;
      }

      penultimate.next = null;
      this.tail = penultimate;
      this.length--;
    }

B4: Viết hàm get

get(index) {
      if (index < 0 || index > this.length) {
        return null;
      }

      if (index === 0) {
        return this.head;
      }

      let current = this.head;
      let i = 0;
      while (i < index) {
        i++;
        current = current.next;
      }
      return current;
    }

B5: Viết hàm delete

    delete(index) {
      if (index < 0 || index > this.length) {
        return null;
      }
      if (index === 0) {
        let deleted = this.head;
        this.head = deleted.next;
        this.length--;
        return deleted;
      }

      let current = this.head;
      let previous;
      let i = 0;

      while (i < index) {
        i++;
        previous = current;
        current = current.next;
      }
      let deleted = current;
      previous.next = current.next;
      this.length--;
      return deleted;
    }

Full code

function createNode(value) {
  return {
    value,
    next: null
  };
}

function createLinkedList() {
  return {
    head: null,
    tail: null,
    length: 0,
    isEmpty() {
      return this.length === 0;
    },
    push(value) {
      const node = createNode(value);

      if (this.length === 0) {
        this.head = node;
        this.tail = node;
        this.length++;
        return node;
      }

      this.tail.next = node;
      this.tail = node;
      this.length++;
    },
    pop() {
      if (this.isEmpty()) {
        return null;
      }
      if (this.head === this.tail) {
        this.head = null;
        this.tail = null;
        this.length--;
        return null;
      }

      let current = this.head;
      let penultimate;

      while (current) {
        if (current.next === this.tail) {
          penultimate = current;
          break;
        }
        current = current.next;
      }

      penultimate.next = null;
      this.tail = penultimate;
      this.length--;
    },
    get(index) {
      if (index < 0 || index > this.length) {
        return null;
      }

      if (index === 0) {
        return this.head;
      }

      let current = this.head;
      let i = 0;
      while (i < index) {
        i++;
        current = current.next;
      }
      return current;
    },
    delete(index) {
      if (index < 0 || index > this.length) {
        return null;
      }
      if (index === 0) {
        let deleted = this.head;
        this.head = deleted.next;
        this.length--;
        return deleted;
      }

      let current = this.head;
      let previous;
      let i = 0;

      while (i < index) {         i++;         previous = current;         current = current.next;       }       let deleted = current;       previous.next = current.next;       this.length--;       return deleted;     },     print() {       let arr = [];       let current = this.head;       while (current) {         arr.push(current.value);         current = current.next;       }       return arr.join("=>");
    }
  };
}

Link tham khảo: https://www.tutorialspoint.com/data_structures_algorithms/linked_list_algorithms.htm

[Series] Cấu trúc dữ liệu và thuật toán trong Javascript – P2. Stack data structure

Stack là 1 dạng cấu trúc dữ liệu tuân theo nguyên tắc LIFO ( Last in First out).
Giống như chồng bát dĩa, chúng ta phải lấy cái dĩa trên cùng trước khi muốn lấy những cái ở phía dưới.

Ví dụ minh họa:

stack-data-structure

Các hàm và thuộc tính cơ bản của Stack:

  • POP
  • PUSH
  • isEmpty
  • peek
  • length

Implement

function createStack() {
  const stack = [];

  return {
    push(item) {
      stack.push(item);
    },
    pop() {
      stack.pop();
    },
    peek() {
      return stack[stack.length - 1];
    },
    get length() {
      return stack.length;
    },
    isEmpty() {
      return stack.length === 0;
    }
  };
}

Link tham khảo: https://www.tutorialspoint.com/data_structures_algorithms/stack_algorithm.htm

[Series] Cấu trúc dữ liệu và thuật toán trong Javascript – P1. Queue data structure

Queue là 1 dạng cấu trúc dữ liệu theo cơ chế FIFO ( first in first out hay last in last out ). Có nghĩa là item nào được đưa vào trước thì sẽ được lấy ra trước

Ví dụ minh họa:

queue_example

Các phương thức và thuộc tính cơ bản của Queue:

  • Add or enqueue
  • Remove or dequeue
  • Peek – Phương thức kiểm tra phần tử removed sắp tới
  • length
  • isEmpty

Implement:

function createQueue() {
  const queue = [];

  return {
    enqueue(item) {
      queue.unshift(item);
    },
    dequeue() {
      queue.pop();
    },
    peek() {
      return queue[queue.length - 1];
    },
    get length() {
      return queue.length;
    },
    isEmpty() {
      return queue.length === 0;
    }
  };
}

Link tham khảo chi tiết: https://www.tutorialspoint.com/data_structures_algorithms/dsa_queue.htm

[Series] Các mẫu thiết kế component trong React – Phần 7: Giới thiệu về Provider Pattern

Review đoạn code sau:

import React from "react";
import ReactDOM from "react-dom";

import { Input } from "reactstrap";

import "./styles.css";

class CheckBox extends React.Component {
  render() {
    return <Layer1 checked={this.props.checked} onCheck={this.props.onCheck} />;
  }
}

const Layer1 = ({ checked, onCheck }) => (
  <Layer2 checked={checked} onCheck={onCheck} />
);
const Layer2 = ({ checked, onCheck }) => (
  <React.Fragment>
    {checked ? "Btn checked" : "Btn unchecked"}
    <Layer3 checked={checked} onCheck={onCheck} />
  </React.Fragment>
);
const Layer3 = ({ checked, onCheck }) => (
  <Layer4 checked={checked} onCheck={onCheck} />
);
const Layer4 = ({ checked, onCheck }) => (
  <Input type="checkbox" checked={checked} onClick={onCheck} />
);
class App extends React.Component {
  state = { checked: false };
  onCheck = () => this.setState(({ checked }) => ({ checked: !checked }));
  render() {
    return <CheckBox checked={this.state.checked} onCheck={this.onCheck} />;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Trong trường hợp này:  việc truyền props checked, onCheck từ App component đến Layer4 component được gọi là Props drilling. Rất có thể lúc mới bắt đầu code React bạn cũng đã từng code như trên. :))

Và hãy thử tưởng tượng xem, sẽ ra sao nếu 1 ngày:

  • Layer4 component không cần checked props nữa.
  • Sau vài ngày suy nghĩ thì vẫn nên cần checked props.
  • Hoặc checked đặt tên không hợp lý đổi lại thành check thì hợp lý hơn.
  • …

Hahaha, nếu mà điều đó xảy ra thật thì mình cá là sẽ có khối người muốn chửi thề đó :))).

Và để khắc phục được vấn đề trên ta có thể dùng API mới mà React đã cung cấp tên là Context. Child components có thể lấy được props từ parent component mà không cần phải thông qua các lớp khác nhau như trên.

Các bước thực hiện:
Bước 1: Khởi tạo CheckBoxContext thông qua hàm React.createContext()

const CheckBoxContext = React.createContext({
  checked: false,
  onCheck: () => {}
});

Bước 2: Khai báo các props cần truyền thông qua Provider.

      <CheckBoxContext.Provider value={this.state} {...this.props}>
        <Layer1 />
      </CheckBoxContext.Provider>

Bước 3. Nhận props thông qua Consumer.

  <CheckBox.Consumer>
    {({ checked, onCheck }) => (
      <Input type="checkbox" checked={checked} onClick={onCheck} />
    )}
  </CheckBox.Consumer>

Full code:

import React from "react";
import ReactDOM from "react-dom";

import { Input } from "reactstrap";

import "./styles.css";

const CheckBoxContext = React.createContext({
  checked: false,
  onCheck: () => {}
});

class CheckBox extends React.Component {
  static Consumer = CheckBoxContext.Consumer;
  onCheck = () => this.setState(({ checked }) => ({ checked: !checked }));
  state = {
    checked: false,
    onCheck: this.onCheck
  };
  render() {
    return (
      <CheckBoxContext.Provider value={this.state} {...this.props}>
        <Layer1 />
      </CheckBoxContext.Provider>
    );
  }
}

const Layer1 = () => <Layer2 />;
const Layer2 = () => (
  <CheckBox.Consumer>
    {({ checked }) => (
      <React.Fragment>
        {checked ? "Btn checked" : "Btn unchecked"}
        <Layer3 />
      </React.Fragment>
    )}
  </CheckBox.Consumer>
);
const Layer3 = () => <Layer4 />;
const Layer4 = () => (
  <CheckBox.Consumer>
    {({ checked, onCheck }) => (
      <Input type="checkbox" checked={checked} onClick={onCheck} />
    )}
  </CheckBox.Consumer>
);
class App extends React.Component {
  render() {
    return <CheckBox />;
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

[Series] Các mẫu thiết kế component trong React – Phần 6: Giới thiệu về Control Props

Control Props Pattern:

  • cho phép user có thể quản lý 1 hay nhiều props trong state component,
  • nếu user không quản lý, tự bản thân component sẽ quản lý  state.
  • ngược lại user sẽ phải tự quản lý sự thay đổi giá trị mà user đã cung cấp cho component

Thực hiện:

  1. Do giá trị state của component có thế từ phía user truyền vào hoặc không nên ta cần hàm getState để trả về state mà component cần sử dụng.
      isControlled = key => this.props[key] !== undefined;
    
      getState = (state = this.state) =>
        Object.entries(state).reduce((combineState, [key, value]) => {
          if (this.isControlled(key)) {
            combineState[key] = this.props[key];
          } else {
            combineState[key] = value;
          }
          return combineState;
        }, {});
  2. Component cung cấp 1 hàm update state:
      • mỗi khi state thay đổi thì gọi hàm handleChangeState với tham số là state đã thay đổi để người dùng có thể update lại props của mình theo ý muốn.
      • chỉ update state nếu các thay đổi không bao gồm props mà user control
      internalSetState = (changes, callback = emptyFnc) => {
        let allChanges;
        this.setState(
          state => {
            const combineState = this.getState(state);
            const changesObject =
              typeof changes === "function" ? changes(combineState) : changes;
            allChanges = changesObject;
            const nonControlledChanges = Object.entries(changesObject).reduce(
              (newChanges, [key, value]) => {
                if (!this.isControlled(key)) {
                  newChanges[key] = value;
                }
                return newChanges;
              },
              {}
            );
            return Object.keys(nonControlledChanges).length > 0
              ? nonControlledChanges
              : null;
          },
          () => {
            this.props.onStateChange(allChanges);
            callback(allChanges);
          }
        );
      };
  3. Cập nhật state ở phía user
      handleStateChange = ({ checked }) => {
        this.setState({ appChecked: checked });
      };

Full code:

import React from "react";
import ReactDOM from "react-dom";

import { Input } from "reactstrap";

import "./styles.css";

function emptyFnc() {}

class CheckBox extends React.Component {
  static defaultProps = {
    onStateChange: () => {}
  };
  state = { checked: false };

  internalSetState = (changes, callback = emptyFnc) => {
    let allChanges;
    this.setState(
      state => {
        const combineState = this.getState(state);
        const changesObject =
          typeof changes === "function" ? changes(combineState) : changes;
        allChanges = changesObject;
        const nonControlledChanges = Object.entries(changesObject).reduce(
          (newChanges, [key, value]) => {
            if (!this.isControlled(key)) {
              newChanges[key] = value;
            }
            return newChanges;
          },
          {}
        );
        return Object.keys(nonControlledChanges).length > 0
          ? nonControlledChanges
          : null;
      },
      () => {
        this.props.onStateChange(allChanges);
        callback(allChanges);
      }
    );
  };

  onCheck = () =>
    this.internalSetState(
      ({ checked }) => ({ checked: !checked }),
      allChanges => console.log("click", allChanges)
    );

  isControlled = key => this.props[key] !== undefined;

  getState = (state = this.state) =>
    Object.entries(state).reduce((combineState, [key, value]) => {
      if (this.isControlled(key)) {
        combineState[key] = this.props[key];
      } else {
        combineState[key] = value;
      }
      return combineState;
    }, {});

  render() {
    return (
      <Input
        type="checkbox"
        checked={this.getState().checked}
        onClick={this.onCheck}
      />
    );
  }
}

class App extends React.Component {
  initialState = { appChecked: false };
  state = this.initialState;

  handleStateChange = ({ checked }) => {
    this.setState({ appChecked: checked });
  };

  render() {
    const { appChecked } = this.state;
    return (
      <React.Fragment>
        <CheckBox checked={appChecked} onStateChange={this.handleStateChange} />
        <CheckBox checked={appChecked} onStateChange={this.handleStateChange} />
      </React.Fragment>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

 

[Series] Các mẫu thiết kế component trong React – Phần 5: Giới thiệu về Render Props Patern và State Reducer

Render props:

  • 1 hàm render JSX dựa trên state và helper argument.
  • simple and powerful pattern
  • flexible trong việc chia sẻ component logic mà vẫn giữ được UI flexibility

State Reducer: là 1 pattern cho phép user có thể quản lý được state của component

Implement:

Ở component ta viết 1 hàm internalSetState dùng để update state ( gồm state đã thay đổi sau khi thực thi component logic và state mà user mong muốn )

  internalSetState = (changes, callback) => {
    this.setState(state => {
      const changesObject =
        typeof changes === "function" ? changes(state) : changes;
      const reducerChange = this.props.stateReducer(state, changesObject);
      return reducerChange;
    }, callback);
  };

Component cung cấp 1 hàm cho phép user định nghĩa state mong muốn thay đổi

  updateStateReducer = (state, changes) => {
    if (this.state.timesClicked > 4) {
      return {
        ...changes,
        checked: false
      };
    }
    return changes;
  };

Full code:

import React from "react";
import ReactDOM from "react-dom";

import { Input } from "reactstrap";

import "./styles.css";

class CheckBox extends React.Component {
  static defaultProps = {
    initialCheck: false,
    onReset: () => {}
  };
  initialState = { checked: this.props.initialCheck };
  state = this.initialState;

  internalSetState = (changes, callback) => {
    this.setState(state => {
      const changesObject =
        typeof changes === "function" ? changes(state) : changes;
      const reducerChange = this.props.stateReducer(state, changesObject);
      return reducerChange;
    }, callback);
  };

  onCheck = () =>
    this.internalSetState(
      ({ checked }) => ({ checked: !checked }),
      () => this.props.handleCheck(this.state.checked)
    );

  reset = () =>
    this.internalSetState(this.initialState, () =>
      this.props.onReset(this.state.checked)
    );

  getStateAndHelper = () => ({
    checked: this.state.checked,
    onCheck: this.onCheck,
    reset: this.reset
  });

  render() {
    return this.props.children(this.getStateAndHelper());
  }
}

class App extends React.Component {
  initialState = { timesClicked: 0 };
  state = this.initialState;

  handleCheck = (...args) => {
    this.setState(({ timesClicked }) => ({ timesClicked: timesClicked + 1 }));
  };

  handleReset = (...args) => this.setState(this.initialState);

  updateStateReducer = (state, changes) => {
    if (this.state.timesClicked > 4) {
      return {
        ...changes,
        checked: false
      };
    }
    return changes;
  };

  render() {
    const { timesClicked } = this.state;
    return (
      <CheckBox
        handleCheck={this.handleCheck}
        stateReducer={this.updateStateReducer}
        onReset={this.handleReset}
      >
        {({ checked, onCheck, reset }) => (
          
{timesClicked > 4 ? "Click reset để active component" : timesClicked > 0 ? `Time Clicked: ${timesClicked}` : null} Reset
)} </CheckBox> ); } } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);

 

 

[Series] Các mẫu thiết kế component trong React – Phần 4: Validate Compound Component Context Cosumers

Đặt trường hợp nếu user đặt Consumer bên ngoài Provider như sau:

  return (
    
CheckBox is checked CheckBox is uncheck
);

App sẽ báo lỗi “Cannot read property ‘checked’ of undefined”. Nguyên nhân do Consumer được đặt bên ngoài Provider ==> Consume không nhận được giá trị từ Provider truyền xuống.

Để giải quyết trường hợp này ta có thể khai báo giá trị mặc định như sau:

const CheckBoxContext = React.createContext({
  checked: false,
  onCheck: () => ({})
});

Nhưng component sẽ không hoạt động như mong muốn. Vì vậy ta nên chuyển sang hướng kiểm tra sự tồn tại của contextValue, nếu có thì hiển thị Component con, nếu không thì báo lỗi.

function CheckBoxConsumer(props) {
  return (
    <CheckBoxContext.Consumer>
      {context => {
        if (!context) {
          throw new Error(
            " Checkbox compound component nên được đặt bên trong Checkbox component"
          );
        }
        return props.children(context);
      }}
    </CheckBoxContext.Consumer>
  );
}

Full code:

import React from "react";
import ReactDOM from "react-dom";

import { Input } from "reactstrap";

import "./styles.css";

const CheckBoxContext = React.createContext();

function CheckBoxConsumer(props) {
  return (
    <CheckBoxContext.Consumer>
      {context => {
        if (!context) {
          throw new Error(
            " Checkbox compound component nên được đặt bên trong Checkbox component"
          );
        }
        return props.children(context);
      }}
    </CheckBoxContext.Consumer>
  );
}

class CheckBox extends React.Component {
  static Checked = ({ children }) => (
    <CheckBoxConsumer>
      {contextValue => (contextValue.checked ? children : null)}
    </CheckBoxConsumer>
  );

  static UnChecked = ({ children }) => (
    <CheckBoxConsumer>
      {contextValue => (contextValue.checked ? null : children)}
    </CheckBoxConsumer>
  );

  static Input = props => (
    <CheckBoxConsumer>
      {contextValue => (
        <Input
          type="checkbox"
          checked={contextValue.checked}
          onClick={contextValue.onCheck}
          {...props}
        />
      )}
    </CheckBoxConsumer>
  );
  state = {
    checked: true
  };
  onCheck = () =>
    this.setState(
      ({ checked }) => ({ checked: !checked }),
      () => this.props.handleCheck(this.state.checked)
    );
  state = {
    checked: false,
    onCheck: this.onCheck
  };
  render() {
    return (
      <CheckBoxContext.Provider value={this.state}>
        {this.props.children}
      </CheckBoxContext.Provider>
    );
  }
}

function App() {
  function handleCheck(...args) {
    console.log("handleCheck", ...args);
  }
  return (
    
CheckBox is checked CheckBox is uncheck
); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);

[Series] Các mẫu thiết kế component trong React – Phần 3: Xây dựng Compound components linh hoạt hơn với Context API

Giả sử: Nếu user sử dụng không theo cấu trúc như sau:

    <CheckBox handleCheck={handleCheck}>
      <CheckBox.Checked>CheckBox is checked</CheckBox.Checked>
      
<CheckBox.UnChecked>CheckBox is uncheck</CheckBox.UnChecked> </CheckBox>

==> Checkbox Component sẽ hoạt động không đúng như mong đợi.

Nguyên nhân: Hàm React.Children.map đã lặp qua các thành phần con của CheckBox component bao gồm: CheckBox.Checked , CheckBox.UnChecked and div
nhưng không lặp qua thành phần con của thành phần con ( là thành phần con của ) ==> không nhận được các props cần thiết

Khắc phục: Sử dụng React Context để truyền các props từ component cha xuống component con 1 cách linh hoạt hơn

Bước 1: Khởi tạo context thông qua hàm React.context()

const CheckBoxContext = React.createContext();

Bước 2: Khởi tạo Provider và khai báo các props cần truyền từ Component cha xuống Component con:

  render() {
    return (
      <CheckBoxContext.Provider
        value={{
          checked: this.state.checked,
          onCheck: this.onCheck
        }}
      >
        {this.props.children}
      </CheckBoxContext.Provider>
    );
  }

Bước 3: Khởi tạo Consumer nhận props từ Component cha:

  static UnChecked = ({ children }) => (
    <CheckBoxContext.Consumer>
      {contextValue => (contextValue.checked ? null : children)}
    </CheckBoxContext.Consumer>
  );

Kết luận:

  • React.Context giúp chúng ta dễ dàng, linh hoạt hơn trong việc truyền các props từ Component cha xuống Component con.
  • Thứ tự thực hiện:
    • Khởi tạo Context thông qua hàm React.createContext().
    • Wrap các thành phần con và khai báo các props cần truyền xuống Component con ở Provider.
    • Sử dụng Consumer để nhận giá trị từ component cha.

[Series] Các mẫu thiết kế component trong React – Phần 2: Xây dựng Checkbox component theo Compound components

Đặt vấn đề: Viết component CheckBox với giao diện gồm: 1 tag Input và 2 tag text thể hiện trạng thái check hoặc uncheck của CheckBox, sao cho người dùng có thể dễ dàng thay đổi thứ tự của của các tag:

Ví dụ:

Trong trường hợp này ta có thể sử dụng Compound component để giải quyết vấn đề trên. Đây  là 1 pattern cho phép người dùng thay đổi giao diện và thứ tự của các thành phần con mà không làm ảnh hưởng đến chức năng chính của component.

Phương pháp thực hiện:

  • Các thành phần con sẽ được viết dưới dạng function component và được xem là static properties của component cha
  • Sử dụng hàm React.Children.map để truyền các state của component cha xuống cho component con.

Các bước thực hiện:

  • Bước 1: Viết các thành phần con
  static Checked = ({ checked, children }) => (checked ? children : null);
  static UnChecked = ({ checked, children }) => (checked ? null : children);
  static Input = ({ checked, onCheck, ...props }) => (
    <Input type="checkbox" checked={checked} onClick={onCheck} {...props} />
  );
  • Bước 2: Truyền state của component cha xuống component con
  render() {
    return React.Children.map(this.props.children, childElement =>
      React.cloneElement(childElement, {
        checked: this.state.checked,
        onCheck: this.onCheck
      })
    );
  }

Full code:

import React from "react";
import ReactDOM from "react-dom";

import { Input } from "reactstrap";

import "./styles.css";

class CheckBox extends React.Component {
  static Checked = ({ checked, children }) => (checked ? children : null);
  static UnChecked = ({ checked, children }) => (checked ? null : children);
  static Input = ({ checked, onCheck, ...props }) => (
    <Input type="checkbox" checked={checked} onClick={onCheck} {...props} />
  );
  state = {
    checked: true
  };
  onCheck = () =>
    this.setState(
      ({ checked }) => ({ checked: !checked }),
      () => this.props.handleCheck(this.state.checked)
    );

  render() {
    return React.Children.map(this.props.children, childElement =>
      React.cloneElement(childElement, {
        checked: this.state.checked,
        onCheck: this.onCheck
      })
    );
  }
}

function App() {
  function handleCheck(...args) {
    console.log("handleCheck", ...args);
  }
  return (
    <CheckBox handleCheck={handleCheck}>
      <CheckBox.Checked>CheckBox is checked</CheckBox.Checked>
      <CheckBox.Input />
      <CheckBox.UnChecked>CheckBox is uncheck</CheckBox.UnChecked>
    </CheckBox>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);