简单实现比React更强大的useState钩子

  一段时间没用React,重新温习了一遍官网新特性hooks的内容,干脆自己写个简单的,某种程度上实现更加强大的功能。

      const makeUseState = () => {
        const newOne = stateAmount => ({
          index: 0,
          whole: stateAmount,
          data: [],
          getData(initData) {
            let i = this.index;
            this.index = (i + 1) % this.whole;
            return [this.data[i] ? this.data[i] : (this.data[i] = initData), this.setData(i)];
          },
          setData(index) {
            return (newData, reRender) => {
              this.data[index] = newData;
              reRender();
            };
          }
        });
        const rootState = {};
        return (data, CpName, seq, stateAmount) => {
          if (rootState[CpName] === undefined) {
            rootState[CpName] = [];
          }
          let store = rootState[CpName];
          if (!store[seq]) {
            store[seq] = newOne(stateAmount);
          }
          return store[seq].getData(data);
        };
      };
      const myUseState = makeUseState();
      const N = props => {
        const { seq, testDiff, update } = props;
        // 后面三个参数解析模板字符串时添加
        const [A, setA] = myUseState('123', 'N', seq, 2);
        const [B, setB] = myUseState('123', 'N', seq, 2);
        const tmout = testDiff === '一' ? 2000 : 4000;
        return (
          <div>
            第{testDiff}个函数组件:
            <br />
            <button onClick={() => setA('123A', update)}>更新</button>
            <br />
            A= {A}
            <br />
            <button onClick={() => setB('123B', update)}>更新</button>
            <br />B = {B}
          </div>
        );
      };
      class M extends Component {
        render() {
          return (
            <div>
              {/* seq,update为解析模板字符串时添加 */}
              <N seq={0} update={() => this.setState({})} testDiff="一" />
              <N seq={1} update={() => this.setState({})} testDiff="二" />
            </div>
          );
        }
      }
      ReactDOM.render(<M />, app);

  记得刚开始用React的时候,各种项目都用函数组件,根本停不下来,简洁明快,即插即用,简直是懒癌患者的福音,然后慢慢要接触到需要状态实例化的场景。

  由于当时的应用场景是,即同一页面不用到两个相同的函数组件,于是继续偷懒,在父类组件的卸载钩子里重置了下函数组件的状态,这样即使切换页面,也看不出破绽,乍一看,好像还实现了有状态组件的功能,后来自己用得都觉得别扭,用行话说,就是不够优雅(我跟你谈进度你跟我谈优雅?!)。特意去官网看了下,有种叫mixin(不是sass的@mixin)的方式和我实现的方式有点像,本质上好像还是脱离不了复制粘贴的嫌疑,什么?!你不怕被面向对象编程的程序员深深地鄙视?老夫行走江湖多年,靠的就是两大神技,一个ctrl+c,一个ctrl+V?恭喜你,赢得了单身汪的尊严和丰满的发量,不过俗话说的好,no think,no gain ,理解往往是最高效的记忆方式。

  刚开始使用useState其实我是拒绝的,心里总有种不安感,就像往拆迁楼的破窗内扔石头,当下扔的时候听到声音了,紧接着心里就会后怕,下次敲代码的时候手都在哆嗦,为啥useState要按顺序索引?为啥中间不能嵌套if判断?

  函数组件肯定是没法跟类组件比的,短小精悍却没法繁衍后代确实是一大憾事,啥都得亲自上阵,一不小心数据就串重了,那么,当然也不可能大费周章用类似Vux,Redux一样专门建个数据中心,简而言之,言而简之,好像用闭包就能搞定,闭包不就是专门用来制造便携式数据仓库的吗?

  首先说下一些个人理解,像 babel 这些将 js,jsx,es都给转成 AST 语法,本质上不就是模板字符串解析吗?函数组件既然没有状态,可以通过模板字符串来记住每个函数组件的编号,这样不管是同类函数组件的哪一个更新,都能对应到自身的state。在我看来,用了useState的函数组件,将视图层和数据层分离得更加明显,条理清晰,便于阅读,将一个个state实例化的过程隐藏到了幕后,这样无疑大大提高了开发人员的工作效率(少敲代码 !== 提高效率)。

  上面的demo函数组件更新部分的实现由于没有涉及到setState部分原理,因此借用了父组件的setState来更新子组件,这样显然会影响渲染性能,数组解构的第二个参数作为函数,其内部可能涉及到将更新后的数据勾连到全局diff计算,总而言之,这里只是简单实现效果。

  废话说了那么多,说下不能用判断条件分割 useState 的原因,毕竟这也是官网特意强调要注意的:

  从上面demo的实现方式基本可以体现我的观点,对于react具体怎么样实现useState我并不清楚原理,只是根据需求实现了一个类似的效果。按照我的实现方式,代码在编译阶段(转义阶段),要对不同的函数组件以及调用次数进行排序编号,即便写在判断条件内的useState也会被置入数组,事实上,在代码 运行阶段,根本也无从判断这个useState是否要走完一轮循环,这可能也是官网强调useState不能放入判断条件的原因,另一方面,也是因为采用数组存储数据的方式决定了一旦不按下标重复遍历数组,就可能出错。

  当然,不得不说目前官网的这个useState还是有点弱的,为啥判断条件里边就不能写useState呢?我想到的解决方式是另外传一个函数,用来决定是否跳过当前下标:

<body>
    <div id="app"></div>
    <div id="app2"></div>
    <script type="text/babel">
      const { useState, Component, useEffect, Fragment } = React;
      const makeUseState = () => {
        const newOne = stateAmount => ({
          index: 0,
          whole: stateAmount,
          data: [],
          getData(initData) {
            let i = this.index;
            this.index = (i + 1) % this.whole;
            return [this.data[i] ? this.data[i] : (this.data[i] = initData), this.setData(i), this.jump.bind(this)];
          },
          jump(bool, num) {
            if (!bool) {
              this.index = (this.index + num) % this.whole;
            }
            return bool;
          },
          setData(index) {
            return (newData, reRender) => {
              this.data[index] = newData;
              reRender();
            };
          }
        });
        const rootState = {};
        return (data, CpName, seq, stateAmount) => {
          if (rootState[CpName] === undefined) {
            rootState[CpName] = [];
          }
          let store = rootState[CpName];
          if (!store[seq]) {
            store[seq] = newOne(stateAmount);
          }
          return store[seq].getData(data);
        };
      };
      const myUseState = makeUseState();
      const N = props => {
        const { seq, testDiff, update } = props;
        // myUseState后面三个入参和所有jump在解析jsx时自动添加
        const [A, setA, jump] = myUseState('123', 'N', seq, 4);
        const [B, setB] = myUseState('123', 'N', seq, 4);
        const cond = testDiff === '一' ? true : false;
     // 解析时自动初始化
        let C, D;
        let setC = () => {},
          setD = () => {};
        // jump函数第二个参数在解析jsx时自动添加,为当前判断条件内所有useState数目,支持判断条件嵌套
        if (jump(cond, 2)) {
          console.log('第一个组件的C将显示');
          [C, setC] = myUseState('C', 'N', seq, 4);
          if (jump(true, 1)) {
            [D, setD] = myUseState('D', 'N', seq, 4);
          }
        }
        const tmout = testDiff === '一' ? 2000 : 4000;
        return (
          <Fragment>
            <br />第{testDiff}个函数组件:
            <br />
            <button onClick={() => setA('123A ' + Math.random(), update)}>更新</button>
            <br />
            A= {A}
            <br />
            <button onClick={() => setB('123B ' + Math.random(), update)}>更新</button>
            <br />B = {B}
            <br />
            <button
              onClick={() => {
          // 此判断在解析时自动添加
                if (!C) {
                  return;
                }
                setC('123C' + Math.random(), update);
              }}
            >
              更新
            </button>
            <br />C = {C}
            <br />
            <button
              onClick={() => {
          // 此判断在解析时自动添加
                if (!D) {
                  return;
                }
                setD('123D' + Math.random(), update);
              }}
            >
              更新
            </button>
            <br />D = {D}
          </Fragment>
        );
      };
      class M extends Component {
        render() {
          return (
            <Fragment>
              {/* seq,update为解析jsx时添加 */}
              <N seq={0} update={() => this.setState({})} testDiff="一" />
              <N seq={1} update={() => this.setState({})} testDiff="二" />
            </Fragment>
          );
        }
      }

      ReactDOM.render(<M />, app);
    </script>
  </body>

  -----CodePen演示-----

  这样做虽然解决了判断条件内可以存在useState的问题,某种程度上来说比React现在的useState更加强大,但是是否真的有必要去兼顾判断条件内可能存在的useState呢?即便自动添加jump和某些重赋值方法调用前的判断,这样也会增加不少代码解析时的开销。

  不过最有说服力的理由还不是上面这个,能把有状态的数据简化到useState这种方式,已经够清晰明了了,在函数内部顶层实现state声明可以应对大部分功能需求,作为一个非构造函数组件,实现了渲染数据的自给自足自更新,和臃肿的类组件相比其在功能实现方面已经足矣。

  话说回来,框架的本质不就是利用了各成一家的模板字符串语法来创造便利的代码书写方式吗?只要你愿意(有时间自己弄套模板语法),比useState更简洁舒服的书写方式肯定存在。

  最后编程的乐趣也不言而喻了,那就是安静地做个懒人,躺在别人的代码上眺望星空。

posted on 2019-09-09 19:46  Lowki  阅读(3632)  评论(0编辑  收藏  举报