精读《React PowerPlug 源码》

1. 弁言

React PowerPlug 是应用 render props 举行更好状况治理的东西库。

React 项目中,平常一个文件就是一个类,状况最细粒度就是文件的粒度。然则文件粒度并不是状况治理最适宜的粒度,所以有了 Redux 之类的全局状况库。

一样,文件粒度也并不是状况治理的最细粒度,更细的粒度或许更适宜,因而有了 React PowerPlug。

比方你会在项目中看到这类头昏眼花的 state:

class App extends React.PureComponent {
  state = {
    name = 1
    isLoading = false
    isFetchUser = false
    data = {}
    disableInput = false
    validate = false
    monacoInputValue = ''
    value = ''
  }

  render () { /**/ }
}

实在真正 App 级别的状况并没有那末多,很多 诸如受控组件 onChange 暂时保存的无意义 Value 找不到适宜的处所存储。

这时候能够用 Value 治理部分状况:

<Value initial="React">
  {({ value, set, reset }) => (
    <>
      <Select
        label="Choose one"
        options={["React", "Preact", "Vue"]}
        value={value}
        onChange={set}
      />
      <Button onClick={reset}>Reset to initial</Button>
    </>
  )}
</Value>

能够看到,这个题目本质上应当拆成新的 React 类处理,但这或许会致使项目构造更杂沓,因而 RenderProps 照样必不可少的。

本日我们就来解读一下 React PowerPlug 的源码。

2. 精读

2.1. Value

这是一个值操纵的东西,功用与 Hooks 中 useState 相似,不过多了一个 reset 功用(Hooks 实在也何尝不能有,但 Hooks 确切没有 Reset)。

用法

<Value initial="React">
  {({ value, set, reset }) => (
    <>
      <Select
        label="Choose one"
        options={["React", "Preact", "Vue"]}
        value={value}
        onChange={set}
      />
      <Button onClick={reset}>Reset to initial</Button>
    </>
  )}
</Value>

源码

State 只存储一个属性 value,并赋初始值为 initial:

export default {
  state = {
    value: this.props.initial
  };
}

要领有 set reset

set 回调函数触发后挪用 setState 更新 value

reset 就是挪用 set 并传入 this.props.initial 即可。

2.2. Toggle

Toggle 是最直接应用 Value 即可完成的功用,因而放在 Value 以后说。Toggle 值是 boolean 范例,迥殊合适合营 Switch 等组件。

既然 Toggle 功用弱于 Value,为何不必 Value 替换 Toggle 呢?这是个好题目,假如你不忧郁自身代码可读性的话,确实能够永久不必 Toggle。

用法

<Toggle initial={false}>
  {({ on, toggle }) => <Checkbox onClick={toggle} checked={on} />}
</Toggle>

源码

中心就是应用 Value 组件,value 重命名为 on,增加了 toggle 要领,继续 set reset 要领:

export default {
  toggle: () => set(on => !on);
}

理所因当,将 value 值限定在 boolean 范围内。

2.3. Counter

与 Toggle 相似,这也是继续了 Value 就能够完成的功用,计数器。

用法

<Counter initial={0}>
  {({ count, inc, dec }) => (
    <CartItem
      productName="Lorem ipsum"
      unitPrice={19.9}
      count={count}
      onAdd={inc}
      onRemove={dec}
    />
  )}
</Counter>

源码

依旧应用 Value 组件,value 重命名为 count,增加了 inc dec incBy decBy 要领,继续 set reset 要领。

与 Toggle 相似,Counter 将 value 限定在了数字,那末比方 inc 就会这么完成:

export default {
  inc: () => set(value => value + 1);
}

这里用到了 Value 组件 set 函数的多态用法。平常 set 的参数是一个值,但也能够是一个函数,回调是当前的值,这里返回一个 +1 的新值。

2.4. List

操纵数组。

用法

<List initial={['#react', '#babel']}>
  {({ list, pull, push }) => (
    <div>
      <FormInput onSubmit={push} />
      {list.map({ tag }) => (
        <Tag onRemove={() => pull(value => value === tag)}>
          {tag}
        </Tag>
      )}
    </div>
  )}
