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. ToggleToggle 是最直接利用 Value 即可实现的功能,因此放在 Value 之后说。Toggle 值是 boolean 类型,特别适合配合 Switch 等组件。
既然 Toggle 功能弱于 Value,为什么不用 Value 替代 Toggle 呢?这是个好问题,如果你不担心自己代码可读性的话,的确可以永远不用 Toggle。
用法 <Toggle initial={false}> {({ on, toggle }) => <Checkbox onClick={toggle} checked={on} />} </Toggle> 源码源码地址
原料:Value
核心就是利用 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 组件,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 组件,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 组件,value 重命名为 values 且初始值为 [],增加了 add remove clear has 方法,保留 reset 方法。