Learn-Redux

Store


Redux Flow Redux Flow

1. Lý thuyết

function createStore(reducer){
 let state = reducer()
 ...
}

Todo App

Todo App Components

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Todo App</title>
    <link rel="stylesheet" href="./css/base.css" />
    <link rel="stylesheet" href="./css/index.css" />
  </head>

  <body>
    <!-- <div id="root"></div> -->
    <section class="todoapp">
      <header class="header">
        <h1>todos</h1>
        <input
          class="new-todo"
          placeholder="What needs to be done?"
          autofocus
        />
      </header>
      <!-- This section should be hidden by default and shown when there are todos -->
      <section class="main">
        <input id="toggle-all" class="toggle-all" type="checkbox" />
        <label for="toggle-all">Mark all as complete</label>
        <ul class="todo-list">
          <!-- These are here just to show the structure of the list items -->
          <!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
          <li class="completed">
            <div class="view">
              <input class="toggle" type="checkbox" checked />
              <label>Taste JavaScript</label>
              <button class="destroy"></button>
            </div>
            <input class="edit" value="Create a TodoMVC template" />
          </li>
          <li>
            <div class="view">
              <input class="toggle" type="checkbox" />
              <label>Buy a unicorn</label>
              <button class="destroy"></button>
            </div>
            <input class="edit" value="Rule the web" />
          </li>
        </ul>
      </section>
      <!-- This footer should be hidden by default and shown when there are todos -->
      <footer class="footer">
        <!-- This should be `0 items left` by default -->
        <span class="todo-count"><strong>0</strong> item left</span>
        <!-- Remove this if you don't implement routing -->
        <ul class="filters">
          <li>
            <a class="selected" href="#/">All</a>
          </li>
          <li>
            <a href="#/active">Active</a>
          </li>
          <li>
            <a href="#/completed">Completed</a>
          </li>
        </ul>
        <!-- Hidden if no completed items are left ↓ -->
        <button class="clear-completed">Clear completed</button>
      </footer>
    </section>
    <!-- Scripts here. Don't remove ↓ -->
    <script type="module" src="./script.js"></script>
  </body>
</html>

2. Mô tả xây dựng

2.1 Hàm createStore

function createStore(reducer){
 let state = reducer()
 ...
}
function html([first, ...values], ...strings) {
  return values
    .reduce(
      (acc, cur) => {
        return acc.concat(strings.shift(), cur);
      },
      [first]
    )
    .filter((x) => (x && x !== true) || x === 0)
    .join('');
}
function createStore(reducer){
 let state = reducer()

  const roots = new Map(); // Ban đầu thì roots sẽ không có dữ liệu
  /**
   * roots : Map(){
   * key : Value
   * div#root : () => `<h1>Hello World!!!</h1>`
   * root: div#root
   * component : () => `<h1>Hello World!!!</h1>`
   * }
   */

  // attach(() => `<h1>Hello World!!!</h1>`, document.getElementById('root'));
  /**
   * roots : Map(1) {div#root => ƒ}
   * {div#root => () => `<h1>Hello World!!!</h1>`}
   * key: div#root
   * value: () => `<h1>Hello World!!!</h1>`
   * [[Prototype]]: Map
   */
 ...
}
roots : Map(){
   key : Value
   //Ví dụ: div#root : () => `<h1>Hello World!!!</h1>`
   //Trong đó root: div#root
   //component : () => `<h1>Hello World!!!</h1>`
  }
Map(){
  // key : Value
  div#root : () => `<h1>Hello World!!!</h1>`
}

Thì:

Khi này hàm createStore sẽ là :

function createStore(reducer) {
  let state = reducer();

  const roots = new Map(); // Ban đầu thì roots sẽ không có dữ liệu
}

2.2 Hàm Render