</List>

源码

依旧应用 Value 组件,value 重命名为 list,增加了 first last push pull sort 要领,继续 set reset 要领。

export default {
  list: value,
  first: () => value[0],
  last: () => value[Math.max(0, value.length - 1)],
  set: list => set(list),
  push: (...values) => set(list => [...list, ...values]),
  pull: predicate => set(list => list.filter(complement(predicate))),
  sort: compareFn => set(list => [...list].sort(compareFn)),
  reset
};

为了应用 React Immutable 更新的特征,因而将 sort 函数由 Mutable 修正为 Immutable,push pull 同理。

2.5. Set

存储数组对象,能够增加和删除元素。相似 ES6 Set。和 List 比拟少了很多功用函数,因而只负担增加、删除元素的简朴功用。

用法

须要注重的是,initial 是数组,而不是 Set 对象。

<Set initial={["react", "babel"]}>
  {({ values, remove, add }) => (
    <TagManager>
      <FormInput onSubmit={add} />
      {values.map(tag => (
        <Tag onRemove={() => remove(tag)}>{tag}</Tag>
      ))}
    </TagManager>
  )}
</Set>

源码

依旧应用 Value 组件,value 重命名为 values 且初始值为 [],增加了 add remove clear has 要领,保存 reset 要领。

完成依旧很简朴,add remove clear 都应用 Value 供应的 set 举行赋值,只需完成几个操纵数组要领即可:

const unique = arr => arr.filter((d, i) => arr.indexOf(d) === i);
const hasItem = (arr, item) => arr.indexOf(item) !== -1;
const removeItem = (arr, item) =>
  hasItem(arr, item) ? arr.filter(d => d !== item) : arr;
const addUnique = (arr, item) => (hasItem(arr, item) ? arr : [...arr, item]);

has 要领则直接复用 hasItem。中心照样应用 Value 的 set 函数一招通吃,将操纵目的锁定为数组范例罢了。

2.6. map

Map 的完成与 Set 很像,相似 ES6 的 Map。

用法

与 Set 差别,Map 许可设置 Key 名。须要注重的是,initial 是对象,而不是 Map 对象。

<Map initial={{ sounds: true, music: true, graphics: "medium" }}>
  {({ set, get }) => (
    <Tings>
      <ToggleCheck checked={get("sounds")} onChange={c => set("sounds", c)}>
        Game Sounds
      </ToggleCheck>
      <ToggleCheck checked={get("music")} onChange={c => set("music", c)}>
        Bg Music
      </ToggleCheck>
      <Select
        label="Graphics"
        options={["low", "medium", "high"]}
        selected={get("graphics")}
        onSelect={value => set("graphics", value)}
      />
    </Tings>
  )}
</Map>

源码

依旧应用 Value 组件,value 重命名为 values 且初始值为 {},增加了 set get clear has delete 要领,保存 reset 要领。

由于运用对象存储数据构造,操纵起来比数组轻易太多,已不须要再诠释了。

值得吐槽的是,作者运用了 != 推断 has:

export default {
  has: key => values[key] != null;
}

这类代码并不值得首倡,首先是不应当运用二元运算符,其次比较引荐写成 values[key] !== undefined,毕竟 set('null', null) 也应当算有值。

2.7. state

State 地道为了替换 React setState 观点,其本质就是换了名字的 Value 组件。

用法

值得注重的是,setState 支撑函数和值作为参数,是 Value 组件自身支撑的,State 组件分外适配了 setState 的另一个特征:兼并对象。

<State initial={{ loading: false, data: null }}>
  {({ state, setState }) => {
    const onStart = data => setState({ loading: true });
    const onFinish = data => setState({ data, loading: false });

    return (
      <DataReceiver data={state.data} onStart={onStart} onFinish={onFinish} />
    );
  }}
</State>

依旧应用 Value 组件,value 重命名为 state 且初始值为 {},增加了 setState 要领,保存 reset 要领。

setState 完成了兼并对象的功用,也就是传入一个对象,并不会掩盖原始值,而是与原始值做 Merge:

export default {
  setState: (updater, cb) =>
    set(
      prev => ({
        ...prev,
        ...(typeof updater === "function" ? updater(prev) : updater)
      }),
      cb
    );
}

2.8. Active

这是一个内置鼠标交互监听的容器,监听了 onMouseUponMouseDown,并依此推断 active 状况。

用法

<Active>
  {({ active, bind }) => (
    <div {...bind}>
      You are {active ? "clicking" : "not clicking"} this div.
    </div>
  )}
</Active>

源码

依旧应用 Value 组件,value 重命名为 active 且初始值为 false,增加了 bind 要领。

bind 要领也奇妙应用了 Value 供应的 set 更新状况:

export default {
  bind: {
    onMouseDown: () => set(true),
    onMouseUp: () => set(false)
  }
};

2.9. Focus

与 Active 相似,Focus 是当 focus 时才触发状况变化。

用法

<Focus>
  {({ focused, bind }) => (
    <div>
      <input {...bind} placeholder="Focus me" />
      <div>You are {focused ? "focusing" : "not focusing"} the input.</div>
    </div>
  )}
</Focus>

源码

依旧应用 Value 组件,value 重命名为 focused 且初始值为 false,增加了 bind 要领。

bind 要领与 Active 千篇一律,仅是监听机遇变成了 onFocusonBlur

2.10. FocusManager

不知道出于什么斟酌,FocusManager 的官方文档是空的,而且 Help wanted。。

正如名字形貌的,这是一个 Focus 控制器,你能够直接挪用 blur 来作废核心。

用法

笔者给了一个例子,在 5 秒后自动落空核心:

<FocusFocusManager>
  {({ focused, blur, bind }) => (
    <div>
      <input
        {...bind}
        placeholder="Focus me"
        onClick={() => {
          setTimeout(() => {
            blur();
          }, 5000);
        }}
      />
      <div>You are {focused ? "focusing" : "not focusing"} the input.</div>
    </div>
  )}
</FocusFocusManager>

源码

依旧应用 Value 组件,value 重命名为 focused 且初始值为 false,增加了 bind blur 要领。

blur 要领直接挪用 document.activeElement.blur() 来触发其 bind 监听的 onBlur 到达更新状况的结果。

By the way, 还监听了 onMouseDownonMouseUp:

export default {
  bind: {
    tabIndex: -1,
    onBlur: () => {
      if (canBlur) {
        set(false);
      }
    },
    onFocus: () => set(true),
    onMouseDown: () => (canBlur = false),
    onMouseUp: () => (canBlur = true)
  }
};

能够企图是防备在 mouseDown 时触发 blur,由于 focus 的机遇平常是 mouseDown

2.11. Hover

与 Focus 相似,只是触发机遇为 Hover。

用法

<Hover>
  {({ hovered, bind }) => (
    <div {...bind}>
      You are {hovered ? "hovering" : "not hovering"} this div.
    </div>
  )}
</Hover>

源码

依旧应用 Value 组件,value 重命名为 hovered 且初始值为 false,增加了 bind 要领。

bind 要领与 Active、Focus 千篇一律,仅是监听机遇变成了 onMouseEnteronMouseLeave

2.12. Touch

与 Hover 相似,只是触发机遇为 Hover。

用法

<Touch>
  {({ touched, bind }) => (
    <div {...bind}>
      You are {touched ? "touching" : "not touching"} this div.
    </div>
  )}
</Touch>

源码

依旧应用 Value 组件,value 重命名为 touched 且初始值为 false,增加了 bind 要领。

bind 要领与 Active、Focus、Hover 千篇一律,仅是监听机遇变成了 onTouchStartonTouchEnd

2.13. Field

与 Value 组件唯一的区分,就是

用法

这个用法和 Value 没区分:

<Field>
  {({ value, set }) => (
    <ControlledField value={value} onChange={e => set(e.target.value)} />
  )}
</Field>

然则用 bind 更简朴:

<Field initial="hello world">
  {({ bind }) => <ControlledField {...bind} />}
</Field>

源码

依旧应用 Value 组件,value 保存稳定,初始值为 '',增加了 bind 要领,保存 set reset 要领。

与 Value 的唯一区分是,支撑了 bind 并封装 onChange 监听,与赋值受控属性 value

