V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
liyuanqiu
V2EX  ›  前端开发

浅谈前端状态管理

  •  
  •   liyuanqiu · 2019-06-01 12:52:37 +08:00 · 2651 次点击
    这是一个创建于 1785 天前的主题,其中的信息可能已经有所发展或是发生改变。

    浅谈前端状态管理

    前端开发者或多或少都听说过,或者使用过 状态管理器:

    redux vuex mobx

    通常我们会选择 redux, vuex, mobx 这些著名的工具。

    那么为什么要使用状态管理器呢?这篇文章,我来谈一谈这个形而上学的问题。

    案例分析

    有一个输入框,输入文本后,在输入框下方展示出所有包含已输入文本的名字。

    简单粗暴的做法:

    codepen

    const names = ["张零", "李一", "王二", "张三", "李四", "王五"];
    const input = document.getElementById("input");
    const list = document.getElementById("list");
    function handleInputChange(e) {
      const { value } = e.target;
      list.innerHTML = "";
      if (value === "") {
        return;
      }
      names.filter(name => name.includes(value)).forEach(name => {
        const p = document.createElement("p");
        p.innerText = name;
        list.append(p);
      });
    }
    input.addEventListener("keyup", handleInputChange);
    

    这段代码完美地实现了上述场景的需求。

    现在我们这个项目要更加工程化一些,input 和 list 是两个程序员开发的,像下面这样:

    // data.js
    const names = ["张零", "李一", "王二", "张三", "李四", "王五"];
    
    // input.js
    const input = document.createElement("input");
    
    // list.js
    const list = document.createElement("list");
    function handleInputChange(e) {
      const { value } = e.target;
      list.innerHTML = "";
      if (value === "") {
        return;
      }
      names.filter(name => name.includes(value)).forEach(name => {
        const p = document.createElement("p");
        p.innerText = name;
        list.append(p);
      });
    }
    input.addEventListener("keyup", handleInputChange);
    

    我们看list.js的代码,有这些隐患:

    • input.js 的引用依赖,这会导致 list 必须晚于 input 加载,但其实这两个组件从意义上讲,并没有这层限制,只是因为程序中有引用才导致这种情况
    • list 直接关注了 input 的具体业务逻辑(按键),这是一个问题,每当增加一种修改 input 值的方式,list 就需要多注册一个回调来追踪这种改变。但细想起来,list 真正应该关注的是 input 的数据,而不是 input 有哪些事件可能会修改其数据
    • input.js 的迭代非常危险,需要时刻关注有哪些模块在依赖他的事件,对自身事件的增删改操作都会对整个应用伤筋动骨

    因此,如果在这种开发模式下去开发一个前端应用,过不了很久,甚至在你的应用变得庞大之前,你已经做不下去了。

    为了避免这种困境,我们来对这个小应用进行改造:

    codepen

    // data.js
    const names = ["张零", "李一", "王二", "张三", "李四", "王五"];
    
    // 共同的数据源 source.js
    const source = (() => {
      let data = "";
      const listeners = [];
      return {
        subscribe(listener) {
          listeners.push(listener);
        },
        setData(newData) {
          data = newData;
          listeners.forEach(listener => listener());
        },
        getData() {
          return data;
        }
      };
    })();
    
    // input.js
    const input = document.createElement("input");
    function handleInputChange(e) {
      source.setData(e.target.value);
    }
    input.addEventListener("keyup", handleInputChange);
    
    // list.js
    const list = document.createElement("list");
    source.subscribe(() => {
      list.innerHTML = "";
      const value = source.getData();
      if (value === "") {
        return;
      }
      names.filter(name => name.includes(value)).forEach(name => {
        const p = document.createElement("p");
        p.innerText = name;
        list.append(p);
      });
    });
    

    这样一来是不是变得逻辑非常清晰,input 与 list 互不关心对方的具体实现,从而可以各自实现高内聚的业务逻辑,且完全没有互相的依赖。

    状态管理器

    其实我们上面的这段代码,就是一个状态管理器的原型。

    // 共同的数据源 source.js
    const source = (() => {
      let data = "";
      const listeners = [];
      return {
        subscribe(listener) {
          listeners.push(listener);
        },
        setData(newData) {
          data = newData;
          listeners.forEach(listener => listener());
        },
        getData() {
          return data;
        }
      };
    })();
    

    它对状态进行了封装,并提供了一套管理状态的范式。各个模块在这个范式的约定下,对状态进行操作,从而分离模块之间的依赖。

    结语

    我们通过这个简单的状态管理器原型,成功分离了两个本就应该各自独立的前端模块,这也印证了状态管理器存在的价值。但是状态管理器所用的模式,并非前端领域独有,我们常用的各种 MessageQueue 系统,都是基于相同的模式在工作,也因为 MQ 的存在,大型分布式系统的各个模块间才得以各自独立,消除强依赖。所以这不是一个新的 Dogma,前后端在各自领域对解藕的探索,最终殊途同归。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2645 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 15:27 · PVG 23:27 · LAX 08:27 · JFK 11:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.