Xử lý Render

  function render(){
    // Vòng lặp qua roots để chuyển ra VIEW
    for(const [root, component] of roots){
      const output = component(); // Callback html() ra chuỗi
      root.innerHTML = output; // Gán chuỗi html vào element #root để hiển thị
    }

Khi này hàm createStore sẽ là :

function createStore(reducer) {
  let state = reducer(); // Sử dụng closure

  const roots = new Map();

  function render(){
    // Vòng lặp qua roots để chuyển ra VIEW
    for(const [root, component] of roots){
      const output = component();
      root.innerHTML = output;
    }
}

2.3 Phương thức attach

const roots = new Map();

function attach(component, root) {
  roots.set(root, component); // Sử dụng method set của đối tượng Map()
  render(); // Sau khi gán xong sẽ render ra view luôn
}
function createStore(reducer) {
  let state = reducer(); // Sử dụng closure

  const roots = new Map();

  // Xử lý Render từng hàm component vào root component thành phần tương ứng
  function render(){
    // Vòng lặp qua roots để chuyển ra VIEW
    for(const [root, component] of roots){
      const output = component();
      root.innerHTML = output;
    }

  // Trả về Object gồm các phương thức để xử lý ra View
  return {
    // 1. Phương thức đẩy component và root element tương ứng vào Roots
    attach(component, root){
      roots.set(root,component); // Sử dụng method set của đối tượng Map()
      render(); // Sau khi gán xong sẽ render ra view luôn
    },
    ...
  }
}

2.3 Phương thức connect

    // let state = reducer();
    function connect(selector = state => state){
      return component => (props, ...args)=>
        component(Object.assign({}, props, selector(state), ...args))
        // Merge các đối tượng vào Object gán vào Object.assign
    },

function createStore(reducer) {
  let state = reducer(); // Sử dụng closure

  const roots = new Map();

  // Xử lý Render từng hàm component vào root component thành phần tương ứng
  function render(){
    // Vòng lặp qua roots để chuyển ra VIEW
    for(const [root, component] of roots){
      const output = component();
      root.innerHTML = output;
    }

  // Trả về Object gồm các phương thức để xử lý ra View
  return {
    // 1. Phương thức đẩy component và root element tương ứng vào Roots
    attach(component, root){
      roots.set(root,component); // Sử dụng method set của đối tượng Map()
      render(); // Sau khi gán xong sẽ render ra view luôn
    },

    // 2. Lọc các state thích hợp chuyển qua View
    connect(selector = state => state){
      return component => (props, ...args)=>
        component(Object.assign({}, props, selector(state), ...args))
        // Merge các đối tượng vào Object gán vào Object.assign
    },
    ...
  }
}

2.4 Phương thức dispatch

function dispatch(action, ...args) {
  state = reducer(state, action, args); // reducer xem như callback
  // Tạo state mới từ state cũ, action và các tham số khác.
  // state được thay đổi mới -> store sẽ được update lại, và cần VIEW thay đổi lại.
  render(); // Update lại VIEW
}
function createStore(reducer) {
  let state = reducer(); // Sử dụng closure

  const roots = new Map();

  // Xử lý Render từng hàm component vào root component thành phần tương ứng
  function render(){
    // Vòng lặp qua roots để chuyển ra VIEW
    for(const [root, component] of roots){
      const output = component();
      root.innerHTML = output;
    }

  // Trả về Object gồm các phương thức để xử lý ra View
  return {
    // 1. Phương thức đẩy component và root element tương ứng vào Roots
    attach(component, root){
      roots.set(root,component); // Sử dụng method set của đối tượng Map()
      render(); // Sau khi gán xong sẽ render ra view luôn
    },

    // 2. Lọc các state thích hợp chuyển qua View
    connect(selector = state => state){
      return component => (props, ...args)=>
        component(Object.assign({}, props, selector(state), ...args))
        // Merge các đối tượng vào Object gán vào Object.assign
    },

    // 3. Dispatch : Thao tác người dùng tác động trên VIEW sẽ đẩy hành động
    dispatch(action,...args){
      state = reducer(state, action, args);
      render(); // Update lại VIEW
    }
  }
}

Xử lý Render