export default {
  bind: {
    value,
    onChange: event => {
      if (isObject(event) && isObject(event.target)) {
        set(event.target.value);
      } else {
        set(event);
      }
    }
  }
};

2.14. Form

这是一个表单东西,有点相似 Antd 的 Form 组件。

用法

<Form initial={{ firstName: "", lastName: "" }}>
  {({ field, values }) => (
    <form
      onSubmit={e => {
        e.preventDefault();
        console.log("Form Submission Data:", values);
      }}
    >
      <input
        type="text"
        placeholder="Your First Name"
        {...field("firstName").bind}
      />
      <input
        type="text"
        placeholder="Your Last Name"
        {...field("lastName").bind}
      />
      <input type="submit" value="All Done!" />
    </form>
  )}
</Form>

源码

依旧应用 Value 组件,value 重命名为 values 且初始值为 {},增加了 setValues field 要领,保存 reset 要领。

表单最主要的就是 field 函数,为表单的每一个控件做绑定,同时设置一个表单唯一 key:

export default {
  field: id => {
    const value = values[id];
    const setValue = updater =>
      typeof updater === "function"
        ? set(prev => ({ ...prev, [id]: updater(prev[id]) }))
        : set({ ...values, [id]: updater });

    return {
      value,
      set: setValue,
      bind: {
        value,
        onChange: event => {
          if (isObject(event) && isObject(event.target)) {
            setValue(event.target.value);
          } else {
            setValue(event);
          }
        }
      }
    };
  }
};

能够看到,为表单的每一项绑定的内容与 Field 组件一样,只是 Form 组件的行动是批量的。

2.15. Interval

Interval 比较有意思,将定时器以 JSX 体式格局供应出来,而且供应了 stop resume 要领。

用法

<Interval delay={1000}>
  {({ start, stop }) => (
    <>
      <div>The time is now {new Date().toLocaleTimeString()}</div>
      <button onClick={() => stop()}>Stop interval</button>
      <button onClick={() => start()}>Start interval</button>
    </>
  )}
</Interval>

源码

供应了 start stop toggle 要领。

完成体式格局是,在组件内部保护一个 Interval 定时器,完成了组件更新、烧毁时的计时器更新、烧毁操纵,能够以为这类定时器的生命周期绑定了 React 组件的生命周期,不必忧郁烧毁和更新的题目。

详细逻辑就不列举了,应用 setInterval clearInterval 函数基本上就能够了。

2.16. Compose

Compose 也是个风趣的组件,能够将上面提到的恣意多个组件组合运用。

用法

<Compose components={[Counter, Toggle]}>
  {(counter, toggle) => (
    <ProductCard
      {...productInfo}
      favorite={toggle.on}
      onFavorite={toggle.toggle}
      count={counter.count}
      onAdd={counter.inc}
      onRemove={counter.dec}
    />
  )}
</Compose>

源码

经由过程递归衬着出嵌套构造,并将每一层构造输出的值存储到 propsList 中,末了一同传递给组件。这也是为何每一个函数 value 平常都要重命名的缘由。

精读《Epitath 源码 – renderProps 新用法》 文章中,笔者就引见了应用 generator 处理高阶组件嵌套的题目。

精读《React Hooks》 文章中,引见了 React Hooks 已完成了这个特征。

所以当你了解了这三种 “compose” 要领后,就能够在适宜的场景运用适宜的 compose 体式格局简化代码。

3. 总结

看完了源码剖析,不知道你是更感兴趣运用这个库呢,照样已摩拳擦掌最先造轮子了呢?不论如何,这个库的思想在一样平常的营业开辟中都应当大批实践。

别的 Hooks 版的 PowerPlug 已 4 个月没有更新了(非官方):react-powerhooks,或许下一个保护者/贡献者 就是你。

议论地点是:
精读《React PowerPlug》 · Issue #129 · dt-fe/weekly

假如你想介入议论,请点击这里,每周都有新的主题,周末或周一宣布。前端精读 – 帮你挑选靠谱的内容。

    原文作者:黄子毅
    原文地址: https://segmentfault.com/a/1190000018269887
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